2 上一篇文章中解读了Object基类,这一篇文章我们来解读下String类,闲话不多说,直接贴源代码(没有什么比看源代码来的更清晰明了)看下String类:
//final 关键字修饰的类不能进行被继承,同时String实现了Comparable,CharSequence,Serializable接口
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
//定义用于字符存储的value数组,由于使用了final修饰,
//表示该value[]一经初始化就不能进行
private final char value[];
//缓存字符串的hashcode值
private int hash; // Default to 0
//省略若干方法,具体方法,接下来依次进行解读
}
接下来我们来看下String的构造方法(不包括过期的构造方法):
1、无参构造
//无参构造方法,给当前value值进行初始化char[0]的数组,
public String() {
this.value = new char[0];
}
2、带有一个字符串对象的有参构造
//初始化一个新创建的字符串对象,它代表了相同的字符序列作为参数;换句话说,新创建的字符串参数字符串的副本,注意这里相当于是复制了一个副本给新的对象,把原对象的hash值也赋给了新的对象;
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
//我们来验证一下
public static void main(String[] args) {
String str ="张三";
String str1 = new String(str);
String str2 = new String(str);
System.out.println(str1.hashCode());
System.out.println(str2.hashCode());
}
//最后输入的hashCode值是相等都是774889
3、带有char[]字符串的有参构造
//带有value[]数组的有参构造,在上面的代码片段中,已经定义了value 实质上就一个类型为char的数组,
//这里新创建的对象同样是一个副本,把传入的char[]数组,原封不动的拷贝到定义的数组中,
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
//这里就会引申出,如果一个如果传入的length大于实际value值,结果会怎么样?看下下面这个例子:
public static void main(String[] args) {
String[] strs1={"123","234","345","456","789"};
String[] str3=Arrays.copyOf(strs1, 8);
System.out.println();
System.out.println();
for(String ele:str3){
System.out.print(ele+";");
}
//结果输出:123;234;345;456;789;null;null;null;
//由上面的结果输出可以看出,自动填充了null值;
4、带有三个参数的构造方法:1、char[]数组,2、偏移量,子数组的第一个字符的索引;3、需要定义的数组的长度;
//分配一个新的字符串,它包含字符的子数组的字符数组参数。偏移量参数是子数组的第一个字符的索引和统计
//参数指定子数组的长度。子数组的内容复制;字符数组的后续修改不会影响新创建的字符串。
public String(char value[], int offset, int count) {
if (offset < 0) {//偏移量小于0时 抛出数组下标御姐
throw new StringIndexOutOfBoundsException(offset);
}
if (count < 0) {//数组长度为0时,抛出数组下标越界
throw new StringIndexOutOfBoundsException(count);
}
// Note: offset or count might be near -1>>>1(2147483647)
if (offset > value.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
this.value = Arrays.copyOfRange(value, offset, offset+count);
}
//这里调用了Arrays数组的copyOfRange方法,从offset这个数组下标开始复制,复制到offset+count结束,
//有关这个Arrays的该方法,在后面的解读Arrays进行详细解读,这里我们只需要知道,就是复制,从offset开始,offset+count结束,取出新的数组,复制到value中,当然复制的区间是左闭右开;
//下面为了更改的理解,写个demo试下
public static void main(String[] args) {
char[] str = { '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a' };
String var= new String(str, 1, 5);
char [] newStr =var.toCharArray();
for (char c : newStr) {
System.out.print(c+",");
}
System.out.println();
System.out.println(var.toString());
}
//返回结果为:2,3,4,5,6,
// 23456
//由此可以推断出就是从offset(给定的char数组的下标索引开始)进行复制,count表示一共复制几个;
5、带有三个参数的构造方法:1、Unicode代码点阵列的数组,2、偏移量,需要复制的第一个字符的索引;3、需要定义的数组的长度;
//分配一个新的字符串,它包含字符的Unicode代码点阵列的子数组参数。偏移量参数是索引子数组的第一个代码点和计算参数指定子数组的长度。子数组的内容转换为字符;int数组的后续修改不会影响新创建的字符串。
//说白了就是基于unicode进行操作,实际开发中,我是没有用过该方法......
public String(int[] codePoints, int offset, int count) {
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
// Note: offset or count might be near -1>>>1.
if (offset > codePoints.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
//final 修饰的基本类型就是一个常量,一经初始化就不能被更改
final int end = offset + count;
// Pass 1: Compute precise size of char[]
//精确计算char[]长度,遍历代码点数组,取出有效的代码点长度
int n = count;
for (int i = offset; i < end; i++) {
int c = codePoints[i];
//判断代码点是不是BMP,其实只要看高16位是否为0即可,看下面给出Character源代码,更容易进行理解
if (Character.isBmpCodePoint(c))//codePoint是否有效的代码
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))//如果是unicode则直接赋值;
v[j] = (char)c;
else
Character.toSurrogates(c, v, j++);//如果不是则进行补充字符;
}
this.value = v;
}
/************************Character部分源代码*************************************/
public static final char MIN_VALUE = '\u0000';
public static final char MAX_VALUE = '\uFFFF';
public static final int MIN_CODE_POINT = 0x000000;
public static final int MAX_CODE_POINT = 0X10FFFF;
public static boolean isBmpCodePoint(int codePoint) {
return codePoint >>> 16 == 0;//优化后的源码,判断最高位是否为0
// Optimized form of:
// codePoint >= MIN_VALUE && codePoint <= MAX_VALUE
// We consistently use logical shift (>>>) to facilitate
// additional runtime optimizations.
}
//这段代码的判断思路是判断codePoint是否在MIN_CODE_POINT(0x000000)和MAX_CODE_POINT(0X10FFFF)之间,但代码作者对这个判断进行了优化。之所以这样优化是因为非增补字符的范围是0x0000-0xffff,高16位都是0,而增补字符就不一定了所以 codePoint >>> 16可以提取出高16位的位信息。又由于codePoint的合理的最大值是MAX_CODE_POINT(0X10FFFF),自然而然的plane < ((MAX_CODE_POINT + 1) >>> 16)就是用来判断codePoint的高16位是否比MAX_CODE_POINT+1的高16位小。这就达成了codePoint <= MAX_CODE_POINT的判断效果。那么codePoint >= MIN_CODE_POINT的判断从何而来?其实plane < ((MAX_CODE_POINT + 1) >>> 16)已经做到了codePoint >= MIN_CODE_POINT的效果,因为如果是非增补字符,那么plane妥妥的是0,如果codePoint是个负数,那么plane < ((MAX_CODE_POINT + 1) >>> 16)妥妥的不会成立。(此时的低16位的高位为1)
public static boolean isValidCodePoint(int codePoint) {
// Optimized form of:
// codePoint >= MIN_CODE_POINT && codePoint <= MAX_CODE_POINT
int plane = codePoint >>> 16;
return plane < ((MAX_CODE_POINT + 1) >>> 16);
}
//从源码注释中可以看出,要么保证dst[index+1],dst[index]都成功,要么都不成功
static void toSurrogates(int codePoint, char[] dst, int index) {
// We write elements "backwards" to guarantee all-or-nothing
//lowSurrogate()返回的主要代理(低代理代码单元)表示指定的代理对补充字符(Unicode代码点)utf - 16编码,如果指定的字符不是一个补充字符,则返回一个未指定的字符
dst[index+1] = lowSurrogate(codePoint);
//highSurrogate()返回的主要代理(高代理代码单元)表示指定的代理对补充字符(Unicode代码点)utf - 16编码。如果指定的字符不是一个补充字符,则返回一个未指定的字符。
dst[index] = highSurrogate(codePoint);
}
/*************************Character部分源代码*************************************/
6、带有四个参数的构造方法,1、字节数组,2、需要复制的第一个字节索引(偏移量),3、长度,4、支持的字符集编码
//给定字节数组,指定指定编码进行解码成数组,
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);
}
//这里涉及到checkBounds方法,那么我们先来看下checkBounds源码
//这里没什么可以解释的 就是判断新截取的数组的索引+长度是否在数组内,并且他是一个私有的方法;不提供外被内使用
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);
}
/***********************StringCoding部分源码*******************************/
static char[] decode(String charsetName, byte[] ba, int off, int len)
throws UnsupportedEncodingException
{
StringDecoder sd = deref(decoder);//StringDecoder 是StringCoding类中定义的内部类
String csn = (charsetName == null) ? "ISO-8859-1" : charsetName;
if ((sd == null) || !(csn.equals(sd.requestedCharsetName())
|| csn.equals(sd.charsetName()))) {
sd = null;
try {
Charset cs = lookupCharset(csn);//循环查找字符集
if (cs != null)
sd = new StringDecoder(cs, csn);//内部类的构造方法
} catch (IllegalCharsetNameException x) {}
if (sd == null)
throw new UnsupportedEncodingException(csn);
set(decoder, sd);
}
return sd.decode(ba, off, len);//进行decode解码操作
}
7、下面几个构造方法都是对6的构造方法的调用,没什么可说的;
public String(byte bytes[], String charsetName)
throws UnsupportedEncodingException {
this(bytes, 0, bytes.length, charsetName);//指定偏移量,去整个数组的长度
}
public String(byte bytes[], int offset, int length) {
checkBounds(bytes, offset, length);
this.value = StringCoding.decode(bytes, offset, length);
}
public String(byte bytes[], int offset, int length) {
checkBounds(bytes, offset, length);
this.value = StringCoding.decode(bytes, offset, length);
}
public String(byte bytes[], Charset charset) {
this(bytes, 0, bytes.length, charset);
}
public String(byte bytes[]) {
this(bytes, 0, bytes.length);
}
看代码的时间,写代码的时间总是很快,已经深夜1点半了,唉年纪大了,伤不起啊....,明天继续...
自以为什么都懂,其实什么都不懂!
by 2018/09/09