Fork me on GitHub

2019-7-17 jdk源码分析(1)-String

jdk源码分析(1)-String

在此之前有无数次下定决心要把JDK的源码大致看一遍。直到最近突然意识到,因为对源码的了解不深导致踩了许多莫名其妙的坑,所以再次下定决心要把常用的类全部看一遍。。。

万事开头难

Java.lang.String 是在lang包下面的一个final类

一、String 声明和成员变量(不可变性)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
1.String 是静态类,不可以被继承,可以被序列化,实现了Comparable接口
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {

2.String的大部分操作都是围绕value这个字符数组定义的。同时String为了并发和一些安全性的考虑被设计成了不可变的类型,表明一旦被初始化完成后就是不可改变的
private final char value[];

注意点:
1.String被声明为final类型:表明String不能被继承,即不能通过继承的方式改变其中的value值。
2.value被声明为final类型:这个final并不能表示value这个字符数组的值不可变,
只是确定了value这个字符数组在内存中的位置是不可变的
3.String并没有提供修改value[]值得方法,并且所有的方法都不是直接返回value[],
而是copy value[]中的值或者新建一个String对象。
所以在通常意义上String是不可变的,但是却不是绝对意义上的不可变

我们可以通过以下几种形式修改value的值

  • final char[] value,只定义了value所指向的内存地址不变,其中的值是可以变的,所以我们可以通过反射直接修改value的值
  • 通过unsafe类替换value[]
  • 通过unsafe类,定位value[]的内存位置修改值

二、构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public String()
public String(String original)
public String(char value[])
public String(char value[], int offset, int count)
public String(int[] codePoints, int offset, int count)
public String(byte ascii[], int hibyte, int offset, int count)
public String(byte ascii[], int hibyte)
public String(byte bytes[], int offset, int length, String charsetName)
public String(byte bytes[], int offset, int length, Charset charset)
public String(byte bytes[], Charset charset)
public String(byte bytes[], int offset, int length)
public String(byte bytes[])
public String(StringBuffer buffer)
public String(StringBuilder builder)

1. 以上15个构造方法除了最后一个,都是将传入的参数copy到value中,并生成hash。这也是符合string的不可变原则

String(char[] value, boolean share) {
// assert share : "unshared not supported";
this.value = value;
}
2. 上面这个构造函数没有复制value数组,而是持有引用,共享value数组。
这是为了加快中间过程string的产生,而最后得到的string都是持有自己独立的value,所以string任然是不可变的

三、常用方法

强调,String方法的所有返回值,都是new的一个新对象,以保证不可变性

1.String.equals

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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;
}

equals首先比较是否指向同一个内存地址,在比较是不是String类,再是长度最后内容注意比较

2.String.hashCode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
1.hashcode使用的数学公式: s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
2.String 经常会用作 hashMap 的 key,希望尽量减少 String 的 hash 冲突
(冲突是指 hash 值相同,从而导致 hashMap 的 node 链表过长,所以通常希望计算的 hash 值尽可能的分散,从而提高查询效率)
3.选择31是因为,如果乘数是偶数,并且结果溢出,
那么信息就会是丢失(与2相乘相当于移位操作)31是一个奇素数,
并且31有个很好的特性(目前大多数虚拟机都支持的优化):31 * i == (i << 5) - i

