JDK1.8源码学习-String
目录
一、String简介
String类是Java中最常用的类之一,所有字符串的字面量都是String类的实例,字符串是常量,在定义之后不能被改变。
二、定义
public final class String implements java.io.Serializable, Comparable<String>, CharSequence{}
1.String类是由final修饰的,表明String类不能被继承,并且String类中的成员方法都默认是final方法。
2.String类是由final修饰的,表明String类一旦创建,就无法被改变,对String对象的任何操作都不会影响到原对象,任何的change操作都会产生新的String对象。
3.java.io.Servializable,标识序列化,Comparable<String>中只有一个compareTo(T o)方法,用于两个实例化对象比较大小,CharSequence表示一个char值的可读序列,CharSequence与String都能用于定义字符串,但CharSequence的值是可读可写序列,而String的值是只读序列。
三、属性
private final char value[]; private int hash; // Default to 0 private static final long serialVersionUID = -6849794470754667710L; private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0];
1.String的内容本质上是使用不可变的char类型的数组来存储的。
2.hash是String实例化对象的hashcode的一个缓存值,这是因为String对象经常被用来进行比较,如果每次比较都重新计算hashcode值的话,是比较麻烦的,保存一个缓存值能够进行优化。
3.serialVersionUID为序列化ID.
4.serialPersistentFields属性用于指定哪些字段需要被默认序列化,具体用法为:
private static final ObjectStreamField[] serialPersistentFields = {
new ObjectStreamField("name", String.class),
new ObjectStreamField("age", Integer.Type)
}
transient用于指定哪些字段不会被默认序列化,两者同时使用时,transient会被忽略。
四、构造方法
4.1、无参构造函数
public String() { this.value = "".value; }
空字符串" "
4.2、参数为String类型
public String(String original) { this.value = original.value; this.hash = original.hash; }
其实就是一个克隆的过程,但是String是不可变的,所以没有太多必要。
4.3、参数为char数组
public String(char value[]) { this.value = Arrays.copyOf(value, value.length); }
传入一个字符数组,将该数组拷贝一份给value。
4.4、参数为char数组
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数组中的offset位置开始,截取count个字符,拷贝到value。
4.5、参数为bytes数组
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); }
从bytes数组中的offset位置开始,将长度为length的字节,以charsetName格式编码,拷贝到value
五、创建String对象
1.直接使用" ",也就是使用"字面量"赋值
String name = "张三";
2.使用连接符"+"来赋值
String name = "张" + "三";
3.使用关键字new来创建对象
String name = new String("张三");
六、常用方法
6.1、equals方法
public boolean equals(Object anObject) { //如果引用的是同一个对象,则返回真 if (this == anObject) { return true; } //如果不是String类型的数据,返回假 if (anObject instanceof String) { String anotherString = (String) anObject; int n = value.length; //如果char数组长度不相等,返回假 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; }
这里重写了Object中的equals方法,用来判断两个对象实际意义上是否相等
6.2、compareTo方法
public int compareTo(String anotherString) { //自身对象字符串长度len1 int len1 = value.length; //被比较对象字符串长度len2 int len2 = anotherString.value.length; //取两个字符串长度的最小值lim int lim = Math.min(len1, len2); char v1[] = value; char v2[] = anotherString.value; int k = 0; //从value的第一个字符开始到最小长度lim处为止,如果字符不相等,返回自身(对象不相等处字符-被比较对象不相等字符) while (k < lim) { char c1 = v1[k]; char c2 = v2[k]; if (c1 != c2) { return c1 - c2; } k++; } //如果前面都相等,则返回(自身长度-被比较对象长度) return len1 - len2; }
用于比较两个字符串的大小,如果两个字符串长度相等则返回0,如果长度不相等,则返回当前字符串的长度减去被比较的字符串的长度。
6.3、hashCode方法
public int hashCode() { int h = hash; //如果hash没有被计算过,并且字符串不为空,则进行hashCode计算 if (h == 0 && value.length > 0) { char val[] = value; //计算过程 //s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1] for (int i = 0; i < value.length; i++) { h = 31 * h + val[i]; } //hash赋值 hash = h; } return h; }
这里重写了hashCode方法,采用多项式进行计算,可以通过不同的字符串得到相同的hash,所以两个String对象的hashCode相同,并不代表两个String是相同的。
算法:假设n = 3
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; // Note: toffset might be near -1>>>1. //如果起始地址小于0或者(起始地址+所比较对象长度)大于自身对象长度,返回假 if ((toffset < 0) || (toffset > value.length - pc)) { return false; } //从所比较对象的末尾开始比较 while (--pc >= 0) { if (ta[to++] != pa[po++]) { return false; } } return true; } public boolean startsWith(String prefix) { return startsWith(prefix, 0); } public boolean endsWith(String suffix) { return startsWith(suffix, value.length - suffix.value.length); }
startsWith和endWith方法也是比较常用的方法,常用来判断字符串以特定的字符开始或结尾。
6.5、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方法用于将指定的字符串参数连接到字符串上。
6.6、replace方法
public String replace(char oldChar, char newChar) { //新旧值先对比 if (oldChar != newChar) { int len = value.length; int i = -1; char[] val = value; //找到旧值最开始出现的位置 while (++i < len) { if (val[i] == oldChar) { break; } } //从那个位置开始,直到末尾,用新值代替出现的旧值 if (i < len) { char buf[] = new char[len]; for (int j = 0; j < i; j++) { buf[j] = val[j]; } while (i < len) { char c = val[i]; buf[i] = (c == oldChar) ? newChar : c; i++; } return new String(buf, true); } } return this; }
replace的参数是char和charSequence,即可以支持字符的替换,也支持字符串的替换(charSequence即字符串序列的意思)
replaceAll的参数是regex,即基于规则表达式的替换,比如可以通过replaceAll("\d","*")把一个字符串所有的数字字符都替换成星号;
相同点:都是全部替换,即把源字符串中的某一字符或者字符串全部替换成指定的字符或者字符串。
不同点:replaceAll支持正则表达式,因此会对参数进行解析(两个参数均是),如replaceAll("\d","*"),而replace则不会,replace("\d","*")就是替换"\d"的字符串,而不会解析为正则。
6.7、trim方法
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; }
trim用于删除字符串的头尾的空格。