1 String
在 JDK8 中,String 类使用 char[] 数组保存值。这个 char 数组使用 final 修饰符修饰,意味着一旦确定就引用即不可修改;此外,String 类中也没有提供修改数组、修改值的方法。上述两种方式保证了 String 值的不可变性。此外 String 类只用 final 修饰,表明其不可继承。
2 String不可变性带来的好处
- 可作为 Hash 的 key,减少计算量。
- 例如String作为HashMap的key,不可变的特性使得hash值也不可变,所以只需要进行一次hash计算。
- 可以用于String Pool。
- 字符串常量池是方法区域中的一个特殊存储区域。当创建字符串时,如果字符串已经存在于池中,则将返回现有字符串的引用,而不是创建新对象。字符串常量池实现的前提条件是Java中String对象是不可变的,字符串的值存放在字符串常量池中。
- 作为参数具有安全性。
- String作为参数,保证了参数一旦确定就不可变。
- 天生的线程安全性。
3 String中equals方法
public boolean equals(Object anObject) {
// 首先使用==判断二者内存是否指向同一个地方
if (this == anObject) {
return true;
}
// 再判断类型是否都为String
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;
}
4 String、StringBuilder与StringBuffer的异同
StringBuilder 和 StringBuffer 均继承自 AbstractStringBuilder ,这个抽象父类里实现了除 toString 以外的所有方法。StringBuilder 在重写方法的基础上没做其他扩展。StringBuffer 在重写方法的同时几乎为所有方法添加了synchronized 关键字用于同步。下面对这三者进行对比:
String | StringBuilder | StringBuffer | |
---|---|---|---|
可变性 | String使用final修饰的char数组,因此不可变 | 使用普通的char数组,可变 | 使用普通的char数组,可变 |
线程安全性 | String由于不可变性天生线程安全 | StringBuilder则不保证线程安全 | StringBuffer由于使用了synchronized关键字同样线程安全 |
性能 | 最差 | 最好 | 中等 |
是否可继承 | 不可 | 不可 | 不可 |
equals方法 | 重写了 | 未重写 | 未重写 |
valueOf方法 | 有该方法 | 没有该方法 | 没有该方法 |
substring方法 | 有该方法 | 没有该方法 | 有该方法 |
toString方法 | 直接返回自身 | 返回一个新的String | 用synchronized修饰,返回一个新的String |
package org.example;
public class App {
public static void main( String[] args ) {
String s1 = "s1";
StringBuilder s2 = new StringBuilder("s2");
StringBuffer s3 = new StringBuffer("s3");
System.out.println(s1 == s1.toString()); // true
System.out.println(s1.equals(s1.toString())); // true
System.out.println(s2.equals(s2.toString())); // false
System.out.println(s3.equals(s3.toString())); // false
String s11 = "s1";
StringBuilder s22 = new StringBuilder("s2");
StringBuffer s33 = new StringBuffer("s3");
System.out.println(s1.equals(s11)); // true
System.out.println(s2.equals(s22)); // false
System.out.println(s3.equals(s33)); // false
String substring = s11.substring(1);
String substring1 = s22.substring(1);
String substring2 = s33.substring(1);
System.out.println(substring); // 1
System.out.println(substring1); // 2 虽然输出了结果,实际上调用的是父类AbstractStringBuilder中的substring方法
System.out.println(substring2); // 3
}
}
5 字符串常量池
字符串常量池(String Pool)保存着所有字符串字面量(literal strings),这些字面量在编译时期就确定。
不仅如此,还可以使用 String 的 intern() 方法在运行过程中将字符串添加到 String Pool 中。当一个字符串调用 intern() 方法时,如果 StringPool 中已经存在一个字符串和该字符串值相等(使用 equals() 方法进行确定),那么就会返回 StringPool 中字符串的引用;否则,就会在 String Pool 中添加一个新的字符串,并返回这个新字符串的引用。
StringBuilder sb = new StringBuilder("sdkfgusdkugjsdkljgb");
sb.append("abc");
String s = sb.toString();
s.intern();
String str = "sdkfgusdkugjsdkljgbabc";
System.out.println(s == str); // true
StringBuilder sb2 = new StringBuilder("sdkfgusaaaaaaaaaaa");
sb2.append("bc");
String s2 = sb2.toString();
//s2.intern();
String str2 = "sdkfgusaaaaaaaaaaabc";
System.out.println(s2 == str2); // false
6 字符串拼接优化
字符串拼接从 JDK5 开始就已经完成优化,并且没有进行新的优化。
- 循环内 String + 常量 会每次 new 一个 StringBuilder ,再调用 append 方法。
- 循环外字符串拼接可以直接使用 String 的 + 操作,没有必要通过 StringBuilder 进行 append。
- 有循环体的话,最好在循环外声明 StringBuilder 对象,在循环内进行手动 append。这样不论循环多少层都只有一个 StringBuilder 对象。
StringBuffer 和 StringBuilder 的扩容策略:当字符串缓冲区容量不足时,原有容量将会加倍,以新的容量来申请内存空间,建立新的char数组,然后将原数组中的内容复制到这个新的数组当中。
因此,对于大对象的扩容会涉及大量的内存复制操作。所以,如果能够预先评估 StringBuilder 或 StringBuffer 的大小,将能够有效的节省这些操作,从而提高系统的性能。
String s = "a";
long begin1 = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
s = s + i;
}
long end1 = System.currentTimeMillis();
System.out.println(end1 - begin1); // 289
StringBuilder s2 = new StringBuilder("a");
long begin2 = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
s2 = s2.append(i);
}
long end2 = System.currentTimeMillis();
System.out.println(end2 - begin2); // 0,是的,没看出,确实是 0
7 字符串和整型的转换
其实 String 和 byte、short、int、long、float、double的转换方法都差不多,这里以 int 为例。
- 把 String 转换成 int
- Integer.ParseInt(str):直接使用静态方法,只产生一个对象,但会抛出 NumberFormatException 异常。
- Integer.valueOf(str).intValue():Integer.valueOf(s) 相当于 new Integer(Integer.parseInt(s)),也会抛 NumberFormatException 异常,但会多产生一个对象。
- 把 int 转换成 String
- String.valueOf( i ):直接使用静态方法,只产生一个对象
- Integer.toString( i ):直接使用静态方法,只产生一个对象
- “” + i:会产生两个对象 ```java // String 转 int String str = “1”; int i = Integer.parseInt(str); int i1 = Integer.valueOf(str).intValue();
// int 转 String int j = 99; String s1 = String.valueOf(j); String s2 = Integer.toString(j); String s3 = “” + j;
System.out.println(s1.getClass()); //class java.lang.String System.out.println(s2.getClass()); //class java.lang.String System.out.println(s3.getClass()); //class java.lang.String ```
参考
- https://www.pdai.tech/md/java/basic/java-basic-lan-basic.html
- https://snailclimb.gitee.io/javaguide/#/docs/java/basis/Java%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86
- https://blog.nowcoder.net/n/8f0280724e074093a7e7b5951098c2bc
- https://imlql.cn/post/5df2d017.html
- https://blog.csdn.net/sindy_yoga/article/details/50760190
- https://www.runoob.com/w3cnote/java-string-and-int-convert.html