小学徒成长系列—String关键源码解析

    添加时间:2013-6-5 点击量:

    按照java官网文档的描述,String类代表字符串,是常量,他们的值在创建之后是不成变的,毕竟String类型是怎么实现这些的呢?


    final关键字


    在商量String类型的道理之前,我们应当先弄清楚关于final关键字的应用:


    1> 若是final润饰的是类的话,那么这个类是不克不及被持续的


    2> 若是final润饰的是办法的话,那么这个办法是不克不及被重写的


    3> 若是final润饰的是变量的话,那么这个变量的值在运行时代是不克不及被批改的


    当然,关于具体的赋值等操纵,可以参考《对象与内存经管》中的最后一点,这里就不再反复了。


    String类与final的不解之缘


    如今,我们开端商量String类吧,下面只是String类的项目组源代码:



     1 public final class String
    
    2 implements java.io.Serializable, Comparable<String>, CharSequence
    3 {
    4 / The value is used for character storage. /
    5 private final char value[]; //用来存储字符串转换而来的字符数组
    6
    7 / The offset is the first index of the storage that is used. /
    8 private final int offset; //字符串肇端字符在字符数组的地位
    9
    10 / The count is the number of characters in the String. /
    11 private final int count; //字符串分化成字符数组后字符的数量
    12 }


    从上方代码,我们知道:


    1> String类是被final润饰的,从安然角度来说,经由过程final润饰后的String类是不克不及被其他类持续的,在程度上的保护了该类,从效力上来说,进步了该类的效力,因为final润饰后会比没有效final润饰的快。


    2> value[], offet, count也是被final润饰的,这时辰三个变量的值必须在编译时代就被断定下来,并且在运行时代不克不及再被批改了。是以,每次我们每次进行字符串批改、拼接的时辰,并不克不及直接批改当前String对象的值,只能从头创建一个新的对象。


    3>我们创建String对象的时辰,String对象还应用字符数组(char[])来存储我们的字符串的。


    String类常用的机关办法


    其实呢,一般景象下,我们应用String类的时辰很少经由过程机关办法来创建String对象的,因为这是不推荐的,然则不知道大师知不知道,



    1 String str = abc;


    这种经由过程直接量创建String对象其实就等效于下面应用了经由过程字符串机关办法创建对象的。



    1 char data[] = {a, b, c};
    
    2 String str = new String(data);


    然则一般景象下应用第二这种体式格式太麻烦了,所以我们都推荐应用第一种体式格式创建String对象。


    下面我们开端讲解一下String类常用的机关办法吧。


    1>无参数的机关办法,这个创建的String对象的值为,重视是是,这个就等效于我们String str = ;具体关于参数的信赖不消讲大师都应当知道了吧,不记得了的伴侣可以看回前面final中列举出的代码注释。



     1 /
    
    2 Initializes a newly created {@code String} object so that it represents
    3 an empty character sequence. Note that use of this constructor is
    4 unnecessary since Strings are immutable.
    5 /
    6 public String() {
    7 this.offset = 0;
    8 this.count = 0;
    9 this.value = new char[0];
    10 }



     2>应用Stirng对象作为机关办法的参数,须要重视的是,经由过程该机关办法创建String对象将会产生2个字符串对象,所以不推荐应用(具体为什么是两个,可以参考博文《小学徒进阶系列—JVM对String的处理惩罚》)



     1 public String(String original) {
    
    2 int size = original.count; //获取源字符串的字符数量
    3 char[] originalValue = original.value; //获取源字符串的字符数组
    4 char[] v; //用于新字符串对象存储字符数组
    5 if (originalValue.length > size) {
    6 int off = original.offset; //获取源字符串肇端字符在字符数组的地位
    7 v = Arrays.copyOfRange(originalValue, off, off+size); //返回将源字符数组复制到新的字符数组中
    8 } else {
    9 // The array representing the String is the same
    10 // size as the String, so no point in making a copy.
    11 v = originalValue;
    12 }
    13 this.offset = 0;
    14 this.count = size;
    15 this.value = v;
    16 }


     其实在机关办法中的各行代码里,我想大师在看这行代码的时辰,最想知道的应当是Arrays.copyOfRange(char[] original,int ,int to)中各个参数的含义吧,官网中的申明是如许子的:



    public static char[] copyOfRange(char[] original,
    
    int
    int to)
    将指定命组的指定局限复制到一个新数组。该局限的初始索引 () 必须位于
    0 和 original.length(包含)之间。original[] 处的值放入副本的初始元素中(除非 == original.length 或 == to)。原数组中后续元素的值放入副本的后续元素。该局限的最后索引 (to)(必须大于便是 )可以大于 original.length,在这种景象下,\\u000 被放入索引大于便是 original.length - 的副本的所有元素中。返回数组的长度为 to -

    参数:
    original
    - 将要从其复制一个局限的数组
    - 要复制的局限的初始索引(包含)
    to
    - 要复制的局限的最后索引(不包含)。(此索引可以位于数组局限之外)。

    返回:
    包含取自原数组指定局限的新数组,截取或用
    0 填充以获得所需长度

    抛出:
    ArrayIndexOutOfBoundsException
    - 若是 < 0 或 > original.length()
    IllegalArgumentException
    - 若是 > to
    NullPointerException
    - 若是 original 为 null


     3>应用字符数组作为String机关办法的参数,前面你我们已经知道了,应用String str = abc,相当于应用该机关办法创建对象



    1 public String(char value[]) {
    
    2 int size = value.length;
    3 this.offset = 0;
    4 this.count = size;
    5 this.value = Arrays.copyOf(value, size);
    6 }



     4>同样应用字符数组作为String机关办法的参数,然则并不是全部都是用来机关字符串对象的,而是只应用从offerset起的count个字符作为String对象的值。



     1 public String(char value[], int offset, int count) {
    
    2 if (offset < 0) {
    3 throw new StringIndexOutOfBoundsException(offset);
    4 }
    5 if (count < 0) {
    6 throw new StringIndexOutOfBoundsException(count);
    7 }
    8 // Note: offset or count might be near -1>>>1.
    9 if (offset > value.length - count) {
    10 throw new StringIndexOutOfBoundsException(offset + count);
    11 }
    12 this.offset = 0;
    13 this.count = count;
    14 this.value = Arrays.copyOfRange(value, offset, offset+count);
    15 }



    String类常用的办法


    在这里,我重点讲解关于String类常用的办法,同时解析它的源代码,具体应用办法和履行成果,读者可以自行测验测验哦,我就不包袱的写出来啦,并且我把这些代码缩起来了,避免整篇博文都是源代码,看的辛苦,大师须要看哪个办法的源代码,就直接展开哪个办法就行了,好啦,言归正传,我们开端解析吧。


    1> public char charAt(int index)


    返回指定索引处的 char 值。索引局限为从 0 到 length() - 1。序列的第一个 char 值位于索引 0 处,第二个位于索引 1 处,依此类推,这类似于数组索引。下面是该办法的源码解析:




    1 public char charAt(int index) {
    
    2 if ((index < 0) || (index >= count)) { //若是index索引 < 0 或者 > 字符串长度,
    3 throw new StringIndexOutOfBoundsException(index); //抛出超出鸿沟的异常
    4 }
    5 return value[index + offset]; //若是索引合法,直接取出在字符数组中索引对应的值
    6 }


    View Code

    2> public String concat(String str)


    将指定字符串连接到此字符串的结尾。若是参数字符串的长度为 0,则返回此 String 对象。
    不然,创建一个新的 String 对象,用来默示由此 String 对象默示的字符序列和参数字符串默示的字符序列连接而成的字符序列。(从上方的注释中,我们已经知道,因为String类型是常量,一旦创建值是不成以改变的,所以只能经由过程创建新的字符串并返回新字符串的引用,确保了字符串不成变及可共享)




     1 public String concat(String str) {
    
    2 int otherLen = str.length(); //获取须要拼接到源字符串后的字符串的长度
    3 if (otherLen == 0) { //若是长度为0
    4 return this; //直接返回该对象
    5 }
    6 //不然开端创建一个新的字符串对象并返回,
    7 //这也是就是String类型一旦创建就不成改变的实现体式格式
    8 char buf[] = new char[count + otherLen];
    9 getChars(0, count, buf, 0); //这两行申明把字符数组复制到buf字符数组中
    10 str.getChars(0, otherLen, buf, count); //具体getChars()办法鄙人面会讲到
    11 return new String(0, count + otherLen, buf); //应用前面说道的第四种机关办法,创建新的字符串对象并返回
    12 }


    View Code

     3> public void getChars(int srcBegin,  int srcEnd, char[] dst, int dstBegin)


    将字符从此字符串复制到目标字符数组。
    要复制的第一个字符位于索引 srcBegin 处;要复制的最后一个字符位于索引 srcEnd-1 处(是以要复制的字符总数是 srcEnd-srcBegin)。要复制到 dst 子数组的字符从索引 dstBegin 处开端,并停止于索引: dstbegin + (srcEnd-srcBegin) - 1
     
    参数:
    srcBegin - 字符串中要复制的第一个字符的索引。
    srcEnd - 字符串中要复制的最后一个字符之后的索引。
    dst - 目标数组。
    dstBegin - 目标数组中的肇端偏移量。




     1 public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
    
    2 //几个if语句都是结实性的断定,信赖大师都能看得懂
    3 if (srcBegin < 0) {
    4 throw new StringIndexOutOfBoundsException(srcBegin);
    5 }
    6 if (srcEnd > count) {
    7 throw new StringIndexOutOfBoundsException(srcEnd);
    8 }
    9 if (srcBegin > srcEnd) {
    10 throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
    11 }
    12 //这就是一个数组的复制办法
    13 System.arraycopy(value, offset + srcBegin, dst, dstBegin, //将源字符数组从索引offset + srcBegin开端,复制到到目标数组dst中,
    14 srcEnd - srcBegin); //复制到目标数组中从索引dstBegin开端,长度为 srcEnd - srcBegin
    15 }


    View Code

     4> indexOf(..)


    这办法常用的有几个,比如public int indexOf(String str) public int indexOf(String str,  int Index),他们都是返回指定子字符串在此字符串中第一次呈现处的索引。只是前者是搜刮全部字符串,而后者是从指定地位开端搜刮。


    public int indexOf(String str)




    1 public int indexOf(String str) {
    
    2 return indexOf(str, 0);
    3 }


    View Code

     public int indexOf(String str,  int Index)




    1 public int indexOf(String str, int Index) {
    
    2 return indexOf(value, offset, count,
    3 str.value, str.offset, str.count, Index);
    4 }


    View Code

     经由过程查看上方两个办法的源代码我们可以发明,这两个办法内部都是应用String类中的一个应用默认权限的indexOf()办法进行实现的,我们一路来进行具体的解析。




     1  /
    
    2
    3 @param source 源字符串的字符数组
    4 @param sourceOffset 源字符串第一个字符在字符数组中的肇端地位
    5 @param target 须要查找的字符串数组
    6 @param targetOffset 须要查找的字符串第一个字符在字符数组中的肇端地位
    7 @param targetCount 须要查找的字符串的长度
    8 @param Index 在源字符串中查找的肇端地位
    9 @return 返回指定字符在此字符串中第一次呈现处的索引,若不存在,返回-1
    10 /
    11 static int indexOf(char[] source, int sourceOffset, int sourceCount,
    12 char[] target, int targetOffset, int targetCount,
    13 int Index) {
    14 if (Index >= sourceCount) { //若是其实地位大于源数组的长度
    15 return (targetCount == 0 ? sourceCount : -1); //若须要查找的字符串长度为0,则返回源字符串的长度,不然返回 -1
    16 }
    17 if (Index < 0) { //结实性的改正,将查询肇端索引改正为0
    18 Index = 0;
    19 }
    20 if (targetCount == 0) { //若是须要查找的字符串长度为0,申明须要查找的字符串为空值
    21 return Index; //直接返回肇端地址
    22 }
    23
    24 char first = target[targetOffset]; //获取须要查找的字符串的第一个字符
    25 int max = sourceOffset + (sourceCount - targetCount); //在源字符串中搜刮停止的索引
    26
    27 forint i = sourceOffset + Index; i <= max; i++) {
    28 / Look for first character. /
    29 if (source[i] != first) { //找到与查找字符串第一个字符相等的字符在源字符串字符数组中的肇端索引
    30 while (++i <= max && source[i] != first);
    31 }
    32
    33 / Found first character, now look at the rest of v2 /
    34 if (i <= max) {
    35 int j = i + 1; //从查找到的第一个字符索引的下一个开端查找
    36 int end = j + targetCount - 1; //停止本次轮回查找的最后一个字符索引
    37 forint k = targetOffset + 1; j < end && source[j] == //若是后面的字符值和查找的字符串的字符值雷同,持续增长j的值,
    38 target[k]; j++, k++);
    39
    40 if (j == end) { //若是j的值与end的值雷同了,则默示找到了须要查找的字符串
    41 / Found whole string. /
    42 return i - sourceOffset; //策画出第一次呈现的地位。
    43 }
    44 }
    45 }
    46 return -1; //返回-1,申明源字符串中不包含须要查找的字符串
    47 }


    View Code

     5> public boolean contains(CharSequence s)


    当且仅当此字符串包含指定的 char 值序列时,返回 true。




    1 public boolean contains(CharSequence s) {
    
    2 return indexOf(s.toString()) > -1;
    3 }


    View Code

    从源代码我们也可以知道,这个办法哄骗的是前面说的indexOf(String str)办法进行实现的,具体就不再细说了。


    6> public boolean isEmpty()


    断定字符串是否为空。




    1 public boolean isEmpty() {
    
    2 return count == 0; //若是字符串对象中的字符数组为空,申明当前字符串长度为0
    3 }


    View Code

    7> startsWith(String prefix)、endsWith(String suffix)


    前者是断定字符串是否以prefix为开首,而后者是断定字符串是否以suffix为结尾。


    public boolean startsWith(String prefix)




    1 public boolean startsWith(String prefix) {
    
    2 return startsWith(prefix, 0);
    3 }


    View Code

     public boolean endsWith(String suffix)




    1 public boolean endsWith(String suffix) {
    
    2 return startsWith(suffix, count - suffix.count);
    3 }


    View Code

    从上述代码我们可以看到,这两个办法同样调用的是一个办法来进行实现的,下面我们也来解析一下这个办法




     1 /
    
    2 @param prefix 须要断定的字符串
    3 @param toffset 开端查找的索引
    4 /
    5 public boolean startsWith(String prefix, int toffset) {
    6 char ta[] = value; //获取当前字符串对象的字符数组
    7 int to = offset + toffset; //获取到开端断定的索引在当前字符串对象的地位
    8 char pa[] = prefix.value; //获取须要断定断定的prefix字符串的字符数组
    9 int po = prefix.offset;
    10 int pc = prefix.count;
    11 // Note: toffset might be near -1>>>1.
    12 if ((toffset < 0) || (toffset > count - pc)) { //若是肇端地位 < 0 或者 开端地位 > 字符串最后找的索引
    13 return false; //此时申明prefix不存在当前字符串对象中
    14 }
    15 while (--pc >= 0) { //开端断定从toffset地位开端的字符串是否和prefix的值想等
    16 if (ta[to++] != pa[po++]) {
    17 return false;
    18 }
    19 }
    20 return true;
    21 }


    View Code

    8> copyValueOf(char data[])


    其实,正确的说,这个别式格式是将data数组转换成字符串对象。




    1 public static String copyValueOf(char data[]) {
    
    2 return copyValueOf(data, 0, data.length);
    3 }


    View Code

    同样,这个办法调用的是下面这第9个办法进行实现的


    9> copyValueOf(char data[], int offset, int count)


    经由过程源代码我们可以知道, 这个办法经由过程调用String类型的机关办法进行创建并且返回的字符串对象




    1 public static String copyValueOf(char data[], int offset, int count) {
    
    2 // All public String constructors now copy the data.
    3 return new String(data, offset, count);
    4 }


    View Code

    10> toCharArray()


    将字符串对象的字符数组复制到一个新的数组中并返回这个新创建的数组。




    1 public char[] toCharArray() {
    
    2 char result[] = new char[count];
    3 getChars(0, count, result, 0); // 将当前字符串对象的数组复制到新创建的result数组中
    4 return result;
    5 }


    View Code

    11>  trim()


    这个办法用于去除当前字符串对象中的首部和尾部的空白,不会去除中心的空白。




     1 public String trim() {
    
    2 int len = count;
    3 int st = 0;
    4 int off = offset; / avoid getfield opcode /
    5 char[] val = value; / avoid getfield opcode /
    6
    7 while ((st < len) && (val[off + st] <= )) {
    8 st++;
    9 }
    10 while ((st < len) && (val[off + len - 1] <= )) {
    11 len--;
    12 }
    13 return ((st > 0) || (len < count)) ? substring(st, len) : this;
    14 }


    View Code

    这段代码很简单,我就不再具体介绍吧,大师本身看就行啦,呵呵,谅解我偷下懒吧。


    String类的equals()和==


    底本呢,equals也是String中的一个常用的办法,可是为什么要零丁放出来讲呢?因为他太首要了,很多初学者都很轻易把他和==给混合了。下面我们讲解下String用这两个进行对象断定时两者的差别吧。


    == 断定的是字符串对象引用地址是否雷同


    equals断定的主如果两个字符串对象中的内容是否雷同。


    我们举个代码作为例子吧,信赖大师运行一次必然就可以或许懂的了。




     1     
    
    2 public static void main(String[] args) {
    3
    4 String x = new String(java); //创建对象x,其值是java
    5 String y = new String(java); //创建对象y,其值是java
    6
    7 System.out.println(x == y); // 应用关系相等斗劲符斗劲对象x和y
    8 System.out.println(x.equals(y)); // 应用关对象的equals()办法斗劲对象x和y
    9
    10 String m = java; //创建对象m,其值是java
    11 String n = java; //创建对象n,其值是java
    12
    13 System.out.println(m == n); // 应用关系相等斗劲符斗劲对象m和n
    14 System.out.println(m.equals(n)); // 应用关对象的equals()办法斗劲对象m和n
    15 }
    16 }


    View Code


    分享到: