JDK源码之String

一、String类型   引用博文连接:  https://blog.csdn.net/ylyg050518/article/details/52352993

成员变量

1 //用于存储字符串
2 private final char value[];
3 
4 //缓存String的hash值
5 private int hash; // Default to 0
6 
7 /** use serialVersionUID from JDK 1.0.2 for interoperability */
8 private static final long serialVersionUID = -6849794470754667710L;

构造方法

//不含参数的构造函数,一般没什么用,因为value是不可变量
public String() {
    this.value = new char[0];
}

//参数为String类型
public String(String original) {
    this.value = original.value;
    this.hash = original.hash;
}

//参数为char数组,使用java.utils包中的Arrays类复制
public String(char value[]) {
    this.value = Arrays.copyOf(value, value.length);
}

//从bytes数组中的offset位置开始,将长度为length的字节,以charsetName格式编码,拷贝到value
public String(byte bytes[], int offset, int length, String charsetName)
        throws UnsupportedEncodingException {
    if (charsetName == null)
        throw new NullPointerException("charsetName");
    checkBounds(bytes, offset, length);
    this.value = StringCoding.decode(charsetName, bytes, offset, length);
}

//调用public String(byte bytes[], int offset, int length, String charsetName)构造函数
public String(byte bytes[], String charsetName)
        throws UnsupportedEncodingException {
    this(bytes, 0, bytes.length, charsetName);
}

常用方法

1,  int hashCode()

 1 public int hashCode() {
 2     int h = hash;
 3     //如果hash没有被计算过,并且字符串不为空,则进行hashCode计算
 4     if (h == 0 && value.length > 0) {
 5         char val[] = value;
 6 
 7         //计算过程
 8         //s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
 9         for (int i = 0; i < value.length; i++) {
10             h = 31 * h + val[i];
11         }
12         //hash赋值
13         hash = h;
14     }
15     return h;
16 }

String类重写了hashCode方法,Object中的hashCode方法是一个Native调用。String类的hash采用多项式计算得来,我们完全可以通过不相同的字符串得出同样的hash,所以两个String对象的hashCode相同,并不代表两个String是一样的。

Object的hashCode方法,

1 public native int hashCode();

源码翻译:

返回该对象的哈希码值。支持此方法是为了提高哈希表(例如 java.util.Hashtable 提供的哈希表)的性能。 
hashCode 的常规协定是: 

在 Java 应用程序执行期间,在对同一对象多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是将对象进行 equals 比较时所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。 
如果根据 equals(Object) 方法,两个对象是相等的,那么对这两个对象中的每个对象调用 hashCode 方法都必须生成相同的整数结果。 
如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么对这两个对象中的任一对象上调用 hashCode 方法不 要求一定生成不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同整数结果可以提高哈希表的性能。 

实际上,由 Object 类定义的 hashCode 方法确实会针对不同的对象返回不同的整数。(这一般是通过将该对象的内部地址转换成一个整数来实现的,但是 JavaTM 编程语言不需要这种实现技巧。)

2, boolean equals(Object anObject)  

 1 public boolean equals(Object anObject) {
 2     //如果引用的是同一个对象,返回真
 3     if (this == anObject) {
 4         return true;
 5     }
 6     //如果不是String类型的数据,返回假
 7     if (anObject instanceof String) {
 8         String anotherString = (String) anObject;
 9         int n = value.length;
10         //如果char数组长度不相等,返回假
11         if (n == anotherString.value.length) {
12             char v1[] = value;
13             char v2[] = anotherString.value;
14             int i = 0;
15             //从后往前单个字符判断,如果有不相等,返回假
16             while (n-- != 0) {
17                 if (v1[i] != v2[i])
18                         return false;
19                 i++;
20             }
21             //每个字符都相等,返回真
22             return true;
23         }
24     }
25     return false;
26 }
1. 内存地址相同,则为真。
2. 如果对象类型不是String类型,则为假。否则继续判断。
3. 如果对象长度不相等,则为假。否则继续判断。
4. 从后往前,判断String类中char数组value的单个字符是否相等,有不相等则为假。如果一直相等直到第一个数,则返回真。

由此可以看出,如果对两个超长的字符串进行比较还是非常费时间的。

3, int compareTo(String anotherString)

