Java 基础理解之字符串

379 阅读3分钟

写在前面

日常工作中经常用到字符串,无论是哪种语言也都有自己对字符串的定义以及使用;今天就聊一聊工作中使用到的字符串。

正文开始

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执行效率高。

以上都是我们翻看文档得出的结论,接着带着问题我们查看一下源码,验证一下结论:

image.png

如图 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实现的非线程安全。