String类的字段主要有下面几个:
final char value[], 字符数组主要用于存放字符
final int offset,字符数组的第一个元素下标
final int count,字符数组中元素的数量统计
int hash,字符串的hash值
构造函数
如果我们平时这样定义数组,那么会得到一个空的字符数组,并且是不可变的。
String str = new String();
public String() {
this.offset = 0;
this.count = 0;
this.value = new char[0];
}
下一种使用字符串来初始化对象:
String str = new String(“abc”);
这里代码中处理了如果初始化的字符串两边有垃圾字符,或者是空格等的情况,会删除掉这些字符。
public String(String original) {
int size = original.count;
char[] originalValue = original.value;
char[] v;
if (originalValue.length > size) {
// The array representing the String is bigger than the new
// String itself. Perhaps this constructor is being called
// in order to trim the baggage, so make a copy of the array.
int off = original.offset;
v = Arrays.copyOfRange(originalValue, off, off+size);
} else {
// The array representing the String is the same
// size as the String, so no point in making a copy.
v = originalValue;
}
this.offset = 0;
this.count = size;
this.value = v;
}
使用字符数组构造字符串
String str = new String(new char[]{‘a’,’b’});
public String(char value[]) {
int size = value.length;
this.offset = 0;
this.count = size;
this.value = Arrays.copyOf(value, size);
}
其他还有很多灵活的构造函数,但是大多使用了Arrays的方法。
常用方法
- 返回字符串长度
public int length() {
return count;
}
- 判断字符串是否为空
public boolean isEmpty() {
return count == 0;
}
- 根据下标查找元素:
public char charAt(int index) {
if ((index < 0) || (index >= count)) {
throw new StringIndexOutOfBoundsException(index);
}
return value[index + offset];
}
- 判断字符串相等
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = count;
if (n == anotherString.count) {
char v1[] = value;
char v2[] = anotherString.value;
int i = offset;
int j = anotherString.offset;
while (n-- != 0) {
if (v1[i++] != v2[j++])
return false;
}
return true;
}
}
return false;
}
从这里可以看出,字符串相等的比较,是先检查字符串的长度是否相同,如果相同,则每一个字符元素进行比较,如果有其中一个不一样,则返回false。
- 字符串比较
public int compareTo(String anotherString) {
int len1 = count;
int len2 = anotherString.count;
int n = Math.min(len1, len2);
char v1[] = value;
char v2[] = anotherString.value;
int i = offset;
int j = anotherString.offset;
if (i == j) {
int k = i;
int lim = n + i;
while (k < lim) {
char c1 = v1[k];
char c2 = v2[k];
if (c1 != c2) {
return c1 - c2;
}
k++;
}
} else {
while (n-- != 0) {
char c1 = v1[i++];
char c2 = v2[j++];
if (c1 != c2) {
return c1 - c2;
}
}
}
return len1 - len2;
}
这个方法就比较玄妙了,首先将参数字符串的相关字段全部都复制一份出来,然后判断两个字符数组第一个元素下标是否相同。
如果相同:从下标开始到较小长度字符串进行循环,每个字符进行比较,如果遇到字符不相等的情况,则做减法。
如果不同,也是从按照较小长度字符串的数据进行循环,每个字符进行比较做减法。
- startsWith(String prefix)
public boolean startsWith(String prefix) {
return startsWith(prefix, 0);
}
public boolean startsWith(String prefix, int toffset) {
char ta[] = value;
int to = offset + toffset;
char pa[] = prefix.value;
int po = prefix.offset;
int pc = prefix.count;
// Note: toffset might be near -1>>>1.
if ((toffset < 0) || (toffset > count - pc)) {
return false;
}
while (--pc >= 0) {
if (ta[to++] != pa[po++]) {
return false;
}
}
return true;
}
这里调用了另外一个startsWith方法,传入了从第几个元素下标开始比较。默认都是0.
这里做的也是遍历传入字符串的每一个元素,判断是否相同。
- hashcode()
public int hashCode() {
int h = hash;
if (h == 0 && count > 0) {
int off = offset;
char val[] = value;
int len = count;
for (int i = 0; i < len; i++) {
h = 31*h + val[off++];
}
hash = h;
}
return h;
}
计算hashcode的方法使用了这个公式。
h = 31*h+val[off++]
hashcode的要求是,同一个字符串不论这个方法调用多少次,这个值总是不变的。而且不能和其他字符串计算出来的值一样。这样会造成冲突。就是说纯粹是为了确保唯一性。
这里使用31的原因是,31在CPU上做乘法时,采用左移计算性能更高。来自网络资料。
- intern()
这个方法应该算是String类里面唯一一个不是用Java实现的了。其源代码如下:
public native String intern();
说明是个本地方法,用C++来实现的。这个方法的作用是什么呢?
先要说下字符串定义的方式:
String str = “abc”;
这种方式也可以定义字符串,不过定义的字符串全部都在常量池中有储存,如果一个String类型的对象调用了intern方法的话,那么会检查这个字符串是否在常量池中有相等的String,如果有则返回常量池的对象,如果没有,则在常量池中定义一个。这样做的办法是性能会好很多,String不在堆上。
看一个例子:
package com.lenovo;
public class StringTest {
public static void main(String[] args) {
String a = "abc";
String b = new String("abc");
System.out.println(a==b);
System.out.println(a==b.intern());
System.out.println(a.equals(b));
}
}
这个输出是:
false
true
true
显然,a,b不是同一个,但是b调用了intern方法后,就是和a同一个对象了。