专栏首页Java编程Java基础—String、StringBuffer、StringBuilder
原创

Java基础—String、StringBuffer、StringBuilder

前言:本系列的主题是平时容易疏忽的知识点,只有基础扎实,在编码的时候才能更注重规范和性能,在出现bug的时候,才能处理更加从容。

String

==问题

        String s6=new String("jiajun");
        String s1="jiajun";
        String s2="jiajun";
        System.out.println(s1==s2);//true
        System.out.println(s1==s6);//false
  • 看常量池中是否已有此字符串,如果有,将指针指向这个字符串
  • 如果使用new来创建字符串对象,那么这个字符串是存放在堆中,无论堆中是否已有这个对象

String对象改变

public String substring(int beginIndex) {
        if (beginIndex < 0) {
            throw new StringIndexOutOfBoundsException(beginIndex);
        }
        int subLen = value.length - beginIndex;
        if (subLen < 0) {
            throw new StringIndexOutOfBoundsException(subLen);
        }
        return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
    }
    
    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);
    }
  • 从源码可以看出,任何操作都是创建一个新的对象,不影响原对象

StringBuffer和StringBuidler

初始容量

  • StringBuilder和StringBuffer的构造参数来初始化容量
public StringBuilder() {
        super(16);
    }
AbstractStringBuilder(int capacity) {
        value = new char[capacity];
    }
  • 默认情况下容量为16
public AbstractStringBuilder append(String str) {
        if (str == null)
            return appendNull();
        int len = str.length();
        ensureCapacityInternal(count + len);
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }
    private void ensureCapacityInternal(int minimumCapacity) {
        // overflow-conscious code
        if (minimumCapacity - value.length > 0) {
            value = Arrays.copyOf(value,
                    newCapacity(minimumCapacity));
        }
    }
    public static char[] copyOf(char[] original, int newLength) {
        char[] copy = new char[newLength];
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }
  • 从源码看出,在执行append方法的时候,会执行ensureCapacityInternal方法来保证容量,而如果超出容量的话,会重新创建一个char数组,并将旧的字符数组复制到新的字符数组

线程安全

public synchronized StringBuffer append(StringBuffer sb) {
        toStringCache = null;
        super.append(sb);
        return this;
    }
 public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
    }
    
 public StringBuilder append(StringBuffer sb) {
        super.append(sb);
        return this;
    }
    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);
    }
  • 可以看出,String的方法是加了synchronzied,也就加了锁,那么而在单线程的情况下或者不用考虑线程安全的情况下,那么StringBuilder的性能是更高的

toString方法

 public synchronized String toString() {
        if (toStringCache == null) {
            toStringCache = Arrays.copyOfRange(value, 0, count);
        }
        return new String(toStringCache, true);
    }
public String toString() {
        // Create a copy, don't share the array
        return new String(value, 0, count);
    }
  • 通过源码发现,toString方法会创建一个新的String对象

性能试验

间接相加和直接相加

public class d {
    public static void main(String[] args) {
        String s="I"+"love"+"jiajun";
        String s1="I";
        String s2="love";
        String s3="jiajun";
        String s4=s1+s2+s3;
    }
}
  • 通过反编译的结果可以看出,第一种方式字符串直接相加,在编译器就直接优化了”Ilovejiajun“
  • 而第二种方式间接相加,从结果可以看出,是先创建一个StringBuilder,然后再apend,最后再toString方法,可以发现性能比第一种低
public class d {
    public static void main(String[] args) {
        String s="I"+"love"+"jiajun";
        String s1="I";
        String s2=s1+"lovejiajun";
        System.out.println(s==s2);
    }
}
  • 同样从反编译的结果可以看出,第二种方式并没有被优化,也是通过StringBuilder来实现的,最后通过toString方法创建一个String对象,所以返回的false
  • 但是当s1是用final修饰的却是不一样的,虚拟机会对其进行优化,所以不会像之前一样创建一个StringBuilder,最后在堆中产生一个对象
public class d {
    public static void main(String[] args) {
        
        final String s1="I";
        
        String s2=s1+"lovejiajun";
        String s3="Ilovejiajun";
        //s1==s3
    }
}

用+和用append

public class Demo3 {
    public static void main(String[] args) {
        run1();
        run2();
    }
    
    public static void run1() {
        long start = System.currentTimeMillis();
        String result = "";
        for (int i = 0; i < 10000; i++) {
            result += i;
        }
        System.out.println(System.currentTimeMillis() - start);
    }

