1. String 类关系
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
复制代码
1.1 final
String 类被 final 关键字修饰,不能被继承,赋值后不可修改。
1.2 实现接口
java.io.Serializable:详解
序列化接口,标记接口(Marker Interface),实现了标记接口的类仅仅是给自身贴了个”标记“,未实现该接口无法被序列化。
Comparable<String>
比较器,值提供一个 compareTo 方法,用于比较两个字符串大小,下面会讲解该方法。
CharSequence
CharSequence 是字符值的可读序列。此接口提供对多种不同类型的字符序列的统一只读访问。
2. 成员变量
查看 String 源码我们可以看到,String 内部存储实际为 char 数组
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
/** 用于存储字符串的值 */
private final byte[] value;
/** 字符串的HashCode */
private int hash; // Default to 0
}
复制代码
3. 构造方法
String 字符串有以下四个常用的构造方法:
// 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());
}
复制代码
4. equals、compareTo 比较字符串
4.1 equals()方法比较字符串
String 重写了 Object 中的 equals()方法
引用是否相同。
判断变量是否为 String。
比较数组长度。
循环遍历数组中每个字符。
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;
if (n == anotherString.value.length) {
// 把两个字符串都转换为 char 数组对比
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
// 循环比对两个字符串的每一个字符
while (n-- != 0) {
// 如果其中有一个字符不相等就 true false,否则继续对比
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
复制代码
equalsIgnoreCase:忽略字符串的大小写进行字符串对比
4.2 compareTo()方法比较字符串
compareTo() 方法用于比较两个字符串,返回的结果为 int 类型的值,源码如下:
public int compareTo(String anotherString) {
int len1 = value.length;
int len2 = anotherString.value.length;
// 获取到两个字符串长度最短的那个 int 值
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;
}
复制代码
从源码中可以看出,compareTo() 方法会循环对比所有的字符,当两个字符串中有任意一个字符不相同时,则 return char1-char2。
两个方法都是用于比较字符串的,他们有一下不同:
equals() 可以接收一个 Object 类型的参数,而 compareTo() 只能接收一个 String 类型的参数;
equals() 返回值为 Boolean,而 compareTo() 的返回值则为 int。
当 equals() 方法返回 true 时,或者 compareTo() 方法返回 0 时,则表示两个字符串相同。
5. substring、concat、replace 字符串处理
5.1 substring()切割字符串
public String substring(int beginIndex, int endIndex) {
// 开始小于0,结束大于字符串长度,结束小于开始均抛出StringIndexOutOfBoundsException
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);
}
// 0到value.length返回自身,否则新建一个String返回
return ((beginIndex == 0) && (endIndex == value.length)) ? this
: new String(value, beginIndex, subLen);
}
复制代码
5.2 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);
// 新建String
return new String(buf, true);
}
复制代码
5.3 replace()替换字符
public String replace(char oldChar, char newChar) {
if (oldChar != newChar) {
int len = value.length;
int i = -1;
char[] val = value; /* avoid getfield opcode */
// 找到第一个oldChar
while (++i < len) {
if (val[i] == oldChar) {
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];
// 挨个替换oldChar
buf[i] = (c == oldChar) ? newChar : c;
i++;
}
// 新建String
return new String(buf, true);
}
}
return this;
}
复制代码
从这三个字符串处理方法可以看出,三个方法操作都不是在原有的字符串上进行的,而是重新生成了一个新的字符串对象。所以进行这些操作后,最原始的字符串并没有被改变。
6. 其他重要方法
indexOf():查询字符串首次出现的下标位置 lastIndexOf():查询字符串最后出现的下标位置 contains():查询字符串中是否包含另一个字符串 toLowerCase():把字符串全部转换成小写 toUpperCase():把字符串全部转换成大写 length():查询字符串的长度 trim():去掉字符串首尾空格 split():把字符串分割并返回字符串数组 join():把字符串数组转为字符串
7. 关联问题
7.1 String 为什么要用 final 修饰?
final:安全,高效
将 String 设计成不可变的,将不会出现其内部值发生改变而造成的严重系统问题,在日常使用中一个常见场景如 dto 类,用作数据传输其内部值就应该置为 final 不可变,已确保上层调用的安全性。
高效可以用 JVM 中的字符串常量池来举例,字符串常量池为我们缓存了字符串,可以提高系统运行的效率,如果创建两个对象 s1,s2 都等于字符串”Java“,其在常量池中只存在一个字符串 Java,只是有两个引用指向它。
7.2 == 和 equals 的区别是什么?
== 对于基本数据类型来说,是用于比较 “值”是否相等的;而对于引用类型来说,是用于比较引用地址是否相同的。
Object 的 equals 就是==,而 String 重写了 equals,用于比较字符串的值。
7.3 String 和 StringBuilder、StringBuffer 有什么区别?
因为 String 类型是不可变的,所以在字符串拼接的时候如果使用 String 的话性能会很低,因此我们就需要使用另一个数据类型 StringBuffer,它提供了 append 和 insert 方法可用于字符串的拼接,它使用 synchronized 来保证线程安全,如下源码所示:
@Override
public synchronized StringBuffer append(Object obj) {
toStringCache = null;
super.append(String.valueOf(obj));
return this;
}
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
复制代码
因为它使用了 synchronized 来保证线程安全,所以性能不是很高,于是在 JDK 1.5 就有了 StringBuilder,它同样提供了 append 和 insert 的拼接方法,但它没有使用 synchronized 来修饰,因此在性能上要优于 StringBuffer,所以在非并发操作的环境下可使用 StringBuilder 来进行字符串拼接。
7.4 String 的 intern() 方法有什么含义?
new String 都是在堆上创建字符串对象。
当调用 intern() 方法时,如果常量池中存在该值直接返回,否则编译器会将字符串添加到常量池中(stringTable 维护),并返回指向该常量的引用。
通过字面量赋值创建字符串(如:String str=”twm”)时,会先在常量池中查找是否存在相同的字符串,若存在,则将栈中的引用直接指向该字符串;若不存在,则在常量池中生成一个字符串,再将栈中的引用指向该字符串。
7.5 String 类型在 JVM 中是如何存储的?
String 常见的创建方式有两种,new String() 的方式和直接赋值的方式
直接赋值:先去字符串常连池中查找是否已经有此值,如果有则会把引用地址直接指向此值,否则会在常连池中创建,然后再把引用指向此值。
new String("abc"):首先"abc"在构造之前是一个常量池中的字符串,然后会在堆上创建一个字符串对象,将内存中的对象引用返回。
评论