这一篇文章是对String类源码的介绍,String类很重要,他不是基本类型,所以大家要清楚,但是他的使用频率和基本数据类型一样。
我们看一下String类的定义
//String是final类型的,属于不可覆盖类型
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
实现了serializable接口和comparable接口,并且是被final修饰的。所以String类是不可以被继承的。
String类的属性:
private final char value[];//构造器接收的字符串全部都存在了char类型的数组中了,有因为value是final类型的,所以不可变
private int hash; // 用来存放String类对象经过hashcode()得到的hash值
private static final long serialVersionUID = -6849794470754667710L;
private static final ObjectStreamField[] serialPersistentFields =
new ObjectStreamField[0];
String的构造方法:
这是String类的所有的构造方法,其中有两个是已经过时的。下面我们具体看一下其中的构造方法吧。
public String() {
this.value = "".value;
}
/*
无参构造器,生成的是一个"",空字符,并不是null
*/
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
/*
一个String类型的为参数(其实就是一个对象的引用)的构造器,这里有递归的感觉,因为original是一个字符串,肯定有value和hash属性,
*/
上面两个构造方法是我们最常用的,第一种,String str=new String(),生成的是一个""空字符,并不是null;
第二种,String st=new String("字符串");生成了一个新的对象。
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
/*
接收一个char类型的数组,但是方法中使用了数组的copyOf方法,复制
*/
public String(char value[], int offset, int count) {
//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) {
//这个语句,已经判断过count为=0,
this.value = "".value;
return;
}
}
if (offset > value.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
this.value = Arrays.copyOfRange(value, offset, offset+count);//复制指定数组的指定范围
}
/*
这个方法,if()语句中的比较,即为巧妙,至少我从来没有这样用过
*/
public String(int[] codePoints, int offset, int count) {
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
if (count <= 0) {
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
if (offset <= codePoints.length) {
this.value = "".value;
return;
}
}
// Note: offset or count might be near -1>>>1.
if (offset > codePoints.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
final int end = offset + count;
// Pass 1: Compute precise size of char[]
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));
}
// Pass 2: Allocate and fill in char[]
final char[] v = new char[n];
for (int i = offset, j = 0; i < end; i++, j++) {
int c = codePoints[i];
if (Character.isBmpCodePoint(c))
v[j] = (char)c;
else
Character.toSurrogates(c, v, j++);
}
this.value = v;
}
/*
接收一个int类型的数组,按指定位置和长度复制
*/
@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;
}
/*
已过时,无法将字节转化为字符
*/
@Deprecated
public String(byte ascii[], int hibyte) {
this(ascii, hibyte, 0, ascii.length);
}
/*
已过时,无法将字节转化为字符
*/
private static void checkBounds(byte[] bytes, int offset, int length) {
if (length < 0)
throw new StringIndexOutOfBoundsException(length);
if (offset < 0)
throw new StringIndexOutOfBoundsException(offset);
if (offset > bytes.length - length)
throw new StringIndexOutOfBoundsException(offset + length);
}
/*
作用,判断传入的参数是个是正确的
*/
public String(byte bytes[], int offset, int length, String charsetName)
throws UnsupportedEncodingException {
if (charsetName == null)
throw new NullPointerException("charNameseString ");
checkBounds(bytes, offset, length);//判断参数是否符合
this.value = StringCoding.decode(charsetName, bytes, offset, length);
}
/*
通过使用指定字符集解码字节数组
*/
public String(byte bytes[], int offset, int length, Charset charset) {
if (charset == null)
throw new NullPointerException("charset");
checkBounds(bytes, offset, length);
this.value = StringCoding.decode(charset, bytes, offset, length);
}
/*
通过使用指定的 charset 解码指定的 byte 子数组,解码方法,都是使用的是 StringCoding.decode()方法,该方法已被重载,
*/
public String(byte bytes[], String charsetName)
throws UnsupportedEncodingException {
this(bytes, 0, bytes.length, charsetName);
}
/*
内部调用了一个以实现的构造器
*/
public String(byte bytes[], Charset charset) {
this(bytes, 0, bytes.length, charset);
}
/*
内部调用了一个以实现的构造器
*/
public String(byte bytes[], int offset, int length) {
checkBounds(bytes, offset, length);
this.value = StringCoding.decode(bytes, offset, length);
}
/*
没有指定解码集,所以使用木偶人的解码方法"iso-8859-1"
*/
public String(byte bytes[]) {
this(bytes, 0, bytes.length);
}
/*
通过使用平台的默认字符集解码指定的 byte 数组
*/
public String(StringBuffer buffer) {
synchronized(buffer) {
this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
}
}
/*
线程安全的,将一个StringBuffer转化为String
*/
public String(StringBuilder builder) {
this.value = Arrays.copyOf(builder.getValue(), builder.length());
}
/*
将一个StringBuilder转化为String
*/
String(char[] value, boolean share) {
// assert share : "unshared not supported";
this.value = value;
}
/*
构造器解码有三种方式,自己编码,有默认方式,charset指定,指定的字符集解码
*/
在做javaweb项目是我们也经常使用new String("字符串".getBytes("iso-8859-1"),"utf-8");解码字符串。
String类的普通方法:
charAt();
public char charAt(int index) {
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return value[index];
}//根据索引值,获得值,就是字符数组的获取
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);//也是复制,从len开始,将str复制到buf
return new String(buf, true);
}//返回的是一个新的字符串对象,
indexof系列方法;获得的是目标字符的索引值
如果让我们来实现这个方法,我们该如何实现呢?
我的思路就是:如"dsadmsaffdms",寻找"dm"的索引值,如何呢?1.将两个字符串变为字符数组A,B,2.循环遍历A,寻找字符d,如果找到,判断第二个字符是不是m,不是,从d的位置继续遍历,再次寻找到d,判断第二个字符,一次类推,md,这种方法太垃圾了,下利率及其低,数据结构中有种算法,kmp算法,效率超高。
public int indexOf(String str) {
return indexOf(str, 0);
}
public int indexOf(String str, int fromIndex) {
return indexOf(value, 0, value.length,
str.value, 0, str.value.length, fromIndex);
}
static int indexOf(char[] source, int sourceOffset, int sourceCount,
String target, int fromIndex) {
return indexOf(source, sourceOffset, sourceCount,
target.value, 0, target.value.length,
fromIndex);
}
static int indexOf(char[] source, int sourceOffset, int sourceCount,
char[] target, int targetOffset, int targetCount,
int fromIndex) {
/*
source:源字符数组 sourceOffset:源数组起始位置0 sourceCount:源数组长度
target:目标字符数组 targetOffset:目标数组起始位置0 targetCount:目标数组长度
fromIndex:源数组起始位置
不知道在方法中定义sourceOffset和targetOffset这两个参数有什么作用,两个值都是为0,在方法中也只是一个0的作用,
为啥还要定义这两个参数
*/
if (fromIndex >= sourceCount) {
//起始索引位置>大于源数组长度
return (targetCount == 0 ? sourceCount : -1);//目标数组长度为0,返回源数组长度,否则返回-1
}
if (fromIndex < 0) {
fromIndex = 0;
}
if (targetCount == 0) {
return fromIndex;
}
char first = target[targetOffset];//获得目标数组第一个字符
int max = sourceOffset + (sourceCount - targetCount); //获得需要遍历的最大索引值
for (int i = sourceOffset + fromIndex; i <= max; i++) {
//开始遍历
/* Look for first character. */
if (source[i] != first) {
//遍历的得到的字符不相等,==i;知道寻找到第一个与之相同的字符
while (++i <= max && source[i] != first);
}
/* Found first character, now look at the rest of v2 */
if (i <= max) {
//寻找到第一个相同的字符,直接比较targetCount个字符
int j = i + 1;
int end = j + targetCount - 1;
for (int k = targetOffset + 1; j < end && source[j]
== target[k]; j++, k++);
if (j == end) {
//读个字符比较后相等,则返回,不等,则继续循环
/* Found whole string. */
return i - sourceOffset;
}
}
}
return -1;
}
replace系列方法
replaceAll(String regex, String replacement);使用replacement替换所有字符串中正则表达式所匹配的字符
replaceFirst(String regex, String replacement);使用replacement替换所有字符串中正则表达式中第一个所匹配的字符
replace(CharSequence target, CharSequence replacement);使用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串
replace(char oldChar, char newChar);用newChar替换字符串中所以的oldChar
replace(char oldChar, char 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) {//++i,先计算后赋值
if (val[i] == oldChar) {
/*判断oldchar存在于字符数组中,存在跳出while循环,并得到改字符第一次出现的位置,
如果不存在,i的值会==len,
下面的判断,就不会再进行了,也就是说,这个字符串中不包含要替换的字符,返回原来的字符串
*/
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;
}
我在想,如果是我设计这个替换的方法,我会用最烂的方法,循环遍历。但是人家设计的,效率比我的高出很多,而且安全。在while语句中,不仅考虑到了安全问题,而且在安全的同时,还获得了第一次出现目标字符的位置。前面的就不需要再次遍历了,我直接无脑复制就行。只需要考虑后面的字符就行。
另外三个替换方法,我就不再上代码了,因为我菜啊,他们全部调用了pattern类里面的方法,这个类是有关正则表达式的,额,我就不看了,不要难为我,有兴趣的,可以看一下啊。
valueof();
substring系列方法(截取):
substring(int baginindex);
substring(int baginindex,int endindex);
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);//起始位置为0,返回本对象,不为0,返回新的对象
}
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);
/*
其实差不多,只不过是多了一个截止位置
*/
}
split();
split(String regex);
split(String regex,int limit);
split()方法经常这样使用, str="1,2,3,4,5,6,7,8" ; String[] s= str.split(","); [1,2,3,4,5,6,7,8]
str.split(",",3);-->第一次:"1","2,3,4,5,6,7,8"
第二次:操作的是"2,3,4,5,6,7,8"-->"2","3,4,5,6,7,8"
第三次:操作的是"3,4,5,6,7,8"-->"3","4,5,6,7,8"
hashcode();
String类有一个hash属性,记录String对象的hash值,整个类中,仅有两处用到了hash属性
一个是在构造方法中:
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
一个就是在hashcode()方法中:
public int hashCode() {
int h = hash;//这个方法,主要是真的上面的构造方法,如果hash有值了,直接返回就行了
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];//hash的值的计算方法
}
hash = h;
}
return h;
}
在object中讲过hashcode的一些知识,大家可以看一下。
equals();
public boolean equals(Object anObject) {
if (this == anObject) {//判断该对象是不是本对象
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) {//比较n次
if (v1[i] != v2[i])
//不相等直接返回true
return false;
i++;
}
return true;
}
}
return false;
}//判断的是内容是否相等,先判断,对象是否相等,因为字符串对象无法修改,字符数组间的比较
重写equals方法必须重写hashcode方法,可以看一下上一篇Object源码,我在最后解释了一下这两个方法的关系。
有一个例子:
static void change(String str){
str="zzzz";
}
public static void main(String[] args){
str="wwww";
change(str);
syso(str);
}
这个例子,最终输出的"wwww"还是"zzzz"?
答:输出的是"wwww",我们通常说java中有两种传递,值传递和引用传递,其实这样说是不对的,java中根本没有引用传递,只有值传递。
传递是,如果是基本数据类型的话,直接传递的是值,就是将值复制一份,即使改变,也不会涉及到实际的值。
如果传递的是一个对象类型的话,
在方法内,修改,会对原来对象进行修改,我们会说String类,也是对象类型,为啥不能再方法内进行修改?String类比较特殊,因为String类无法修改,每次操作都是生成一个新的对象。
好了,String类就分析这么多了。
对了,有两个问题,
一,java中对"+"进行了重载吗?为什么?
二,字符串常量池的使用。