Java数据类型—StringBuilder与StringBuffer「建议收藏」

Java数据类型—StringBuilder与StringBuffer「建议收藏」StringBuilder与StringBuffer作用就是用来处理字符串,但String类本身也具备很多方法可以用来处理字符串,那么为什么还要引入这两个类呢?前面我们讲解到String对象的不可变性,以及它的不足那就是创建新的对象,因为它是不可变的,所以你对它的操作逻辑就体现在另外一个对象里,那就是你的操作新创建的对象。…

大家好,又见面了,我是你们的朋友全栈君。

StringBuilder与StringBuffer

StringBuilder与StringBuffer作用就是用来处理字符串,但String类本身也具备很多方法可以用来处理字符串,那么为什么还要引入这两个类呢?前面我们讲解到String 对象的不可变性,以及它的不足那就是创建新的对象,具体你可以查看文章String进阶之不可变性,因为它是不可变的,所以你对它的操作逻辑就体现在另外一个对象里,那就是你的操作新创建的对象。

这种操作最常见的就是字符串的拼接,所以我们几天学习的这两个类都是为了解决这个问题的,那既然都是为了解决这个问题的,为什么会有两个类的,我们后面慢慢分析

初识

首先看下面的例子

    @Test
    public void testPerformance(){ 
   
        String str0 = "hello,world";

        long start = System.currentTimeMillis();
        for (int i = 0; i < 100000; i++) { 
   
            str0 += i;
        }
        System.out.println(System.currentTimeMillis() - start);

        StringBuilder sb = new StringBuilder("hello,world");
        long start1 = System.currentTimeMillis();
        for (int i = 0; i < 100000; i++) { 
   
            sb.append(i);
        }
        System.out.println(System.currentTimeMillis() - start1);

        StringBuffer sbf = new StringBuffer("hello,world");
        long start2 = System.currentTimeMillis();
        for (int i = 0; i < 100000; i++) { 
   
            sbf.append(i);
        }
        System.out.println(System.currentTimeMillis() - start2);
    }

上述代码中3处循环完成了同样的功能,字符串拼接,执行的结果如下:

38833
4
4

可以看出执行时间差别很大,为了解决String不擅长的大量字符串拼接这种业务场景,所以我们引入了StringBuffer和StringBuilder.

首先我们分析一下为什么String在大量字符串拼接这种场景下这么慢?其实前面我们已经说到了,这我我们再解释一下

因为String本身不可变,我们对String的任何操作都会返回一个新的对象,然后当前String变量指向新的对象,而原来的String对象就会被GC回收,那么在循环中就会大量快速的创建新的对象,大量原来的对象会不断的被GC回收,消耗的时间是非常恐怖的,而且内存占用非常大。

但是我们从输出结果看到另外一个问题,那就是StringBuffer与StringBuilder的允许时间基本一致,那为什么需要定义两个功能相似的类呢?

接下来我们再看一段代码

    @Test
    public void testSafe() throws InterruptedException { 
   
        String str0 = "hello,world";

        StringBuilder sb = new StringBuilder(str0);
        for (int i = 0; i < 100; i++) { 
   
            new Thread(() -> { 
   
                try { 
   
                    TimeUnit.MILLISECONDS.sleep(1);
                } catch (InterruptedException e) { 
   
                    e.printStackTrace();
                }
                sb.append("a");
            }).start();
        }


        StringBuffer sbf = new StringBuffer(str0);
        for (int i = 0; i < 100; i++) { 
   
            new Thread(() -> { 
   
                try { 
   
                    TimeUnit.MILLISECONDS.sleep(1);
                } catch (InterruptedException e) { 
   
                    e.printStackTrace();
                }
                sbf.append("a");
            }).start();
        }
        // 等待工作线程运行结束
        while (Thread.activeCount()>2){ 
   

        }
        System.out.println("StringBuilder:"+sb.toString().length());
        System.out.println("StringBuffer:"+sbf.toString().length());
    }

输出结果

StringBuilder:109
StringBuffer:111

看出什么了吗,本来拼接完程度应该是111的,但是现在呢,StringBuilder是109,说明了StringBuilder不是安全的,也就是说再多线程的环境下,我们应该使用StringBuffer

