Java——String
String1、基本用法2、String内部3、编码转换4、不可变性5、常量字符串6、hashCode7、正则表达式1、基本用法字符串的基本使用是比较简单直接的。可以通过常量定义String变量Stringname老马说编程;也可以通过new创建String变量StringnamenewString(老马说编程);String可以直接使用和运算符如Stringname老马;name说编程;Stringdescritpion探索编程本质;System.out.println(namedescritpion);输出为老马说编程探索编程本质String类包括很多方法以方便操作字符串比如publicbooleanisEmpty()//判断字符串是否为空publicintlength()//获取字符串长度publicStringsubstring(intbeginIndex)//取子字符串publicStringsubstring(intbeginIndex,intendIndex)//取子字符串publicintindexOf(intch)//查找字符返回第一个找到的索引位置没找到返回-1publicintindexOf(Stringstr)//查找子串返回第一个找到的索引位置没找到返回-1publicintlastIndexOf(intch)//从后面查找字符publicintlastIndexOf(Stringstr)//从后面查找子字符串publicbooleancontains(CharSequences)//判断字符串中是否包含指定的字符序列publicbooleanstartsWith(Stringprefix)//判断字符串是否以给定子字符串开头publicbooleanendsWith(Stringsuffix)//判断字符串是否以给定子字符串结尾publicbooleanequals(ObjectanObject)//与其他字符串比较看内容是否相同publicbooleanequalsIgnoreCase(StringanotherString)//忽略大小写比较是否相同publicintcompareTo(StringanotherString)//比较字符串大小publicintcompareToIgnoreCase(Stringstr)//忽略大小写比较publicStringtoUpperCase()//所有字符转换为大写字符返回新字符串原字符串不变publicStringtoLowerCase()//所有字符转换为小写字符返回新字符串原字符串不变publicStringconcat(Stringstr)//字符串连接返回当前字符串和参数字符串合并结果publicStringreplace(charoldChar,charnewChar)//字符串替换替换单个字符//字符串替换替换字符序列返回新字符串原字符串不变publicStringreplace(CharSequencetarget,CharSequencereplacement)publicStringtrim()//删掉开头和结尾的空格返回新字符串原字符串不变publicString[]split(Stringregex)//分隔字符串返回分隔后的子字符串数组看个String的简单例子按逗号分隔hello, worldStringstrhello, world;String[]arrstr.split(, );//arr[0]为hello, arr[1]为world。String的操作大多简单直接不再赘述。从调用者的角度了解了String的基本用法下面我们进一步来理解String的内部代码基于Java 7。2、String内部String类内部用一个字符数组表示字符串实例变量定义为privatefinalcharvalue[];String有两个构造方法可以根据char数组创建String变量publicString(charvalue[])publicString(charvalue[],intoffset,intcount)需要说明的是String会根据参数新创建一个数组并复制内容而不会直接用参数中的字符数组。String中的大部分方法内部也都是操作的这个字符数组。比如length()方法返回的是这个数组的长度。substring()方法是根据参数调用构造方法String(char value[], int offset, int count)新建了一个字符串。indexOf()方法查找字符或子字符串时是在这个数组中进行查找。String中还有一些方法与这个char数组有关publiccharcharAt(intindex)//返回指定索引位置的char//返回字符串对应的char数组 注意返回的是一个复制后的数组而不是原数组publicchar[]toCharArray()//将char数组中指定范围的字符复制入目标数组指定位置publicvoidgetChars(intsrcBegin,intsrcEnd,chardst[],intdstBegin)与Character类似String也提供了一些方法按代码点对字符串进行处理具体不再赘述。publicintcodePointAt(intindex)publicintcodePointBefore(intindex)publicintcodePointCount(intbeginIndex,intendIndex)publicintoffsetByCodePoints(intindex,intcodePointOffset)3、编码转换String内部是按UTF-16BE处理字符的对BMP字符使用一个char两个字节对于增补字符使用两个char四个字节。我们在第2.3节介绍过各种编码不同编码可能用于不同的字符集使用不同的字节数目以及不同的二进制表示。如何处理这些不同的编码呢这些编码与Java内部表示之间如何相互转换呢Java使用Charset类表示各种编码它有两个常用静态方法publicstaticCharsetdefaultCharset()publicstaticCharsetforName(StringcharsetName)第一个方法返回系统的默认编码比如在笔者的计算机中执行如下语句System.out.println(Charset.defaultCharset().name());输出为UTF-8。第二个方法返回给定编码名称的Charset对象与我们在2.3节介绍的编码相对应其charset名称可以是US-ASCII、ISO-8859-1、windows-1252、GB2312、GBK、GB18030、Big5、UTF-8等比如CharsetcharsetCharset.forName(GB18030);String类提供了如下方法返回字符串按给定编码的字节表示publicbyte[]getBytes()publicbyte[]getBytes(StringcharsetName)publicbyte[]getBytes(Charsetcharset)第一个方法没有编码参数使用系统默认编码第二个方法参数为编码名称第三个方法参数为Charset。String类有如下构造方法可以根据字节和编码创建字符串也就是说根据给定编码的字节表示创建Java的内部表示。publicString(bytebytes[],intoffset,intlength,StringcharsetName)publicString(bytebytes[],Charsetcharset)4、不可变性与包装类类似String类也是不可变类即对象一旦创建就没有办法修改了。String类也声明为了final不能被继承内部char数组value也是final的初始化后就不能再变了。String类中提供了很多看似修改的方法其实是通过创建新的String对象来实现的原来的String对象不会被修改。比如concat()方法的代码publicStringconcat(Stringstr){intotherLenstr.length();if(otherLen0){returnthis;}intlenvalue.length;charbuf[]Arrays.copyOf(value,lenotherLen);str.getChars(buf,len);returnnewString(buf,true);}通过Arrays.copyOf方法创建了一块新的字符数组复制原内容然后通过new创建了一个新的String最后一行调用的是String的另一个构造方法其定义为String(char[]value,booleanshare){//assert share : unshared not supported;this.valuevalue;}这是一个非公开的构造方法直接使用传递过来的数组作为内部数组。与包装类类似定义为不可变类程序可以更为简单、安全、容易理解。但如果频繁修改字符串而每次修改都新建一个字符串那么性能太低这时应该考虑Java中的另两个类StringBuilder和StringBuffer。5、常量字符串Java中的字符串常量是非常特殊的除了可以直接赋值给String变量外它自己就像一个String类型的对象可以直接调用String的各种方法。我们来看代码System.out.println(老马说编程.length());System.out.println(老马说编程.contains(老马));System.out.println(老马说编程.indexOf(编程));实际上这些常量就是String类型的对象在内存中它们被放在一个共享的地方这个地方称为字符串常量池它保存所有的常量字符串每个常量只会保存一份被所有使用者共享。当通过常量的形式使用一个字符串的时候使用的就是常量池中的那个对应的String类型的对象。Stringname1老马说编程;Stringname2老马说编程;System.out.println(name1name2);输出为true。为什么呢可以认为老马说编程在常量池中有一个对应的String类型的对象我们假定名称为laoma上面的代码实际上就类似于StringlaomanewString(newchar[]{老,马,说,编,程});Stringname1laoma;Stringname2laoma;System.out.println(name1name2);实际上只有一个String对象三个变量都指向这个对象name1name2也就不言而喻了。需要注意的是如果不是通过常量直接赋值而是通过new创建就不会返回true了看下面的代码Stringname1newString(老马说编程);Stringname2newString(老马说编程);System.out.println(name1name2);输出为false。为什么呢上面代码类似于StringlaomanewString(newchar[]{老,马,说,编,程});Stringname1newString(laoma);Stringname2newString(laoma);System.out.println(name1name2);String类中以String为参数的构造方法代码如下publicString(Stringoriginal){this.valueoriginal.value;this.hashoriginal.hash;}hash是String类中另一个实例变量表示缓存的hashCode值。可以看出name1和name2指向两个不同的String对象只是这两个对象内部的value值指向相同的char数组。其内存布局如图所示。所以name1name2不成立但name1.equals(name2)是true。6、hashCodehash这个实例变量它的定义如下privateinthash;//Default to 0hash变量缓存了hashCode方法的值也就是说第一次调用hashCode方法的时候会把结果保存在hash这个变量中以后再调用就直接返回保存的值。我们来看下String类的hashCode方法代码如下publicinthashCode(){inthhash;if(h0value.length0){charval[]value;for(inti0;ivalue.length;i){h31 hval[i];}hashh;}returnh;}如果缓存的hash不为0就直接返回了否则根据字符数组中的内容计算hash计算方法是s[0]31^(n-1)s[1]31^(n-2)...s[n-1]s表示字符串s[0]表示第一个字符n表示字符串长度s[0]*31^(n-1)表示31的(n-1)次方再乘以第一个字符的值。为什么要用这个计算方法呢使用这个式子可以让hash值与每个字符的值有关也与每个字符的位置有关位置ii1的因素通过31的n-i次方表示。使用31大致是因为两个原因一方面可以产生更分散的散列即不同字符串hash值也一般不同另一方面计算效率比较高31h与32h-h即h5-h等价可以用更高效率的移位和减法操作代替乘法操作。7、正则表达式String类中有一些方法接受的不是普通的字符串参数而是正则表达式。什么是正则表达式呢正则表达式可以理解为一个字符串但表达的是一个规则一般用于文本的匹配、查找、替换等。Java中有专门的类如Pattern和Matcher用于正则表达式但对于简单的情况String类提供了更为简洁的操作String中接受正则表达式的方法有publicString[]split(Stringregex)//分隔字符串publicbooleanmatches(Stringregex)//检查是否匹配publicStringreplaceFirst(Stringregex,Stringreplacement)//字符串替换publicStringreplaceAll(Stringregex,Stringreplacement)//字符串替换至此关于String的用法、原理和特性等基本介绍完了。关于String的实现原理值得了解的是Java 9对String的实现进行了优化它的内部不是char数组而是byte数组如果字符都是ASCII字符它就可以使用一个字节表示一个字符而不用UTF-16BE编码节省内存。