Java 语言使用 String 类用来表明字符串,实际上 String 对象的值是一个常量,一旦建立后不能被改变。正式由于其不可变,因此它是线程安全地,能够多个线程共享。java
相信对于 String 的使用你们都再熟悉不过的了,这里就了解下 JDK 中怎么实现 String 类的。mysql
--java.lang.Object
--java.lang.String
复制代码
public final class String implements java.io.Serializable, Comparable<String>, CharSequence
复制代码
String 类被声明为 final,说明它不能再被继承。同时它实现了三个接口,分别为 Serializable、Comparable 和 CharSequence。其中 Serializable 接口代表其能够序列化;sql
InputStream 被定为 public 且 abstract 的类,实现了Closeable接口。数组
Closeable 接口表示 InputStream 能够被close,接口定义以下:缓存
public interface Closeable extends AutoCloseable {
public void close() throws IOException;
}
复制代码
private final byte[] value;
private final byte coder;
private int hash;
static final boolean COMPACT_STRINGS;
static {
COMPACT_STRINGS = true;
}
static final byte LATIN1 = 0;
static final byte UTF16 = 1;
public static final Comparator<String> CASE_INSENSITIVE_ORDER = new CaseInsensitiveComparator();
复制代码
LATIN1
或UTF16
。该内部类主要是提供排序的比较器,实现了Comparator
接口和compare
方法,另一个readResolve
方法用于替换反序列化时的对象。compare
核心方法的逻辑是,根据二者编码是否相同作处理,若是相同则分 Latin1 或 UTF16 两种状况比较,相似地,若是二者编码不一样,则须要用 Latin1 编码与 UTF16 编码比较,而 UTF16 则要与 Latin1 比较。安全
private static class CaseInsensitiveComparator
implements Comparator<String>, java.io.Serializable {
private static final long serialVersionUID = 8575799808933029326L;
public int compare(String s1, String s2) {
byte v1[] = s1.value;
byte v2[] = s2.value;
if (s1.coder() == s2.coder()) {
return s1.isLatin1() ? StringLatin1.compareToCI(v1, v2)
: StringUTF16.compareToCI(v1, v2);
}
return s1.isLatin1() ? StringLatin1.compareToCI_UTF16(v1, v2)
: StringUTF16.compareToCI_Latin1(v1, v2);
}
private Object readResolve() { return CASE_INSENSITIVE_ORDER; }
}
复制代码
有不少种构造方法,看主要的几个。没有参数的构造方法直接将空字符串的 value 和 coder 进行赋值。bash
public String() {
this.value = "".value;
this.coder = "".coder;
}
复制代码
相似的,传入 String 对象的构造方法则将该对象对应的 value 、coder 和 hash 进行赋值。网络
public String(String original) {
this.value = original.value;
this.coder = original.coder;
this.hash = original.hash;
}
复制代码
构造方法传入 char 数组时,主要逻辑就是若是 COMPACT_STRINGS 为 true,即便用紧凑布局的话,则尝试将其转换成为 LATIN1 编码(即ISO-8859-1编码),这里说尝试是由于 char 数组中可能包含了非 LATIN1 编码,此时是压缩失败的,只有数组中所有都为 LATIN1 编码时才能压缩成功。相似的还有传入 int 数组的,int 类型占用4个字节,只有所有符合转换才能转成 LATIN1 编码。并发
public String(char value[]) {
this(value, 0, value.length, null);
}
String(char[] value, int off, int len, Void sig) {
if (len == 0) {
this.value = "".value;
this.coder = "".coder;
return;
}
if (COMPACT_STRINGS) {
byte[] val = StringUTF16.compress(value, off, len);
if (val != null) {
this.value = val;
this.coder = LATIN1;
return;
}
}
this.coder = UTF16;
this.value = StringUTF16.toBytes(value, off, len);
}
复制代码
构造方法传入 byte 数组时,同时会传入 charsetName,即编码。核心操做为StringCoding.decode
,它会先根据编码对 byte 数组进行解码,解码过程会判断是否所有都在 LATIN1 编码内,若是是则使用 LATIN1 编码,不然使用 UTF16 编码,而且将解码后对应的 byte 数组赋值给 String 对象的 value。机器学习
public String(byte bytes[], int offset, int length, String charsetName)
throws UnsupportedEncodingException {
if (charsetName == null)
throw new NullPointerException("charsetName");
checkBoundsOffCount(offset, length, bytes.length);
StringCoding.Result ret =
StringCoding.decode(charsetName, bytes, offset, length);
this.value = ret.value;
this.coder = ret.coder;
}
复制代码
字符串的长度应该是字符的长度,而不是字节数组的长度,因此这里作了右移操做,LATIN1 编码时coder()
为0,字符串长度等于字节数组长度。UTF16 编码时coder()
为1,字符串等于字节数组长度一半。
public int length() {
return value.length >> coder();
}
复制代码
经过判断 byte 数组长度是否为0来判断字符串对象是否为空。
public boolean isEmpty() {
return value.length == 0;
}
复制代码
取字符须要根据编码来操做,若是是 LATIN1 编码,则直接取 byte 数组中对应索引的元素,并转成 char 类型便可。若是是 UTF16 编码,由于它每一个 UTF16 编码占用两个字节,因此须要将索引乘以2后做为最终索引取得两个字节并转换成 char 类型,具体实现逻辑如getChar
方法所示。
public char charAt(int index) {
if (isLatin1()) {
return StringLatin1.charAt(value, index);
} else {
return StringUTF16.charAt(value, index);
}
}
static char getChar(byte[] val, int index) {
index <<= 1;
return (char)(((val[index++] & 0xff) << HI_BYTE_SHIFT) |
((val[index] & 0xff) << LO_BYTE_SHIFT));
}
复制代码
获取字符串对应索引的 Unicode 代码点,根据编码作不一样处理。若是是 LATIN1 编码,直接将 byte 数组对应索引的元素与0xff
作&操做并转成 int 类型。相应的,UTF16 编码也须要对应作转换,它包含了两个字节。
public int codePointAt(int index) {
if (isLatin1()) {
checkIndex(index, value.length);
return value[index] & 0xff;
}
int length = value.length >> 1;
checkIndex(index, length);
return StringUTF16.codePointAt(value, index, length);
}
复制代码
用于返回指定索引值前一个字符的代码点,实现与codePointAt
方法相似,只是索引值要减1。
public int codePointBefore(int index) {
int i = index - 1;
if (i < 0 || i >= length()) {
throw new StringIndexOutOfBoundsException(index);
}
if (isLatin1()) {
return (value[i] & 0xff);
}
return StringUTF16.codePointBefore(value, index);
}
复制代码
用于获得指定索引范围内代码点的个数,若是是 Latin1 编码则直接索引值相减,由于每一个字节确定都属于一个代码点。若是是 UTF16 编码则要检查是否存在 High-surrogate 代码和 Low-surrogate 代码,若是存在则说明须要4个字节来表示一个字符,此时要把 count 减1。
public int codePointCount(int beginIndex, int endIndex) {
if (beginIndex < 0 || beginIndex > endIndex ||
endIndex > length()) {
throw new IndexOutOfBoundsException();
}
if (isLatin1()) {
return endIndex - beginIndex;
}
return StringUTF16.codePointCount(value, beginIndex, endIndex);
}
private static int codePointCount(byte[] value, int beginIndex, int endIndex, boolean checked) {
assert beginIndex <= endIndex;
int count = endIndex - beginIndex;
int i = beginIndex;
if (checked && i < endIndex) {
checkBoundsBeginEnd(i, endIndex, value);
}
for (; i < endIndex - 1; ) {
if (Character.isHighSurrogate(getChar(value, i++)) &&
Character.isLowSurrogate(getChar(value, i))) {
count--;
i++;
}
}
return count;
}
public static int codePointCount(byte[] value, int beginIndex, int endIndex) {
return codePointCount(value, beginIndex, endIndex, false /* unchecked */);
}
复制代码
该方法用于返回 String 中从给定的 index 处偏移 codePointOffset 个 Unicode 代码点的索引,要注意 Unicode 代码可能两个字节也可能四个字节。逻辑为:
public int offsetByCodePoints(int index, int codePointOffset) {
if (index < 0 || index > length()) {
throw new IndexOutOfBoundsException();
}
return Character.offsetByCodePoints(this, index, codePointOffset);
}
public static int offsetByCodePoints(CharSequence seq, int index,
int codePointOffset) {
int length = seq.length();
if (index < 0 || index > length) {
throw new IndexOutOfBoundsException();
}
int x = index;
if (codePointOffset >= 0) {
int i;
for (i = 0; x < length && i < codePointOffset; i++) {
if (isHighSurrogate(seq.charAt(x++)) && x < length &&
isLowSurrogate(seq.charAt(x))) {
x++;
}
}
if (i < codePointOffset) {
throw new IndexOutOfBoundsException();
}
} else {
int i;
for (i = codePointOffset; x > 0 && i < 0; i++) {
if (isLowSurrogate(seq.charAt(--x)) && x > 0 &&
isHighSurrogate(seq.charAt(x-1))) {
x--;
}
}
if (i < 0) {
throw new IndexOutOfBoundsException();
}
}
return x;
}
复制代码
用于获取字符串对象指定范围内的字符到目标 char 数组中,主要是根据两种编码作不一样处理,若是是 LATIN1 编码则直接将 byte 数组对应索引的元素与0xff
作&操做并转成 char 类型。而若是是 UTF16 编码则须要两个字节一块儿转为 char 类型。
public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
checkBoundsBeginEnd(srcBegin, srcEnd, length());
checkBoundsOffCount(dstBegin, srcEnd - srcBegin, dst.length);
if (isLatin1()) {
StringLatin1.getChars(value, srcBegin, srcEnd, dst, dstBegin);
} else {
StringUTF16.getChars(value, srcBegin, srcEnd, dst, dstBegin);
}
}
复制代码
获取字符串指定编码的字节数组,好比 charsetName 为 utf8,则将字符串转为 utf8 编码后对应的字节数组。若是不传参数则使用 JVM 默认编码,即Charset.defaultCharset()
。
public byte[] getBytes(String charsetName)
throws UnsupportedEncodingException {
if (charsetName == null) throw new NullPointerException();
return StringCoding.encode(charsetName, coder(), value);
}
public byte[] getBytes() {
return StringCoding.encode(coder(), value);
}
复制代码
用于比较两字符串对象是否相等,若是引用相同则返回 true。不然判断比较对象是否为 String 类的实例,是的话转成 String 类型,接着比较编码是否相同,分别以 LATIN1 编码和 UTF16 编码进行比较。
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String aString = (String)anObject;
if (coder() == aString.coder()) {
return isLatin1() ? StringLatin1.equals(value, aString.value)
: StringUTF16.equals(value, aString.value);
}
}
return false;
}
复制代码
该方法用于比较字符串之间内容是否相等,逻辑为:
nonSyncContentEquals
方法。nonSyncContentEquals
方法。equals
方法比较。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);
}
int n = cs.length();
if (n != length()) {
return false;
}
byte[] val = this.value;
if (isLatin1()) {
for (int i = 0; i < n; i++) {
if ((val[i] & 0xff) != cs.charAt(i)) {
return false;
}
}
} else {
if (!StringUTF16.contentEquals(val, cs, n)) {
return false;
}
}
return true;
}
复制代码
nonSyncContentEquals
方法逻辑为:
private boolean nonSyncContentEquals(AbstractStringBuilder sb) {
int len = length();
if (len != sb.length()) {
return false;
}
byte v1[] = value;
byte v2[] = sb.getValue();
if (coder() == sb.getCoder()) {
int n = v1.length;
for (int i = 0; i < n; i++) {
if (v1[i] != v2[i]) {
return false;
}
}
} else {
if (!isLatin1()) {
return false;
}
return StringUTF16.contentEquals(v1, v2, len);
}
return true;
}
复制代码
该方法用于对比字符串是否相等,并且是忽略大小写。若是是本身与本身对比则不为空则为true,不然须要二者长度相等且regionMatches
方法返回true才为true。
public boolean equalsIgnoreCase(String anotherString) {
return (this == anotherString) ? true
: (anotherString != null)
&& (anotherString.length() == length())
&& regionMatches(true, 0, anotherString, 0, length());
}
复制代码
regionMatches
方法逻辑为:
regionMatches
方法,这里为 true,忽略此方法。coder() == other.coder()
为 true,即二者编码同样时,若是为 Latin1 编码,则以 Latin1 方式比较,不然以 UTF16 方式比较。public boolean regionMatches(boolean ignoreCase, int toffset,
String other, int ooffset, int len) {
if (!ignoreCase) {
return regionMatches(toffset, other, ooffset, len);
}
if ((ooffset < 0) || (toffset < 0)
|| (toffset > (long)length() - len)
|| (ooffset > (long)other.length() - len)) {
return false;
}
byte tv[] = value;
byte ov[] = other.value;
if (coder() == other.coder()) {
return isLatin1()
? StringLatin1.regionMatchesCI(tv, toffset, ov, ooffset, len)
: StringUTF16.regionMatchesCI(tv, toffset, ov, ooffset, len);
}
return isLatin1()
? StringLatin1.regionMatchesCI_UTF16(tv, toffset, ov, ooffset, len)
: StringUTF16.regionMatchesCI_Latin1(tv, toffset, ov, ooffset, len);
}
复制代码
该方法用于比较两个字符串,主要的逻辑为:
coder() == anotherString.coder()
,即二者编码相同时,若是为 Latin1 编码则以 Latin1 的方式进行比较。不然以 UTF16 方式进行比较,具体如何比较下面以 Latin1 编码为例子。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);
}
复制代码
Latin1 编码的比较逻辑为:
public static int compareTo(byte[] value, byte[] other) {
int len1 = value.length;
int len2 = other.length;
int lim = Math.min(len1, len2);
for (int k = 0; k < lim; k++) {
if (value[k] != other[k]) {
return getChar(value, k) - getChar(other, k);
}
}
return len1 - len2;
}
复制代码
该方法相似 compareTo 方法,只是忽略大小写。实现经过CaseInsensitiveComparator
内部类来实现。
public int compareToIgnoreCase(String str) {
return CASE_INSENSITIVE_ORDER.compare(this, str);
}
复制代码
该方法用于检测指定区域字符串是否相等,其逻辑为:
regionMatches
方法。coder() == other.coder()
为 true,即二者编码同样时,若是为 Latin1 编码,则以 Latin1 方式比较,不然以 UTF16 方式比较。public boolean regionMatches(boolean ignoreCase, int toffset,
String other, int ooffset, int len) {
if (!ignoreCase) {
return regionMatches(toffset, other, ooffset, len);
}
if ((ooffset < 0) || (toffset < 0)
|| (toffset > (long)length() - len)
|| (ooffset > (long)other.length() - len)) {
return false;
}
byte tv[] = value;
byte ov[] = other.value;
if (coder() == other.coder()) {
return isLatin1()
? StringLatin1.regionMatchesCI(tv, toffset, ov, ooffset, len)
: StringUTF16.regionMatchesCI(tv, toffset, ov, ooffset, len);
}
return isLatin1()
? StringLatin1.regionMatchesCI_UTF16(tv, toffset, ov, ooffset, len)
: StringUTF16.regionMatchesCI_Latin1(tv, toffset, ov, ooffset, len);
}
复制代码
大小写敏感的比较逻辑:
coder() == other.coder()
,即二者编码相同时,若是为 Latin1 编码则直接比较每一个字节,而若是为 UTF16 编码则须要将位移和长度都扩大一倍,由于 UTF16 占用的空间是 Latin1 的两倍,而后再比较每一个字节是否相等。public boolean regionMatches(int toffset, String other, int ooffset, int len) {
byte tv[] = value;
byte ov[] = other.value;
if ((ooffset < 0) || (toffset < 0) ||
(toffset > (long)length() - len) ||
(ooffset > (long)other.length() - len)) {
return false;
}
if (coder() == other.coder()) {
if (!isLatin1() && (len > 0)) {
toffset = toffset << 1;
ooffset = ooffset << 1;
len = len << 1;
}
while (len-- > 0) {
if (tv[toffset++] != ov[ooffset++]) {
return false;
}
}
} else {
if (coder() == LATIN1) {
while (len-- > 0) {
if (StringLatin1.getChar(tv, toffset++) !=
StringUTF16.getChar(ov, ooffset++)) {
return false;
}
}
} else {
while (len-- > 0) {
if (StringUTF16.getChar(tv, toffset++) !=
StringLatin1.getChar(ov, ooffset++)) {
return false;
}
}
}
}
return true;
}
复制代码
该方法用于检测字符串是否以某个前缀开始,而且能够指定偏移。逻辑为:
coder() == prefix.coder()
,即二者编码相同时,若是为 Latin1 编码则直接比较每一个字节是否相等,若是为 UTF16 编码则要将位移扩大一倍,再比较每一个字节。public boolean startsWith(String prefix) {
return startsWith(prefix, 0);
}
public boolean startsWith(String prefix, int toffset) {
if (toffset < 0 || toffset > length() - prefix.length()) {
return false;
}
byte ta[] = value;
byte pa[] = prefix.value;
int po = 0;
int pc = pa.length;
if (coder() == prefix.coder()) {
int to = isLatin1() ? toffset : toffset << 1;
while (po < pc) {
if (ta[to++] != pa[po++]) {
return false;
}
}
} else {
if (isLatin1()) {
return false;
}
while (po < pc) {
if (StringUTF16.getChar(ta, toffset++) != (pa[po++] & 0xff)) {
return false;
}
}
}
return true;
}
复制代码
该方法用于检查是否以某个字符串结尾,间接调用startsWith
方法便可实现。
public boolean endsWith(String suffix) {
return startsWith(suffix, length() - suffix.length());
}
复制代码
该方法返回字符串对象的哈希值,若是已经有缓存了则直接返回,不然根据不一样编码分别计算哈希值。
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
hash = h = isLatin1() ? StringLatin1.hashCode(value)
: StringUTF16.hashCode(value);
}
return h;
}
复制代码
下面分别是 Latin1 编码和 UTF16 编码的哈希值计算逻辑,遍历地执行h = 31 * h + (v & 0xff)
和 h = 31 * h + getChar(value, i)
运算。
public static int hashCode(byte[] value) {
int h = 0;
for (byte v : value) {
h = 31 * h + (v & 0xff);
}
return h;
}
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;
}
复制代码
该方法用于查找字符串中第一个出现某字符或字符串的位置,有多种方法参数。可传入 int 类型,也可传入 String 类型,另外还能传入开始位置。根据编码的不一样分别调用 StringLatin1 和 StringUTF16 的indexOf
方法。
public int indexOf(int ch) {
return indexOf(ch, 0);
}
public int indexOf(int ch, int fromIndex) {
return isLatin1() ? StringLatin1.indexOf(value, ch, fromIndex)
: StringUTF16.indexOf(value, ch, fromIndex);
}
public int indexOf(String str) {
if (coder() == str.coder()) {
return isLatin1() ? StringLatin1.indexOf(value, str.value)
: StringUTF16.indexOf(value, str.value);
}
if (coder() == LATIN1) {
return -1;
}
return StringUTF16.indexOfLatin1(value, str.value);
}
public int indexOf(String str, int fromIndex) {
return indexOf(value, coder(), length(), str, fromIndex);
}
复制代码
Latin1 编码查找逻辑,
public static int indexOf(byte[] value, int ch, int fromIndex) {
if (!canEncode(ch)) {
return -1;
}
int max = value.length;
if (fromIndex < 0) {
fromIndex = 0;
} else if (fromIndex >= max) {
return -1;
}
byte c = (byte)ch;
for (int i = fromIndex; i < max; i++) {
if (value[i] == c) {
return i;
}
}
return -1;
}
public static boolean canEncode(int cp) {
return cp >>> 8 == 0;
}
复制代码
相似地,对于 UTF16 编码也作相似处理,但由于 unicode 包含了基本多语言平面(Basic Multilingual Plane,BMP)外,还存在补充平面。而传入的值为 int 类型(4字节),因此若是超出 BMP 平面,此时须要4个字节,分别用来保存 High-surrogate 和 Low-surrogate,此时就须要对比4个字节。
另外,若是查找子字符串则是从子字符串第一个字符开始匹配直到子字符串彻底被匹配成功。
-------------推荐阅读------------
个人开源项目汇总(机器&深度学习、NLP、网络IO、AIML、mysql协议、chatbot)
跟我交流,向我提问:
欢迎关注: