【JAVA源码分析——Java.lang】String源码分析

时间:2021-06-09 16:02:48

String 基本实现

初学java经常会误认为String是java基本类型,实际上String并非Java基本类型,String本质上是对char数组的封装

以下是String实现相关源码

    /** 
* 字符实际存储方式
* 由于是fianl,所以String在创建之后,字符数组是无法进行改变
* 所以对于String重新赋值,实际上是对重新划分内存空间,创建String
*/

private final char value[];

/**
* 构造方法
* 进行new操作的时候,实际上是把原字符串的字符数组赋给新字符串的字符数组
**/

public String(String original) {
this.value = original.value;
this.hash = original.hash;
}

/**
* 构造方法
* 实质上String 的创建是划分内存,创建字符数组
* Arrays.copyOf()实际调用底层System.arraycopy方法,进行内存划分
**/

public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}

String 接口分析

String类型所实现的接口

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

Serializable接口和Comparable接口大家都比较熟悉,一个是序列化接口,一个是比较able类接口,分别对应

//序列化标记号
private static final long serialVersionUID = -6849794470754667710L;

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;
}

CharSequence接口,着这里我们得看一下,为什么常用的String类型不能算是基本类型了

//String的实际内部实现
private final char value[];

CharSequence接口的基本定义节选如下,基本意思是 该接口定义的是 可读 的 字符 序列,提供了 标准 只读对不同类型的 字符序列。

A CharSequence is a readable sequence of char values. This interface provides uniform, read-only access to many different kinds of char sequences. 

根据String的定义来看,字符序列实际的体现就是String实际内容是char型数组,只读体现在final上。Final描述的属性是常量属性,对于数组来说,其数组引用无法改变。对于这方面的讨论,之后会有详细的讨论。

//CharSequence提供了如下方法

//包内可见
int length();
char charAt(int index);
CharSequence subSequence(int start, int end);

//公共
public String toString();

这几种方法也常用到其中subSequence的实际实现就是调用了subString方法

public CharSequence subSequence(int beginIndex, int endIndex) {
return this.substring(beginIndex, endIndex);
}

此处的toString方法是将实现该接口的对象提供可转换为 字符序列的方法,虽然命名上与Object的toString方法重复,但从理念上是不同的

//Object toString方法
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

String 加和等于的实现

记得开始学习String时候,对于String的最大疑惑就是 为何String 能实现 +和= 却不是基本类型。

实际上如果我们做个很有意思的实验的话,可以看到如下结果。

//原始代码
System.out.println("Hello" + " world!");

//反编译代码
System.out.println("Hello world!");

编译器在进行编译之时,会对不必要的代码进行优化,也就是说一些简单的+运算,编译器已经帮我们做了优化。

//源代码
public static void main(String[] args) {
String s1 = new String("abcd");

char[] chars = {'a', 'b', 'c', 'd'};

String s2 = new String(chars);

// System.out.println("s1:" + s1);
// System.out.println("s2:" + s2);
}

//反编译
Code:
0: new #2 // class java/lang/String
3: dup
4: ldc #3 // String abcd
6: invokespecial #4 // Method java/lang/String."<init>":(Ljava/lang/String;)V
9: astore_1
10: ldc #5 // String 1234
12: astore_2
13: aload_1
14: astore_3
15: new #2 // class java/lang/String
18: dup
19: aload_1
20: invokespecial #4 // Method java/lang/String."<init>":(Ljava/lang/String;)V

从反编译来看,实际上String操作=实际上,是对char数组部分内存进行复制

静态String + 实现

我们知道,java文件在运行之后会被编译为class文件,class文件的基本信息会在jvm方法区中的运行常量池中进行加载,其加载之时就包括字面量的相关信息。实际上反映在jvm之中的字面量,在加载之初就会开辟内存进行存储。

动态String + 实现
JAVA堆主要负责 对象的实例分配,虚拟机栈负责对象引用以及基本数据类型。而在运行时,基本数据类型的内存分配主要集中在虚拟机栈中,而堆中是对于基本数据类型的引用。

