String实现的接口
public final class String implements java.io.Serializable, Comparable<String>, CharSequence
final
String 在类上修饰了final关键字,这也就是说明,不论你的String 变量如何做修改,原地址上的String对象不会做任何操作,所有的String对象修改,都可以理解为重新生成了一个新的对象。
java.io.Serializable(序列化)
Serializable 接口是序列化。序列化是将对象变为可传输内容的过程, 反序列化则是将可传输内容转化为对象的过程。
Java原生序列化是通过IO包中的ObjectInputStream和ObjectOutputStream实现的。ObjectOutputStream类负责实现序列化, ObjectInputStream类负责实现反序列化。
仅需知道序列化是为了在内存将对象状态保存即可。
该这个序列化接口没有任何方法和域。应该仅仅是标识语义(猜测)
Comparable<String>(可比较)
定义了int compareTo(T o)
方法。
CharSequence(字符序列)
定义如下方法
int length();
char charAt(int index);
CharSequence subSequence(int start, int end);
String toString();
default IntStream chars();
default IntStream codePoints()
注意: IntStream为jdk1.8补充的功能
主要变量
private final char value[];
很明显value是用来存放String 值的一个字符数组,比如 String str = "123"
,实质上是将 str 存放在一个char[] 数组中
private int hash; // Default to 0
hash为hashcode()
一个缓存,否则每次都需要计算一下hashcode
private static final long serialVersionUID = -6849794470754667710L;
serialVersionUID序列化唯一性,避免对象不一致。
private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0];
serialPersistentFields只是一个序列化代码。
构造函数
无参构造函数
public String() {
this.value = "".value;
}
可以看出,如果 String str = new String();
将会得到一个value为空的字符串;
String类型初始化
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
由此处可以看出,String类型初始化对象的value和hash值都是直接拷贝,例子如下
String str = "a123";
String str2 = "a123"
String a = new String("a123");
String b = new String("a123");
String c = new String("a123");
String d = new String("a123");
结合JVM理解,由于使用了new
关键字,一定会在堆中开辟新的内存空间,但是由于Java中根本就不存在两个完全一样的字符串对象,所以这几个之间的关系应该是
- 栈a --> 堆a --> 拷贝自字符串常量池 a123
- 栈b --> 堆b --> 拷贝自字符串常量池 a123
- 栈c --> 堆c --> 拷贝自字符串常量池 a123
- 栈d --> 堆d --> 拷贝自字符串常量池 a123
- 栈str --> 字符串常量池 a123
- 栈str2 --> 字符串常量池 a123
但是由于abcd4个对象都是由 a123的value
和hash
进行拷贝的值,所以
1)java中的==
是内存地址的比较,所以a,b,c,d 互不相等,但是str和str是相等的
2)equals是比较字符串中的value
和hash
,所以a,b,c,d,str,str2都相等
字符数组初始化
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
将字符数组char[]
拷贝给同为字符数组的value
字符数组初始化2
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);
}
该方法的参数意义char value[]
:一个字符数组int offset
:这个数组的指定下标int count
:向后数几位
也就是说,String({'a','b','c'}, 1, 2)
,则是以{'a','b','c'}
下标为[1]的值(‘b’)开始,数2位数的值,即为{'b','c'}
数组为value生成字符串。
通过Unicode码位数组来创造字符串
public String(int[] codePoints, int offset, int count) {
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
if (count <= 0) {
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
if (offset <= codePoints.length) {
this.value = "".value;
return;
}
}
// Note: offset or count might be near -1>>>1.
if (offset > codePoints.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
final int end = offset + count;
// Pass 1: Compute precise size of char[]
int n = count;
for (int i = offset; i < end; i++) {
int c = codePoints[i];
if (Character.isBmpCodePoint(c))
continue;
else if (Character.isValidCodePoint(c))
n++;
else throw new IllegalArgumentException(Integer.toString(c));
}
// Pass 2: Allocate and fill in char[]
final char[] v = new char[n];
for (int i = offset, j = 0; i < end; i++, j++) {
int c = codePoints[i];
if (Character.isBmpCodePoint(c))
v[j] = (char)c;
else
Character.toSurrogates(c, v, j++);
}
this.value = v;
}
这个方法其实是对照Unicode表,来根据int数组在Unicode表的位置,来生成字符数组。
字节数组构造函数
public String(byte ascii[], int hibyte, int offset, int count)
public String(byte ascii[], int hibyte)
这两个鉴于官方给予deprecated,这里就不做分析阅读。
需要指定编码
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, Charset charset) {
if (charset == null)
throw new NullPointerException("charset");
checkBounds(bytes, offset, length);
this.value = StringCoding.decode(charset, bytes, offset, length);
}
字段意义和字符数组构造类似,对bytes数组进行范围指定切割
public String(byte bytes[], String charsetName)
throws UnsupportedEncodingException {
this(bytes, 0, bytes.length, charsetName);
}
public String(byte bytes[], Charset charset) {
this(bytes, 0, bytes.length, charset);
}
这两个函数只是默认为bytes不做范围切割
不指定编码(选择默认编码)
按照decode()
方法描述,如果没有指定编码,会以ISO-8859-1进行解码
byte是网络传输或存储的序列化形式,
所以在很多传输和存储的过程中需要将byte[]数组和String进行相互转化,
byte是字节,char是字符,字节流和字符流需要指定编码,不然可能会乱码,
bytes字节流是使用charset进行编码的,想要将他转换成unicode的char[]数组,
而又保证不出现乱码,那就要指定其解码方法
public String(byte bytes[]) {
this(bytes, 0, bytes.length);
}
StringBuffer和StringBuilder
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());
}
由上可以知道,由于synchronized
关键字的存在,StringBuffer是线程安全而builder不是。
内部方法
length()和isEmpty()
public int length() {
return value.length;
}
public boolean isEmpty() {
return value.length == 0;
}
即为简单的判断char[]
数组的长读
charAt(int index)和codePointAt(int index)
public char charAt(int index) {
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return value[index];
}
public int codePointAt(int index) {
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return Character.codePointAtImpl(value, index, value.length);
}
charAt(int index)
返回当前字符串在下标为index
的字符,即为char[index]
codePointAt(int index)
返回当前字符串在下标为index
的unicode字符编码
另: 还有codePointBefore(int index)
,codePointCount(int beginIndex, int endIndex)
,offsetByCodePoints(int index, int codePointOffset)
等unicode方法,这里就不讨论。
equals(Object anObject)
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方法
- 先比较内存地址是否一致,如果内存地址一致,直接返回true。
- 如果内存地址不一致,判断anObject是否为String的实例,如果不是,直接返回false。
- 如果内存地址不一致且anObject为String的实例,则先比较 value数组长度,再遍历当前String和 anObject的字符数组
总结
只要两个String的value一致,equals()
就会返回true,不用考虑地址和hash值。
equalsIgnoreCase()
public boolean equalsIgnoreCase(String anotherString) {
return (this == anotherString) ? true
: (anotherString != null)
&& (anotherString.value.length == value.length)
&& regionMatches(true, 0, anotherString, 0, value.length);
}
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;
}
在equalsIgnoreCase
三目运算的最后,执行了regionMatches()
,该方法将两个比较对象的char[] value
都提出来,进行遍历比值,只要toLowerCase()
和toUpperCase()
其中一个达成,即认为当前字符为相等。
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;
}
- hash值是int类型。
- 如果String的length==0或者hash值为0,则直接返回0
- 如果上述条件不满足,则通过算法计算hash值
indexOf()
public int indexOf(int ch) {
return indexOf(ch, 0);
}
这一种indexOf()传入的参数认为是unicode码下标,通过查询码表获取字符再做循环
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);
}
static int indexOf(char[] source, int sourceOffset, int sourceCount,
String target, int fromIndex) {
return indexOf(source, sourceOffset, sourceCount,
target.value, 0, target.value.length,
fromIndex);
}
static int indexOf(char[] source, int sourceOffset, int sourceCount,
char[] target, int targetOffset, int targetCount,
int fromIndex) {
// 当字符串的开始判断位置大于当前字符串的大小时,如果目标字符串的长度为0,则返回0,否则返回-1;
if (fromIndex >= sourceCount) {
return (targetCount == 0 ? sourceCount : -1);
}
// 如果字符串的开始判断位小于0,则使其等于0(从开头进行判断)
if (fromIndex < 0) {
fromIndex = 0;
}
// 如果目标字符串长度为0,则返回0
if (targetCount == 0) {
return fromIndex;
}
// 获取目标字符的第一个值
char first = target[targetOffset];
// 这里限定一下遍历的上线,因为寻找第一个匹配字符,不需要查满整个数组
int max = sourceOffset + (sourceCount - targetCount);
for (int i = sourceOffset + fromIndex; i <= max; i++) {
/* Look for first character. */
if (source[i] != first) {
// 找到第一个和first相等的位置,找不到就继续加,但是个人认为++i和i++这种不易区分的代码尽量不要写在这种地方
while (++i <= max && source[i] != first);
}
/* Found first character, now look at the rest of v2 */
// 获得目标字符串的长度以及下标终点,如果遍历后内容一致,就返回,否则继续循环
if (i <= max) {
int j = i + 1;
int end = j + targetCount - 1;
for (int k = targetOffset + 1; j < end && source[j]
== target[k]; j++, k++);
if (j == end) {
/* Found whole string. */
return i - sourceOffset;
}
}
}
return -1;
}
public int indexOf(String str)
是一个较为常用的方法,判断当前字符串内是否包含指定字符串,并返回第一次返回的位置。
前面的所有方法最终都将皮球踢到了static int indexOf(char[] source, int sourceOffset, int sourceCount, char[] target, int targetOffset, int targetCount, int fromIndex)
方法中。
substring
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);
}
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);
}
substring是用来裁切字符串方法,通过指定下标,获取字符数组区间,再调用构造函数,返回新串。
concat
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);
}
concat用于链接字符串,还是用数组的copyOf实现,制作一个新的数组。
valueOf(int /long/so on)
valueOf是直接调用封装类的toString方法。
资料参考
https://yq.aliyun.com/articles/672181
关于JVM部分由于没有看过JVM源码,结论都是通过书籍以及别人博客学习,若有错误欢迎指正 ?