【刨根问底】String源码解析

String这个类,到底是个什么玩意?底层是怎么搞的呢?本文带来详细分析:

开始发车,String源码: 


String定义


public final class String implements java.io.Serializable,	 Comparable<String>, CharSequence {	 //... 	 }
  • String是一个final类,既不能被继承的类

  • String类实现了java.io.Serializable接口,可以实现序列化

  • String类实现了Comparable<String>,可以用于比较大小(按顺序比较单个字符的ASCII码)

  • String类实现了 CharSequence 接口,表示是一个有序字符的序列,因为String的本质是一个char类型数组

System常用的构造函数:

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

再看看vlaue变量是个什么?

private final char value[];

是个char数组,并且也是final修饰,

由此证明:

String的内容是不可变的,并且String的底层就是使用char数组来存储的。

再看看hash变量是个什么?

/** Cache the hash code for the string */	private int hash; // Default to 0

hash用来保存某一个String实例自己的哈希值,可以说是哈希值的一个缓存,因此String特别适合放入HashMap中,作为key来使用。每次插入一个键值対时,不需要重新计算key的哈希值,直接取出key的缓存hash值即可,在一定程度上,加快了HashMap的效率


常用的构造函数


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

接收一个字符数组,利用Arrays.copyOf()方法进行拷贝。Arrays.copyOf()方法源码:。

public static char[] copyOf(char[] original, int newLength) {	     //重新new了一个数组copy用来存储	     char[] copy = new char[newLength];	     System.arraycopy(original, 0, copy, 0,	Math.min(original.length, newLength));	      return copy;	 }

System.arraycopy()方法源码。是个native修饰的方法,直接调用底层C或者其他语言。

public static native void arraycopy(Object src,  int  srcPos,	Object dest, int destPos,	int length);

从以上源码,可以看得出,这个构造方法没有直接使用传入的字符数组的引用,而是使用该数组的一个拷贝,保证了String类的不可变性。我们无法通过在外部改变此数组中某些元素的值,来改变构造后的String的值。

解码构造函数:

public String(byte bytes[], Charset charset) {	this(bytes, 0, bytes.length, charset);	}	public String(byte bytes[], int offset, int length, Charset charset) {	if (charset == null)	throw new NullPointerException("charset");	//参数校验    	checkBounds(bytes, offset, length);	//解码	this.value =  StringCoding.decode(charset, bytes, offset, length);	}

接收一个字符数组,从offset位置开始复制,一共选取count位:

public String(char value[], int offset, int count) {	if (offset < 0) {	throw new StringIndexOutOfBoundsException(offset);	}	if (count <= 0) {	if (count < 0) {	throw new StringIndexOutOfBoundsException(count);	}	if (offset <= value.length) {	this.value = "".value;	return;	}	}	// Note: offset or count might be near -1>>>1.	if (offset > value.length - count) {	throw new StringIndexOutOfBoundsException(offset + count);	}	this.value = Arrays.copyOfRange(value, offset, offset+count);	}


常用方法


equals、hashCode、indexOf、valueOf等方法。

equals() 方法

源码:

public boolean equals(Object anObject) {	//如果是同一个对象则返回true	if (this == anObject) {	return true;	}	//判断类型是否为String类型	if (anObject instanceof String) {	String anotherString = (String)anObject;	int n = value.length;	//判断长度	if (n == anotherString.value.length) {	                //把两个String的char数组进行比较	char v1[] = value;	char v2[] = anotherString.value;	int i = 0;	while (n-- != 0) {	                    //只要有一个不一样就返回false	if (v1[i] != v2[i])	return false;	i++;	}	return true;	}	}	return false;	}

equals()方法一上来没有直接比较两个字符串的字符数组元素,在比较超长的字符串时,节省了大量的时间。

尤其是这些代码的风格,也希望大家看看人家是怎么写代码的。

hashCode() 方法

源码:

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

String类同样也重写了hashCode()方法,用于计算String实例的哈希值。

哈希值相同的两个字符串不一定相同,相同的字符串的哈希值一定相同。

 indexOf 方法 

indexOf(String str):返回str第一次出现在字符串中的位置

public int indexOf(String str) {	return indexOf(str, 0);	}	public int indexOf(String str, int fromIndex) {	return indexOf(value, 0, value.length,	str.value, 0, str.value.length, fromIndex);	}

其实调用的是另外一个重载方法indexOf()方法源码:

static int indexOf(char[] source, int sourceOffset, int sourceCount,	char[] target, int targetOffset, int targetCount,	int fromIndex) {	//1、当开始查找位置 大于等于 源字符串长度时,如果[查找字符串]为空,则:	//返回字符串的长度,否则返回-1.	if (fromIndex >= sourceCount) {	return (targetCount == 0 ? sourceCount : -1);	}	//2、如果fromIndex 小于 0,则从 0开始查找。	if (fromIndex < 0) {	fromIndex = 0;	}	//3、如果[查找字符串]为空,则返回fromIndex	if (targetCount == 0) {	return fromIndex;	}	//4、开始查找,从[查找字符串]中得到第一个字符,标记为first	char first  = target[targetOffset];	//4.1、计算[源字符串最大长度]	int max = sourceOffset + (sourceCount - targetCount);	//4.2、遍历查找	for (int i = sourceOffset + fromIndex; i <= max; i++) {	//4.2.1、从[源字符串]中,查找到第一个匹配到[目标字符串]first的位置	//for循环中,增加while循环	/* Look for first character. */	if (source[i] != first) {	while (++i <= max && source[i] != first);	}	//4.2.2、如果在[源字符串]中,找到首个[目标字符串],	//则匹配是否等于[目标字符串]	/* Found first character, now look at the rest of v2 */	if (i <= max) {	//4.2.2.1、得到下一个要匹配的位置,标记为j	int j = i + 1;	//4.2.2.2、得到其余[目标字符串]的长度,标记为end	int end = j + targetCount - 1;	//4.2.2.3、遍历,其余[目标字符串],从k开始,	//如果j不越界(小于end,表示:其余[目标字符串]的范围),	//同时[源字符串]==[目标字符串],则	//自增,继续查找匹配。j++、k++	for (int k = targetOffset + 1; j < end && source[j] ==	target[k]; j++, k++);	//4.2.2.4、如果j与end相等,则表示:	//源字符串中匹配到目标字符串,匹配结束,返回i。	if (j == end) {	/* Found whole string. */	return i - sourceOffset;	}	}	}	//其余情况,返回-1.	return -1;	}

intern()方法

源码:

public native String intern();

可以看得出,它是一个native修饰的本地方法。当调用此方法时,会首先在方法区中的常量池中使用equals()寻找是否存在此字符串,如果存在,直接返回此字符串的引用。如果不存在时,会首先将此字符串添加到常量池中,再返回该字符串的引用。

这个方法也是面试常问之一,后面会专门针对这个方法一个分享

valueOf()方法


public static String valueOf(long l) {	return Long.toString(l);	}

这里调用的是Long里的toString方法。

public static String toString(long i) {	if (i == Long.MIN_VALUE)	return "-9223372036854775808";	int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);	char[] buf = new char[size];	getChars(i, size, buf);	return new String(buf, true);	}

这里最关键的是stringSize()方法

static int stringSize(long x) {	long p = 10;	for (int i=1; i<19; i++) {	if (x < p)	return i;	p = 10*p;	}	return 19;	}

到这里大家是不是很熟悉了,

比如书:Long a=100;这里就是100<10*10*10,所以长度返回是3

然后创建char数据,然后拆分a放进char数组中,然后char数组转换成String字符串。

 //这里使用的default修饰,因为String和Long在他同一个包目录下。	String(char[] value, boolean share) {	// assert share : "unshared not supported";	this.value = value;	  }

compareTo()方法

案例和源码:

 public void test() {	// 因为o的ASCII码为: 111	// 因为a的ASCII码为: 97	// 所以差为 : 111 - 97 = 14	// 返回值为:14, 与compareTo返回结果一致	System.out.println(compares("hellojava", "hellajava"));	}
public static int compares(String firstString, String lastString) {	/*	          * 算法思路分析: 	          *     1. 获取2个字符串, 首先把2个字符串都转化为字符数组 (为后面一个一个字符进行比较做铺垫)	          *     2. 获取2个字符串的长度, 并把最短的字符串长度作为循环的次数 (这样可以避免数组越界的异常)	          *     3. 把2个字符串从0开始遍历, 比较每一个字符, 若字符不相等时, 则返回两个字符串的差值	          *     4. 如果遍历的字符串都相等时, 则返回两个字符串的长度差	          * 	          * 方法结果: 	          *     1. 若两个字符串长度和字符都相等时, 则返回0	          *     2. 若两个字符长度不相等, 但大串完全包含(顺序和字符都相等)小串字符时, 则返回两个字符串的长度的差值	          *         举例: 	          *             大串: helloworlds	          *             小串: helloworld	          *             因为大串完全包含小串, 所以返回长度的差值, 为1	          *     3. 若两个字符串长度和字符都不相等时, 则返回比较过程中, 某个索引位置上的字符之差	          *         举例: 	          *             串1: hellojavas	          *             串2: hellajava	          *             遍历比较后, 索引4的字符不同, 所以返回两个字符的差值14, 'o' - 'a' = 14	          */	         	/*	          * 1. 获取2个字符串, 首先把2个字符串都转化为字符数组 (为后面一个一个字符进行比较做铺垫)	          */	char[] firstCh = firstString.toCharArray();	char[] lastCh = lastString.toCharArray();	         	/*	          * 2. 获取2个字符串的长度, 并把最短的字符串长度作为循环的次数 (这样可以避免数组越界的异常)	          */	int firstLength = firstCh.length;	int lastLength = lastCh.length;	int lim = Math.min(firstLength, lastLength);		// 用k记录比较的索引	int k = 0;	while(k < lim) {	char c1 = firstCh[k];	char c2 = lastCh[k];		// 3. 把2个字符串从0开始遍历, 比较每一个字符, 若字符不相等时, 则返回两个字符串的差值	if(c1 != c2) {	return c1 - c2;	}		// 如果字符相等, 则让索引加1	k++;	}		// 4. 如果遍历的字符串都相等时, 则返回两个字符串的长度差	return firstLength - lastLength;	}

按字典顺序比较两个字符串。 比较是基于字符串中每个字符的Unicode值。 由该String对象表示的字符序列按字典顺序与由参数字符串表示的字符序列进行比较。 如果String对象按字典顺序排列在参数字符串之前,结果为负整数。 结果是一个正整数,如果String对象按字典顺序跟随参数字符串。 如果字符串相等,结果为零; compareTo返回0 ,当equals(Object)方法将返回true 。

ok,今天就分享到这里。如果你有一点点收获那我也没有白写。


猜你喜欢

转载自blog.51cto.com/10983206/2563640