加的问题解决了,但final的String value是如何实现等于的呢?
首先我们可以想到 char[] value = “”这种双引号实现的赋值是可以的,但final修饰的char数组是无法重新引用新的内存区域的,也就是说 char数组可以重新赋值数组元素,但不可以变更数组长度。但实际用到的String 长度变更的赋值是如何实现的呢?

实际上

String a = "abc";

String a = a + "d"

String a = new String("abc" + d");

实际上发生指针改变的Stirng类的引用指正改变,此时的内存划分实在虚拟机栈中的,JVM主要GC实现在堆内存中,因此这种方式一方面浪费内存,另外一方面效率十分差。
在上文中可以看到,底层对于String的赋值工作其实是System.arraycopy进行内存分配,但由于final决定了String不能进行字符数组扩容及改变,所以实际上是重新new了一个String对象。


为了解决String在字符变换时的效率差问题,JDK提供了StringBuffer和StringBuilder

//与String相比,减去了Comparable接口,两个都继承了AbstractStringBuilder
public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence


public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence


//AbstractStringBuilder的定义如下,其增加了Appendable,对应的就是append方法
abstract class AbstractStringBuilder implements Appendable, CharSequence
//实际字符数组存储
char[] value;

StringBuffer和StringBuilder都继承了AbstractStringBuilder ,实际存储方式取消了final修饰符,所以StringBuffer和StringBuilder的字符数组长度可变且可以重新赋值

为了给StringBuilder和StringBuffer提供 +的功能,AbstractStringBuilder通过Appendable接口提供了append方法。

/**
* 属性,Char 数组
*/
char[] value;

/**
* 属性,标记数组长度
*/
int count;

/**
* 无参数构造方法,子类 序列化必须提供
*/
AbstractStringBuilder() {
}

/**
* 创建一个指定容量的char数组
*/
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}


//StringBuffer和StringBuilder都是通过调用父类的构造方法进行构造,默认申请16个长度
public StringBuffer() {
super(16);
}

/**
* 自定义长度申请
*/
public StringBuffer(int capacity) {
super(capacity);
}

StringBuffer和StringBuilder实现capacity变换长度是如何实现的呢?

//AbstractStringBuilder的expandCapacity方法
void expandCapacity(int minimumCapacity) {
int newCapacity = value.length * 2 + 2;
if (newCapacity - minimumCapacity < 0)
newCapacity = minimumCapacity;
if (newCapacity < 0) {
if (minimumCapacity < 0) // overflow
throw new OutOfMemoryError();
newCapacity = Integer.MAX_VALUE;
}

//这里 Arrays类虽然常用,但copyOf方法没有接触过吧
value = Arrays.copyOf(value, newCapacity);
}

//Arrays copyOf
public static char[] copyOf(char[] original, int newLength) {
char[] copy = new char[newLength];
//调用System.arraycopy
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}

/**
注释如下
Copies an array from the specified source array, beginning at the specified position, to the specified position of the destination array. A subsequence of array components are copied from the source array referenced by src to the destination array referenced by dest. The number of components copied is equal to the length argument. The components at positions srcPos through srcPos+length-1 in the source array are copied into positions destPos through destPos+length-1, respectively, of the destination array.
*/
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);

String Buffer和StringBuilder主要的区别在于,StringBuffer是线程安全的,StringBuilder非线程安全的。

其主要实现是通过synchronized实现线程安全

//String Buffer实现
public synchronized StringBuffer append(Object obj) {
super.append(String.valueOf(obj));
return this;
}

//String Builder实现
public StringBuilder append(double d) {
super.append(d);
return this;
}

compareToIgnoreCase、compareTo 和 regionMatches

compareToIgnoreCase这个方法在String 中的实现很有意思,String在源码中生成静态内部类CaseInsensitiveComparator实现忽略大小比较。

仔细查看源码,我们可以看到compare实现了两次比较,之所以使用两次比较,是因为在不同语言环境下(格鲁吉亚语 、希腊语等)单独的toUpperCase或toLowerCase解决不了比较问题。因此使用两次比较。

针对上面的问题,为解决平台平台无关化,考虑String日后扩展,因此将该比较单独抽象做静态内部类。

此处,个人认为对于可访问性最小化原则,compare访问性应该该更为包内可见。

public static final Comparator<String> CASE_INSENSITIVE_ORDER
= new CaseInsensitiveComparator();

