对比

引言

在JAVA中字符串的表示有三种方式 String StringBuilder StringBuffer .
关于String 需要注意两点:
1.String是不可变的字符串,它的底层是一个用final修饰的字符数组
StringBuffer以及StringBuilder - 图1
2.String 对象赋值之后就会在字符串常量池中缓存,如果下次创建会判定常量池是否已经有缓存对象,如果有的话直接返回该引用给创建者。
什么是字符串常量池?
Java中的字符串常量池(String Pool)是Java堆内存中的一片内存空间。
我们知道String是java中比较特殊的类,我们可以使用new运算符创建String对象,也可以用双引号(”“)创建字串对象,看下图:
StringBuffer以及StringBuilder - 图2
String s1 = “Cat”当我们用这种方式创建字符串对象的时候,首先会去字符串常量池中查找看有没有“Cat”字符串,如果有则返回它的地址给s1,如果没有则在常量池中创建“Cat”字符串,并将地址返回给s1.
String s3 = new String(“Cat”)当我们用这种方式创建字符串对象的时候,首先会去字符串常量池中查找看有没有“Cat”字符串,如果没有则在常量池中创建“Cat”字符串,然后在堆内存中创建“Cat”字符串,并将堆内存中的地址返回给s3.

  1. public class HelloWorld {
  2. public static void main(String []args) {
  3. String s1 = "cat";
  4. String s2 = "cat";
  5. String s3 = new String("cat");
  6. String s4 = new String("cat");
  7. System.out.println(s1==s2);
  8. System.out.println(s1==s3);
  9. System.out.println(s3==s4);
  10. }
  11. }
  12. >True
  13. >False
  14. >False

上面的操作就很令人费解 其中使用引号创建对象的时候,会直接在常量池中创建 使用关键字创建的时候会在常量池和堆上都创建

所以结果 s1 == s2 为true s1==s3为false,s1和s2都指向了常量池中的“Cat”而s3指向了堆内存中的“Cat”
大家想想 如果有这么一行代码 String str = new String(“hello”)
在内存中会创建几个字符串对象?
答案是一个或两个
如果常量池中已经存在“hello”,则会在堆内存中创建一个“hello”对象,如果常量池中不存在则在常量池中创建一个,在堆内存中创建一个
通过引入字符串常量池的概念,让字符串处理的效率得到了提高,这是jvm对字符串的一种优化手段。
当我们做拼接字符串操作的时候:
String str = “you”;
Str = str+”win”;
底层是这样的:
StringBuffer以及StringBuilder - 图3
Str刚开始指向常量池中的“you”,拼接字符串“win”的时候又开辟了两块块内存空间一块保存“win”,一块保存拼接以后生成的字符串“ you win”并且str指向拼接以后的字符串,在这个过程中一共占用了三块内存空间,所以效率是非常低下的。(原因就是string原代码的中char[]是final修饰的)

  1. StringBuilder StringBuffer都继承于:AbstractStringBuilder
  2. 他们的底层使用的是没有用final修饰的字符数组:char[]

abstract class AbstractStringBuilder implements Appendable, CharSequence {
/**
* The value is used for character storage.
*/
char[] value;
所以在做字符串拼接的时候就在原来的内存上进行拼接,不会浪费内存空间。
StringBuilder和StringBuffer的区别是
StringBuilder是线程不安全的,它的执行效率比StriingBuffer要高
StringBuffer是线程安全的,它的执行效率比StringBuilder要低

总结

1.String字符串是不可变的。
2.在修改字符串操作比较多的时候用StringBuilder或StringBuffer.
在要求线程安全的情况下用StringBuffer
在不要求线程安全的情况下用StringBuilder

StringBuffer源码解析

类的定义

  1. public final class StringBuffer extends AbstractStringBuilder implements java.io.Serializable, CharSequence

从StringBuffer定义中可以看到

  • StringBuffer是final修饰的类,不可继承
  • 继承了AbstractStringBuilder类
  • 实现了java.io.Serializable接口,可以序列化
  • 实现了CharSequence接口,表示StringBuffer一个可读的char序列

    字段属性

    1. //从父类AbstractStringBuilder继承
    2. char[] value;
    3. //从父类继承
    4. int count;
    5. //缓存,toString的时候才缓存,修改就置为null
    6. private transient char[] toStringCache;
    7. //序列化版本号
    8. static final long serialVersionUID = 4383685877147921099L;

    从上面可以看出

  • StringBuffer 本质是一个char数组

  • 保存了char数组中可以用字符的长度,注意:count不是char数组的长度
  • toStringCache缓存了char数组可用字符的值,transient表示不可序列化
  • count表示可用字符的数量

    构造方法

