Java之String详解

String

底层实现
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
	/** The value is used for character storage. */
	private final char value[];   //char数组,存储内容
	private int hash;           //存放哈希值,默认为0
	……
}

String类是被final所修饰的,因此String类对象不可变,也不可继承。String变量存储的是对String对象的引用,即地址,String对象里存储的才是字符串的具体值。

不变的优势:
(1)高效性。Java中经常会用到字符串的哈希码(hashcode)。字符串的不可变能保证其hashcode永远保持一致,创建String对象时,同时存储其hash值,这样每次使用一个字符串的hashcode的时候不用重新计算一次,更加高效;
(2)便捷性。在其它类中对String对象的引用更加方便;
(3)安全性。String被广泛的使用在其他Java类中充当参数。比如网络连接、打开文件等操作。如果字符串可变,那么类似操作可能导致安全问题。比如,引用同一String对象的修改会导致该连接中的字符串内容被修改,可变的字符串也可能导致反射的安全问题。
(4)线程安全。在多个线程之间共享而无需同步。

:String对象不是绝对不变的,也可以通过反射的方式进行修改。

赋值方式

(1)字面量赋值 eg:String str = “Hello”;

直接赋值:JVM会首先去字符串常量池中寻找是否有equals(“Hello”)的String对象,如果有,就把该“Hello”对象在字符串常量池中的引用复制给字符串变量str;如若没有,就在堆中新建一个对象,同时把引用驻留在字符串常量池中,再把引用赋给字符串变量str。
用该方法创建字符串时,无论创建多少次,只要字符串的值(内容)相同,那么它们所指向的都是堆中的同一个对象。

(2)new关键字创建新对象 eg:String str = new String(“Hello”);
利用new来创建字符串时,在编译期,若常量池中已经存在字面量“hello”,则直接引用,若不存在,则“Hello”会被加入到Class文件的常量池中;在运行期,无论字符串常量池中是否有与当前值相同的对象引用,都会在堆中新开辟一块内存,创建一个新的字符串对象,这个对象所对应的字符串字面量是保存在字符串常量池中的,new出来的对象str保存的是堆中刚刚创建出来的的字符串对象的引用。
在这里插入图片描述

总结:常量池是方法区的一部分,线程共享,且它是线程安全的,具有相同值的引用指向常量池中同一个位置,如果常量池中没有新的值,那么就会新建一个常量来交给新的引用。
对于同一个对象,new出来的字符串存放在中,而直接赋值给变量的字符串存放在常量池里。

intern()
intern方法可以在运行期向运行时常量池中增加常量。intern()有两个作用,一是将字符串字面量放入常量池(如果池中没有的话),第二个是返回这个常量的引用
使用:很多时候,程序中得到的字符串在编译期是无法确定的,那么也就没办法在编译期被加入到常量池中。比如字符串拼接时,非字面量的拼接s3 = s1+s2,整个拼接操作会被编译成StringBuilder.append,这种情况编译器是无法知道其确定值的,只有在运行期才能确定。此时使用intern对那些大量使用的字符串进行定义,每次JVM运行到这段代码的时候,就会直接把常量池中该字面值的引用返回,这样可以减少大量字符串对象的创建。
在这里插入图片描述

比较

“==”比较的是在堆中创建的对象的地址, equals比较的就是字面量的内容。

拼接

效率(用时短到长):StringBuilder < StringBuffer < concat < + < StringUtils.join

(1)“+”
(语法糖,Java唯一的运算重载)
底层原理:将String转成StringBuilder后,使用其append方法以及toString方法进行处理。

  1. 在for循环中,采用“+”进行拼接,每次都会new一个StringBuilder,频繁的新建对象不仅仅会耗费时间,还会造成内存资源的浪费。所以在循环中,推荐使用StringBuilder拼接;
  2. 表达式右边是纯字符串常量,那么存放在常量池里面。eg:String str = “Hello” + “World”;
  3. 表达式右边如果存在字符串引用,那么就存放在堆里面。eg:String str = str1 + str2。

(2)contact

public String concat(String str) {
    int otherLen = str.length();
    if (otherLen == 0) {
        return this;
    }
    int len = value.length;
    char buf[] = Arrays.copyOf(value, len + otherLen);
    str.getChars(buf, len);
    return new String(buf, true);
}

底层原理:首先创建一个字符数组,长度是已有字符串和待拼接字符串的长度之和,再把两个字符串的值复制到新的字符数组中,最后使用这个字符数组创建一个新的String对象并返回。

(3)StringBuilder
其append方法继承AbstractStringBuilder类

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;
}

append会直接拷贝字符到内部的字符数组中,如果字符数组长度不够,会进行扩容

(4)StringBuffer

public synchronized StringBuffer append(String str) {
    toStringCache = null;
    super.append(str);
    return this;
}

StringBuffer和StringBuilder类似,最大的区别就是StringBuffer是线程安全的,该方法使用synchronized进行声明。

(5)StringUtils.join
StringUtils.join也是使用了StringBuilder,并且其中还有很多其他操作,耗时较长,擅长处理字符串数组或者列表的拼接。

参考
Java详解【String】+【StringBuilder vs StringBuffer】+【字符串拼接】
为什么阿里巴巴不建议在for循环中使用"+"进行字符串拼接
我终于搞清楚了和String有关的那点事儿。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值