写在前面
日常工作中经常用到字符串,无论是哪种语言也都有自己对字符串的定义以及使用;今天就聊一聊工作中使用到的字符串。
正文开始
1. 字符串的定义
Java中字符串的定义为英文字符双引号包裹的String类型;注意有别与其他语言的单引号也是字符串,Java中单引号对应的是字符类型。
// 注意是双引号
String string = "";
// 或者
String string1 = new String("hello world");
// 字符 A (只能是单个字符)
char c = 'A';
// A 对应的Unicode码
int n = 'A';
// 字符串A
String s = "A";
// 多行字符串
String s = """
Hello
World
""";
2. 字符串的拼接
一般常见的操作使用运算符+,复杂情况下会用到StringBuff StringBuild;我们先看一个基本的用法:
String string1 = "hello";
String string2 = "world";
String string = string1 + " " + string2;
System.out.println(string);
// hello world
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("hello");
stringBuilder.append(" ");
stringBuilder.append("world");
System.out.println(stringBuilder);
// hello world
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("hello");
stringBuffer.append(" ");
stringBuffer.append("world");
System.out.println(stringBuffer);
hello world
//
对于实现字符串的拼接这类需求都可以完成,那么为什么同一功能会出现三种不同的实现呢,三种方式有什么区别么?
带着疑问出发翻看文档,得出如下结论:
1. String类型相对于后两者是不可变的对象;
2. StringBuffer和StringBuilder,前者是线程安全,后者是线程不安全;
所以 在执行String类型的拼接会产生新对象,效率较低;StringBuilder 比 StringBuffer执行效率高。
以上都是我们翻看文档得出的结论,接着带着问题我们查看一下源码,验证一下结论:
如图 StringBuffer和StringBuilder 有着一样的继承以及实现所以从使用的角度来说两者没有任何区别,所有开发的API均为通用的;不一样的是在 StringBuffer的源码中方法均被synchronized修饰;这就解释了StringBuffer为什么是线程安全;
// 下面是两者部分源码的截取
// StringBuff
@Override
public synchronized StringBuffer append(double d) {
toStringCache = null;
super.append(d);
return this;
}
/**
* @throws StringIndexOutOfBoundsException {@inheritDoc}
* @since 1.2
*/
@Override
public synchronized StringBuffer delete(int start, int end) {
toStringCache = null;
super.delete(start, end);
return this;
}
// ---- 分割线----
// StringBuild
@Override
public StringBuilder append(double d) {
super.append(d);
return this;
}
/**
* @throws StringIndexOutOfBoundsException {@inheritDoc}
*/
@Override
public StringBuilder delete(int start, int end) {
super.delete(start, end);
return this;
}
继续查看 String的源码
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
/** Cache the hash code for the string */
private int hash; // Default to 0
/** use serialVersionUID from JDK 1.0.2 for interoperability */
private static final long serialVersionUID = -6849794470754667710L;
/**
* Class String is special cased within the Serialization Stream Protocol.
*
* A String instance is written into an ObjectOutputStream according to
* <a href="{@docRoot}/../platform/serialization/spec/output.html">
* Object Serialization Specification, Section 6.2, "Stream Elements"</a>
*/
private static final ObjectStreamField[] serialPersistentFields =
new ObjectStreamField[0];
private final char value[]; 可以看到 Java 使用字符数组来存储字符串,并且用 final 修饰,保证其不可变性。这也就解释为什么 String 实例不可变的原因。
3. 使用总结
综上所述:
- String 用于少量的数据的处理;
- StringBuilder 不涉及并发的大数据的处理;
- StringBuffer 并发下大数据的处理;
一些注意事项:
- String 某些情况下,字符串拼接会被Java Compiler编译成了StringBuffer对象的拼接 (在循环中,不会有优化);
- 构造 StringBuffer 或 StringBuilder 时应尽可能指定它们的容量(默认的 capacity 为16;
- StringBuffer 更适用于全局变量,在保证安全的同时有着不错的性能;
最后分享高频使用的一个字符串的拼接方法 (String.join()是JDK8新增方法):
List<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
// 传入String类型的List
String join = String.join(" ",list);
System.out.println(join);
String[] str = new String[]{"hello","world"};
// 传入String类型的数组
String join2 = String.join(" ",str);
System.out.println(join2);
注意 String.join -> StringJoiner 是基于StringBuilder实现的非线程安全。