    1. //空构造函数,默认指定char的长度为16
    2. public StringBuffer() {
    3. super(16);
    4. }
    5. //传入char数组的初始化长度
    6. public StringBuffer(int capacity) {
    7. super(capacity);
    8. }
    9. //传入一个字符串,默认char数组长度为字符串长度加16
    10. //将str保存到char数组中去
    11. public StringBuffer(String str) {
    12. super(str.length() + 16);
    13. append(str);
    14. }
    15. //传入一个CharSequence对象,默认char数组长度为CharSequence对象长度加16
    16. //将CharSequence对象保存到char数组中去
    17. public StringBuffer(CharSequence seq) {
    18. this(seq.length() + 16);
    19. append(seq);
    20. }

    从构造方法中可以看出

  • StringBuffer中的char数组默认长度是16

  • 可以自己指定默认长度
  • 构造方法可以传入String对象和CharSequence对象,预留16个char的长度

    方法

  • StringBuffer中所有的方法都是使用synchronized修饰的,因此StringBuffer是线程安全的

    length 方法

    1. @Override
    2. public synchronized int length() {
    3. //返回count表示可用字符的数量,跟char数组长度不一定相等
    4. return count;
    5. }

    capacity 方法

    1. @Override
    2. public synchronized int capacity() {
    3. //value的长度表示容量
    4. return value.length;
    5. }

    ensureCapacity 方法

    1. @Override
    2. public synchronized void ensureCapacity(int minimumCapacity) {
    3. //调用父类方法
    4. super.ensureCapacity(minimumCapacity);
    5. }
    6. public void ensureCapacity(int minimumCapacity) {
    7. //参数验证
    8. if (minimumCapacity > 0)
    9. ensureCapacityInternal(minimumCapacity);
    10. }
    11. private void ensureCapacityInternal(int minimumCapacity) {
    12. // overflow-conscious code
    13. //确保容量,容量不足则扩容到minimumCapacity
    14. if (minimumCapacity - value.length > 0) {
    15. value = Arrays.copyOf(value,
    16. newCapacity(minimumCapacity));
    17. }
    18. }

    charAt 方法

    1. @Override
    2. public synchronized char charAt(int index) {
    3. //参数检查
    4. if ((index < 0) || (index >= count))
    5. throw new StringIndexOutOfBoundsException(index);
    6. //直接返回
    7. return value[index];
    8. }

    append 方法

    1. @Override
    2. public synchronized StringBuffer append(String str) {
    3. //将缓存置为null
    4. toStringCache = null;
    5. //调用父类的方法
    6. super.append(str);
    7. return this;
    8. }
    9. public AbstractStringBuilder append(String str) {
    10. //参数检查
    11. if (str == null)
    12. return appendNull();
    13. //获取参数的长度
    14. int len = str.length();
    15. //char数组长度检查,如果长度不够会进行扩容,扩容的大小为count+len
    16. ensureCapacityInternal(count + len);
    17. //将传入的str添加到char数组后面
    18. str.getChars(0, len, value, count);
    19. //重新设置当前可用字符的数量
    20. count += len;
    21. return this;
    22. }

    delete 方法

    1. @Override
    2. public synchronized StringBuffer delete(int start, int end) {
    3. //将缓存置为null
    4. toStringCache = null;
    5. //调用父类方法
    6. super.delete(start, end);
    7. return this;
    8. }
    9. public AbstractStringBuilder delete(int start, int end) {
    10. //参数检查
    11. if (start < 0)
    12. throw new StringIndexOutOfBoundsException(start);
    13. //如果结尾大于可用字符的数量把end的值设置为count
    14. if (end > count)
    15. end = count;
    16. //参数检查
    17. if (start > end)
    18. throw new StringIndexOutOfBoundsException();
    19. //计算需要删除的数量
    20. int len = end - start;
    21. if (len > 0) {
    22. //把start+len作为起始位置,count作为结束位置去覆盖start作为起始位置,覆盖数量为count-end
    23. //类似于后面的前移,覆盖中间删除部分
    24. System.arraycopy(value, start + len, value, start, count - end);
    25. //重新计算可用字符长度,这里删除的话应该减小
    26. count -= len;
    27. }
    28. return this;
    29. }

    replace 方法

    1. @Override
    2. public synchronized StringBuffer replace(int start, int end, String str) {
    3. //将缓存置为null
    4. toStringCache = null;
    5. //调用父类方法
    6. super.replace(start, end, str);
    7. return this;
    8. }
    9. public AbstractStringBuilder delete(int start, int end) {
    10. //参数检查
    11. if (start < 0)
    12. throw new StringIndexOutOfBoundsException(start);
    13. //如果结尾大于可用字符的数量把end的值设置为count
    14. if (end > count)
    15. end = count;
    16. //参数检查
    17. if (start > end)
    18. throw new StringIndexOutOfBoundsException();
    19. //计算需要删除的数量
    20. int len = end - start;
    21. if (len > 0) {
    22. //把start+len作为起始位置,count作为结束位置去覆盖start作为起始位置,覆盖数量为count-end
    23. //类似于后面的前移,覆盖中间删除部分
    24. System.arraycopy(value, start + len, value, start, count - end);
    25. //重新计算可用字符长度,这里删除的话应该减小
    26. count -= len;
    27. }
    28. return this;
    29. }