public int compareTo(String anotherString) {
    //自身对象字符串长度len1
    int len1 = value.length;
    //被比较对象字符串长度len2
    int len2 = anotherString.value.length;
    //取两个字符串长度的最小值lim
    int lim = Math.min(len1, len2);
    char v1[] = value;
    char v2[] = anotherString.value;

    int k = 0;
    //从value的第一个字符开始到最小长度lim处为止,如果字符不相等,返回自身(对象不相等处字符-被比较对象不相等字符)
    while (k < lim) {
        char c1 = v1[k];
        char c2 = v2[k];
        if (c1 != c2) {
            return c1 - c2;
        }
        k++;
    }
    //如果前面都相等,则返回(自身长度-被比较对象长度)
    return len1 - len2;
}

equals方法是重写了Object的equals方法,而compareTo是因为String implements  Comparable<String>,重写了Comparable的compareTo方法。

boolean startsWith(String prefix,int toffset)

 1 public boolean startsWith(String prefix, int toffset) {
 2     char ta[] = value;
 3     int to = toffset;
 4     char pa[] = prefix.value;
 5     int po = 0;
 6     int pc = prefix.value.length;
 7     // Note: toffset might be near -1>>>1.
 8     //如果起始地址小于0或者(起始地址+所比较对象长度)大于自身对象长度,返回假
 9     if ((toffset < 0) || (toffset > value.length - pc)) {
10         return false;
11     }
12     //从所比较对象的末尾开始比较
13     while (--pc >= 0) {
14         if (ta[to++] != pa[po++]) {
15             return false;
16         }
17     }
18     return true;
19 }
20 
21 public boolean startsWith(String prefix) {
22     return startsWith(prefix, 0);
23 }
24 
25 public boolean endsWith(String suffix) {
26     return startsWith(suffix, value.length - suffix.value.length);
27 }

 4,String concat(String str)

 1 public String concat(String str) {
 2     int otherLen = str.length();
 3     //如果被添加的字符串为空,返回对象本身
 4     if (otherLen == 0) {
 5         return this;
 6     }
 7     int len = value.length;
 8     //复制指定的数组,截取或用 null 字符填充(如有必要),以使副本具有指定的长度。
10     char buf[] = Arrays.copyOf(value, len + otherLen);
11     str.getChars(buf, len);
12     return new String(buf, true);
13 }

 5,String trim()

 1 public String trim() {
 2     int len = value.length;
 3     int st = 0;
 4     char[] val = value;    /* avoid getfield opcode */
 5 
 6     //找到字符串前段没有空格的位置
 7     while ((st < len) && (val[st] <= ' ')) {
 8         st++;
 9     }
10     //找到字符串末尾没有空格的位置
11     while ((st < len) && (val[len - 1] <= ' ')) {
12         len--;
13     }
14     //如果前后都没有出现空格,返回字符串本身
15     return ((st > 0) || (len < value.length)) ? substring(st, len) : this;
16 }
其中的val[st] <= ' ',比较的是字符在字典位置,String trim()只能去掉字符串的前后空格,中间空格去不掉!
可以通过
char[] toCharArray() 得到字符串的字符数组
 
 1  public static void main(String[] args) {
 2         String str = "  klp m ";
 3         //得到字符串字符数组
 4         char[] val = str.toCharArray();
 5 
 6         System.out.println("字符是"+val[4]+"!");
 7         System.out.println("字符字典位置"+Integer.valueOf(val[4])+"!");
 8         System.out.println("字符是"+val[0]+"!");
 9         System.out.println("字符字典位置"+Integer.valueOf(val[1])+"!");
10     }

结果:

1 字符是p!
2 字符字典位置112!
3 字符是 !
4 字符字典位置32!

 6,String intern()

1  public native String intern();

源码解释:

返回字符串对象的规范化表示形式。 
一个初始为空的字符串池,它由类 String 私有地维护。 

当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串(用 equals(Object) 方法确定),则返回池中的字符串。
否则,将此 String 对象添加到池中,并返回此 String 对象的引用。 它遵循以下规则:对于任意两个字符串 s 和 t,当且仅当 s.equals(t) 为
true 时,s.intern() == t.intern() 才为 true。 所有字面值字符串和字符串赋值常量表达式都使用 intern 方法进行操作。字符串字面值在 Java Language Specification 的 §3.10.5 定义。 返回: 一个字符串,内容与此字符串相同,但一定取自具有唯一字符串的池。