3.String.intern

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* Returns a canonical representation for the string object. * <p>
* A pool of strings, initially empty, is maintained privately by the
* class {@code String}.
* <p>
* When the intern method is invoked, if the pool already contains a
* string equal to this {@code String} object as determined by
* the {@link #equals(Object)} method, then the string from the pool is
* returned. Otherwise, this {@code String} object is added to the
* pool and a reference to this {@code String} object is returned.
* <p>
* It follows that for any two strings {@code s} and {@code t},
* {@code s.intern() == t.intern()} is {@code true}
* if and only if {@code s.equals(t)} is {@code true}.
* <p>
* All literal strings and string-valued constant expressions are
* interned. String literals are defined in section 3.10.5 of the * <cite>The Java&trade; Language Specification</cite>.
* @return a string that has the same contents as this string, but is guaranteed to be from a pool of unique strings.
*/
public native String intern();

intern这是一个native方法,主要用来查询常量池的字符串 注释中也写了
1. 如果常量池中中存在当前字符串,就会直接返回此字符串(此时当前字符串和常量池中的字符串一定不相同)
2. 如果常量池中没有,就将当前字符串加入常量池后再返回(此时和常量池的实现相关)
3. 这里有关常量池的设计,使用了享元模式

将字符串加入常量池的两种方式:

  • 编译期生成的各种字面量和符号引用
  • 运行期间通过intern方法将常量放入常量池

常量池在不同JDK中的区别:

  • 在 JDK6 以及以前的版本中,字符串的常量池是放在堆的 Perm 区的,Perm 区是一个类静态的区域,主要存储一些加载类的信息,常量池,方法片段等内容
  • 在 JDK7 的版本中,字符串常量池已经从 Perm 区移到正常的 Java Heap 区域
  • 在 JDK8 则直接使用 Meta 区代替了 Perm 区,并且可以动态调整 Mata 区的大小

四、string 其他方法

主要都是操作CharSequence

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public int length() 
public char charAt(int index)
public byte[] getBytes(String charsetName)
public boolean equalsIgnoreCase(String anotherString)
public int compareTo(String anotherString)
public boolean startsWith(String prefix, int toffset)
public boolean endsWith(String suffix)
public int indexOf(int ch, int fromIndex)
public int lastIndexOf(int ch)
public String substring(int beginIndex)
public String concat(String str)
public String replace(char oldChar, char newChar)
public boolean matches(String regex)
public boolean matches(String regex)
public String[] split(String regex, int limit)
public static String join(CharSequence delimiter, CharSequence... elements)
public String toLowerCase(Locale locale)
public String toUpperCase(Locale locale)
public String trim()
public char[] toCharArray()
public static String format(String format, Object... args)
public static String valueOf(Object obj)

对于以上的方法的原理由于内容太多 不一一描述了

五、StringBuilder和StringBuffer

由于String对象是不可变的,所以进行字符串拼接的时候就可以使用StringBuilderStringBuffer两个类

从图中可以看到StringBuilderStringBuffer也是实现的CharSequence接口,同时他们实现了Appendable接口,具有对字符串动态操作的能力.

从他们父类AbstractStringBuilder的源码来看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
abstract class AbstractStringBuilder implements Appendable, CharSequence {
char[] value;
int count;

public void ensureCapacity(int minimumCapacity) {
if (minimumCapacity > 0)
ensureCapacityInternal(minimumCapacity);
}

public AbstractStringBuilder append(***) {
....
}

public AbstractStringBuilder insert(***) {
....
}
}

1.他们同样持有一个字符数组 char[] value,并且每次在对字符串进行操作的时候需要首先对数组容量进行确定,不足的时候需要扩容

2.他们每个对字符串进行操作的方法,都会返回自身,所以我们可以使用链式编程的方式进行操作。
另外现在还有一种通过泛型类定义链式操作的方式

3.StringBuilder 和 StringBuffer 的 API 都是互相兼容的,
只是StringBuffer的每个方法都用的 synchronized 进行同步,所以是线程安全的

操作符重载:

  • 每当我们要就行字符串拼接的时候,自然会使用到+,同时++=也是 java 中仅有的两个重载操作符
  • 可以直接使用StringBuffer或者StringBuilder以提高性能;当然如果遇到类似String s = “a” + “b” + “c” + ...类似的连续加号的时候,JVM 会自动优化为一个 StringBuilder。

六、总结

  1. 主要介绍了String的一些基础的知识 在中间有些方法的源码无法很详细的介绍
  2. 万事开头难 这也算是我对string的一个总结吧
  3. 还有很多细节没有写完,比如 String 在用于锁对象时,需要使用 intern 来保证是同一把锁
-------------本文结束感谢您的阅读-------------
坚持原创技术分享,您的支持将鼓励我继续创作!