Java源码分析-String类分析

本头条号将会继续分析JDK8的源码,欢迎关注和收藏,也会将分析笔记开源。

位置:java.lang

String类是除了Object类外,最基础的类,最重要的类,在开发过程中使用最常用的类,也是使用最多的类。String类的定义如下:

<code>

public

final

class

String

implements

java

.

io

.

Serializable

,

Comparable

<

String

>,

CharSequence

/<code>

String类由final修饰符修饰,是不可被子类继承的类,是不可改变的类。String类实现了Serializable、Comparable、CharSequence三个接口,实现Serializable接口,对象就可以进行序列化,实现Comparable接口,String类的对象之间就可以进行比较,CharSequence接口提供了如下方法:

<code> 

int

length

()

;

char

charAt

(

int

index);

CharSequence

subSequence

(

int

start,

int

end);

public

String

toString

()

;/<code>

String实现CharSequence的上述所有接口方法,为String提供计算长度、定位字符串某个位置的字符、返回字符串的子字符串以及标准的输出字符。String类有如下几个重要的属性:

<code> 

private

final

char

value[];

private

int

hash;

private

static

final

ObjectStreamField[] serialPersistentFields =

new

ObjectStreamField[

0

];/<code>

String类底层本质是用char数组进行保存字符的,char数组value属性用来保存字符串,hash属性是哈希码的值,默认值为0,serialPersistentFields属性用于指定哪些字段需要被默认序列化。这些属性都是final修饰,不可改变,String类也没有提供获取和修改这些属性的方法。

常用的构造方法

<code> 

public

String

(

) {

this

.

value

=

""

.

value

; }

public

String

(

String original

) {

this

.

value

= original.

value

;

this

.hash = original.hash }

public

String

(

char

value

[]) {

this

.

value

= Arrays.copyOf(

value

,

value

.length); }

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()); }/<code>

String类是不可变的类,在初始化的过程中都是采用副本进行赋值

。String() 构造器是默认的构造器,用空字符串的副本进行初始化( this.value = "".value;),String(String original)构造器将字符串original的value和hash属性的副本赋值给新的字符串的value和hash属性。String(char value[])将char 数组的复制给this.value,是用 Arrays.copyOf方法复制的, Arrays.copyOf方法底层是新创建一个char数组,然后利用本地方法System.arraycopy将value数组的值复制给新创建char数组。

String(StringBuffer buffer) 构造函数将StringBuffer的char数组复制给字符串,这个构造器用synchronized对buffer加锁,原因是String类是不可变的,在多线程的环境下是安全的,但是StringBuffer类是不安全的,为了保证用StringBuffer初始化字符串时的安全性而加锁。最后一个常用的构造方法是String(StringBuilder builder),利用StringBuilder进行初始化,将StringBuilder的char数组复制给字符串,StringBuilder类是安全的。

用的方法

<code>

public

int

length

(

) {

return

value

.length; }/<code>

length()方法返回字符串的长度,这个长度就是底层char数组的长度。

<code>

public

boolean

isEmpty

(

) {

return

value

.length ==

0

; }/<code>

isEmpty()方法判断字符串的长度是否等于0,实际判断的是底层char数组的长度是否等于0。这个方法具有误导性,实际上并不是判空操作,在开发过程中判断一个字符串是否为空,常用if(str==null || str.length()==0)进行判空操作。

<code>

public

char

charAt

(

int

index) {

if

((index

0

) || (index >=

value

.length)) {

throw

new

StringIndexOutOfBoundsException(index); }

return

value

[index]; }/<code>

charAt(int index) 方法返回给定位置index的字符,首先判断给定位置的index是否合法,如果合法,返回char数组value的index位置的字符,否则,返回


StringIndexOutOfBoundsException异常。

<code>

void

getChars

(

char

dst[],

int

dstBegin) { System.arraycopy(

value

,

0

, dst, dstBegin,

value

.length); }/<code>

getChars(char dst[], int dstBegin)方法将字符串的value数组从dst数组的dstBegin位置复制到dst数组中。利用了 System.arraycopy本地方法复制。

<code>

public

void

getBytes

(

int

srcBegin,

int

srcEnd,

byte

dst[],

int

dstBegin) {

if

(srcBegin

0

) {

throw

new

StringIndexOutOfBoundsException(srcBegin); }

if

(srcEnd >

value

.length) {

throw

new

StringIndexOutOfBoundsException(srcEnd); }

if

(srcBegin > srcEnd) {

throw

new

StringIndexOutOfBoundsException(srcEnd - srcBegin); } Objects.requireNonNull(dst);

int

j = dstBegin;

int

n = srcEnd;

int

i = srcBegin;

char

[] val =

value

;

while

(i < n) { dst[j++] = (

byte

)val[i++]; } }/<code>

