问题:
理解 Java的字符串,String、StringBuffer、StringBuilder 有什么区别?
知识点
- 字符串设计和实现考量 String是Immutable(线程安全、字符串常量池复用)。Immutable对象在拷贝时候不需要额外复制数据。至于为什么imumutable,源码如下:
//关键点 final
public final class Stringimplements java.io.Serializable, Comparable<String>, CharSequence {// The associated character storage is managed by the runtime. We only// keep track of the length here.//关键点final private// private final char value[];private final int count;
复制代码
- StringBuffer、StringBuilder底层都是利用可修改的数组(JDK 9之后是byte)数组,都继承了AbstractStringBuilder,里面包含了基本操作。区别StringBuffer增加了synchronized。相关源码如下:
//AbstractStringBuilder 没有加final 也没private修饰
abstract class AbstractStringBuilder implements Appendable, CharSequence {char[] value;int count;private static final int MAX_ARRAY_SIZE = 2147483639;AbstractStringBuilder() {}
复制代码
//StringBuilder截取部分源码
static final long serialVersionUID = 4383685877147921099L;public StringBuilder() {super(16);}public StringBuilder(int var1) {super(var1);}public StringBuilder(String var1) {super(var1.length() + 16);this.append(var1);}public StringBuilder(CharSequence var1) {this(var1.length() + 16);this.append(var1);}public StringBuilder append(Object var1) {return this.append(String.valueOf(var1));}public StringBuilder append(String var1) {super.append(var1);return this;}public StringBuilder append(StringBuffer var1) {super.append(var1);return this;}public StringBuilder append(CharSequence var1) {super.append(var1);return this;}public StringBuilder append(CharSequence var1, int var2, int var3) {super.append(var1, var2, var3);return this;}
复制代码
//StringBuffer部分源码 多了synchronized
public StringBuffer(CharSequence seq) {this(seq.length() + 16);append(seq);}public synchronized int length() {return count;}public synchronized int capacity() {return value.length;}public synchronized void ensureCapacity(int minimumCapacity) {if (minimumCapacity > value.length) {expandCapacity(minimumCapacity);}}
复制代码
- 字符串缓存 String在Java 6 以后提供了intern()方法,目的是提示JVM把相应字符串缓存起来,以便重复使用。
注意:Java 6这种历史版本,并不推荐大量使用intern(),因为缓存的字符串是存在“永久代”中,这个空间比较有限。也基本不会被FULLGC之外的垃圾收集照顾到。所以,使用不当,容易OOM。后续版本中,被放到堆中,设置永久代在Java 8中被元数据区替代了。
intern()感兴趣可以参考文章:Java提高篇——理解String 及 String.intern() 在实际中的应用
- String自身也有相关优化 有兴趣可以自己查看相关文章,主要是char数组换成了byte数组加上一个标志编码所谓的coder。
回答问题:
String的创建机理
由于String在Java世界中使用过于频繁,Java为了避免在一个系统中使用大量的Java对象,引入了字符串常量池的概念。其运行机制是:创建一个字符串的时候,首先检查池中是否有相等的字符串对象,如果有就不需要创建,直接从池中找到对象引用,如果没有的话,新建字符串对象,返回对象引用。但是,通过new方法创建的不会检查常量池是否存在,而是直接在堆中或者栈中创建一个新的对象,也不会把对象放入池中。上面所说的只适用于直接给String引用赋值的情况。
注意:String是immutable Strng提供了inter()方法可以将Strng对象添加到池中,并且返回该对象的引用。(如果由equals()确定池中有该字符串,那就直接返回)。
当两个String对象拥有相等的值的时候,他们只引用字符串中同一个拷贝。当同一个字符串大量出现的时候,可以大量节省内存空间。
StringBuffer/StringBuilder
StringBuffer/StringBuilder相同点:
- String/StringBuilder相对于String的话,他们值是可以改变的,并且值改变后,他们引用不会变。他们在构造的时候使用一个默认的数组,在后续加入数据后超过默认大小后会创建更大的数组,并且将原来数组的内容复制过来,再丢弃旧的数组。因此,项目开发的时候,对于较大对象的扩容会性能,因此,能预估大小,最好不过。
StringBuffer/StringBuilder不同点:
- 另外StringBuffer是线程安全(方法定义前面使用了synchronize),StringBuilder不是。StringBuffer性能要低于StringBuilder。
应用场景:
String 适用于常量声明。如:
public static final int DEVICE_NOT_AVAILABLE_ERROR_CODE = 390004;// 设备未启用或者被锁住
复制代码
StringBuffer,适用于频繁进行字符串运算(如拼接、替换、删除等),并且运行在多线程环境下,建议使用,比如Http参数解析和封装。
/*** 获取POST请求的base url** @param requestHolder* @return* @throws AlipayApiException*/private String getRequestUrl(RequestParametersHolder requestHolder) throws AlipayApiException {StringBuffer urlSb = new StringBuffer(serverUrl);try {String sysMustQuery = WebUtils.buildQuery(requestHolder.getProtocalMustParams(),charset);String sysOptQuery = WebUtils.buildQuery(requestHolder.getProtocalOptParams(), charset);urlSb.append("?");urlSb.append(sysMustQuery);if (sysOptQuery != null & sysOptQuery.length() > 0) {urlSb.append("&");urlSb.append(sysOptQuery);}} catch (IOException e) {throw new AlipayApiException(e);}return urlSb.toString();}
复制代码
StringBuilder,适用于频繁进行字符串运算(如拼接、替换、删除等),并且运行在单线程环境下,建议使用,比如Json的封装。
/*** Extracts absolute path form a given URI. E.g., passing* <code>http://google.com:80/execute?query=cat#top</code>* will result in <code>/execute?query=cat#top</code>.** @param uri URI which absolute path has to be extracted,* @return the absolute path of the URI,*/private String getAbsolutePathFromAbsoluteURI(URI uri) {String rawPath = uri.getRawPath();String rawQuery = uri.getRawQuery();String rawFragment = uri.getRawFragment();StringBuilder absolutePath = new StringBuilder();if (rawPath != null) {absolutePath.append(rawPath);} else {absolutePath.append("/");}if (rawQuery != null) {absolutePath.append("?").append(rawQuery);}if (rawFragment != null) {absolutePath.append("#").append(rawFragment);}return absolutePath.toString();}复制代码
参考:
- Java提高篇——理解String 及 String.intern() 在实际中的应用
- String、StringBuffer、StringBuilder部分源码
- 自己开发Demo中部分代码
- 极客时间APP核心技术第五讲|String、StringBuffer、StringBuilder有什么区别?
声明:此为原创,转载请联系作者
作者:微信公众号添加公众号-遛狗的程序员 ,或者可以扫描以下二维码关注相关技术文章。