JDK 源码阅读001:String
本文剖析了 String
的源码(1.8),主要讲述了 String
的一些特性以及一些核心方法的设计与实现。
一、String 的不可变性
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
}
上面的String 源码解释了 String 的不可变性。
-
String 被 final 修饰,意味着该类不能被继承,也就没有子类,无法通过继承的方式覆写 String 的方法。
final 修饰 class,可以参考 Java 虚拟机规范
A class can be declared
final
if its definition is complete and no subclasses are desired or required.It is a compile-time error if the name of a
final
class appears in theextends
clause (§8.1.4) of another class declaration; this implies that afinal
class cannot have any subclasses.It is a compile-time error if a class is declared both
final
andabstract
, because the implementation of such a class could never be completed (§8.1.1.1).Because a
final
class never has any subclasses, the methods of afinal
class are never overridden (§8.4.8.1). -
String 底层使用一个 value 字符数组来存储,使用
private
和final
修饰,意味着value
一旦被赋值,内存地址不会再改变(final
),并且不能再被修改(private
)
二、String.intern() 方法
String
类的intern()
方法涉及到一个叫做 “常量池“ 的概念,可以理解成 Java 系统级别的缓存。
- 直接使用双引号声明出来的
String
对象会直接存储在常量池中。 - 如果不是用双引号声明的
String
对象,可以使用String
提供的intern
方法。intern 方法会从字符串常量池中查询当前字符串是否存在,若不存在就会将当前字符串放入常量池中
JAVA 使用 jni 调用c++实现的StringTable
的intern
方法, StringTable
的intern
方法跟Java中的HashMap
的实现是差不多的, 只是不能自动扩容。默认大小是1009。
要注意的是,String的String Pool是一个固定大小的Hashtable
,默认值大小长度是1009,如果放进String Pool的String非常多,就会造成Hash冲突严重,从而导致链表会很长,而链表长了后直接会造成的影响就是当调用String.intern
时性能会大幅下降(因为要一个一个找)。
在 jdk6中StringTable
是固定的,就是1009的长度,所以如果常量池中的字符串过多就会导致效率下降很快。在jdk7中,StringTable
的长度可以通过一个参数指定:
-XX:StringTableSize=99991
三、String 的构造函数和若干重要方法
1. 构造函数
String
类有4个核心构造函数
// String 为参数的构造方法
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
// char[] 为参数构造方法
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
// StringBuffer 为参数的构造方法
public String(StringBuffer buffer) {
synchronized(buffer) {
this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
}
}
// StringBuilder 为参数的构造方法
public String(StringBuilder builder) {
this.value = Arrays.copyOf(builder.getValue(), builder.length());
}
2. 使用频率很高的几个方法
length()
// 字符串长度
public int length() {
return value.length;
}
isEmpty()
public boolean isEmpty() {
return value.length == 0;
}
charAt()
public char charAt(int index) {
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return value[index];
}
equals()
和 equalsIgnoreCase()
public boolean equals(Object anObject) {
// 对象引用相同直接返回 true
if (this == anObject) {
return true;
}
// 判断需要对比的值是否为 String 类型,如果不是则直接返回 false
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
// 比较底层数组长度,长度不同,直接返回 false,长度相同,遍历字符数组,循环比较,只要有一个字符不同,返回 false
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;
}
直接使用 equals()
方法时,应该小心空指针。非空字符串放在前面,或者直接使用 apache commons 相关工具类 StringUtils
:
// StringUtils::equals
public static boolean equals(String str1, String str2) {
return str1 == null ? str2 == null : str1.equals(str2);
}
equalsIgnoreCase()
: 该方法 与 equals()
不同之处在于前者忽略大小写比较字符串。
compareTo()
和 compareToIgnoreCase()
// 返回值为 0 表示等于,正数表示大于,负数表示小于
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 c1 = v1[k];
char c2 = v2[k];
if (c1 != c2) {
return c1 - c2;
}
k++;
}
return len1 - len2;
}
compareToIgnoreCase()
:比较忽略大小写
其他
- indexOf():查询字符串首次出现的下标位置
- lastIndexOf():查询字符串最后出现的下标位置
- contains():查询字符串中是否包含另一个字符串
- toLowerCase():把字符串全部转换成小写
- toUpperCase():把字符串全部转换成大写
- trim():去掉字符串首尾空格
- replace():替换字符串中的某些字符
- split():把字符串分割并返回字符串数组
- join():把字符串数组转为字符串