getBytes方法将字符串的char数组复制给byte数组。srcBegin参数表示的从value数组的srcBegin位置,srcEnd参数表示的从value数组的srcEnd位置,复制srcBegin位置到srcEnd位置的字符。这个方法首先判断参数是否越界,getBytes方法并不是直接操作字符串中的chat数组value,而是复制了一个value的副本val,原因是避免getfield操作,最后用循环遍历副本val各个位置的值转为byte类型赋值给dst数组。

<code>

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

; }/<code>

String类重写了Object父类的equals方法,首先判断比较的对象anObject是否是这个对象this,如果是直接返回true,否则继续判断这个对象是否属于String类型,如果是的话,判断下anObject对象的value数组的长度是否等于这个字符串的value数组的长度,如果是的话,遍历两个比较对象的value数组中的每个位置的字符是否相等,如果相等返回true,否则就返回false。

equals方法比较巧妙,先判断是否是this和判断两个比较字符之间长度,这两个判断提高了快速判断两个字符串是否相等,只有这两个判断都成立了,才遍历比较两个字符相同位置的字符是否相等。

<code>

public

boolean

contentEquals

(CharSequence cs)

{

if

(cs

instanceof

AbstractStringBuilder) {

if

(cs

instanceof

StringBuffer) {

synchronized

(cs) {

return

nonSyncContentEquals((AbstractStringBuilder)cs); } }

else

{

return

nonSyncContentEquals((AbstractStringBuilder)cs); } }

if

(cs

instanceof

String) {

return

equals(cs); }

char

v1[] = value;

int

n = v1.length;

if

(n != cs.length()) {

return

false

; }

for

(

int

i =

0

; i < n; i++) {

if

(v1[i] != cs.charAt(i)) {

return

false

; } }

return

true

; }/<code>

contentEquals方法是判断字符序列(CharSequence)的内容与字符串的内容是否相等。传入的参数是CharSequence,首先判断传入的参数是否是AbstractStringBuilder类型,AbstractStringBuilder类型是StringBuffer类和StringBuilder类的父类,如果属于AbstractStringBuilder类型,则判断是否属于StringBuffer类型,如果是则用synchronized加锁调用nonSyncContentEquals方法,如果属于StringBuilder类则不加锁调用nonSyncContentEquals方法。如果参数属于String类型,则调用equals方法,如果属于一般的CharSequence类型,则判断字符串的长度与比较的字符序列的长度是否相等,如果不相等返回false,否则继续遍历比较两个比较对象底层char数组的每个位置的字符是否相等。最后再来分析下nonSyncContentEquals方法,nonSyncContentEquals方法如下:

<code>

private

boolean

nonSyncContentEquals

(

AbstractStringBuilder sb

) {

char

v1[] =

value

;

char

v2[] = sb.getValue();

int

n = v1.length;

if

(n != sb.length()) {

return

false

; }

for

(

int

i =

0

; i < n; i++) {

if

(v1[i] != v2[i]) {

return

false

; } }

return

true

; }/<code>

nonSyncContentEquals方法就是判断两个比较对象底层的char数组之间的相同位置的字符是否相等。首先判断比较序列之间的长度是否相等,然后遍历两个对象底层数组相同位置的字符是否相等。

<code>

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

compareTo方法比较两个字符串的大小,首先获取两个字符串的长度,然后获得两个字符串的长度中较小的长度lim,遍历比较两个字符串小于lim范围里字符的大小,当两个字符串相同位置的字符不相等时,返回两个字符相减的结果,如果较小长度lim范围里,两个字符串的字符都相等,那么比较两个字符串的长度大小。

<code>

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;

if

((toffset

0

) || (toffset >

value

.length - pc)) {

return

false

; }

while

(--pc >=

0

) {

if

(ta[to++] != pa[po++]) {

return

false

; } }

return

true

; }

public

boolean

startsWith

(

String prefix

) {

return

startsWith(prefix,

0

); }

public

boolean

endsWith

(

String suffix

) {

return

startsWith(suffix,

value

.length - suffix.

value

.length); }/<code>

startsWith方法是从指定索引开始的字符串的子字符串是否包含指定的前缀。首先进行边界的判断,参数toffset是否小于0,以及toffset是否大于字符串的长度减去指定前缀的长度,如果toffset小于0或者toffset大于字符串的长度减去指定前缀的长度,则返回false,否则,遍历指定前缀与指定索引开始的字符串的子字符串的字符是否都相等,否则返回false。如果都不满足上述的条件,返回true。

startsWith(String prefix)方法和endsWith(String suffix) 都是在startsWith(String prefix, int toffset)方法的基础上来的,只是toffset的值不一样,当toffset等于0时,就变成了startsWith(String prefix)方法;当toffset等于value.length - suffix.value.length时,就变成了endsWith(String suffix)方法。

<code>

public

