之前写过一篇关于 String 类、StringBuilder 和 StringBuffer 的基本介绍,今天从 String 类的部分源码来看 String 类(本文基于 JDK 1.8)
- String 类的实例化
- 常用方法
- String 类的不可变
- 总结
1. String 类的构造器
先看看 String 类中定义的一个常量和一个变量:
/** The value is used for character storage. */
//存储字符
private final char value[];
/** Cache the hash code for the string */
//字符串的哈希值
private int hash; // Default to 0
翻了以下源码,发现从 100 - 600 行左右都是写的构造器,数不胜数,这里就选几个常见的吧:
先来说说连开发者都觉得没用的构造器吧:
//初始化一个 String 对象,它代表了一个空的字符序列
//我们平常直接就 String str = "";就好了
public String() {
this.value = "".value;
}
//初始化一个和参数一模一样的对象,除非你是要保留副本,不然没什么用处
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
然后是根据字符数组来进行初始化的构造器:
/**
* 用一个字符数组当前所储存的字符来初始化字符串,
* 后续对数组的修改将不会对字符串有影响,感觉在做
* String 类的题目时,较常用到这个方法
*/
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
//截取字符数组中的某一部分来初始化
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);
}
最后是根据 StringBuffer 和 StringBuilder 来进行初始化的构造器:
//StringBuffer 是线程安全的,所以需要加锁
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());
}
这里还有个很有意思的包私有构造器(看不懂):
/*
* Package private constructor which shares value array for speed.
* this constructor is always expected to be called with share==true.
* a separate constructor is needed because we already have a public
* String(char[]) constructor that makes a copy of the given char[].
*/
String(char[] value, boolean share) {
// assert share : "unshared not supported";
this.value = value;
}
后来去 Google 了一下有关字符串共享的相关内容,还是不太明白,可能是因为常量池的原因,在常量池中存放着字符串的引用,就会导致字符串被共享,这个构造器可能就是共享时的构造吧(待确定),文末给出写文时查询这一部分内容的链接,这一部分内容等下一篇讲述常量池时一起说说
2. 常用方法
1. 基本的
常用的 length(), charAt(), isEmpty() 等等都是根据根据字符数组来做操作的,就看其中一个吧:
public char charAt(int index) {
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return value[index];
}
2. hashCode()
这个方法是重写了 Object 类的 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;
}
在 哈希值等于 0 且字符串不为空时,才计算哈希值,否则就返回 0,计算方法就是 for 循环里的,举个简单的例子算一下:
字符串 ab 的哈希值就是 97 * 32 + 98 得出的
通过对字符串的哈希值的比较,能够得出两个字符串的值是否相同,但是却不能用来比较两个对象的引用:
3. join()
这个方法以前还都没听过,这次刚好看见了,就说一下用法:
//第一个参数时分隔符,第二个是可变参数
public static String join(CharSequence delimiter, CharSequence... elements) {
Objects.requireNonNull(delimiter);
Objects.requireNonNull(elements);
// Number of elements not likely worth Arrays.stream overhead.
StringJoiner joiner = new StringJoiner(delimiter);
for (CharSequence cs: elements) {
joiner.add(cs);
}
return joiner.toString();
}
这里引出另一个类:StringJoiner,这里不介绍,如果只有一个字符串,就不会添加分隔符,若有多个,则在每两个拼接的字符串中添加分隔符:
如果要拼接多个字符,并且要添加分隔符,用 join() 会方便很多
4. equals()
== 和 equals() 的区别相信都应该知道了,前者比较引用(引用相同,值自然就相同),后者只比较值:
public boolean equals(Object anObject) {
//如果引用相同,就直接返回 true
if (this == anObject) {
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) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
3. String 类的不可变
一直都在说 String 类是不可变的,究竟是怎么实现呢?
首先,String 类本身被声明为 final:public final class String
,不能被继承,所以不可能重写 String 类的方法(只读),整个 String 类的核心:private final char value[]
也被声明为 final
其次,在源码中的返回值为 String 类型的方法,如果方法中修改了字符串的结构,在最后都是 return new String(xxx) 这样子,如果没有修改字符串结构,就返回这个字符串,也就是所说的:任何修改字符串结构的操作都会产生一个新的 String 对象
4. 总结
其实对于 String 类,最重要的点就是不可变,至于其它的一些操作,感兴趣的可以去翻一下源码(3100 行,不多),注释写的非常清楚,很多操作其实伴随注释看一遍就可理解),就没有写在这篇文章里
最近开始着手准备明年的春招了,所以刚好写博客复习一下 Java 的一些基础知识,下一篇会介绍一下常量池
参考资源:
很早之前的一篇文章,JDK 版本还没到 1.8,所以看一看就好了http://chunlong.github.io/blog/2013/05/23/something-about-string/