3-1 阅读源代码--String

String、Integer、Long、Enum、BigDecimal、ThreadLocal、ClassLoader & URLClassLoader、ArrayList & LinkedList、 HashMap & LinkedHashMap & TreeMap & CouncurrentHashMap、HashSet & LinkedHashSet & TreeSet

转:https://www.cnblogs.com/ouym/p/8981074.html

1类签名与注释

public final class String
    implements java.io.Serializable, Comparable, CharSequence

String类被定义为final类型的,所以String对象一旦创建了,就是不可变的。

String类实现了Serializable接口,表示可以序列化。

String类实现了Comparable接口,表示String类型可以相互比较。(通过compareTo方法)

String类实现了CharSequence接口,CharSequence是char值的可读序列。

 

String类代表字符串。 Java程序中的所有字符串文字(例如"abc" )都被实现为此类的实例。Strings是不可变的,它们的值在创建后不能被更改。字符串缓冲区支持可变字符串。

Java语言为字符串连接运算符(+)提供特殊支持,并为其他对象转换为字符串。 字符串连接是通过StringBuilder (或StringBuffer )类及其append方法实现的。 字符串转换是通过toString方法来实现,该方法定义在Object类中。

 

2基本属性

private final char value[];

private int hash; // Default to 0

String用字符数组存储值,内部还保留了自己的hash值。为什么要将hash值作为成员属性呢?我们先看一下String的hashCode方法

复制代码
 1 public int hashCode() {
 2         int h = hash;
 3         if (h == 0 && value.length > 0) {
 4             char val[] = value;
 5 
 6             for (int i = 0; i < value.length; i++) {
 7                 h = 31 * h + val[i];
 8             }
 9             hash = h;
10         }
11         return h;
12     }
复制代码

当调用String的hashCode方法时,首先会从hash属性取值,当hash属性值为0时(第一次调用该方法),才会重新计算hash值。hash值得计算如line7所示。计算后会将值赋给hash属性。这里保证只在第一次调用hashCode方法的时候计算hash值,之后直接从hash属性取值。这种优化只有当String是final的时候才可以,若String可变的话,hash值也会变化。

 

3构造方法

String的构造方法有很多,下面是eclipse中outline的所有构造方法的截图。

红色方框部分表示废弃方法(我的版本JDK1.8)。通过outline可以看出,String可以通过String、字符数组、int数组、字节数组、StringBuffer/StringBuilder构造。下面贴出几个常用构造方法的代码。

复制代码
//1构造空String
public String() {
        this.value = "".value;
    }

//2通过String构造
public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
    }

//3通过char数组构造
public String(char value[]) {
        this.value = Arrays.copyOf(value, value.length);
    }

//4通过byte数组构造
 public String(byte bytes[]) {
        this(bytes, 0, bytes.length);
    }
public String(byte bytes[], int offset, int length) {
        checkBounds(bytes, offset, length);
        this.value = StringCoding.decode(bytes, offset, length);
    }

//5通过字符串缓冲区构造
public String(StringBuffer buffer) {
        synchronized(buffer) {
            this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
        }
    }
 public String(StringBuilder builder) {
        this.value = Arrays.copyOf(builder.getValue(), builder.length());
    }
复制代码

这里有几点自己的理解:

(1)在通过char数组构造的时候,是通过Arrays的copyOf复制了一个新的数组(开辟了新的数组存储空间),这里不是通过简单的引用传递。

(2)byte数组不能直接变成char数组,需要通过StringCodingdecode方法。

(3)注意通过StringBuffer构造的时候加了同步,因为StringBuffer是线程安全的,这里复制的时候加锁也要保证线程安全

 

4常用方法

(1)equals方法

 

复制代码
public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }
复制代码

 

只有满足类型一样,长度相等,char数组所有的值都相等时equals方法才返回true。

 

(2)compareTo方法

复制代码
public int compareTo(String anotherString) {
        int len1 = value.length;
        int len2 = anotherString.value.length;
        int lim = Math.min(len1, len2);
        char v1[] = value;
        char v2[] = anotherString.value;

        int k = 0;
        while (k < lim) {
            char c1 = v1[k];
            char c2 = v2[k];
            if (c1 != c2) {
                return c1 - c2;
            }
            k++;
        }
        return len1 - len2;
    }
复制代码

挨个比较字符数组的值,当较短的串所有字符都和较长串的相等时返回串的长度差。当compareTo方法的返回值等于0时,表示被比较的字符串和当前字符串相等,返回值大于0时表示当前字符串较大,返回值小于0表示当前字符串较小。

 

(3)startsWith和endsWith

复制代码
//startsWith
public boolean startsWith(String prefix) {
        return startsWith(prefix, 0);
    }

public boolean startsWith(String prefix, int toffset) {
        char ta[] = value;
        int to = toffset;
        char pa[] = prefix.value;
        int po = 0;
        int pc = prefix.value.length;
        // Note: toffset might be near -1>>>1.
        if ((toffset < 0) || (toffset > value.length - pc)) {
            return false;
        }
        while (--pc >= 0) {
            if (ta[to++] != pa[po++]) {
                return false;
            }
        }
        return true;
    }

//endsWith
public boolean endsWith(String suffix) {
        return startsWith(suffix, value.length - suffix.value.length);
    }
复制代码

starsWhith方法是从偏移量开始,挨个比较自身字符数组的值和参数对应的字符数组的值。endsWith用长度差当作偏移量。

 

(4)indexOf(String)方法