//静态内部类
private static class CaseInsensitiveComparator
implements Comparator<String>, java.io.Serializable {

private static final long serialVersionUID = 8575799808933029326L;

public int compare(String s1, String s2) {
int n1 = s1.length();
int n2 = s2.length();
int min = Math.min(n1, n2);
for (int i = 0; i < min; i++) {
char c1 = s1.charAt(i);
char c2 = s2.charAt(i);
if (c1 != c2) {
//第一次比较
c1 = Character.toUpperCase(c1);
c2 = Character.toUpperCase(c2);
if (c1 != c2) {
//第二次比较
c1 = Character.toLowerCase(c1);
c2 = Character.toLowerCase(c2);
if (c1 != c2) {
// No overflow because of numeric promotion
return c1 - c2;
}
}
}
}
return n1 - n2;
}
}

public int compareToIgnoreCase(String str) {
return CASE_INSENSITIVE_ORDER.compare(this, str);
}

CaseInsensitiveComparator实现Comparator和Serializable接口。Comparator这个接口有没有看到有些熟悉,String里边扩展了接口comparable接口。 jdk中对于命名使用是比较规范的,这点值得借鉴。

public interface Comparator<T> {
int compare(T o1, T o2);
boolean equals(Object obj);
}

public interface Comparable<T> {
public int compareTo(T o);
}

对于comparator,我们可以看到compare方法的实现是趋于静态工厂方法的,而Comparable方法是属于类型能力接口,所比较的是对象主体。

两次比较的策略所能解决平台无关化问题,但随之带来的问题是性能的牺牲。String中提供了regionMatches,regionMatches所返回的是boolean型值,所以只能匹配是否相等,并不能比较大小,但根据源码来看,该方法提供了单次比较的实现。

public boolean regionMatches(boolean ignoreCase, int toffset,
String other, int ooffset, int len) {
char ta[] = value;
int to = toffset;
char pa[] = other.value;
int po = ooffset;
// Note: toffset, ooffset, or len might be near -1>>>1.
if ((ooffset < 0) || (toffset < 0)
|| (toffset > (long)value.length - len)
|| (ooffset > (long)other.value.length - len)) {
return false;
}
while (len-- > 0) {
char c1 = ta[to++];
char c2 = pa[po++];
if (c1 == c2) {
continue;
}
if (ignoreCase) {
// If characters don't match but case may be ignored,
// try converting both characters to uppercase.
// If the results match, then the comparison scan should
// continue.
char u1 = Character.toUpperCase(c1);
char u2 = Character.toUpperCase(c2);
if (u1 == u2) {
continue;
}
// Unfortunately, conversion to uppercase does not work properly
// for the Georgian alphabet, which has strange rules about case
// conversion. So we need to make one last check before
// exiting.
if (Character.toLowerCase(u1) == Character.toLowerCase(u2)) {
continue;
}
}
return false;
}
return true;
}

String 常用方法介绍

返回指定位置的字符,从0开始记

public char charAt(int index) 

返回指定位置的字符的unicode编码,从0开始记

public int codePointAt(int index)

返回指定位置前一位的字符的unicode编码,从0开始记
其实也可以理解为指定位置字符unicode编码,从1开始记

public int codePointBefore(int index)

查询范围 endIndex-beginIndex
endIndex范围取 0~ length
endIndex范围取 0~ length + 1

public int codePointCount(int beginIndex, int endIndex)

比较大小
从首位开始比较,若不同输出 s - anotherString 值
相同输出0

public int compareTo(String anotherString)

比较大小忽略大小写

public int compareToIgnoreCase(String str)

合并字符串
相当于append

public String concat(String str)

替换字符串

public String replace(char oldChar, char newChar)

正则表达式匹配

public boolean matches(String regex)

判断是否包含
CharSequence接口String及其配套类(StringBuffer、StringBuilfer)实现

public boolean contains(CharSequence s)

替换 正则表达式匹配到的第一个 字符串

public String replaceFirst(String regex, String replacement)

替换 正则表达式匹配到的所有字符串

public String replaceAll(String regex, String replacement)

替换字符串中 target 为 replacement

public String replace(CharSequence target, CharSequence replacement)