常用的几个方法
length()
public int length() {
return value.length >> coder();
}
byte coder() {
//这里根据压缩标识,来返回byte值,其中UTF16的值是1,coder是不确定的
//但是可以确定的是,coder的值跟对应编码方式下一个字符所占的byte数组长度有关
//例如:UTF16类型的数据,一个字符会占据两个byte数组位,所以计算长度时需要将byte数组长度缩小2倍
return COMPACT_STRINGS ? coder : UTF16;
}
它返回的是字符串的长度,length的数值其实是字符串中Unicode的code units,即:编码单元数目。
isEmpty()
public boolean isEmpty() {
return value.length == 0;
}
char charAt(int index)
返回字符串中指定下标index所在的那个字符,index的取值方位必须是0到length-1的方位,并且字符串的第一个字符的下标是0,也就是说是从0开始计数。
public char charAt(int index) {
//判断是否是压缩格式版的字符串
if (isLatin1()) {
//压缩版:放弃高八位
return StringLatin1.charAt(value, index);
} else {
//同时保留高八位和低八位
return StringUTF16.charAt(value, index);
}
}
private boolean isLatin1() {
return COMPACT_STRINGS && coder == LATIN1;
}
//StringLatin1.charAt
public static char charAt(byte[] value, int index) {
if (index < 0 || index >= value.length) {
throw new StringIndexOutOfBoundsException(index);
}
//这里使用了0xff,它就是十进制的255,二进制就是11111111
//对于Java中,byte类型转成int,高位会随机填充值,通过它来保证低位可靠性,并且放弃高位数
//因为&操作中,超过0xff的部分,全部都会变成0,而对于0xff以内的数据,它不会影响原来的值
return (char)(value[index] & 0xff);
}
//StringUTF16.charAt
public static char charAt(byte[] value, int index) {
checkIndex(index, value);
return getChar(value, index);
}
static char getChar(byte[] val, int index) {
assert index >= 0 && index < length(val) : "Trusted caller missed bounds check";
index <<= 1;
//HI_BYTE_SHIFT和LO_BYTE_SHIFT跟当前系统环境有关,存在一个大端还是小端的判定isBigEndian()
//所谓大小端:大端就是高字节存储在低位,低字节存储在高位,小端正好与之想反
return (char)(((val[index++] & 0xff) << HI_BYTE_SHIFT) |
((val[index] & 0xff) << LO_BYTE_SHIFT));
}
static {
if (isBigEndian()) {
HI_BYTE_SHIFT = 8;
LO_BYTE_SHIFT = 0;
} else {
HI_BYTE_SHIFT = 0;
LO_BYTE_SHIFT = 8;
}
}
仔细考虑上面的StringUTF16.charAt方法,它有一个index<<=1的操作,之所以会这样,也是跟byte存储有关,因为StringUTF16不会丢弃任何一位,所以它的byte数组相邻两个位置一个存储高八位,一个存储低八位。所以任何传入的index,如果换算成byte数组中的位置需要将其值扩展成二倍作为index的开始,并且byte数组存储这些数据的时候,将高八位放在数组前一个位置,低八位放在数组的后一个位置,形成
boolean equals(Object anObject)
String重写了equals方法,当且仅当传入的参数是一个String类型,并且其中的字符序列与调用对象中的字符序列相同才会返回true。
public boolean equals(Object anObject) {
//首先用==比较,如果相等,说明就是同一个对象,肯定是相等的
if (this == anObject) {
return true;
}
//前置判断:必须是String类型,否则肯定不相等
if (anObject instanceof String) {
String aString = (String)anObject;
//coder方法其实就是获取字符串采用的编码方式,如果编码方式都不一样,肯定结果为false
if (coder() == aString.coder()) {
//根据数据是否是压缩数据,采用不同的比较方式
//数据压缩弃了高八位,一个八位就只占据一个byte数组位
//如果是非压缩版,一个字符对应两个byte数组位
return isLatin1() ? StringLatin1.equals(value, aString.value)
: StringUTF16.equals(value, aString.value);
}
}
return false;
}
byte coder() {
return COMPACT_STRINGS ? coder : UTF16;
}
private boolean isLatin1() {
return COMPACT_STRINGS && coder == LATIN1;
}
//StringLatin1.equals
public static boolean equals(byte[] value, byte[] other) {
//首先需要保证byte数组长度必定相同
if (value.length == other.length) {
//逐个比对两个数组内部相同下标上的内容
for (int i = 0; i < value.length; i++) {
if (value[i] != other[i]) {
return false;
}
}
return true;
}
return false;
}
//StringUTF16.equals
public static boolean equals(byte[] value, byte[] other) {
if (value.length == other.length) {
int len = value.length >> 1;
for (int i = 0; i < len; i++) {
//这里使用的getChar方法就是前面charAt方法中的那个getChar方法
//其实就是根据index和byte获取高位和低位拼接后得到的字符,然后比较char是否不同
if (getChar(value, i) != getChar(other, i)) {
return false;
}
}
return true;
}
return false;
}
indexOf 方法
indexOf方法有很多重载的方法,该方法的作用是可以获取一个字符在字符串中的位置,不同的重载方法只是针对这个功能做了不同的业务划分,下面逐个讨论。
//这个是经常使用的一种,传入一个字符串,获取字符串在源字符串中第一次出现的位置
//注意,这里有个情况,就是字符串可以是多个字符,而不单单只是一个字符的字符串
public int indexOf(String str) {
if (coder() == str.coder()) {
return isLatin1() ? StringLatin1.indexOf(value, str.value)
: StringUTF16.indexOf(value, str.value);
}
if (coder() == LATIN1) { // str.coder == UTF16
return -1;
}
return StringUTF16.indexOfLatin1(value, str.value);
}
//StringLatin1.indexOf
public static int indexOf(byte[] value, byte[] str) {
//如果传入的是一个空字符串,默认直接返回0
if (str.length == 0) {
return 0;
}
//如果源字符串本身是一个空字符串,返回-1
if (value.length == 0) {
return -1;
}
return indexOf(value, value.length, str, str.length, 0);
}
public static int indexOf(byte[] value, int valueCount, byte[] str, int strCount, int fromIndex) {
byte first = str[0];
//这里的max计算可以这么理解:
//源byte长度减去待比较byte数组长度,得到的值是可以完全包含待比较数组的最大index
//换句话说,如果超出了这个值,那么剩余的byte位数与待比较的byte数组长度就不一致了,肯定不具有可比性了
int max = (valueCount - strCount);
for (int i = fromIndex; i <= max; i++) {
//查询第一个源byte数组的开始位字符内容是否与待比较数据的首位是否相等
if (value[i] != first) {
//如果不相等就继续寻找源数组的下一位,直至找到与待比较数组的首位内容相等的位置
while (++i <= max && value[i] != first);
}
// 到此处,说明已经发现了一位相等的数据,或者i的值已经超出max
if (i <= max) {
//排除i超出max的可能,剩下的就是在已经找到的首字符的基础上,继续查询余下的字符
//所以这里的j是从i所示的位置的下一位开始,结束的index为开始位+待比较数组长度-1
int j = i + 1;
int end = j + strCount - 1;
//一个没有循环体的循环,为的就是比较待比较数组中剩余的内容与源数组i之后的内容,
//连续strCount位之内的数据是否相等,如果完全相等,i就是需要的值
//中间若存在不同的情况,说明待比较数组不是源数组的一个子数组,继续循环直至结束返回-1
for (int k = 1; j < end && value[j] == str[k]; j++, k++);
if (j == end) {
// Found whole string.
return i;
}
}
}
return -1;
}
上面这种是经常使用的一种,此外还有一些不常使用的方式:
public int indexOf(String str, int fromIndex) {
return indexOf(value, coder(), length(), str, fromIndex);
}
static int indexOf(byte[] src, byte srcCoder, int srcCount,
String tgtStr, int fromIndex) {
byte[] tgt = tgtStr.value;
byte tgtCoder = tgtStr.coder();
int tgtCount = tgtStr.length();
if (fromIndex >= srcCount) {
//指定的开始查找的位置不小于源字符序列的最大长度,
return (tgtCount == 0 ? srcCount : -1);
}
//开始位置为负数,其实就是从头开始查找
if (fromIndex < 0) {
fromIndex = 0;
}
//待查询的字符串是一个空串,直接返回查询的起始位置
if (tgtCount == 0) {
return fromIndex;
}
if (tgtCount > srcCount) {
return -1;
}
if (srcCoder == tgtCoder) {
//这里用到的indexOf方法就是上一段代码中展示的那个indexOf方法
return srcCoder == LATIN1
? StringLatin1.indexOf(src, srcCount, tgt, tgtCount, fromIndex)
: StringUTF16.indexOf(src, srcCount, tgt, tgtCount, fromIndex);
}
if (srcCoder == LATIN1) { // && tgtCoder == UTF16
return -1;
}
// srcCoder == UTF16 && tgtCoder == LATIN1) {
return StringUTF16.indexOfLatin1(src, srcCount, tgt, tgtCount, fromIndex);
}
lastIndexOf(byte[] src, byte srcCoder, int srcCount,String tgtStr, int fromIndex)
这个方法是在常用的lastIndexOf((String str, int fromIndex)方法的底层调用方法,作用就是为了获取从fromIndex位置开始,tgtStr字符串在src字符串中最后一次出现的数组下标。
static int lastIndexOf(byte[] src, byte srcCoder, int srcCount,
String tgtStr, int fromIndex) {
//将待查找的字符串转换成byte数组
byte[] tgt = tgtStr.value;
//获取字符编码格式
byte tgtCoder = tgtStr.coder();
//待查找字符串的长度(字符串长度,非byte数组长度)
int tgtCount = tgtStr.length();
//获取字符下标能够达到的最右端值,超过该值src中剩余的字符串长度就比tgtStr长度短了
int rightIndex = srcCount - tgtCount;
//这里不像indexOf,一旦超过了,强制将其变成能够达到的最大下标值
if (fromIndex > rightIndex) {
fromIndex = rightIndex;
}
//如果传入的开始查询的位置为负数,直接返回-1
if (fromIndex < 0) {
return -1;
}
//如果待查找的字符串是一个空串,始终都能查到,返回值就是传入的fromIndex值
if (tgtCount == 0) {
return fromIndex;
}
if (srcCoder == tgtCoder) {
//这里StringLatin1.lastIndexOf方法下面已经贴出来了
//至于StringUTF16.lastIndexOf就不贴了,因为代码逻辑基本一致,只是对于tgt数组长度有了扩展
return srcCoder == LATIN1
? StringLatin1.lastIndexOf(src, srcCount, tgt, tgtCount, fromIndex)
: StringUTF16.lastIndexOf(src, srcCount, tgt, tgtCount, fromIndex);
}
if (srcCoder == LATIN1) { // && tgtCoder == UTF16
return -1;
}
// srcCoder == UTF16 && tgtCoder == LATIN1
return StringUTF16.lastIndexOfLatin1(src, srcCount, tgt, tgtCount, fromIndex);
}
//StringLatin1.lastIndexOf
public static int lastIndexOf(byte[] src, int srcCount,
byte[] tgt, int tgtCount, int fromIndex) {
int min = tgtCount - 1;
//计算src数组下标查询上限值
int i = min + fromIndex;
//strLastIndex用于后面获取tgt数组中最后一个字符的内容
int strLastIndex = tgtCount - 1;
//因为是StringLatin1,所以直接可以将对应byte数组中的内容强制转换成char
//& 0xff操作则保证了转换成的char高位能够正确,确保结果的准确性
char strLastChar = (char)(tgt[strLastIndex] & 0xff);
startSearchForLastChar:
while (true) {
//不停在rc数组中从后往前找字符与tgt数组的最后一个字符进行对比
while (i >= min && (src[i] & 0xff) != strLastChar) {
i--;
}
//若退出上面的while循环,则只有两种可能:
//i < min:查找失败
//(src[i] & 0xff) == strLastChar:正好找到
if (i < min) {
return -1;
}
//到了此处,说明已经找到了匹配的字符
//后面的就是不停地继续在src中倒序查询tgt数组中倒数第二个字符,第三个字符。。以此类推
int j = i - 1;
int start = j - strLastIndex;
//用于获取tgt数组在上一个字符位置基础上前移一位的字符
int k = strLastIndex - 1;
while (j > start) {
if ((src[j--] & 0xff) != (tgt[k--] & 0xff)) {
i--;
continue startSearchForLastChar;
}
}
return start + 1;
}
}
substring(int beginIndex)
public String substring(int beginIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
int subLen = length() - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
if (beginIndex == 0) {
return this;
}
//下面这句是求取子串的核心逻辑
return isLatin1() ? StringLatin1.newString(value, beginIndex, subLen)
: StringUTF16.newString(value, beginIndex, subLen);
}
针对上面方法的最后return语句,因为涉及到是否丢弃高八位的情况,所以采用了不同的方式进行子串的拷贝工作,这两种方式虽然代码有些不同,但最终调用的都是Arrays工具类中的copyOfRange方法,这个方法最终会调用System.arraycopy方法,它是一个native方法,效率很高。
生成子串的大致逻辑就是:将源字符串对应的byte数组按照beginIndex开始位置和需要截取的长度,让后将返回的byte数组通过String构造器构造一个新的字符串对象。这个过程因为会涉及到编码的问题,所以StringLatin1和StringUTF16的处理会有所不同,StringLatin1就是正常的截取逻辑然后构建,但是StringUTF16在构造子串的时候,如果压缩标识位为true,就需要对子串进行压缩操作,其实就是将其高八位丢弃,然后就是跟StringLatin1操作类似就行了。如果压缩标识位为false,在截取的时候需要对截取的初始位置以及长度都要扩大一倍,即左移一位,然后再进行拷贝,在拷贝的时候,需要额外注意,每个字符占两个byte数组位。
hashCode()
我电脑上当前的JDK版本中,hash码的计算分了两种情况:StringLatin1计算和StringUTF16计算,即:分为压缩情况下的hash计算和非压缩的hash计算。
在JDK的注释中,提出了一个hash的计算公式:
s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
//其实上面的也可以看成是:
s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]*31^0
这里s就代表字符串内部的字符数组,n代表字符串的长度,这个是整体的计算思路,但是JDK中字符串底层使用的是byte数组,所以字符还牵扯到具体的存储格式问题,存在格式压缩的情况。这里使用31数字,网上有选用这个数计算的各种原因,可以查阅相关博客了解具体内容,这里只要记住一点:31这个数是奇素数,它可以保证hashCode尽可能大的基础上,虚拟机在计算的时候还会有一定的优化,要知道:31 * n == (n << 5) - n这个等式的结果是true,它是成立的。
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
hash = h = isLatin1() ? StringLatin1.hashCode(value)
: StringUTF16.hashCode(value);
}
return h;
}
//StringLatin1.hashCode
//对于压缩版的数据,它的字符长度其实与value的长度是一致的,直接套用上面的公式就可以了
public static int hashCode(byte[] value) {
int h = 0;
//for循环结束之后,就可以得到一个赋值公式:
//value[0]*31^(n-1) + ... + value[n - 1]
for (byte v : value) {
h = 31 * h + (v & 0xff);
}
return h;
}
//StringUTF16.hashCode
//大致的算法没变,只是需要处理以下value数组中内容,数组中连续两个位置合在一起才表示一个字符
//getChar方法的作用就是根据i值获取i以及i+1位置上的两个byte数据,并且合并成一个char返回
public static int hashCode(byte[] value) {
int h = 0;
int length = value.length >> 1;
for (int i = 0; i < length; i++) {
h = 31 * h + getChar(value, i);
}
return h;
}
compareTo(String anotherString)
public int compareTo(String anotherString) {
byte v1[] = value;
byte v2[] = anotherString.value;
if (coder() == anotherString.coder()) {
return isLatin1() ? StringLatin1.compareTo(v1, v2)
: StringUTF16.compareTo(v1, v2);
}
return isLatin1() ? StringLatin1.compareToUTF16(v1, v2)
: StringUTF16.compareToLatin1(v1, v2);
}
compareTo是String实现的Comparable接口中的方法,逻辑比较简单,就是将两个字符串对应的byte数组进行比较,逐个byte位置进行比较,不同的是StringUTF16是两两byte位置进行比较,所以会有一个转换操作,将连续两个byte数组中的内容组合起来转成char类型再进行比较,但是最终的逻辑都是一样,逐个比较String里面的字符。
intern()
它是一个native方法,它会返回一个规范化表示的字符串对象。字符串常量池是由String自行维护的,它初始时是空的。如果intern方法被调用,此时若常量池中已经存在一个字符串对象与调用方法的对象equals相等,那么常量池中的字符串对象将被返回,否则该对象将被添加到常量池中并返回在常量池中的引用。
任意两个字符串s和t,如果s.intern() == t.intern()的结果为true,那么s.equals(t)的结果必然也是true。所有的字符串字面量和字符串常量表达式都会在常量池中。
因为篇幅有限,其他方法就不再一一介绍了,在前面介绍的基础上,基本看看源码都能大致了解的差不多。