int

indexOf

(

int

ch,

int

fromIndex) { final

int

max =

value

.length;

if

(fromIndex

0

) { fromIndex =

0

; }

else

if

(fromIndex >= max) {

return

-1

; }

if

(ch < Character.MIN_SUPPLEMENTARY_CODE_POINT) { final

char

[]

value

=

this

.

value

;

for

(

int

i = fromIndex; i < max; i++) {

if

(

value

[i] == ch) {

return

i; } }

return

-1

; }

else

{

return

indexOfSupplementary(ch, fromIndex); } }/<code>

在讲解indexOf(int ch, int fromIndex) 方法时,先了解下Unicode 编码,Unicode 编码是为了解决能够使计算机实现跨语言、跨平台的文本转换及处理。可以容纳世界上所有文字和符号的字符编码方案。本来想讲解下Unicode ,但是发现网上已经通俗易懂讲解Unicode 编码的文章,所以就不展开来讲了,感兴趣的可以参考如下博客:

<code>

http

:/<code>

indexOf(int ch, int fromIndex) 方法首先进行边界判断,如果输入的参数字符ch小于65535,就直接遍历String类型的char数组value,参数小于65535,说明该字符由两个字节组成。当value数组中的字符与参数ch相等,就返回该位置的索引。如果输入的参数字符大于等于65535,就调用indexOfSupplementary(int ch, int fromIndex) 方法,该方法如下:

<code>

private

int

indexOfSupplementary

(

int

ch,

int

fromIndex) {

if

(Character.isValidCodePoint(ch)) { final

char

[]

value

=

this

.

value

; final

char

hi = Character.highSurrogate(ch); final

char

lo = Character.lowSurrogate(ch); final

int

max =

value

.length -

1

;

for

(

int

i = fromIndex; i < max; i++) {

if

(

value

[i] == hi &&

value

[i +

1

] == lo) {

return

i; } } }

return

-1

; }/<code>

当输入参数大于等于两个字节,即大于等于65535时,调用indexOfSupplementary(int ch, int fromIndex) 方法,当输入的参数字符ch是合法的码点,获取输入参数ch的大端字符和小端字符,比较ch的大端字符和小端字符与value数组第i个位置和第i+1个位置的值是否相等,相等就返回索引i。如果没有找到相等的值,就返回-1。

<code>

public

int

lastIndexOf

(

int

ch,

int

fromIndex) {

if

(ch < Character.MIN_SUPPLEMENTARY_CODE_POINT) { final

char

[]

value

=

this

.

value

;

int

i = Math.min(fromIndex,

value

.length -

1

);

for

(; i >=

0

; i--) {

if

(

value

[i] == ch) {

return

i; } }

return

-1

; }

else

{

return

lastIndexOfSupplementary(ch, fromIndex); } }

private

int

lastIndexOfSupplementary

(

int

ch,

int

fromIndex) {

if

(Character.isValidCodePoint(ch)) { final

char

[]

value

=

this

.

value

;

char

hi = Character.highSurrogate(ch);

char

lo = Character.lowSurrogate(ch);

int

i = Math.min(fromIndex,

value

.length -

2

);

for

(; i >=

0

; i--) {

if

(

value

[i] == hi &&

value

[i +

1

] == lo) {

return

i; } } }

return

-1

; }/<code>

lastIndexOf(int ch, int fromIndex)方法与indexOf(int ch, int fromIndex) 方法逻辑基本一样,只是两者在遍历数组value的顺序不一样,lastIndexOf(int ch, int fromIndex)是从数组后面往前面遍历,indexOf(int ch, int fromIndex) 是从数组前面往后面遍历。

indexOf和lastIndexOf方法都很多其他参数的重载方法,都是在indexOf(int ch, int fromIndex) 方法和lastIndexOf(int ch, int fromIndex)方法的基础上拓展而来的,只要理解这两个方法,其他重载的方法都比较简单,这里就不分析了。

<code>

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); }/<code>

substring(int beginIndex)方法返回从beginIndex位置开始的子字符串,首先判断beginIndex是否合法,当beginIndex小于0或者子字符串的小于0时(value.length - beginIndex),抛出
StringIndexOutOfBoundsException异常。当beginIndex等于0时,直接返回this,返回字符串是原来的字符串,否则创建新的String对象返回。

<code>

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); }/<code>

substring(int beginIndex, int endIndex) 方法是返回从beginIndex开始到endIndex位置结束的子字符串。substring(int beginIndex, int endIndex) 首先判断beginIndex和endIndex的合法性,当beginIndex和endIndex不合法时,抛出
StringIndexOutOfBoundsException异常。beginIndex等于0并且endIndex 等于 value.length,返回this(原字符串),否则重新创建一个String对象返回。

<code>

public

String

concat

