手把手实例对比String、StringBuilder字符串的连接效率及StringBuilder和StringBuffer线程安全的比较

一、字符串连接的效率问题

使用String连接字符串时为什么慢?

小知识点

java中对数组进行初始化后,该数组所占的内存空间、数组长度都是不可变的。

创建一个字符串,为字符串对象分配内存空间,会耗费掉一定的时间(CPU)与空间(内存)代价,作为最基础的数据类型,大量频繁的创建字符串,极大程度地影响程序的性能。

过多无用的中间对象

每次连接字符串时都会创建一个新的String对象,随着拼接次数的增多,这个对象会越来越大。 如,进行100次拼接需要创建100个String对象才能够达到目的。

StringBuilder在连接时为什么效率更高?

字符数组的扩容机制:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

private void ensureCapacityInternal(int minimumCapacity) {

         // 最小所需容量minimumCapacity是否比原数组长度要长

        // overflow-conscious code

        if (minimumCapacity - value.length > 0) {

            value = Arrays.copyOf(value,

                    newCapacity(minimumCapacity));

        }

    }

     

private int newCapacity(int minCapacity) {

         // 计算扩容之后的容量newCapacity

        // overflow-conscious code

        int newCapacity = (value.length << 1) + 2;

        // 扩容后还小于所需的最小容量

        if (newCapacity - minCapacity < 0) {

            // 设置新容量为最小所需容量minimumCapacity

            newCapacity = minCapacity;

        }

        // newCapacity是否溢出,newCapacity是否比数组所能分配的最大容量 MAX_ARRAY_SIZE 还要大。

        return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)

            ? hugeCapacity(minCapacity)

            : newCapacity;

    }

 

    private int hugeCapacity(int minCapacity) {

        // 最小所需容量minCapacity大于Integer.MAX_VALUE时抛出内存溢出异常

        if (Integer.MAX_VALUE - minCapacity < 0) { // overflow

            throw new OutOfMemoryError();

        }

        // 如果minCapacity介于MAX_ARRAY_SIZE和Integer.MAX_VALUE之间,则新的容量为minCapacity,否则直接使用MAX_ARRAY_SIZE作为新的容量。

        return (minCapacity > MAX_ARRAY_SIZE)

            ? minCapacity : MAX_ARRAY_SIZE;

    }

向原StringBuilder对象中追加字符串时: 

1.追加对象str为null时追加'null'字符 

2.确认是否需要进行扩容操作

  • 最小所需容量minimumCapacity是否比原数组长度要长,即当原数组长度不能满足所需最小容量时进行扩容操作。
  • 计算扩容之后的容量newCapacity,newCapacity = (value.length * 2) + 2。
  • 扩容后是否还小于所需的最小容量,如果小于则直接设置新容量为最小所需容量minimumCapacity。
  • newCapacity是否溢出,newCapacity是否比数组所能分配的最大容量 MAX_ARRAY_SIZE 还要大。如果是的话则判断,最小所需容量minCapacity大于Integer.MAX_VALUE时抛出内存溢出异常,如果minCapacity介于MAX_ARRAY_SIZE和Integer.MAX_VALUE之间,则新的容量为minCapacity,否则直接使用MAX_ARRAY_SIZE作为新的容量。

3.str.getChars()将str追加到value的末尾 

效率高的原因

  1. 扩容机制保证了,只有在满足扩容条件 minimumCapacity - value.length > 0 时才会进行扩容生成新的数组,所以大部分情况都是在对原数组进行操作,避免了产生过多的无用char[]对象,节省了系统资源的开销。

代码

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

/**

 * 比较字符串连接速度

 *

 * @Author: lingyejun

 * @Date: 2019/8/17

 * @Describe:

 * @Modified By:

 */

public class LinkCompare {

 

    /**

     * 原始字符串连接

     *

     * @param times

     */

    public static void linkByString(int times) {

 

        Long startTime = System.currentTimeMillis();

 

        String initStr = "";

        for (int i = 0; i < times; i++) {

            initStr = initStr + i;

        }

 

        Long endTime = System.currentTimeMillis();

 

        System.out.println("String 连接 " + times + " 次 消耗:" + (endTime - startTime) + "ms");

    }

 

    /**

     * 使用StringBuilder连接字符串

     *

     * @param times

     */

    public static void linkByStringBuilder(int times) {

 

        Long startTime = System.currentTimeMillis();

 

        StringBuilder initStr = new StringBuilder();

        for (int i = 0; i < times; i++) {

            initStr.append(i);

        }

 

        Long endTime = System.currentTimeMillis();

 

        System.out.println("StringBuilder 连接 " + times + " 次 消耗:" + (endTime - startTime) + "ms");

    }

 

 

