String 类的声明
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */ private final char value[];
}
- String 类是 final -> 不可以被子类继承
- 实现了 Serializable 接口 -> 可以序列化
- 实现 Comparable 接口 -> 可以序列化
String 底层由 char[] 到 byte[] 的转变
- 使得 String 的占用的内存减少一半 -> GC 次数的减少
- 引入了编码检测(Latin-1)的开销 -> 引入 coder 字段区分不同的编码
String 类的 hashCode 方法
private int hash; // 缓存字符串的哈希码
public int hashCode() {
int h = hash; // 从缓存中获取哈希码
// 如果哈希码未被计算过(即为 0)且字符串不为空,则计算哈希码
if (h == 0 && value.length > 0) {
char val[] = value; // 获取字符串的字符数组
// 遍历字符串的每个字符来计算哈希码
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i]; // 使用 31 作为乘法因子
}
hash = h; // 缓存计算后的哈希码
}
return h; // 返回哈希码
}
String 的方法介绍
substring(int beginIndex)截取字符串indexOf(char[] source, int sourceOffset, int sourceCount, char[] target, int targetOffset, int targetCount, int fromIndex)查找一个子字符串在原字符串中第一次出现的位置,并返回该位置的索引length()用于返回字符串长度isEmpty()用于判断字符串是否为空charAt()用于返回指定索引处的字符valueOf()用于将其他类型的数据转换为字符串getBytes()用于返回字符串的字节数组trim()用于去除字符串两侧的空白字符toCharArray()用于将字符串转换为字符数组
String 的不可变性
- String 类由 final 修饰
- 数据存在
char[]数组中也是由 final 修饰 -> String 对象是没有办法被修改的 -> String 类的一些方法实现最终都返回了新的字符串对象
好处
- 保证 String 对象的安全性
- 保证哈希值不会频繁变更
- 可以实现字符串常量池
字符串常量池
由于字符串的使用频率实在是太高了,所以 Java 虚拟机为了提高性能和减少内存开销,在创建字符串对象的时候进行了一些优化,特意为字符串开辟了一块空间——也就是字符串常量池
// 会创建两个对象
// 字符串常量池一个,堆上一个
String s = new String("二哥");
// 只创建一个对象
// 字符串常量池一个,引用变量 s 存储在栈上
String s = "三妹";
字符串常量池在内存中的位置
Java 7 之前
字符串常量池位于永久代(Permanent Generation)的内存区域中,主要用来存储一些字符串常量(静态数据的一种) ->我们创建一个字符串常量时,它会被储存在永久代的字符串常量池中。如果我们创建一个普通字符串对象,则它将被储存在堆中
Java 7
将字符串常量池从永久代中移动到堆中。这个改变也是为了更好地支持动态语言的运行时特性
Java 8
永久代(PermGen)被取消,并由元空间(Metaspace)取代。元空间是一块本机内存区域,和 JVM 内存区域是分开的。不过,元空间的作用依然和之前的永久代一样,用于存储类信息、方法信息、常量池信息等静态数据。
String.intern() 方法
该方法会从字符串常量池中查找这个字符串是否存在,若存在,则放回字符串常量池中的对象的引用
String s1 = new String("二哥三妹");
String s2 = s1.intern();
System.out.println(s1 == s2);
// s1 的对象是存在堆区的对象
// s2是字符串常量池的应用
// 故不相同
StringBuffer和StringBuilder的区别
// StringBuffer
public final class StringBuffer extends AbstractStringBuilder implements Serializable, CharSequence {
public StringBuffer() {
super(16);
}
public synchronized StringBuffer append(String str) {
super.append(str);
return this;
}
public synchronized String toString() {
return new String(value, 0, count);
}
// 其他方法
}
- StringBuffer 操作字符串的方法加了
synchronized关键字进行了同步,主要是考虑到多线程环境下的安全问题,所以如果在非多线程环境下,执行效率就会比较低,因为加了没必要的锁。 - => 为增加在单线程的执行效率,Java 还提供了 StringBuilder
- => 我们可以在
TreadLocal中多线程的使用 StringBuilder
public final class StringBuilder extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
{
// ...
public StringBuilder append(String str) {
super.append(str);
return this;
}
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}
// ...
}
new String("二哥") + new String("三妹");
// Java 会将上述代码解释为以下代码
new StringBuilder().append("二哥").append("三妹").toString();
StringBuilder 的内部实现
/**
* Constructs a string builder with no characters in it and an
* initial capacity of 16 characters.
*/
public StringBuilder() {
super(16);
}
StringBuilder 对象创建时,会为 value 分配一定的内存空间(初始容量 16),用于存储字符串。 #Todo 其他方法
字符串相等判断:Java中的equals() 与== 的区别和用法
- == 操作符用于比较两个对象的地址是否相等
.equal()方法用于比较两个对象的内容是否相等
// String 源码的 equal 的比较方法
public boolean equals(Object anObject) {
// 如果引用地址相同则一定相同
if (this == anObject) {
return true;
}
// 否则再判断内容是否相同
if (anObject instanceof String) {
String aString = (String)anObject;
if (coder() == aString.coder()) {
return isLatin1() ? StringLatin1.equals(value, aString.value)
: StringUTF16.equals(value, aString.value);
}
}
return false;
}
优雅的拼接 String 字符长
循环体内,拼接字符串最好使用 StringBuilder 的
append()方法,而不是 + 号操作符。原因就在于循环体内如果用 + 号操作符的话,就会产生大量的 StringBuilder 对象,不仅占用了更多的内存空间,还会让 Java 虚拟机不停的进行垃圾回收,从而降低了程序的性能
循环体内,拼接字符串最好使用 StringBuilder 的 append() 方法,而不是 + 号操作符
Append() 源码
public StringBuilder append(String str) {
super.append(str);
return this;
}
public AbstractStringBuilder append(String str) {
// 判断字符串是否为 NULL
if (str == null)
return appendNull();
int len = str.length();
// 确保当前 AbstractStringBuilder 对象有足够的容量来容纳新添加的字符串内容
// count 是当前字符序列的长度,加上要追加的字符串长度后,检查是否需要扩容
ensureCapacityInternal(count + len);
// 将传入的字符串 str 从索引 0 开始的 len 个字符复制到当前对象的字符数组 value 中,
// 复制操作从当前字符序列的末尾(count)开始
str.getChars(0, len, value, count);
count += len;
return this;
}
在 Java 中拆分字符串:详解 String 类的 split () 方法
特殊分隔符会报错
- 反斜杠
\(ArrayIndexOutOfBoundsException) - 插入符号
^(同上) - 美元符号
$(同上) - 逗点
.(同上) - 竖线
|(正常,没有出错) - 问号
?(PatternSyntaxException)
- 星号
*(同上) - 加号
+(同上) - 左小括号或者右小括号
()(同上) - 左方括号或者右方括号
[](同上) - 左大括号或者右大括号
{}(同上)
需要采用正则表达式去分离