    substring 方法

    1. @Override
    2. public synchronized String substring(int start, int end) {
    3. //调用父类方法
    4. return super.substring(start, end);
    5. }
    6. public String substring(int start, int end) {
    7. //参数验证
    8. if (start < 0)
    9. throw new StringIndexOutOfBoundsException(start);
    10. if (end > count)
    11. throw new StringIndexOutOfBoundsException(end);
    12. if (start > end)
    13. throw new StringIndexOutOfBoundsException(end - start);
    14. //从valule中返回start到end的字符串
    15. return new String(value, start, end - start);
    16. }
  • substring(int start)的实质是sbustring(start, count)

    insert 方法

    1. @Override
    2. public synchronized StringBuffer insert(int offset, String str) {
    3. //缓存设置为null
    4. toStringCache = null;
    5. //调用父类方法
    6. super.insert(offset, str);
    7. return this;
    8. }
    9. public AbstractStringBuilder insert(int offset, String str) {
    10. //验证参数合法性
    11. if ((offset < 0) || (offset > length()))
    12. throw new StringIndexOutOfBoundsException(offset);
    13. //如果传入的String为空,替换为”null“字符串
    14. if (str == null)
    15. str = "null";
    16. //获取插入字符串的长度
    17. int len = str.length();
    18. //检查char数组容量,容量不够则扩容
    19. ensureCapacityInternal(count + len);
    20. //把offset作为起始位置,count作为结束位置去覆盖offset + len作为起始位置,覆盖数量为count - offset
    21. //类似于offset后面的后移,空出插入部分
    22. System.arraycopy(value, offset, value, offset + len, count - offset);
    23. //把str的内容填充进去
    24. str.getChars(value, offset);
    25. //重新计算count的值
    26. count += len;
    27. return this;
    28. }

    indexOf 方法

    1. @Override
    2. public int indexOf(String str) {
    3. //注意,这个方法没有用synchronized修饰
    4. //调用父类方法
    5. return super.indexOf(str);
    6. }
    7. @Override
    8. public synchronized int indexOf(String str, int fromIndex) {
    9. //调用父类方法
    10. return super.indexOf(str, fromIndex);
    11. }
    12. public int indexOf(String str, int fromIndex) {
    13. //最后调用String的indexOf
    14. return String.indexOf(value, 0, count, str, fromIndex);
    15. }

    lastIndexOf 方法

    1. @Override
    2. public int lastIndexOf(String str) {
    3. //注意,这个方法没有用synchronized修饰
    4. //调用父类方法
    5. return lastIndexOf(str, count);
    6. }
    7. @Override
    8. public synchronized int lastIndexOf(String str, int fromIndex) {
    9. //调用父类方法
    10. return super.lastIndexOf(str, fromIndex);
    11. }
    12. public int lastIndexOf(String str, int fromIndex) {
    13. //最后调用的是String的lastIndexOf方法
    14. return String.lastIndexOf(value, 0, count, str, fromIndex);
    15. }

    reverse 方法

    1. @Override
    2. public synchronized StringBuffer reverse() {
    3. //将缓存置为null
    4. toStringCache = null;
    5. //调用父类方法
    6. super.reverse();
    7. return this;
    8. }
    9. public AbstractStringBuilder reverse() {
    10. //是否含有unicode编码,包含Unicode编码的话,替换后高低位会改变位置
    11. boolean hasSurrogates = false;
    12. int n = count - 1;
    13. //采取二分法 n-1>>1 等同于 (n-1)/2
    14. for (int j = (n - 1) >> 1; j >= 0; j--) {
    15. //根据对称性左右替换
    16. int k = n - j;
    17. char cj = value[j];
    18. char ck = value[k];
    19. value[j] = ck;
    20. value[k] = cj;
    21. //判断是否含有unicode编码
    22. if (Character.isSurrogate(cj) ||
    23. Character.isSurrogate(ck)) {
    24. hasSurrogates = true;
    25. }
    26. }
    27. //包含unicode编码
    28. if (hasSurrogates) {
    29. reverseAllValidSurrogatePairs();
    30. }
    31. return this;
    32. }
    33. /**
    34. * Outlined helper method for reverse()
    35. */
    36. private void reverseAllValidSurrogatePairs() {
    37. for (int i = 0; i < count - 1; i++) {
    38. char c2 = value[i];
    39. //如果包含Unicode编码,将高低位位置改变回来
    40. if (Character.isLowSurrogate(c2)) {
    41. char c1 = value[i + 1];
    42. if (Character.isHighSurrogate(c1)) {
    43. value[i++] = c1;
    44. value[i] = c2;
    45. }
    46. }
    47. }
    48. }

    toString 方法

    1. @Override
    2. public synchronized String toString() {
    3. //如果缓存为空,重新设置缓存
    4. if (toStringCache == null) {
    5. toStringCache = Arrays.copyOfRange(value, 0, count);
    6. }
    7. //直接使用缓存的数组
    8. return new String(toStringCache, true);
    9. }