(

String str

) {

int

otherLen = str.length();

if

(otherLen ==

0

) {

return

this

; }

int

len =

value

.length;

char

buf[] = Arrays.copyOf(

value

, len + otherLen); str.getChars(buf, len);

return

new

String(buf,

true

); }/<code>

concat(String str)方法的作用是拼接两个字符串,当拼接的字符串str的长度等于0,直接返回原字符串。否则通过 Arrays.copyOf将value数组复制到长度为len + otherLen的字符数组buf中,然后将字符串复制到buf数组中,最后用buf数组创建新的字符串返回。

<code>

public

String

replace

(

char

oldChar,

char

newChar) {

if

(oldChar != newChar) {

int

len =

value

.length;

int

i =

-1

;

char

[] val =

value

;

while

(++i < len) {

if

(val[i] == oldChar) {

break

; } }

if

(i < len) {

char

buf[] =

new

char

[len];

for

(

int

j =

0

; j < i; j++) { buf[j] = val[j]; }

while

(i < len) {

char

c = val[i]; buf[i] = (c == oldChar) ? newChar : c; i++; }

return

new

String(buf,

true

); } }

return

this

; }/<code>

replace(char oldChar, char newChar)方法的作用是将字符串中老字符oldChar替换为新字符newChar,如果oldChar等于newChar,直接返回原来的字符串,否则先找到字符串中与oldChar相等的第一个字符的位置i。如果找到字符串中与oldChar相等的第一个字符的位置i,将用两步来替换旧的值,第一步将小于i位置的字符保存在buf,第二部将所有等于oldChar值的替换为newChar,如果不等于oldChar将原来的字符串保存在buf,最后通过buf创建新的String对象。

<code>

public

String

trim

(

) {

int

len =

value

.length;

int

st =

0

;

char

[] val =

value

;

while

((st < len) && (val[st] <=

' '

)) { st++; }

while

((st < len) && (val[len -

1

] <=

' '

)) { len--; }

return

((st >

0

) || (len

value

.length)) ? substring(st, len) :

this

; }/<code>

trim() 方法是去除字符串两边的空字符‘ ’, trim()方法从字符串的前面和后面分别遍历字符,寻找不等于空字符‘ ’的位置,如果原来字符串前面和后面存在空字符串' ',返回去除空字符串' '的子字符串,否则,返回原来的字符串。

<code>

public

char

[]

toCharArray

(

) {

char

result[] =

new

char

[

value

.length]; System.arraycopy(

value

,

0

, result,

0

,

value

.length);

return

result; }/<code>

toCharArray()方法返回字符串的char数组,通过本地方法 System.arraycopy复制字符串的字符到char数组中。该方法中有一段注释说明不能使用Arrays.copyOf复制字符到char数组中,是因为类初始化的顺序问题。这注释也没有很清楚解释为什么toCharArray()中不能用Arrays.copyOf复制字符。经过查找资料,找到一段清楚的解释:

引用:
https://my.oschina.net/u/3268478/blog/3011267

虽然String 和Arrays 都属于rt.jar中的类,但是BootstrapClassloader 在加载这两个类的顺序是不同的。所以当String.class被加载进内存的时候,Arrays此时没有被加载,所以直接使用肯定会抛异常。而System.arrayCopy是使用native代码,则不会有这个问题。


<code>

public

native

String

intern

()

;/<code>

intern()是本地方法,当调用 intern 方法时,如果常量池中已经该字符串,则返回池中的字符串;否则将此字符串添加到常量池中,并返回字符串的引用。对于两个字符串s和t,如果s.intern() == t.intern()为true,则s.equals(t)为true。intern()方法在JVM的函数为
Java_java_lang_String_intern:

<code>JNIEXPORT jobject JNICALL
Java_java_lang_String_intern(JNIEnv *env, jobject 

this

) {

return

JVM_InternString(env,

this

); }/<code>


Java_java_lang_String_intern函数返回调用JVM_InternString函数的结果。JVM_InternString函数的方法为:

<code>JVM_ENTRY(jstring, JVM_InternString(JNIEnv *env, jstring 

str

)) JVMWrapper(

"JVM_InternString"

); JvmtiVMObjectAllocEventCollector oam;

if

(

str

== NULL)

return

NULL; oop string = JNIHandles::resolve_non_null(

str

); oop result = StringTable::intern(string, CHECK_NULL);

return

(jstring) JNIHandles::make_local(env, result); JVM_END/<code>

JVM_InternString函数从StringTable常量池中寻找字符串,StringTable相等于java中hashTable表,如果不存在这个字符串,那么就将这个字符串的引用保存在StringTable常量池中,如果存在这个字符串,就返回这个字符串的引用。StringTable常量池中的字符串越来越多的时候,查找效率会越来越低,所有也不能大量的使用intern(),否则导致性能下降。


分享到:


相關文章: