浅谈字符串的拼接

文章目录

  • 浅谈字符串的拼接
    • 1、JVM中常量池的存放
    • 2、字符串的声明
    • 3、String
    • 4、StringBuilder
    • 5、StringBuffer
    • 6、StringJoiner

浅谈字符串的拼接

1、JVM中常量池的存放

​    在谈字符串拼接之前,我们首先了解一下字符串常量池

 Java7之前,常量池是存放在方法区中的。

 Java7,常量池存放到了堆中。

 Java8之后,运行时常量池和静态常量池存放在元空间中,而字符串常量池存放在堆中。

2、字符串的声明

 String s = "hello";  //直接通过双引号""声明字符串
  • 到字符串常量池中查找该字符串是否已经存在.
    • 存在,会直接返回该引用,
    • 不存在,则会在堆内存中创建该字符串对象, 然后到字符串常量池中注册该字符串。
String s = new String("hello"); //使用new关键字创建字符串
  • JVM将不会查询字符串常量池, 它将会直接在堆内存中创建一个字符串对象, 并返回给所属变量。

小结

​  当用new关键字创建字符串对象时, 不会查询字符串常量池; 当用双引号直接声明字符串对象时, 虚拟机将会查询字符串常量池.

​  字符串常量池提供了字符串的复用功能, 除非我们要显式创建新的字符串对象, 否则对同一个字符串虚拟机只会维护一份拷贝。

        String s1 = new String("123");
        String s2 = new String("123");

        String s3 = "456";
        String s4 = "456";
        
        System.out.println(s1==s2); //false
        System.out.println(s3==s4); //true

  可以用上面的代码去验证我们的结论,==对于对象来说比较的是地址,上面的代码也说明了如果我们不显示创建对象,它将会直接在堆内存中创建一个字符串对象, 并返回给所属变量。

3、String

    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);
        return new String(buf, true);
    }


​    翻阅源码我们可以看出,对于String类, 凡是涉及到返回参数类型为String类型的方法, 在返回的时候都会通过new关键字创建一个新的字符串对象

​   如果我们通过 反编译代码 可以看出当用String类拼接字符串时, 每次都会生成一个StringBuilder对象, 然后调用两次append()方法把字符串拼接好, 最后通过StringBuilder的toString()方法new出一个新的字符串对象。

​    很显然如果拼接的次数过多, 创建对象所带来的时延会降低系统效率, 同时会造成巨大的内存浪费,使JVM不得不进行GC,进一步降低了系统效率。

4、StringBuilder

   StringBuilder和StringBuffer都继承自AbstractStringBuilder类, 通过查看该类的源码, 得知StringBuilder和StringBuffer两个类也是通过char类型数组实现的(AbstractStringBuilder类中)

    @Override
    public StringBuilder append(char[] str) {
     
        super.append(str);
        return this;
    }

​  我们通过阅读源码也可以看出, StringBuilder类, 大多数方法都会返回StringBuilder对象自身 。也就是说在拼接字符串的时候StringBuilder只需要调用一次append()方法,显然StringBuilder比String效率高效很多

5、StringBuffer

   StringBuffer在方法上添加了 synchronized关键字, 证明它的方法绝大多数方法都是线程同步方法 (insert方法没有),也就是说在多线程的环境下使用StringBuffer保证线程安全, 在单线程环境下使用StringBuilder获得更高的效率。

    @Override
    public synchronized StringBuffer append(CharSequence s) {
     
        toStringCache = null;
        super.append(s);
        return this;
    }

 大家有兴趣也可以去阅读一下AbstractStringBuilder的源码。

    public AbstractStringBuilder append(char[] str) {
     
        int len = str.length;
        ensureCapacityInternal(count + len);
        System.arraycopy(str, 0, value, count, len);
        count += len;
        return this;
    }

6、StringJoiner

 这个类是我在阅读String类的时候发现的,附上String的部分源码

    public static String join(CharSequence delimiter, CharSequence... elements) {
     
        Objects.requireNonNull(delimiter);
        Objects.requireNonNull(elements);
        // Number of elements not likely worth Arrays.stream overhead.
        StringJoiner joiner = new StringJoiner(delimiter);
        for (CharSequence cs: elements) {
     
            joiner.add(cs);
        }
        return joiner.toString();
    }

​    可以看出这个类和StringBuilder有异曲同工之妙, StringJoiner是Java8新出的一个类,用于构造由分隔符分隔的字符序列,也就是运用了StringBuilder的一个拼接字符串的封装处理。

    public StringJoiner(CharSequence delimiter) {
     
        this(delimiter, "", "");
    }


    public StringJoiner(CharSequence delimiter,
                        CharSequence prefix,
                        CharSequence suffix) {
     
        Objects.requireNonNull(prefix, "The prefix must not be null");
        Objects.requireNonNull(delimiter, "The delimiter must not be null");
        Objects.requireNonNull(suffix, "The suffix must not be null");
        // make defensive copies of arguments
        this.prefix = prefix.toString();
        this.delimiter = delimiter.toString();
        this.suffix = suffix.toString();
        this.emptyValue = this.prefix + this.suffix;
    }

 这从它的构造函数也很容易得出,下面来举个简单的例子

        StringJoiner sj = new StringJoiner(":", "[", "]");
        sj.add("1").add("2").add("3");
        System.out.println(sj.toString());
		//输出:[1:2:3]

我们再继续阅读源码,会发现它和StringBuilder的联系,部分源码如下

    public StringJoiner add(CharSequence newElement) {
     
        prepareBuilder().append(newElement);
        return this;
    }

    private StringBuilder prepareBuilder() {
     
        if (value != null) {
     
            value.append(delimiter);
        } else {
     
            value = new StringBuilder().append(prefix);
        }
        return value;
    }

   我们发现StringJoiner底层依旧使用的 StringBuilder,第一次添加数据时,会生成StringBuilder对象,并添加 “前缀”,后续添加字符时,追加 “分隔符”,最后调用 append 方法。

本文参考如下:

StringJoiner

从底层彻底搞懂String,StringBuilder,StringBuffer的实现

你可能感兴趣的:(JavaSE,java,字符串)