一、概述
/**
* String是java.lang下的一个类。
* String表示UTF-16格式的字符串,它可以表示Java程序中所有的文本。
* 它是一个常量,在创建之后,值不会被改变。String缓冲池支持可变的字符串。
* 字符串可以进行比较、拼接、初始化、查找字符位置等操作。
* 1、字符串之间的比较:str1和str2在值上是相等的。
* String str1 = "abc";
* char data[] = {'a', 'b', 'c'};
* String str2 = new String(data);
* System.out.println(str1==str2); // false,因为是两个常量,内存地址不同
* System.out.println(str1.equals(str2)); // true,因为常量的值是相同的
* 2、字符串之间的拼接:String str = str1 + str2;
* 字符串是用“+”拼接,本质上是通过StringBuilder或StringBuffer的append()方法实现的。
* 3、字符串的初始化:字符串有多种初始方式,如下
* String str = "abc";
* String str = new String("abc"); // 此种方式的参数不能为null
* ......
* 4、查找字符位置:
* String str = "abc";
* char c = str.charAt(1);
* ........
*
* @author Lee Boynton
* @author Arthur van Hoff
* @author Martin Buchholz
* @author Ulf Zibis
* @see java.lang.Object#toString()
* @see java.lang.StringBuffer
* @see java.lang.StringBuilder
* @see java.nio.charset.Charset
* @since JDK1.0
*/
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
.......
}
二、String中的成员变量
// 1、value用于存储字符串中的字符,当字符串被初始化时,value存储每一个由字符串拆分的字符
// String内部是以char数组的形式存储,数组的长度是int类型,
// 那么String允许的最大长度就是Integer.MAX_VALUE了,即2147483647。
private final char value[];
// 2、缓存字符串的hash code值,默认值为0
private int hash;
// 3、序列化值
private static final long serialVersionUID = -6849794470754667710L;
// 4、用于String类的序列化
private static final ObjectStreamField[] serialPersistentFields =
new ObjectStreamField[0];
/**
* 按CompareTognoreCase排序字符串对象的比较器。此比较器是可序列化的。
* 请注意,此比较器不考虑区域设置,并且将导致某些区域设置的排序不理想。
* java.text包提供了collator以允许对区域设置敏感的排序。
*
* @see java.text.Collator#compare(String, String)
* @since 1.2
*/
// 请参考:附、其他方法 --> 4、CaseInsensitiveComparator内部类
public static final Comparator<String> CASE_INSENSITIVE_ORDER
= new CaseInsensitiveComparator();
三、构造方法
1、无参构造
// 初始化一个空字符串,由于字符串的不可变性,此方法没有必要
public String() { this.value = "".value; }
2、单参构造
// 新创建的字符串本质上是参数字符串的副本
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
/**
* 分配一个新的String来表示这个char数组的序列,这个char类型的数组被复制到这个数组中,
* 随后对数组的修改不会影响到新创建的字符串。其本质上是调用了Arrays.copyOf(char[], int)
* 方法实现的复制,而Arrays.copy()方法的本质是创建一个新的char[],并调用
* arraycopy(Object, int, Object, int, int)方法实现对char[]的复制,
* 最后Arrays.copyOf(char[], int)方法返回这个新建的经过复制后的char[]对象。
*
* @param value String的初始化参数值
*/
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
/**
* 构造一个通过使用平台默认字符集解码指定的byte数组组成的新的String。
* 新String的长度是字符集的函数,因此可能不等于byte数组的长度。
* 当给定的byte在给定的字符集中无效的情况下,此构造方法的行为没有指定。
* 如果需要对解码过程进行更多的控制,则应该使用java.nio.charset.CharsetDecoder类。
*
* @param bytes 要解码为字符的byte
* @since JDK1.1
*/
public String(byte bytes[]) {
this(bytes, 0, bytes.length);
}
/**
* 分配一个新的字符串,字符串内容是参数buffer内容的复制,
* 后续对buffer的修改不会影响到新的字符串。
*
* @param buffer 一个StringBuffer对象
*/
public String(StringBuffer buffer) {
synchronized(buffer) {
this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
}
}
/**
* 分配一个新的字符串,字符串内容是参数builder内容的复制,
* 后续对builder的修改不会影响新创建的字符串。
* 提供此构造方法的目的是,更容易的迁移到StringBuilder。
*
* @param builder 一个StringBuilder对象
* @since 1.5
*/
public String(StringBuilder builder) {
this.value = Arrays.copyOf(builder.getValue(), builder.length());
}
3、两参构造
// 该构造方法已过时,不建议使用
@Deprecated
public String(byte ascii[], int hibyte) {
this(ascii, hibyte, 0, ascii.length);
}
/**
* 通过使用指定的charset解码的byte数组,构造一个新的String对象。
* 这个新的String是字符集的函数,因此可能不等于byte数组的长度。
* 当给定的bytes并不符合给定的字符集时,此构造方法的行为没有指定。
*
* @param bytes 要解码为字符的bytes
* @param charsetName 支持的java.nio.charset.Charset的charset
* @throws UnsupportedEncodingException 如果并不支持指定的字符集格式
* @since JDK1.1
*/
public String(byte bytes[], String charsetName) throws UnsupportedEncodingException {
this(bytes, 0, bytes.length, charsetName);
}
/**
* 通过使用平台默认的字符集解码指定的byte的数组,构造一个新的String。
* 这个新的String是字符集的函数,因此可能不等于byte数组的长度。
* 此方法总是使用此字符集的默认替代字符串来替代错误输入和不可映射的字符序列。
* 如果需要对解码过程进行更多的控制,则应该使用java.nio.charset.CharsetDecoder类。
*
* @param bytes 要解码为字符的bytes
* @param charset 用于解码bytes的java.nio.charset.Charset类
* @since 1.6
*/
public String(byte bytes[], Charset charset) {
this(bytes, 0, bytes.length, charset);
}
/*
* 为了更快地去共享value数组的值而提供的私有构造器。当share为true时,此构造器将会被调用。
* 因为已经有了一个公共的char数组复制的构造方法String(char[]),因此需要一个单独的构造函数。
*/
String(char[] value, boolean share) {
// 断言:share一定是不支持非共享的。
this.value = value;
}
4、三参构造
/**
* 分配一个新的字符串,该字符串包含字符数组参数中的一个子数组。
* 例如:char[] chars = {'a', 'b', 'c', 'd', 'e'};
* String s1 = new String(chars, 1, 3);
* System.out.println(s1); // 输出为“bcd”
*
* @param value 源char数组
* @param offset 子数组的第一个字符的索引
* @param count 子数组的长度
* @throws IndexOutOfBoundsException 如果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);
}
// 判断子数组第一个字符的索引是否小于等于字符数组value的长度,
// 如果小于,则将字符串置为空字符串
if (offset <= value.length) {
this.value = "".value;
return;
}
}
// 注意:offset或者count的值可以接近-1>>>1(2147483647)
// 原因请参考:字符串中的成员变量char[] value
// 当子数组的第一个字符的索引+字符长度>字符串数组value的长度时,抛出数组下标越界异常
if (offset > value.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
// 拷贝数组value中值到字符串的value属性中
this.value = Arrays.copyOfRange(value, offset, offset+count);
}
/**
* 分配一个新的字符串,它包含Unicode代码点数组参数的一个子数组的字符,
* 后续对int[] codePoints的修改并不会影响新创建的字符串。
* @param codePoints Array that is the source of Unicode code points
*
* @param offset 子数组第一个代码点的索引
* @param count 子数组长度
* @throws IllegalArgumentException 如果在codePoints中发现任何无效的Unicode代码点
* @throws IndexOutOfBoundsException 如果offset和count超出了codePoints数组的范围
* @since 1.5
*/
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);
}
// 判断子数组第一个字符的索引是否小于等于int数组codePoints的长度,
// 如果小于,则将字符串置为空字符串
if (offset <= codePoints.length) {
this.value = "".value;
return;
}
}
// 注意:offset或者count的值可以接近-1>>>1(2147483647)
// 当子数组的第一个字符的索引+字符长度>字符串数组value的长度时,抛出数组下标越界异常
if (offset > codePoints.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
final int end = offset + count;
// 步骤1:计算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));
}
// 步骤2:分配并填充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;
}
/**
* 使用默认的平台字符集解码指定的bytes子数组,从而构建一个新的字符串。
* 新String的长度是字符集的函数,因此可能不等于该子数组的长度。
* 当给定的byte在给定字符集中无效的情况下,此构造方法的行为没有指定。
* 如果需要对解码过程进行更多的控制,则应该使用java.nio.charset.CharsetDecoder类。
*
* @param bytes 要解码为字符的bytes数组
* @param offset 要解码的第一个byte的索引
* @param length 要解码的byte的长度
* @throws IndexOutOfBoundsException 如果offset和length参数超出了bytes数组的范围
* @since JDK1.1
*/
public String(byte bytes[], int offset, int length) {
// 校验offset、length是否不小于0并且在bytes数组下标的范围内;
// 详情请参考:附、其他方法 --> 1、checkBoundd(byte[], int, int)
checkBounds(bytes, offset, length);
//
this.value = StringCoding.decode(bytes, offset, length);
}
5、四参构造
/**
* 已废弃,不建议使用。分配一个新的String,它是根据一个8位整数值数组的子数组构造的。
* @param ascii 要转换成字符的byte
* @param hibyte 每个16位Unicode代码单元的前8位
* @param offset 初始化偏移量
* @param count 字符串长度
* @throws IndexOutOfBoundsException 如果offset或count参数非法则抛出数据下标越界异常
*
* @see #String(byte[], int)
* @see #String(byte[], int, int, java.lang.String)
* @see #String(byte[], int, int, java.nio.charset.Charset)
* @see #String(byte[], int, int)
* @see #String(byte[], java.lang.String)
* @see #String(byte[], java.nio.charset.Charset)
* @see #String(byte[])
*/
@Deprecated
public String(byte ascii[], int hibyte, int offset, int count) {
// 校验offset、count的值是否非法,以及ascii字节数组的大小是否在(offset+count)之内
checkBounds(ascii, offset, count);
// 创建一个大小为count的新字符数组
char value[] = new char[count];
if (hibyte == 0) {
for (int i = count; i-- > 0;) {
value[i] = (char)(ascii[i + offset] & 0xff);
}
} else {
hibyte <<= 8;
for (int i = count; i-- > 0;) {
value[i] = (char)(hibyte | (ascii[i + offset] & 0xff));
}
}
this.value = value;
}
/**
* 通过使用指定的字符集解码指定的 byte 子数组,构造一个新的 String。
* 新String的长度是一个字符集函数,因此可能不等于子数组的长度。
* 当给定 byte 在给定字符集中无效的情况下,此构造方法的行为没有指定。
* 如果需要对解码过程进行更多控制,则应该使用java.nio.charset.CharsetDecoder类。
*
* @param bytes 要解码为字符的 bytes
* @param offset 要解码的第一个 byte 的索引
* @param length 要解码的byte数
* @param charsetName 受支持charset的名称
* @throws UnsupportedEncodingException 如果指定的字符集不受支持
* @throws IndexOutOfBoundsException 如果offset和length参数索引字符超出bytes数组的范围
* @since JDK1.1
*/
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);
}
/**
* 通过使用指定的charset解码指定的byte子数组,构造一个新的String。
* 新String的长度是字符集的函数,因此可能不等于子数组的长度。
* 此方法总是使用此字符集的默认替代字符串替代错误输入(malformed-input)和不可映射字符
* (unmappable-character)序列。如果需要对解码过程进行更多控制,则可使用CharsetDecoder类。
*
* @param bytes 要解码为字符的bytes
* @param offset 要解码的第一个bytes的索引
* @param length 要解码的byte数
* @param charset 用来解码bytes的charset,不能为空
* @throws IndexOutOfBoundsException 如果offset和length参数索引字符超出bytes数组的边界
* @since 1.6
*/
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);
}
四、length()方法
/**
* 返回字符串的长度,即字符数组value的长度
* @return 这个对象表示的字符序列的长度
*/
public int length() {
return value.length;
}
五、isEmpty()方法
/**
* @return 如果length是0则返回true;否则返回false
* @since 1.6
*/
public boolean isEmpty() {
return value.length == 0;
}
六、charAt(int)方法
/**
* 返回字符串中指定索引(index)对应的字符,索引的范围是[0, length() -1]。
* 字符串的索引是从0开始,索引1在第二位,索引2在第三个位置,以此类推。
* 如果索引指定的char值是代理项,则返回代理项值。
*
* @param index 字符串的索引
* @return 指定索引(index)对应的值
* @exception IndexOutOfBoundsException 如果 index 参数为负或大于此字符串的长度。
*/
public char charAt(int index) {
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return value[index];
}
七、codePoint相关方法
1、codePointAt(int)
/**
* 返回在index位置的字符的Unicode编码值。 这个index的范围是[0, length()-1]。
* 举例如下:
* String str = "abcd";
* int point = str.codePointAt(0); // point=97
* 由于index为0,对应的值是'a',而'a'的Unicode值是97,所以point的值为97.
* 如果给定索引指定的char值属于高代理项范围,则后续索引小于此String的长度;
* 如果后续索引处的char值属于低代理项范围,则返回该代理项对相应的增补代码点。
* 否则,返回给定索引处的char值。
*
* @param index 索引
* @return 返回index位置的字符的Unicode值
* @exception IndexOutOfBoundsException 如果index为负或大于等于此字符串的长度。
* @since 1.5
*/
public int codePointAt(int index) {
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return Character.codePointAtImpl(value, index, value.length);
}
2、codePointBefore(int)
/**
* 返回在index位置的字符的Unicode编码值。 这个index的范围是[1, length()]。
* 举例如下:
* String str = "abcd";
* int point = str.codePointBefore(1); // point=97
* 实质上,在指定范围内,codePointBefore(i) = codePointAt(i-1)。
* 如果(index - 1)处的char值属于低代理项范围,则(index - 2)为非负;
* 如果(index - 2)处的char值属于高低理项范围,则返回该代理项对的增补代码点值。
* 如果(index - 1)处的char值是未配对的低(高)代理项,则返回代理项值。
*
* @param index 索引
* @return 给定索引前面的字符的Unicode值。
* @exception IndexOutOfBoundsException 如果index参数小于1或大于此字符串的长度
* @since 1.5
*/
public int codePointBefore(int index) {
int i = index - 1;
if ((i < 0) || (i >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return Character.codePointBeforeImpl(value, index, 0);
}
3、codePointCount(int)
/**
* 返回此String的指定字符串范围中的Unicode代码点数。字符串范围是[beginIndex, endIndex-1]。
* 因此,该文本范围的长度(用char表示)是 endIndex-beginIndex。
* 该文本范围内每个未配对的代理项计为一个代码点。
* codePointCount()与length()的区别是:
* codePointCount()就是准确计算unicode字符的数量,而length()计算的是char的数量
*
* @param beginIndex 字符串中指定的第一个字符的索引,
* 范围是[0, length()]且 beginIndex <= endIndex
* @param endIndex 字符串中指定的最后一个字符的索引,范围是[0, length()]
* @return 指定范围的字符串数目
* @exception IndexOutOfBoundsException
* beginIndex或endIndex不在指定范围内或beginIndex>endIndex
* @since 1.5
*/
public int codePointCount(int beginIndex, int endIndex) {
if (beginIndex < 0 || endIndex > value.length || beginIndex > endIndex) {
throw new IndexOutOfBoundsException();
}
return Character.codePointCountImpl(value, beginIndex, endIndex - beginIndex);
}
4、offsetByCodePoints(int, int)
/**
* 返回此String中从给定的index处偏移codePointOffset个代码点的索引。
* 文本范围内由index和codePointOffset给定的未配对代理项各计为一个代码点
*
* @param index 要偏移的索引
* @param codePointOffset 代码点中的偏移量
* @return String的索引
* @exception IndexOutOfBoundsException 如果index为负或大于字符串长度;
* 或者codePointOffset为正,且以index开头子字符串的代码点比codePointOffset少;
* 如果codePointOffset为负,且index前面子字符串的代码点比codePointOffset的绝对值少。
* @since 1.5
*/
public int offsetByCodePoints(int index, int codePointOffset) {
if (index < 0 || index > value.length) {
throw new IndexOutOfBoundsException();
}
return Character.offsetByCodePointsImpl(value, 0, value.length,
index, codePointOffset);
}
八、getChars/Bytes相关方法
1、getChars(int, int, char[], int)
/**
* 从字符串中的value[]复制字符到指定的char数组中。
* 复制的范围是value[srcBegin, srcEnd),
* 粘贴到目标数组中的位置是dst[dstBegin, srcBegin-srcEnd)
*
* @param srcBegin 需要复制的value[]中的第一个字符的索引
* @param srcEnd 需要复制的value[]中的最后一个字符的索引
* @param dst 目标数组
* @param dstBegin 目标数组的偏移量
* @exception IndexOutOfBoundsException
* 以下情况满足任意一个都会抛出异常:
* 1.srcBegin、srcEnd或dstBegin为负数
* 2.srcBegin > srcEnd
* 3.srcEnd大于字符串长度
* 4.dstBegin+(srcEnd-srcBegin) > dst.length
*/
public void getChars(int srcBegin, int srcEnd, char 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);
}
// 请参考:System 源码解析
System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
}
2、getBytes相关方法
/**
* 从字符串中复制字符到指定的byte数组中,作用和getChars(int, int, char[], int)方法类似。
* 每一个byte接收相应字符的8个低位。不复制每个字符的高位,他们不参与任何方式的转换
*
* @deprecated 此方法无法将字符正确的转换为字节。
* 从JDK1.1起,完成该转换的是getBytes()方法,该方法使用平台默认的字符集。
* @param srcBegin 需要复制的字符串中第一个字符的下标
* @param srcEnd 需要复制的字符串中最后一个字符的下标
* @param dst 目标字节数组
* @param dstBegin 目标数组的偏移量
* @throws IndexOutOfBoundsException
* 以下情况满足任意一个都会抛出异常:
* 1.srcBegin、srcEnd或dstBegin为负数
* 2.srcBegin > srcEnd
* 3.srcEnd大于字符串长度
* 4.dstBegin+(srcEnd-srcBegin) > dst.length
*/
@Deprecated
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;
/* 尽量避免getfield命令的执行:在一个方法中需要大量引用实例域变量的时候,
* 使用方法中的局部变量代替引用可以减少getfield操作的次数,提高性能。
*/
char[] val = value; /* avoid getfield opcode */
while (i < n) {
dst[j++] = (byte)val[i++];
}
}
/**
* 使用指定的字符集将此String编码为byte序列,并将结果存储到一个新的byte数组中。
* 当此字符串不能使用给定的字符集编码时,此方法的行为没有指定。
* 如果需要对编码过程进行更多的控制,则应该使用java.nio.charset.CharsetEncoder类。
*
* @param charsetName java.nio.charset.Charset支持的字符集
* @return 编码后的byte数组
* @throws UnsupportedEncodingException 如果指定的字符集不受支持
* @since JDK1.1
*/
public byte[] getBytes(String charsetName) throws UnsupportedEncodingException {
if (charsetName == null) throw new NullPointerException();
return StringCoding.encode(charsetName, value, 0, value.length);
}
/**
* 使用指定的字符集将此String编码为byte序列,并将结果存储到一个新的byte数组中。
* 此方法总是使用此字符集的默认值替代byte数组的错误输出和不可映射的字符序列。
* 如果需要对编码过程进行更多的控制,则应该使用java.nio.charset.CharsetEncoder类。
*
* @param charset 用于编码String的java.nio.charset.Charset类
* @return 编码后的byte数组
* @since 1.6
*/
public byte[] getBytes(Charset charset) {
if (charset == null) throw new NullPointerException();
return StringCoding.encode(charset, value, 0, value.length);
}
/**
* 使用平台默认的字符集将String编码为byte序列,并将结果存储到一个新的byte数组中。
* 当此字符串不能使用给定的字符集编码时,此方法的行为没有指定。
* 如果需要对编码过程进行更多的控制,则应该使用java.nio.charset.CharsetEncoder类。
*
* @return 编码后的byte数组
* @since JDK1.1
*/
public byte[] getBytes() {
return StringCoding.encode(value, 0, value.length);
}
九、equals相关方法
1、equals(Object)
/**
* 比较指定对象和字符串是否相等。
* 当且仅当参数非空且指定对象anObject的字符序列和字符串相同返回true
*
* @param anObject 要与字符串比较的对象
* @return 如果给定的对象与此字符串等价则返回true,否则返回false
* @see #compareTo(String)
* @see #equalsIgnoreCase(String)
*/
public boolean equals(Object anObject) {
// 1.对比是否是当前字符串对象
if (this == anObject) {
return true;
}
// 2.如果anObject的运行时类型是字符串,则强转为字符串,否则返回false
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;
// 3.对比anObject中的每一个字符和String中的字符是否相等
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
2、contentEquals(StringBuffer)
/**
* 将字符串和指定的StringBuffer进行比较(区分字母大小写)。
* 当且仅当字符串序列和StringBuffer序列相同时返回true,此方法在StringBuffer上同步。
* 即String的内容和缓冲区重的内容相同才会返回true,举例如下:
* String str = "abc";
* StringBuffer sb = new StringBuffer("abc");
* boolean result = str.contentEquals(sb);
* result为true,因为String中的内容是“abc”,而sb中的内容也是“abc”
*
*
* @param sb 和字符串比较的StringBuffer对象
* @return 当且仅当字符串序列和StringBuffer序列相同时返回true,否则返回false
* @since 1.4
*/
public boolean contentEquals(StringBuffer sb) {
// 请参考:九、equals相关方法 --> 3、contentEquals(CharSequence)
return contentEquals((CharSequence)sb);
}
3、contentEquals(CharSequence)
/**
* 将字符串和指定的CharSequence序列进行比较(字母区分大小写)。
* 当且仅当字符串序列和指定序列一致时,返回true。
* 注意:当CharSequence是一个StringBuffer时,这个方法是同步的。
*
* @param cs 与String比较的CharSequence
* @return 当且仅当字符串序列和指定序列一致时,返回true;否则返回false
* @since 1.5
*/
public boolean contentEquals(CharSequence cs) {
// 当参数cs类型是StringBuffer时,synchronized使用设置成同步方法;
// 当参数cs类型为StringBuilder时,不设置
if (cs instanceof AbstractStringBuilder) {
if (cs instanceof StringBuffer) {
synchronized(cs) {
return nonSyncContentEquals((AbstractStringBuilder)cs);
}
} else {
return nonSyncContentEquals((AbstractStringBuilder)cs);
}
}
// 当参数cs类型是一个字符串时,使用equals(Object)比较
if (cs instanceof String) {
return equals(cs);
}
// 参数cs类型是一个泛型CharSequence,先判断两者长度,然后再逐个字符比较
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;
}
4、equalsIgnoreCase(String)
/**
* 比较当前字符串和其他字符串anotherString的值,忽略大小写的情况。
* 如果两个字符串的长度相同,而且在忽略大小写的情况下相对应的字符值相同,则视为相等。
* 以下几种情况如果至少有一个成立,则可以认为字符c1和c2相等:
* 1. c1==c2 为true;
* 2. 对每个字符应用方法java.lang.Character.toUpperCase(char)会产生相同的结果
* 3. 对每个字符应用方法java.lang.Character.toLowerCase(char)会产生相同的结果
*
* @param anotherString 和此字符串比较的其他字符串
* @return 参数非空且字符序列在忽略大小写下等值则返回true,否则返回false
* @see #equals(Object)
*/
public boolean equalsIgnoreCase(String anotherString) {
return (this == anotherString) ? true
:(anotherString != null)
&& (anotherString.value.length == value.length)
&& regionMatches(true, 0, anotherString, 0, value.length);
// regionMatches方法的作用是:
// 参考:十一、regionMatches相关方法 --> regionMatches(boolean, int, String, int)
}
十、compareTo相关方法
1、compareTo(String)
/**
* 按字典顺序比较两个字符串。这个比较结果基于每个字符串中字符的Unicode值。
* 字符串str和anotherString比较的三种情况
* 1、如果str > anotherString,则结果为正数;
* 2、如果str = anotherString,则结果为0;
* 3、如果str < anotherString,则结果为负数
* 示例(1)如下:
* String str = "abcf";
* String anotherString = "abdeg";
* int ss = str.compareTo(anotherString); // ss=-1
* int aa = anotherString.compareTo(str); // aa=1
* 示例(2)如下:
* String str = "abcd";
* String anotherString = "abcdef";
* int ss = str.compareTo(anotherString); // ss=-2
* int aa = anotherString.compareTo(str); // aa=2
* 解析:因为str和anotherString比较时,当比较到索引为2的位置时,
* str中的‘c’的Unicode值是99,而anotherString中的‘d’的Unicode值是100,
* 所以str和anotherString比较结果为99-100=-1,反过来结果为100-99=1.
* 如果str和anotherString相同索引位置字符Unicode一样,但两者长度不同,
* 则比较结果为两者长度的差值。
*
* @param anotherString 与当前字符串比较的其他字符串
* @return 如果相等,则返回0;
* 如果不相等则返回两者对应字符的Unicode之差;
* 如果索引位置对应的字符都相等,两者长度不同,则返回两者长度之差。
*/
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) {
// 如果对应索引下字符不同,则返回两个字符的Unicode之差
return c1 - c2;
}
k++;
}
// 返回str和anotherString字符串长度之差
return len1 - len2;
}
2、compareToIgnoreCase(String)
/**
* 在不考虑大小写的情况下,按字典顺序比较两个字符串大小,作用和compareTo(String)类似。
*
* @param str 被比较的字符串
* @return 根据指定两个字符串的大小(不考虑大小写),分别返回一个负整数、0 或一个正整数。
* @since 1.2
*/
public int compareToIgnoreCase(String str) {
// 本质上调用的是CaseInsensitiveComparator.compare(String, String)方法,
// 请参考:附、其他方法 --> 4、CaseInsensitiveComparator内部类
return CASE_INSENSITIVE_ORDER.compare(this, str);
}
十一、regionMatches相关方法
1、regionMatches(int, String, int, int)
/**
* 测试两个字符串的区域(子串)是否相等。
* 比较的区域s是:当前字符串this[toffset, toffset+len)和other[ooffset, ooffset+len)。
* 当且仅当以下条件至少有一个为true时,结果返回false:
* 1、toffset是负数;
* 2、ooffset是负数;
* 3、toffset+len > this.length;
* 4、ooffset+len > other.length;
* 5、存在非负整数k(k<len),它满足:this.charAt(toffset+k) != other.charAt(offset+len)
* 即两子串同一下标位置对应的字符不相等。
*
* @param toffset 当前字符串子区域的起始偏移量
* @param other 和当前字符串比较的字符串参数
* @param ooffset other字符串子区域的起始偏移量
* @param len 需要比较的长度
* @return this[toffset, toffset+len) == other[ooffset, ooffset+len);否则返回false
*/
public boolean regionMatches(int toffset, String other, int ooffset, int len) {
char ta[] = value;
int to = toffset;
char pa[] = other.value;
int po = ooffset;
// 注意:toffset, ooffset, 或 len 可能接近 -1>>>1(2147483647)
// 原因请参考:字符串中的成员变量char value[]
if ((ooffset < 0) || (toffset < 0)
|| (toffset > (long)value.length - len)
|| (ooffset > (long)other.value.length - len)) {
return false;
}
while (len-- > 0) {
if (ta[to++] != pa[po++]) {
return false;
}
}
return true;
}
2、regionMathes(boolean, int, String, int, int)
/**
* 测试两个字符串的区域(子串)是否相等,此方法可以设置是否忽略字符大小写,
* 作用和regionMatches(boolean, int, String, int, int)相同。
* 比较的区域s是:当前字符串this[toffset, toffset+len)和other[ooffset, ooffset+len)。
* 当且仅当以下条件至少有一个为true时,结果返回false:
* 1、toffset是负数;
* 2、ooffset是负数;
* 3、toffset+len > this.length;
* 4、ooffset+len > other.length;
* 5、存在非负整数k(k<len),它满足:this.charAt(toffset+k) != other.charAt(offset+len)
* 即两子串同一下标位置对应的字符不相等。
* 6、如果ignoreCase==true,并且存在非负整数k(k<len),它满足:
* Character.toLowerCase(this.charAt(toffset+k)) !=
* Character.toLowerCase(other.charAt(ooffset+k)),
* 即两子串同一下标位置对应的字符在忽略大小写的情况下也不相等。
*
* @param ignoreCase 是否忽略大小写
* @param toffset 当前字符串子区域的起始偏移量
* @param other 和当前字符串比较的字符串参数
* @param ooffset other字符串子区域的起始偏移量
* @param len 需要比较的长度
* @return this[toffset, toffset+len) == other[ooffset, ooffset+len);否则返回false
*/
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;
// 注意:toffset, ooffset或len可能接近-1>>>1(2147483647)
// 原因请参考:字符串中的成员变量char value[]
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;
}
// 忽略字符大小写参数为true
if (ignoreCase) {
// 将字符全部转换成大写进行比较,如果相等则继续下一个
char u1 = Character.toUpperCase(c1);
char u2 = Character.toUpperCase(c2);
if (u1 == u2) {
continue;
}
// 不幸的是,转换为大写字母对格鲁吉亚字母表不起作用,
// 因为格鲁吉亚字母表有奇怪的大小写转换规则。所以需要在离开前做最后一次检查。
if (Character.toLowerCase(u1) == Character.toLowerCase(u2)) {
continue;
}
}
return false;
}
return true;
}
十二、starts/endsWith相关方方法
1、startsWith(String, int)
/**
* 测试字符串从指定索引toffset开始的子字符串是否以指定的前缀prefix开始。举例如下:
* String str = "abcdef";
* boolean sw = str.startsWith("bcd", 1); // true
* 解析:字符串从索引为1开始的子串为“bcdef”,“bcdef”是以前缀“bcd”开头的,所以,返回值为true。
*
* @param prefix 前缀
* @param toffset 此字符串开始查找的位置
* @return 如果参数表示的字符序列是此对象从索引toffset处开始的子字符串前缀,则返回true;
* 否则返回false。
* 如果toffset是负数或toffset > this.length,则结果返回false;
* 否则,结果与以下表达式相同:this.substring(toffset).startsWith(prefix)
*/
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;
// 注意:toffset可能接近-1>>>1(2147483647)。原因请参考:字符串中的成员变量char value[]
if ((toffset < 0) || (toffset > value.length - pc)) {
return false;
}
while (--pc >= 0) {
if (ta[to++] != pa[po++]) {
return false;
}
}
return true;
}
2、startsWith(String)
/**
* 测试字符串是否以指定的前缀开始
*
* @param prefix 前缀
* @return 如果字符串序列是以前缀prefix开始,则返回true;否则,返回false;
* 如果prefix为“”或等于(#equals(Object))当前字符串,返回结果也为true
* @since 1. 0
*/
public boolean startsWith(String prefix) {
return startsWith(prefix, 0);
}
3、endsWith(String)
/**
* 测试字符串是否以指定后缀suffix结束
*
* @param suffix 后缀
* @return 如果字符串序列是以指定后缀suffix结束,则返回true;否则返回false
* 如果参数suffix是空字符串或等于(#equals(Object))当前字符串,则返回
*/
public boolean endsWith(String suffix) {
return startsWith(suffix, value.length - suffix.value.length);
}
十三、hashCode()
/**
* 返回字符串对象的hashcode值。这个hashcode是根据下面这个公式计算来的:
* s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
* 其中,s[i]是字符串中第i个字符,n是字符串的长度,^代表指数运算
* (空字符串的hash值是0)
* 举例说明:
* 1、求String s = "a"中s的hashCode值:
* value = ['a'];
* value.length = 1; 循环1次
* h = 31*0 + val[0] = 0 + 97 = 97. ('a'的UTF-8编码值为97)
* 2、求String s = "ab"中s的hashCode值:
* value = {'a','b'};
* value.length = 2,循环两次;
* 第一次(val[0]='a'): h = 31*0 + val[0] = 0 + 97 = 97; ('a'的UTF-8编码值为97)
* 第二次(val[1]='b', h=97): h = 31*h +val[1]
* = 31*97 + 98 = 3105; ('b'的UTF-8编码值为98)
* 3、求String s = "我"中s的hashCode值:
* value = {'我'};
* value.length = 1; 循环一次
* h = 31*h + val[0] = 31*0 + 25105 = 25105; ('25105'的UTF-8编码值为25105)
* 其他字符串的hashCode求法与此相同。汉字的UTF-8字符集对照表见此章节。
*
* @return 返回对象的hash code值
*/
public int hashCode() {
// 当String初始化的时候(非零值初始化),hash值为0
int h = hash;
// 校验:如果当前的hash值为0,并且String的初始化值的长度大于0(即不为空字符串),
// 则根据String的初始化值以及UTF-8编码,对该字符串进行求hashcode运算。
if (h == 0 && value.length > 0) {
// 获取String初始化值的字符数组形式
char val[] = value;
// 遍历该val数组,根据数组长度以及当前字符的在UTF-8编码中的值求出hashcode的值
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
附属资源:UTF-8字符集汉字对照表
十四、indexOf相关方法
1、index(int)
/**
* Returns the index within this string of the first occurrence of
* the specified character. If a character with value
* {@code ch} occurs in the character sequence represented by
* this {@code String} object, then the index (in Unicode
* code units) of the first such occurrence is returned. For
* values of {@code ch} in the range from 0 to 0xFFFF
* (inclusive), this is the smallest value <i>k</i> such that:
* <blockquote><pre>
* this.charAt(<i>k</i>) == ch
* </pre></blockquote>
* is true. For other values of {@code ch}, it is the
* smallest value <i>k</i> such that:
* <blockquote><pre>
* this.codePointAt(<i>k</i>) == ch
* </pre></blockquote>
* is true. In either case, if no such character occurs in this
* string, then {@code -1} is returned.
*
* @param ch a character (Unicode code point).
* @return the index of the first occurrence of the character in the
* character sequence represented by this object, or
* {@code -1} if the character does not occur.
*/
public int indexOf(int ch) {
return indexOf(ch, 0);
}
2、indexOf(int, int)
/**
* Returns the index within this string of the first occurrence of the
* specified character, starting the search at the specified index.
* <p>
* If a character with value {@code ch} occurs in the
* character sequence represented by this {@code String}
* object at an index no smaller than {@code fromIndex}, then
* the index of the first such occurrence is returned. For values
* of {@code ch} in the range from 0 to 0xFFFF (inclusive),
* this is the smallest value <i>k</i> such that:
* <blockquote><pre>
* (this.charAt(<i>k</i>) == ch) {@code &&} (<i>k</i> >= fromIndex)
* </pre></blockquote>
* is true. For other values of {@code ch}, it is the
* smallest value <i>k</i> such that:
* <blockquote><pre>
* (this.codePointAt(<i>k</i>) == ch) {@code &&} (<i>k</i> >= fromIndex)
* </pre></blockquote>
* is true. In either case, if no such character occurs in this
* string at or after position {@code fromIndex}, then
* {@code -1} is returned.
*
* <p>
* There is no restriction on the value of {@code fromIndex}. If it
* is negative, it has the same effect as if it were zero: this entire
* string may be searched. If it is greater than the length of this
* string, it has the same effect as if it were equal to the length of
* this string: {@code -1} is returned.
*
* <p>All indices are specified in {@code char} values
* (Unicode code units).
*
* @param ch a character (Unicode code point).
* @param fromIndex the index to start the search from.
* @return the index of the first occurrence of the character in the
* character sequence represented by this object that is greater
* than or equal to {@code fromIndex}, or {@code -1}
* if the character does not occur.
*/
public int indexOf(int ch, int fromIndex) {
final int max = value.length;
if (fromIndex < 0) {
fromIndex = 0;
} else if (fromIndex >= max) {
// Note: fromIndex might be near -1>>>1.
return -1;
}
if (ch < Character.MIN_SUPPLEMENTARY_CODE_POINT) {
// handle most cases here (ch is a BMP code point or a
// negative value (invalid 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);
}
}
十五、subString相关方法
十六、concat(String)
/**
* Concatenates the specified string to the end of this string.
* <p>
* If the length of the argument string is {@code 0}, then this
* {@code String} object is returned. Otherwise, a
* {@code String} object is returned that represents a character
* sequence that is the concatenation of the character sequence
* represented by this {@code String} object and the character
* sequence represented by the argument string.<p>
* Examples:
* <blockquote><pre>
* "cares".concat("s") returns "caress"
* "to".concat("get").concat("her") returns "together"
* </pre></blockquote>
*
* @param str the {@code String} that is concatenated to the end
* of this {@code String}.
* @return a string that represents the concatenation of this object's
* characters followed by the string argument's characters.
*/
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);
}
十七、replace相关方法
十八、matches(String)
十九、contains(CharSequence)
二十、split相关方法
二十一、join相关方法
二十二、toLower/UpperCase相关方法
二十三、trim()
/**
* Returns a string whose value is this string, with any leading and trailing
* whitespace removed.
* <p>
* If this {@code String} object represents an empty character
* sequence, or the first and last characters of character sequence
* represented by this {@code String} object both have codes
* greater than {@code '\u005Cu0020'} (the space character), then a
* reference to this {@code String} object is returned.
* <p>
* Otherwise, if there is no character with a code greater than
* {@code '\u005Cu0020'} in the string, then a
* {@code String} object representing an empty string is
* returned.
* <p>
* Otherwise, let <i>k</i> be the index of the first character in the
* string whose code is greater than {@code '\u005Cu0020'}, and let
* <i>m</i> be the index of the last character in the string whose code
* is greater than {@code '\u005Cu0020'}. A {@code String}
* object is returned, representing the substring of this string that
* begins with the character at index <i>k</i> and ends with the
* character at index <i>m</i>-that is, the result of
* {@code this.substring(k, m + 1)}.
* <p>
* This method may be used to trim whitespace (as defined above) from
* the beginning and end of a string.
*
* @return A string whose value is this string, with any leading and trailing white
* space removed, or this string if it has no leading or
* trailing white space.
*/
public String trim() {
int len = value.length;
int st = 0;
char[] val = value; /* avoid getfield opcode */
while ((st < len) && (val[st] <= ' ')) {
st++;
}
while ((st < len) && (val[len - 1] <= ' ')) {
len--;
}
return ((st > 0) || (len < value.length)) ? substring(st, len) : this;
}
二十四、toString() 方法
// @return 返回这个字符串本身,即字符串的内容
public String toString() {
return this;
}
详细请参考文章:Object 源码解析
二十五、toCharArray()
二十六、format相关方法
二十七、valueOf相关方法
二十八、intern()
/**
* 返回字符串对象的规范化表示形式。
* 一个初始化为空的字符串池,它由String类私有地维护
* 当调用intern方法时,如果池中已经包含一个等于此String对象的字符串(用equals(Object)确定),
* 则返回池中的字符串。否则,将此String对象添加到池中,并返回此String对象的引用。
* 它遵循:任意两个字符串s和t,当且仅当s.equals(t)为true时,s.intern()==t.intern()返回true
* 所有字面值字符串和字符串赋值常量表达式都被保留,
* 字符串字面值在 Java Language Specification 的 §3.10.5 定义。
* 举例说明(JDK1.8环境下):
* 示例一:
* String s1 = "abc";
* String s2 = new String("abc");
* 1.s1==s2 返回false,因为s1存于常量池,s2存于堆中,两者引用不同
* 2.s1.intern()==s2 返回false,因为s1.intern()的引用是从常量池中获取的,
* 而s2是从堆中获取的,两者引用不同
* 3.s1==s2.intern() 返回true,因为执行“String s1 = "abc"”后,
* 常量池中就会存在“abc”常量,s1是常量池中“abc”字符串的引用;
* s2.intern()执行时,会先从常量池中查找“abc”字符串是否存在,
* 如果存在则返回此字符串的引用,否则将此字符串添加到常量池中后返回常量池中此字符串的引用
* 常量池中已存在“abc”,所以s1和s2.intern()返回的引用是同一个
* 示例二:
* String str1 = new StringBuilder("hello").append("word").toString();
* 1.str1==str1.intern() 返回true,因为上面的例子等价于
* String x = "hello";
* String y = "word";
* String str1 = new StringBuilder(x).append(y).toString();
* “helloword”最先创建在堆中,str1.intern()在字符串常连池中。(符合首次出现原则)
* String str2 = new StringBuilder("hellojava").toString();
* 2.str2==str2.intern() 返回false,因为上面的例子等价于
* String x = "hellojava";
* String str2 = new StringBuilder(x).toString();
* “hellojava”最先创建在常量池中,运行结果为false。(违反了首次出现原则)
* 注释:进行字符串拼接运算时:
* 1.当+号一边存在字符串变量时,会先回寻找字符串常量池中是否存在已经拼接好的字符串。
* 如果不存在,则会在里面创建一个新的,然后在堆中创建一个指向这个常量的对象。
* 即str1="a"; str2="b"; str3="ab"; 运行str3==a+b 返回false;
* 2.当+号两边都是字符串常量时,则先回寻找字符串常量池中是否存在已经拼接好的字符串。
* 如果不存在,则会在里面创建一个新的,不会在堆中创建新的对象。
* 即str3="ab"; 运行str3=="a"+"b"; 返回true
*
* @return 一个内容与此字符串相同的字符串,此字符串一定取自具有唯一字符串池
*/
public native String intern();
附、其他方法
1、checkBoundd(byte[], int, int)
/* 通常用于检查字符串构造函数中的参数(偏移量、长度的值)是否符合预定的大小。
* 此方法校验了三个部分:
* 1) 长度是否小于0;
* 2) 偏移量是否小于0;
* 3) 偏移量是否大于字符数组剩余长度,即长度+偏移量是否超出了字符串的下标返回。
*/
private static void checkBounds(byte[] bytes, int offset, int length) {
if (length < 0)
throw new StringIndexOutOfBoundsException(length);
if (offset < 0)
throw new StringIndexOutOfBoundsException(offset);
if (offset > bytes.length - length)
throw new StringIndexOutOfBoundsException(offset + length);
}
2、 static char[] decode(byte[] ba, int off, int len)
// 解码bytep[]数组中的字符,从off(off从0开始)开始,解码长度为len
static char[] decode(byte[] ba, int off, int len) {
// 获取默认字符集的名称
String csn = Charset.defaultCharset().name();
try {
// 使用默认字符集和缓存提供的变量执行decode()方法
return decode(csn, ba, off, len);
} catch (UnsupportedEncodingException x) {
warnUnsupportedCharset(csn);
}
try {
return decode("ISO-8859-1", ba, off, len);
} catch (UnsupportedEncodingException x) {
// 如果在虚拟机初始化的时候执行了此段代码,MessageUtils是我们获取任何类型错误消息
// 的唯一方法。
MessageUtils.err("ISO-8859-1 charset not available: " + x.toString());
// 如果我们找不到ISO-8859-1(一个必需的编码),那么安装就有严重的问题。
System.exit(1);
return null;
}
}
3、getChars(char[], int)
/**
* 复制字符串中所有的值到字符数组dst中,复制的位置是从字符数组的dstBegin开始的。
* 举例:String str = "abc";
* char[] charArr = {'0', '1', '2', '3', '4', '5'};
* str.getChars(charArr, 1);
* // charArr的值将会变为{'0', 'a', 'b', 'c', '4', '5'}
* 这个方法不会执行任何有关于范围的校验
*/
void getChars(char dst[], int dstBegin) {
// 请参考:System 源码解析
System.arraycopy(value, 0, dst, dstBegin, value.length);
}
4、CaseInsensitiveComparator内部类
// 实现了Comparator接口的内部类,它可以对字符串在忽略大小写的情况下进行比较
private static class CaseInsensitiveComparator
implements Comparator<String>, java.io.Serializable {
// 从JDK 1.2.2开始,使用serialVersionUID实现互操作性
private static final long serialVersionUID = 8575799808933029326L;
public int compare(String s1, String s2) {
int n1 = s1.length();
int n2 = s2.length();
int min = Math.min(n1, n2);
for (int i = 0; i < min; i++) {
char c1 = s1.charAt(i);
char c2 = s2.charAt(i);
if (c1 != c2) {
c1 = Character.toUpperCase(c1);
c2 = Character.toUpperCase(c2);
if (c1 != c2) {
c1 = Character.toLowerCase(c1);
c2 = Character.toLowerCase(c2);
if (c1 != c2) {
// 由于数值提升,没有溢出
return c1 - c2;
}
}
}
}
return n1 - n2;
}
// 替换反序列化对象
private Object readResolve() { return CASE_INSENSITIVE_ORDER; }
}
==== 已阅读至源码代码1480行 ====
此篇博客待续.........