关于String类的一些浅谈
作为频繁使用的String类,一个合格的程序员需要对其本来的面貌有所了解。
大家都知道基本类型中并不包括String类,java中的对象大都是存放在堆中的,但是String对象是一个特例,它被存放在常量池中;
当创建了String类的对象时,首先会在常量池中其查看这个类是否存在,如果存在则不会创建,如果不存在才创建,这一部分程序员是看不见。
String类:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
}
点开String会发现String是被final修饰的,也就是说,String不能被继承,同时它实现了Serializable接口可以序列化和反序列化,实现了Comparable支持字符串的比较,实现了CharSequence接口说明它是一个字符序列。
成员变量:
private final char value[];
private int hash; //字符串的hashcode,默认是0
/** use serialVersionUID from JDK 1.0.2 for interoperability */
private static final long serialVersionUID = -6849794470754667710L;
value[]是用来存储字符串的,String字符串实际上就是字符数组组成的,而且这个数组也是被final修饰的,不然会造成数据的不安全。
有参构造:
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
该构造函数经常被用来做面试题,问new String(“abc”);共创建了几个对象?
答案是两个,字符串"abc"创建一个对象放在常量池中,new String()又创建一个对象放在堆中。
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
通过整个char数组参数来构造String对象,实际将参数char数组值复制给String对象的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);
}
截取入参数组的一部分来构造String对象,具体哪一部分由offset和count决定,其中做了些参数检查,传入非法参数会报数组越界异常StringIndexOutOfBoundsException
//…
常用方法:
Equals():(面试有可能问到:Equals如何实现?)
public boolean equals(Object anObject) {
// Object是所有类直接或间接的父类
if (this == anObject) {
//首先是跟自己比,看是否是相同的对象,如果是直接返回true,且这里比的是内存地址
return true;
}
if (anObject instanceof String) {
//判断被比较的对象是不是String类的,只有是同类才有比下去的必要
String anotherString = (String)anObject;
int n = value.length;
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;
}
length():
public int length() {
return value.length;
}
获取字符串的长度,实际上就是返回内部数组的长度。因为char数组被final修饰是不可变的,只要构造完成char数组中的内容长度都不会改变,所以这里可以直接返回数组的长度。
substring(int beginIndex):
public String substring(int beginIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
int subLen = value.length - beginIndex; //截取的位置不能比字符串长
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return (beginIndex == 0) ? this : new String(value, beginIndex, subLen); //如果beginIndex=0则返回原串,否则创建一个新的字符串返回。
}
截取字符串的子串。截取从指定位置beginIndex开始(包含这个位置),到字符串结束之间的字符串内容。
substring(int beginIndex, int endIndex):
public String substring(int beginIndex, int 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);//如果beginIndex,endIndex是从0到字符串的长度,就返回本身,否则新建字符串返回
}
截取原字符串的子串,同上个方法类似,上一个方法的结束位置是原串的结尾,这个方法是指定结束位置endIndex(不包含这个位置)。需要注意的是这个方法是包含头不包含尾。
hashCode():
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
这里的hashCode重写了Object的hashCode方法。
java中的hashCode有两个作用。
一:Object的hashCode返回对象的内存地址。
二:对象重写的hashCode配合基于散列的集合一起正常运行,这样的散列集合包括HashSet、HashMap以及HashTable等。对于大量的元素比较时直接比较equals效率低下,可先判断hashCode再判断equals,因为不同的对象可能返回相同的 hashCode(如"Aa"和"BB"的hashCode就一样),所以比较时有时需要再比较equals。hashCode只是起辅助作用。
为了使字符串计算出来的hashCode尽可能的少重复,即降低哈希算法的冲突率,设计者选择了31这个乘数。选31有两个好处。1:31是一个不大不小的质数,是作为 hashCode 乘子的优选质数之一,其它的像37、41、43也是不错的乘数选择。2:31可以被 JVM 优化,31 * i = (i << 5) - i。计算hashCode的原理也很简单,即用原hashCode乘以31再加上char数组的每位值。
replace(char oldChar, char newChar):
将字符串中所有的旧字符oldChar,替换为新的字符newChar
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) {
//先找到字符串中第一次出现oldChar字符的位置i
if (val[i] == oldChar) {
break;
}
}
if (i < len) {
char buf[] = new char[len];
for (int j = 0; j < i; j++) {
//将之前的字符数组复制给新数组buf
buf[j] = val[j];
}
/**然后从i后将字符数组中的内容复制给buf,如果字符为oldCha则替换为newChar.然后再通过buf创建新的字符串返回**/
while (i < len) {
char c = val[i];
buf[i] = (c == oldChar) ? newChar : c;
i++;
}
return new String(buf, true);
}
}
return this;
}
Intern():
public native String intern();
这个方法是本地方法,无方法体。
使用String的intern方法可以节省内存。在某些情况下可以使用 intern() 方法,它能够使内存中的不同字符串都只有一个实例对象。
原来在常量池中找不到时,复制一个副本放到常量池,1.7后则是将在堆上的地址引用复制到常量池。
看下面的例子:
public static void main(String[] args) {
String str2 = new String("hello") + new String("world");
str2.intern(); // 使用intern方法后str2与str1指向同一个对象,否则它们指向两个不同的对象。这样就能达到节省内存的效果。
String str1 = "helloworld";
System.out.println("比较结果:");
System.out.println(str2 == str1);
}
运行结果:
剩余方法参看原类。
欢迎留言讨论,感谢你的关注!