一、String 类
1:定义
public final class String implements java.io.Serializable, Comparable<String>, CharSequence{}
String是final类型的,表示该类不能被继承,同时该类实现了三个接口:java.io.Serializable
、 Comparable<String>
、 CharSequence
2:属性
private final char value[];
private int hash; // Default to 0
缓存字符串的的hash code .默认值是0
private static final long serialVersionUID = -6849794470754667710L;
private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0];
因为String
实现了Serializable
接口,所以支持序列化和反序列化支持。Java的序列化机制是通过在运行时判断类的serialVersionUID
来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID
与本地相应实体(类)的serialVersionUID
进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常(InvalidCastException
)。
3:构造方法
(1):使用字节数组构造一个String
(2)使用字符数组、字符串构造一个String
(3)使用StringBuffer和StringBuider构造一个String
public String(StringBuffer buffer) {
synchronized(buffer) {
this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
}
}
public String(StringBuilder builder) {
this.value = Arrays.copyOf(builder.getValue(), builder.length());
}
(4)特殊的保护类型的构造方法
String(char[] value, boolean share)
// assert share : "unshared not supported";目前不支持使用false,只使用true
this.value = value;
}
String(char[] value, boolean share) 和 String(char[] value)
有两点区别
第一个,多了一个参数: boolean share
,在方法体中根本没被使用,也给了注释,目前不支持使用false,只使用true。那么可以断定,加入这个share的只是为了区分于String(char[] value)方法,不加这个参数就没办法定义这个函数,只有参数不能才能进行重载。
第二个区别就是具体的方法实现不同。我们前面提到过,String(char[] value)
方法在创建String的时候会用到 会用到Arrays
的copyOf
方法将value中的内容逐一复制到String当中,而这个String(char[] value, boolean share)
方法则是直接将value的引用赋值给String的value。那么也就是说,这个方法构造出来的String和参数传过来的char[] value
共享同一个数组。
String对象一旦被创建就是固定不变的了,对String对象的任何改变都不影响到原对象,相关的任何change操作都会生成新的对象
4:方法
(1):返回字符串的长度
public int length() {
return value.length;
}
(2)返回字符串是否为空
public boolean isEmpty() {
return value.length == 0;
}
(3)返回字符串中第(index+1)个字符
public char charAt(int index) {
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return value[index];
}
(4)比较方法
内存地址相同,则为真。
如果对象类型不是String类型,则为假。否则继续判断。
如果对象长度不相等,则为假。否则继续判断。
从后往前,判断String类中char数组value的单个字符是否相等,有不相等则为假。如果一直相等直到第一个数,则返回真。
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;
}
(5)
先从0开始判断字符大小。如果两个对象能比较字符的地方比较完了还相等,就直接返回自身长度减被比较对象长度,如果两个字符串长度相等,则返回的是0,巧妙地判断了三种情况。
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;
}
(6)为什么选31
第一,31是一个不大不小的质数,是作为 hashCode 乘子的优选质数之一。另外一些相近的质数,比如37、41、43等等,也都是不错的选择。那么为啥偏偏选中了31呢?请看第二个原因。
第二、31可以被 JVM 优化,31 * i = (i << 5) - i
。数字31有一个很好的特性,即乘法运算可以被移位和减法运算取代,来获取更好的性能.31只占用5bits!
(2^5 = 32
,是不是很小。当字符串长度不是很长时,用质数2做为乘子算出的哈希值,数值不会很大。也就是说,哈希值会分布在一个较小的数值区间内,分布性不佳,最终可能会导致冲突率上升。\
如果用一个较大的大质数101会产生什么样的结果呢?根据上面的分析,不用再担心哈希值会分布在一个小的区间内了,因为101^5 = 10,510,100,501
。但是要注意的是,这个计算结果太大了。如果用 int 类型表示哈希值,结果会溢出,最终导致数值信息丢失。)
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;
}
(7) 测试此字符串从指定索引开始的子字符串是否以指定前缀开始。
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;
}
(8)将指定字符串连接到此字符串的结尾。
先判断被添加字符串是否为空来决定要不要创建新的对象。
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);
}
(9) 返回一个新的字符串,它是通过用 newChar
替换此字符串中出现的所有 oldChar
得到的。
最开始先找出旧值出现的位置,这样节省了一部分对比的时间
public String replace(char oldChar, char newChar) {
//新旧值先对比
if (oldChar != newChar) {
int len = value.length;
int i = -1;
char[] val = value; /* avoid getfield opcode */
//找到旧值最开始出现的位置
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;
}
(10)截取字符串
public String substring(int beginIndex, int endIndex) { //beginIndex - 起始索引(包括) endIndex - 结束索引(不包括)
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > value.length) {
throw new StringIndexOutOfBoundsException(endIndex);
}
int subLen = endIndex - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return ((beginIndex == 0) && (endIndex == value.length)) ? this
: new String(value, beginIndex, subLen);
}
(11)去掉首位空格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;
}