一、类定义
public final class String implements java.io.Serializable, Comparable<String>, CharSequence
String是一个final的类,不能被继承。
实现了Serializable表示当前的类是能够进行实例化的对象。
实现了 Comparable<String> 接口。其中包含的方法有:public int compareTo(T o);。这个方法表示String比较大小。
实现了 CharSequence接口,其中的方法有:
int length();//获取到当前String中包含的字符串的长度。
char charAt(int index);//获取支付串某个位置上的字符
CharSequence subSequence(int start, int end);//截取支付串。
二、属性字段:
private final char value[];//支付串
private int hash; //当前字符串对应的hash值
private static final long serialVersionUID = -6849794470754667710L;
三、内部类:
无
四、构造函数:
//无参的类,基本是构造一个长度为0的变量
public String() {
this.value = new char[0];
}
//复制一个String的对象。内部地址不同。equals = true
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
//根据一个char[]来创建一个 String。
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
其中Arrays.copyOf(value, value.length):首先创建一个char[],然后调用System.arraycopy方法将就的数据复制给新的数据。
其中System.arraycopy 是一个native的方法。在底层实现内存块的拷贝
/**
*根据一个char[]数组的部分字段创建一个String。也是调用了Arrays.copyOfRange.
**/
public String(char value[], 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 > value.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
this.value = Arrays.copyOfRange(value, offset, offset+count);
}
/**
* 根据一个int数组来创建一个String.
**/
public String(int[] codePoints, int offset, int count) {
//检查边界
//计算宽度,主要是由于有些int —> char可能需要两位来表示。对于一些无法转变为 char的int就直接抛出异常。
//前面部分代码省略
int n = count;
for (int i = offset; i < end; i++) {
int c = codePoints[i];
if (Character.isBmpCodePoint(c))
continue;
else if (Character.isValidCodePoint(c))
n++;
else throw new IllegalArgumentException(Integer.toString(c));
}
// 将当前的int[]转变为 一个char[]。java使用unicode。16位,但是不够,所以拿出了一部分使用32位来表示字符。
final char[] v = new char[n];
for (int i = offset, j = 0; i < end; i++, j++) {
int c = codePoints[i];
if (Character.isBmpCodePoint(c))//使用一位char来表示当前的对象
v[j] = (char) c;
else
Character.toSurrogates(c, v, j++);//否者需要两位char来表示一个int。这个是 16位表示所有的unicode但是不够,所以使用了两位来表示。
}
}
/**
*将byte[]转变为 String,
* 首先是byte是8位,而char 是16位。如果hibyte 表示高8位的值。
* 所以如果,hibyte = 0 就默认的 低八位 & 0xff就好
* 如果 hibyte != 0 ,指定了高八位 != 0 。首先将 hibyte左移8位,然后和低8位拼成一个新的char。
*/
@Deprecated
public String(byte ascii[], int hibyte, int offset, int count) {
checkBounds(ascii, offset, count);
char value[] = new char[count];
if (hibyte == 0) {
for (int i = count; i-- > 0;) {
value[i] = (char)(ascii[i + offset] & 0xff);
}
} else {
hibyte <<= 8;
for (int i = count; i-- > 0;) {
value[i] = (char)(hibyte | (ascii[i + offset] & 0xff));
}
}
this.value = value;
}
/**
*通过 byte[]以及 chartSet进行编码,将byte转变为 Char 。
* 其中StringCodeing中编码的单写一篇。
**/
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);
}
/**
*将一个StringBuffer转变为一个String。
**/
public String(StringBuffer buffer) {
synchronized(buffer) {
this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
}
}
StringBuffer是线程不安全的。这里给这个对象进行加锁。
综上所述:
对于java中String的构造方法主要有这样几种类型:
char[],String,int[],byte[],StringBuilder/StringBuffer.
其中char[] 中使用了Array.copy的方法来帮助创建一个新的char[].
int[]转变为 一个char的时候,会有 char 是一个16位,但是 int 是32位,只有部分 int能够转变为char。java中使用Unicode来表示char的,16位能够表示65535个字符,但是Unicode不止这么多,所以从16位当中拿出了一部分来拼成32位来表示所有的Unicode。
byte[]转变为char的时候,从8位转为16位。其中就有编码的问题。 StringCoding方法来帮助 byte转变为 char。这个单讲。
StringBuilder/StringBuffer单讲。
五、String中常见的方法:
//比较两个字符串是否相等。
public boolean equals(Object anObject) {
if (this == anObject) {//如果两个字符串内存地址相等,就返回true。
return true;
}
if (anObject instanceof 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) {//最多比较n次,从低位向高位进行比较
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
//比较连个字符串的大小,
public int compareTo(String anotherString) {
int len1 = value.length;
int len2 = anotherString.value.length;
int lim = Math.min(len1, len2);//找出两个长度小的那一个。
char v1[] = value;
char v2[] = anotherString.value;
int k = 0;
while (k < lim) {//从头开始依次比较char,小的那个返回。
char c1 = v1[k];
char c2 = v2[k];
if (c1 != c2) {
return c1 - c2;
}
k++;
}
return len1 - len2;//如果前面都一样,长度长的那个大。
}
//计算 hashCode。
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {//如果当前的hash值没有计算过,同时这个字符串的长度 > 0
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];//使用一个表达式来表示当前的系统。
}
hash = h;//将计算出来的 hash值赋给 当前的 String的hash值。
}
return h;
}
Hash值还是依赖 char[]中的值来计算出来的。
//判断当前的String从 toffset开始是否以prefix开头。
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.
if ((toffset < 0) || (toffset > value.length - pc)) {//如果 开头支付串的长度比被比较的支付串长,直接返回否定
return false;
}
while (--pc >= 0) {
if (ta[to++] != pa[po++]) {//然后又开头字符串开始一个个的比较,如果不相等就返回false。
return false;
}
}
return true;
}
//将字符串str 写到 当前字符串的后面。
public String concat(String str) {
int otherLen = str.length();//获取到被拼接的支付串的长度
if (otherLen == 0) {//如果被拼接的支付串的长度 == 0.返回当前的字符串
return this;
}
int len = value.length;
char buf[] = Arrays.copyOf(value, len + otherLen);//创建一个 当前字符串和拼接支付串长度和 的新字符串。然后将当前的字符串拷贝到新字符串中
str.getChars(buf, len);//然后将 拼接支付串添加到尾部。
return new String(buf, true);//根据新字符串创建一个新的 String。
}
// 替换字符
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++) {//先把不需要替换的字符拷贝到新的char[]来。
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;
}
替换算法,首先看是否需要进行替换,然后找到第一个需要被替换的字符的位置,接着从第一个需要替换的位置的支付开始进行依次比较。
最后创建新的字符串。
/**
*将所有的空格和 回车换行符都去除
**/
public String trim() {
int len = value.length;
int st = 0;
char[] val = value; /* avoid getfield opcode */
while ((st < len) && (val[st] <= ' ')) {//从头开始,找出第一个不小于 空格 的char的位置
st++;
}
while ((st < len) && (val[len - 1] <= ' ')) {//从尾部开始,找出第一个 不小于 空格的char的位置。
len--;
}
return ((st > 0) || (len < value.length)) ? substring(st, len) : this;//根据位置来判断创建一个新的支付串。
}
这里先找出第一个的位置,而不是简单的创建。这样做能够使用Array.copy方法更好的发挥当前系统的威力。
/**
*将当前的支付串 显示的写到 一个pool中去。同时返回这个对象在 pool中的引用。
**/
public native String intern();
看一段测试代码:
String abc = "abc”; //当前的abc的引用是String的pool中的引用
String aString = new String(abc);//这个引用是在堆上的
String bString = new String(abc);//这个是堆上的
System.out.println(aString == bString);//返回false
aString = aString.intern();//这个时候aString已经指向了 pool中的“abc”
bString = bString.intern();//这个时候bString也指向了 pool中的 “abc"
System.out.println(aString == bString);// true
System.out.println(abc == aString);//true
对于String中的方法,其中都是对这个char[]数组进行操作。
其中做了小步的优化,首先生成小的结果,直接返回。例如 equals,首先判断是否 内存相等,然后判断类型相等人,然后判断长度相等,最后在开始一个个的char的比较。一般人可能会直接想到挨个的比较,这样也是一个正确的算法,但是性能比起java提供的要差很多。很大一部分的equals()方法可能就到不了 逐个比较的时候。