    /**

     * 使用StringBuffer连接字符串

     *

     * @param times

     */

    public static void linkByStringBuffer(int times) {

 

        Long startTime = System.currentTimeMillis();

 

        StringBuffer initStr = new StringBuffer();

        for (int i = 0; i < times; i++) {

            initStr.append(i);

        }

 

        Long endTime = System.currentTimeMillis();

 

        System.out.println("StringBuffer 连接 " + times + " 次 消耗:" + (endTime - startTime) + "ms");

    }

 

 

    public static void main(String[] args) {

 

        // 100000000

        linkByStringBuilder(40000);

        //-XX:+PrintGCDetails

        //linkByString(40000);

 

    }

}

二、StringBuilder和String Buffer的线程安全比较

验证StringBuffer的线程安全性

线程不安全的原因

1

2

3

4

5

6

7

8

9

10

public StringBuilder append(String str) {

        super.append(str);

        return this;

    }

     

public synchronized StringBuffer append(String str) {

        toStringCache = null;

        super.append(str);

        return this;

    }

测试代码

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

import java.util.ArrayList;

import java.util.List;

 

/**

 * StringBuilder和StringBuffer的并发测验

 *

 * @Author: lingyejun

 * @Date: 2019/8/17

 * @Describe:

 * @Modified By:

 */

public class SecurityCompare {

 

    public void stringBuilderTest() {

 

        // 初始化StringBuilder

        StringBuilder stringBuilder = new StringBuilder();

 

        // joinList

        List<StringBuilderThread> joinList = new ArrayList<>();

 

        // 模拟并发场景

        for (int i = 0; i < 1000; i++) {

            StringBuilderThread sbt = new StringBuilderThread(stringBuilder);

            sbt.start();

            joinList.add(sbt);

        }

 

        // 等待append线程执行完毕后再执行主线程

        for (StringBuilderThread thread : joinList) {

            try {

                thread.join();

            catch (InterruptedException e) {

                e.printStackTrace();

            }

        }

 

        // 打印最终的结果

        System.out.println("StringBuilder 并发append的结果: " + stringBuilder.length());

    }

 

    public void stringBufferTest() {

 

        // 初始化StringBuffer

        StringBuffer stringBuffer = new StringBuffer();

 

        // joinList

        List<StringBufferThread> joinList = new ArrayList<>();

 

        // 模拟并发场景

        for (int i = 0; i < 1000; i++) {

            StringBufferThread sbf = new StringBufferThread(stringBuffer);

            sbf.start();

            joinList.add(sbf);

        }

 

        // 等待append线程执行完毕后再执行主线程

        for (StringBufferThread thread : joinList) {

            try {

                thread.join();

            catch (InterruptedException e) {

                e.printStackTrace();

            }

        }

 

        // 打印最终的结果

        System.out.println("StringBuffer 并发append的结果: " + stringBuffer.length());

    }

 

 

    public static void main(String[] args) {

 

        SecurityCompare securityCompare = new SecurityCompare();

 

        securityCompare.stringBuilderTest();

        securityCompare.stringBufferTest();

 

    }

 

    public static class StringBuilderThread extends Thread {

 

        private StringBuilder stringBuilder;

 

        public StringBuilderThread(StringBuilder stringBuilder) {

            this.stringBuilder = stringBuilder;

        }

 

        @Override

        public void run() {

            try {

                Thread.sleep(10);

            catch (InterruptedException e) {

                e.printStackTrace();

            }

            stringBuilder.append("a");

            try {

                Thread.sleep(10);

            catch (InterruptedException e) {

                e.printStackTrace();

            }

 

        }

    }

 

    private static class StringBufferThread extends Thread {

 

        private StringBuffer stringBuffer;

 

        public StringBufferThread(StringBuffer stringBuffer) {

            this.stringBuffer = stringBuffer;

        }

 

        @Override

        public void run() {

            try {

                Thread.sleep(10);

            catch (InterruptedException e) {

                e.printStackTrace();

            }

            stringBuffer.append("a");

            try {

                Thread.sleep(10);

            catch (InterruptedException e) {

                e.printStackTrace();

            }

 

        }

    }

}

三、结论

  1. String为固定长度的字符串,StringBuilder和StringBuffer为变长字符串。
  2. StringBuffer是线程安全的,StringBuilder是非线程安全的。
  3. StringBuilder和StringBuffer的默认初始容量是16,可以提前预估好字符串的长度,进一步减少扩容带来的额外开销。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值