下面我们对比了String、StringBuffer与StringBuilder的区别

String StringBuffer StringBuilder
final修饰,不可继承 final修饰,不可继承 final修饰,不可继承
字符串常量,创建后不可变 字符串变量,可动态修改 字符串变量,可动态修改
不存在线程安全问题 线程安全,所有public方法由synchronized修改 线程不安全
大量字符串拼接效率最低 大量字符串拼接效率非常高 大量字符串拼接效率最高

StringBuffer与StringBuilder实现非常类似,下面以StringBuilder简单说明一下append()方法基本原理

源码剖析

这里我们以StringBuilder为例,

1. StringBuilder的构造方法

StringBuilder sb1 = new StringBuilder();
StringBuilder sb2 = new StringBuilder(100);

StringBuilder对字符串的操作是通过char[]来实现的,通过默认构造器创建的StringBuilder,其内部创建的char[]的默认长度为16,当然可以调用重载的构造器传递初始长度(推荐这样,因为这样可以减少数组扩容次数,提高效率)。

     */
    public StringBuilder() { 
   
        super(16);
    }

    /** * Constructs a string builder with no characters in it and an * initial capacity specified by the {@code capacity} argument. * * @param capacity the initial capacity. * @throws NegativeArraySizeException if the {@code capacity} * argument is less than {@code 0}. */
    public StringBuilder(int capacity) { 
   
        super(capacity);
    }

    /** * Constructs a string builder initialized to the contents of the * specified string. The initial capacity of the string builder is * {@code 16} plus the length of the string argument. * * @param str the initial contents of the buffer. */
    public StringBuilder(String str) { 
   
        super(str.length() + 16);
        append(str);
    }

    /** * Constructs a string builder that contains the same characters * as the specified {@code CharSequence}. The initial capacity of * the string builder is {@code 16} plus the length of the * {@code CharSequence} argument. * * @param seq the sequence to copy. */
    public StringBuilder(CharSequence seq) { 
   
        this(seq.length() + 16);
        append(seq);
    }

2. StringBuilder的append()方法

每次调用append(str)方法时,会首先判断数组长度是否足以添加传递来的字符串

/** * Appends the specified string to this character sequence. * <p> * The characters of the {@code String} argument are appended, in * order, increasing the length of this sequence by the length of the * argument. If {@code str} is {@code null}, then the four * characters {@code "null"} are appended. * * @param str a string. * @return a reference to this object. */
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;
}
/** * For positive values of {@code minimumCapacity}, this method * behaves like {@code ensureCapacity}, however it is never * synchronized. * If {@code minimumCapacity} is non positive due to numeric * overflow, this method throws {@code OutOfMemoryError}. */
private void ensureCapacityInternal(int minimumCapacity) { 
   
    // overflow-conscious code
    if (minimumCapacity - value.length > 0) { 
   
        value = Arrays.copyOf(value,
                newCapacity(minimumCapacity));
    }
}

如果传递的字符串长度 + 数组已存放的字符的长度 > 数组的长度,这时就需要进行数据扩容了

/** * Returns a capacity at least as large as the given minimum capacity. * Returns the current capacity increased by the same amount + 2 if * that suffices. * Will not return a capacity greater than {@code MAX_ARRAY_SIZE} * unless the given minimum capacity is greater than that. * * @param minCapacity the desired minimum capacity * @throws OutOfMemoryError if minCapacity is less than zero or * greater than Integer.MAX_VALUE */
private int newCapacity(int minCapacity) { 
   
    // overflow-conscious code
    int newCapacity = (value.length << 1) + 2;
    if (newCapacity - minCapacity < 0) { 
   
        newCapacity = minCapacity;
    }
    return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
        ? hugeCapacity(minCapacity)
        : newCapacity;
}

扩容规则如下:默认将数组长度设置为“ (当前数组长度 * 2) + 2”,但如果按此规则扩容后的数组也不足以添加新的字符串,就需要将数组长度设置为“数组内字符长度 + 传递的字符串长度”。