详细使用方法及解释参考链接:  https://blog.csdn.net/wjzhang5514/article/details/70209403

 1 public static void main(String[] args) {
 2         String s1 = "HelloWorld";
 3         String s2 = new String("HelloWorld");
 4         String s3 = "Hello";
 5         String s4 = "World";
 6         String s5 = "Hello" + "World";
 7         String s6 = s3 + s4;
 8 
 9         System.out.println(s1 == s2);
10         System.out.println(s1 == s5);
11         System.out.println(s1 == s6);
12         System.out.println(s1 == s6.intern());
13         System.out.println(s2 == s2.intern());
14     }

结果:

1 false
2 true
3 false
4 true
5 false

解释:

s1 创建的 HelloWorld 存在于方法区中的常量池其中的字符串池,而 s2 创建的 HelloWorld 存在于堆中,故第一条 false 。

s5 的创建是先进行右边表达式的字符串连接,然后才对 s5 进行赋值。赋值的时候会搜索池中是否有 HelloWorld 字符串,
若有则把 s5 引用指向该字符串,若没有则在池中新增该字符串。显然 s5 的创建是用了 s1 已经创建好的字面量,故
true 。 第三个比较容易弄错,s6 = s3 + s4; 其实相当于 s6 = new String(s3 + s4); s6 是存放于堆中的,不是字面量。所以 s1 不等于 s6 。 第四个 s6.intern() 首先获取了 s6 的 HelloWorld 字符串,然后在字符串池中查找该字符串,找到了 s1 的 HelloWorld 并返回。
这里如果事前字符串池中没有 HelloWorld 字符串,那么还是会在字符串池中创建一个 HelloWorld 字符串再返回。总之返回的不是堆中的 s6 那个字符串。 第四条也能解释为什么第五条是
false 。s2是堆中的 HelloWorld,s2.intern() 是字符串池中的 HelloWorld 。String s6 = (s3 + s4).intern();
String s7 = (s3 + s4).intern(); 则s7 存储的 HelloWorld 是存放字符串池中!!!

关于String类是不可变类
所谓不可变类,就是创建该类的实例后,该实例的属性是不可改变的,java提供的包装类和java.lang.String类都是不可变类。当创建它们的实例后,其实例的属性是不可改变的。
但是需要注意的是:
String s="abc";
s="def";
s是字符串对象的”abc”引用,即引用是可以变化的,跟对象实例的属性变化没有什么关系,

 String类被设计成不可变的原因

 1,字符串常量池的需要

字符串常量池(String pool, String intern pool, String保留池) 是Java方法区中一个特殊的存储区域, 当创建一个String对象时,假如此字符串值已经存在于常量池中,

则不会创建一个新的对象,而是引用已经存在的对象。 如下面的代码所示,将会在字符串常量池中只创建一个实际String对象. 
代码如下:

1 String s1 = "abcd"; 
2 String s2 = "abcd"; 

假若字符串对象允许改变,那么将会导致各种逻辑错误,比如改变一个对象会影响到另一个独立对象. 严格来说,这种常量池的思想,是一种优化手段.

1 String s1= "ab" + "cd"; 
2 String s2= "abc" + "d"; 

也许这个问题违反新手的直觉, 但是考虑到现代编译器会进行常规的优化, 所以他们都会指向常量池中的同一个对象. 或者,你可以用 jd-gui 之类的工具查看一下编译后的class文件. 

2. 允许String对象缓存HashCode

Java中String对象的哈希码被频繁地使用, 比如在hashMap 等容器中。

字符串不变性保证了hash码的唯一性,因此可以放心地进行缓存.这也是一种性能优化手段,意味着不必每次都去计算新的哈希码. 
3. 安全性

String被许多的Java类(库)用来当做参数,例如 网络连接地址URL,文件路径path,还有反射机制所需要的String参数等, 假若String不是固定不变的,将会引起各种安全隐患。 
假如有如下的代码:

1 boolean connect(string s){
2     if (!isSecure(s)) {
3 throw new SecurityException();
4 }
5     // 如果在其他地方可以修改String,那么此处就会引起各种预料不到的问题/错误
6     causeProblem(s);
7 }

4. 线程安全 
因为字符串是不可变的,所以是多线程安全的,同一个字符串实例可以被多个线程共享。这样便不用因为线程安全问题而使用同步。字符串自己便是线程安全的。

总体来说, String不可变的原因包括 设计考虑,效率优化问题,以及安全性这三大方面.

 ----------------------------------------------------若有不正之处,欢迎大家指正,不胜感激!!!!!

猜你喜欢

转载自www.cnblogs.com/sqy123/p/9120334.html