复制代码
 1 public int indexOf(String str) {
 2         return indexOf(str, 0);
 3     }
 4 
 5 public int indexOf(String str, int fromIndex) {
 6         return indexOf(value, 0, value.length,
 7                 str.value, 0, str.value.length, fromIndex);
 8     }
 9 
10 static int indexOf(char[] source, int sourceOffset, int sourceCount,
11             String target, int fromIndex) {
12         return indexOf(source, sourceOffset, sourceCount,
13                        target.value, 0, target.value.length,
14                        fromIndex);
15     }
16 
17 static int indexOf(char[] source, int sourceOffset, int sourceCount,
18             char[] target, int targetOffset, int targetCount,
19             int fromIndex) {
20         if (fromIndex >= sourceCount) {
21             return (targetCount == 0 ? sourceCount : -1);
22         }
23         if (fromIndex < 0) {
24             fromIndex = 0;
25         }
26         if (targetCount == 0) {
27             return fromIndex;
28         }
29 
30         char first = target[targetOffset];
31         int max = sourceOffset + (sourceCount - targetCount);
32 
33         for (int i = sourceOffset + fromIndex; i <= max; i++) {
34             /* Look for first character. */
35             if (source[i] != first) {
36                 while (++i <= max && source[i] != first);
37             }
38 
39             /* Found first character, now look at the rest of v2 */
40             if (i <= max) {
41                 int j = i + 1;
42                 int end = j + targetCount - 1;
43                 for (int k = targetOffset + 1; j < end && source[j]
44                         == target[k]; j++, k++);
45 
46                 if (j == end) {
47                     /* Found whole string. */
48                     return i - sourceOffset;
49                 }
50             }
51         }
52         return -1;
53     }
复制代码

主要实现在line17-53,这里就是挨个对比匹配。通过indexOf(String str)调用line17的方法是没有什么问题,因为sourceOffset偏移量总是为0,但是看源码的时候总感觉max(line31)的逻辑会导致数组越界问题。举个例子如下:

复制代码
public class Test {

    public static void main(String[] args) {
        
        String a = "abc1234";
        char[] aa = {'a','b','c','1','2','3','4'};
        String b = "25";
        char[] bb = {'2','5'};
        System.out.println(indexOf(aa,2,aa.length,
                bb,0,bb.length,0));
    
    }
    
    static int indexOf(char[] source, int sourceOffset, int sourceCount,
            char[] target, int targetOffset, int targetCount,
            int fromIndex) {
              //省略...
        }

      
}
复制代码
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 7
    at com.ouym.test.Test.indexOf(Test.java:44)
    at com.ouym.test.Test.main(Test.java:19)

 

(5)substring

复制代码
public String substring(int beginIndex) {
        if (beginIndex < 0) {
            throw new StringIndexOutOfBoundsException(beginIndex);
        }
        int subLen = value.length - beginIndex;
        if (subLen < 0) {
            throw new StringIndexOutOfBoundsException(subLen);
        }
        return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
    }

public String substring(int beginIndex, int endIndex) {
        if (beginIndex < 0) {
            throw new StringIndexOutOfBoundsException(beginIndex);
        }
        if (endIndex > value.length) {
            throw new StringIndexOutOfBoundsException(endIndex);
        }
        int subLen = endIndex - beginIndex;
        if (subLen < 0) {
            throw new StringIndexOutOfBoundsException(subLen);
        }
        return ((beginIndex == 0) && (endIndex == value.length)) ? this
                : new String(value, beginIndex, subLen);
    }
复制代码

String的subString方法是通过构造方法String(value, beginIndex, subLen)来实现的,截取的字串包含beginIndex,不包含endIndex。举个例子

String a = "0123456";
System.out.println(a.substring(2,4));

输出:23

 

(6)concat

复制代码
public String concat(String str) {
        int otherLen = str.length();
        if (otherLen == 0) {
            return this;
        }
        int len = value.length;
        //新建字符数组buf,并将主调String对象的value数组复制到buf
        char buf[] = Arrays.copyOf(value, len + otherLen);
        //将参数str的字符数组填充到buf的剩余空间
        str.getChars(buf, len);
        return new String(buf, true);
    }
复制代码

 

(7)replace

复制代码
 1 public String replace(char oldChar, char newChar) {
 2         if (oldChar != newChar) {
 3             int len = value.length;
 4             int i = -1;
 5             char[] val = value; /* avoid getfield opcode */
 6 
 7             while (++i < len) {
 8                 if (val[i] == oldChar) {
 9                     break;
10                 }
11             }
12             if (i < len) {
13                 char buf[] = new char[len];
14                 for (int j = 0; j < i; j++) {
15                     buf[j] = val[j];
16                 }
17                 while (i < len) {
18                     char c = val[i];
19                     buf[i] = (c == oldChar) ? newChar : c;
20                     i++;
21                 }
22                 return new String(buf, true);
23             }
24         }
25         return this;
26     }
复制代码

replace方法主要分四步:首先,找到oldChar的下标index。然后将index之前的所有字符复制到新的char数组。接下来将oldchar的值变成newchar。最后将剩下的复制到char数组并返回构造的新String。

注意:replace方法是通过数组复制并构造新的String实现的。因为String是final的,内部的char数组也是final的。

 

5一些问题

(1)codePoint(代码点)

代码点是指一个编码表中的某个字符对应的代码值,也就是Unicode编码表中每个字符对应的数值。

举个例子:

"sfdg".codePointAt(1); 返回结果是102,就是ASCII码中的小写字母 f

你可能感兴趣的:(Java成神之路--基础篇)