String
首先来看一下 String 类在继承树的什么位置、实现了什么接口、父类是谁,这是源码分析的几大重要因素。
String 没有继承任何接口,不过实现了三个接口,分别是 Serializable、Comparable、CharSequence 接口
- Serializable :这个序列化接口没有任何方法和域,仅用于标识序列化的语意。
- Comparable:实现了 Comparable 的接口可用于内部比较两个对象的大小
- CharSequence:字符串序列接口,CharSequence 是一个可读的 char 值序列,提供了 length(), charAt(int index), subSequence(int start, int end) 等接口,StringBuilder 和 StringBuffer 也继承了这个接口
重要属性
字符串是什么,字!符!串!你品,你细品。你会发现它就是一连串字符组成的串。
也就是说
String str = "abc"; // === char data[] = {'a', 'b', 'c'}; String str = new String(data);
原来这么回事啊!
所以,String 中有一个用于存储字符的 char 数组 value[]
,这个数组存储了每个字符。另外一个就是 hash 属性,它用于缓存字符串的哈希码。因为 String 经常被用于比较,比如在 HashMap 中。如果每次进行比较都重新计算其 hashcode 的值的话,那无疑是比较麻烦的,而保存一个 hashcode 的缓存无疑能优化这样的操作。
String 可以通过许多途径创建,也可以根据 Stringbuffer 和 StringBuilder 进行创建。
毕竟我们本篇文章探讨的不是源码分析的文章,所以涉及到的源码不会很多。
除此之外,String 还提供了一些其他方法
charAt
:返回指定位置上字符的值getChars
: 复制 String 中的字符到指定的数组equals
: 用于判断 String 对象的值是否相等indexOf
: 用于检索字符串substring
: 对字符串进行截取concat
: 用于字符串拼接,效率高于 +replace
:用于字符串替换match
:正则表达式的字符串匹配contains
: 是否包含指定字符序列split
: 字符串分割join
: 字符串拼接trim
: 去掉多余空格toCharArray
: 把 String 对象转换为字符数组valueOf
: 把对象转换为字符串
StringBuilder
StringBuilder 类表示一个可变的字符序列,我们知道,StringBuilder 是非线程安全的容器,一般适用于单线程
场景中的字符串拼接操作,下面我们就来从源码角度看一下 StringBuilder
首先我们来看一下 StringBuilder 的定义
public final class StringBuilder extends AbstractStringBuilder implements java.io.Serializable, CharSequence {...}
StringBuilder 被 final 修饰,表示 StringBuilder 是不可被继承的,StringBuilder 类继承于 AbstractStringBuilder类。实际上,AbstractStringBuilder 类具体实现了可变字符序列的一系列操作,比如:append()、insert()、delete()、replace()、charAt() 方法等。
StringBuilder 实现了 2 个接口
- Serializable 序列化接口,表示对象可以被序列化。
- CharSequence 字符序列接口,提供了几个对字符序列进行只读访问的方法,例如 ength()、charAt()、subSequence()、toString() 方法等。
StringBuilder 使用 AbstractStringBuilder 类中的两个变量作为元素
char[] value; // 存储字符数组 int count; // 字符串使用的计数
StringBuffer
StringBuffer 也是继承于 AbstractStringBuilder ,使用 value 和 count 分别表示存储的字符数组和字符串使用的计数,StringBuffer 与 StringBuilder 最大的区别就是 StringBuffer 可以在多线程场景下使用,StringBuffer 内部有大部分方法都加了 synchronized
锁。在单线程场景下效率比较低,因为有锁的开销。
StringBuilder 和 StringBuffer 的扩容问题
我相信这个问题很多同学都没有注意到吧,其实 StringBuilder 和 StringBuffer 存在扩容问题,先从 StringBuilder 开始看起
首先先注意一下 StringBuilder 的初始容量
public StringBuilder() { super(16); }
StringBuilder 的初始容量是 16,当然也可以指定 StringBuilder 的初始容量。
在调用 append 拼接字符串,会调用 AbstractStringBuilder 中的 append 方法
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; }
上面代码中有一个 ensureCapacityInternal
方法,这个就是扩容方法,我们跟进去看一下
private void ensureCapacityInternal(int minimumCapacity) { // overflow-conscious code if (minimumCapacity - value.length > 0) { value = Arrays.copyOf(value, newCapacity(minimumCapacity)); } }
这个方法会进行判断,minimumCapacity 就是字符长度 + 要拼接的字符串长度,如果拼接后的字符串要比当前字符长度大的话,会进行数据的复制,真正扩容的方法是在 newCapacity
中
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,如果扩容后的长度还比拼接后的字符串长度小的话,那就直接扩容到它需要的长度 newCapacity = minCapacity,然后再进行数组的拷贝。
4
总结
本篇文章主要描述了 String 、StringBuilder 和 StringBuffer 的主要特性,String、StringBuilder 和 StringBuffer 的底层构造是怎样的,以及 String 常量池的优化、StringBuilder 和 StringBuffer 的扩容特性等。
如果有错误的地方,还请大佬们提出宝贵意见。