因此假如我们知道拼接的字符串大概长度有100多字符,我们就可以设置初始长度150或200,这样就可以避免或减少数组扩容的次数,从而提高效率。

总结

  1. StringBuilder与StringBuffer 都是为了解决大量字符串拼接时的性能问题,其实就是为了解决String类在拼接过程中产生的大量对象的问题,因为这会导致大量内内存分配和GC 问题
  2. StringBuilder与StringBuffer 二者之间的主要区别是线程是否安全,StringBuffer 通过对方法添加synchronized关键字保证了线程安全。
  3. StringBuilder与StringBuffer底层都是依赖字符数组实现的,不同于String 的是String 底层的字符数组是不可变的,也就是final 修饰的,其实这就是StringBuilder与StringBuffer可变的原因,动态扩展字符数组来实现的。
  4. 为了提高StringBuilder与StringBuffer的性能我们可以通过设置合适的容量来避免数组库容。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/153409.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 个人能不能开发ctp期货交易_什么是程序化交易期货

    个人能不能开发ctp期货交易_什么是程序化交易期货接触CTP也才半年多,一边学习一边摸索,看到各大CTP的QQ群里,也都是在问一些很菜的问题,就简单总结和介绍下,今天主要是基础知识,即CTP程序的基础和开源的Demo版本:CTP交易接口是由::::::上海期货信息技术有限公司::::::开发的,提供C++的接口,网上也有很多C++的Demo版本,可以直接使用。1:上期所的接口为两个.dll、两个.lib和四个.h文件,初学者可以不要C

    2022年10月8日
    0
  • 这7个web前端开发写代码软件,你过用几个?[通俗易懂]

    群里的朋友,经常问到web前端开发写代码用那个软件好?今天在这里统一回答下,主流的web前端开发写代码的软件有这些Webstorm、Vscode、SublimeText、HBuilder、Dreamweaver、notepad++、editplus等,做前端这么多年了,下面谈下我的使用感受吧。1.WebStorm【推荐】WebStorm是jetbra…

    2022年4月11日
    75
  • 遍历hashmap的三种方式_java map 遍历删除

    遍历hashmap的三种方式_java map 遍历删除在Java中有多种遍历HashMap的方法,注意Java中所有的Map类型都实现了共有的Map接口,所以接下来方法适用于所有Map(如:HaspMap,TreeMap,LinkedMap,HashTable,etc)方法1使用For-Each迭代entries这是最常见的方法,并在大多数情况下更可取的。当你在循环中需要使用Map的键和值时,就可以使用这个方法Mapmap=newHashM…

    2022年9月8日
    1
  • bt种子php啥格式的,bt种子是什么意思(bt种子的格式及文件结构)

    bt种子php啥格式的,bt种子是什么意思(bt种子的格式及文件结构)-文字稿-如果你想要下载一集不存在的回形针视频,你会怎么做?最简单的方法当然是找一个有资源的哥们——每羊,让他把这期视频发给你。早期互联网,大家就是这么共享文件的,但是这样也有很多问题。比如下载的人一多,每个人分配到的带宽就变小了,下载速度会变慢。更危险的是,这期视频是敏感资源,你的哥们本来就不应该分享给你,如果每羊被抓了,大家也都别下载了。针对这些问题,美国工程师BramCohen在20…

    2025年5月26日
    1
  • 数据挖掘算法与现实生活中的应用案例[通俗易懂]

    数据挖掘算法与现实生活中的应用案例[通俗易懂]如何分辨出垃圾邮件”、“如何判断一笔交易是否属于欺诈”、“如何判断红酒的品质和档次”、“扫描王是如何做到文字识别的”、“如何判断佚名的著作是否出自某位名家之手”、“如何判断一个细胞是否属于肿瘤细胞”等等,这些问题似乎都很专业,都不太好回答。但是,如果了解一点点数据挖掘的知识,你,或许会有柳暗花明的感觉。本文,主要想简单介绍下数据挖掘中的算法,以及它包含的类型。然后,通过现实中触手可及的、活生生的案例

    2022年6月19日
    91
  • 正則表達式匹配号码

    正則表達式匹配号码

    2022年2月4日
    32

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号