    public static void run2() {
         long start = System.currentTimeMillis();
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < 10000; i++) {
            builder.append(i);
        }
        System.out.println(System.currentTimeMillis() - start);
    }
    //输出:223 1
  • 从实验发现,用append效率更高,从实验一发现,当字符串相加的时候,实际上每次都会重新初始化StringBuilder然后执行相加,这样效率并不高

初始化容量

public class Demo3 {
    public static void main(String[] args) {
        test1();
        test2();
    }

    public static void test1() {
        StringBuilder sb = new StringBuilder(7000000);
        long start = System.currentTimeMillis();
        for (int i = 0; i < 1000000; i++) {
            sb.append("jiajun");
        }
        long end=System.currentTimeMillis()-start;
        System.out.println(end);
    }

    public static void test2() {

        StringBuilder sb = new StringBuilder();
        long start = System.currentTimeMillis();
        for (int i = 0; i < 1000000; i++) {
            sb.append("jiajun");
        }
        long end=System.currentTimeMillis()-start;
        System.out.println(end);
    }
    //输出:18 26
}
  • 通过实验可以看出,适当的初始化容量可以提高性能,因为当不初始化容量的时候,如果此时append超出容量,那么将会从新创建一个char数组,并且进行复制

总结

  • 用new创建对象的时候,会在堆中创建对象,而如果是直接用引号形式的话,会先看常量池是否有此字符串,有的话指向常量池的字符串
  • StringBuilder是非线程安全的,StringBuffer是线程安全的
  • 使用StringBuilder和StringBuffer的时候最好初始化一个合适的容量,因为如果默认容量不够的话,会重新创建一个char数组,再进行复制
  • 字符串相加的时候,直接相加的时候,编译器会进行优化,而如果是间接相加的时候,实际上会创建一个StringBuilder来进行append

我有一个微信公众号,经常会分享一些Java技术相关的干货。如果你喜欢我的分享,可以用微信搜索“Java团长”或者“javatuanzhang”关注。

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 5月 TIOBE 排行榜:Java 和 C 语言正在被超越

    自 2016 年初,Java 和 C 语言就出现了明显的下颓趋势,与去年相比,这两种语言的市场占有率均下滑了 6%有余。根据 TIOBE 的数据显示,原本的 J...

    云资讯小编
  • 【 ES 私房菜 】系统运维数据分析平台架构

    网管系统在日常运行过程中会产生各类日志数据,比如 WEB、DB 以及系统等。所以,我们急需一个可以集中收集、分析并输出表报的日志平台,毋庸置疑,ES 就是最佳“...

    张戈
  • 快速上手 Kotlin 11 招

    这篇文章主要是写给需要快速上手 Kotlin 的 Java 程序员看的,这时候他们关注的是如何 Kotlin 写出类似某些 Java 的写法,所以本文基本不涉及...

    bennyhuo
  • 利用 Jquery + css 自制无限极下拉分类

    网上相关例子基本都是 ztree,然后个人去看了看官网,看了半天没找到合适又简单的,ztree,由于界面不适合项目里面的,要是修改他的样式我还可能出现更多的错误...

    世玉
  • Java 虚拟机管理的内存运行时数据区域解释

    Java 虚拟机在执行 Java 程序的过程中会把它所管理的内存划分为若干个不同数据区域。这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的...

    李鹏
  • 如何快速部署国人开源的 Java 博客系统 Tale

    前几天,在社区看到有国人开发者开源了用 Java 写的一个博客系统 Tale。从作者放出的 Demo 站点来看,效果还是不错的。本书梳理了一下部署 Tale 的...

    EarlGrey
  • Java 架构师学习路线

    Java 架构师,首先要是一个高级 java 攻城狮,熟练使用各种框架,并知道它们实现的原理。jvm虚拟机原理、调优,懂得 jvm 能让你写出性能更好的代码;池...

    李鹏
  • 理解 JDK 中的 MethodHandle

    MethodHandle 更像是在Java语法规则内手写字节码:自己创建方法签名(MethodType),自己决定调用方式,自己注意访问控制 ,最后还要自己决定...

    serena
  • 35 个 Java 代码性能优化总结

    代码优化,一个很重要的课题。可能有些人觉得没用,一些细小的地方有什么好修改的,改与不改对于代码的运行效率有什么影响呢?这个问题我是这么考虑的,就像大海里面的鲸鱼...

    Java后端工程师
  • apt 与 JavaPoet 自动生成代码

    本文通过介绍腾讯视频项目中,adapter 创建 View 的例子,向大家介绍,如何通过自定义注解处理器自动生成代码,以及如何调试自定义注解处理器。首先,介绍一...

    吴涛

扫码关注云+社区

领取腾讯云代金券