String不可变

String不可变

字符串不变性 下面这张图展示了这段代码做了什么  String s = "abcd";  s = s.concat("ef");

 

再以下面一段代码为例:

1 String str="abc"; 2 System.out.println(str); 3 str=str+"de"; 4 System.out.println(str); 

  

如果运行这段代码会发现先输出“abc”,然后又输出“abcde”,好像是str这个对象被更改了,其实,这只是一种假象罢了,JVM对于这几行代码是这样处理的,首先创建一个String对象str,并把“abc”赋值给str,然后在第三行中,其实JVM又创建了一个新的对象也名为str,然后再把原来的str的值和“de”加起来再赋值给新的str,而原来的str就会被JVM的垃圾回收机制(GC)给回收掉了,所以,str实际上并没有被更改,也就是前面说的String对象一旦创建之后就不可更改了。所以,Java中对String对象进行的操作实际上是一个不断创建新的对象并且将旧的对象回收的一个过程,所以执行速度很慢。

 

 

String 类为什么要这样设计成不可变呢?

我们可以从性能以及安全方面来考虑:

  • 安全
    • 引发安全问题,譬如,数据库的用户名、密码都是以字符串的形式传入来获得数据库的连接,或者在socket编程中,主机名和端口都是以字符串的形式传入。因为字符串是不可变的,所以它的值是不可改变的,否则黑客们可以钻到空子,改变字符串指向的对象的值,造成安全漏洞。
    • 保证线程安全,在并发场景下,多个线程同时读写资源时,会引发竞态条件,由于 String 是不可变的,不会引发线程的问题而保证了线程安全。
    • HashCode,当 String 被创建出来的时候,hashcode也会随之被缓存,hashcode的计算与value有关,若 String 可变,那么 hashcode 也会随之变化,针对于 Map、Set 等容器,他们的键值需要保证唯一性和一致性,因此,String 的不可变性使其比其他对象更适合当容器的键值。
  • 性能
    • 当字符串是不可变时,字符串常量池才有意义。字符串常量池的出现,可以减少创建相同字面量的字符串,让不同的引用指向池中同一个字符串,为运行时节约很多的堆内存。若字符串可变,字符串常量池失去意义,基于常量池的String.intern()方法也失效,每次创建新的 String 将在堆内开辟出新的空间,占据更多的内存。

 

 

String 真的真的真的 "不可变 " 吗?

上面的例子肯定是不可变的,下面这个就尴尬了。

String str = "Hello Python"; System.out.println(str); // Hello Python Field field = String.class.getDeclaredField("value"); field.setAccessible(true); char[] value = (char[])field.get(str); value[6] = 'J'; value[7] = 'a'; value[8] = 'v'; value[9] = 'a'; value[10] = '!'; value[11] = '!'; System.out.println(str); // Hello Java!! //通过反射,我们改变了底层的字符数组的值,实现了字符串的 “不可变” 性,这是一种骚操作,不建议这么使用,违反了 Java 对 String 类的不可变设计原则,会造成一些安全问题。

 

========String方法========

1、+和concat

+和concat都可以用来拼接字符串,但在使用上有什么区别呢,先来看看这个例子。

public static void main(String[] args) {    // example1    String str1 = "s1";    System.out.println(str1 + 100);//s1100    System.out.println(100 + str1);//100s1    String str2 = "s2";    str2 = str2.concat("a").concat("bc");    System.out.println(str2);//s2abc    // example2    String str3 = "s3";    System.out.println(str3 + null);//s3null    System.out.println(null + str3);//nulls3    String str4 = null;    System.out.println(str4.concat("a"));//NullPointerException    System.out.println("a".concat(str4));//NullPointerException }

concat源码:

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

可以得出以下结论:

  • +可以是字符串或者数字及其他基本类型数据,而concat只能接收字符串。
  • +左右可以为null,concat为会空指针。
  • 如果拼接空字符串,concat会稍快,在速度上两者可以忽略不计,如果拼接更多字符串建议用StringBuilder。
  • 从字节码来看+号编译后就是使用了StringBuiler来拼接,所以一行+++的语句就会创建一个StringBuilder,多条+++语句就会创建多个,所以为什么建议用StringBuilder的原因。

 

1) Arrays.copyOf()内部是通过System.arraycopy()实现的。

2) System.arraycopy()通过对srcPos,destPos的设置完成对数组的任意部分复制功能;而Arrays.copyOf()只能从下标为0的元素开始复制。

3)System.arraycopy()中会因为srcPos+length > src.length 或 destPos+length > dest.length而报ArrayIndexOutOfBoundsException;而Arrays.copyOf()中不会因此而报错,因为Arrays.copyOf()返回的为方法内部新建的一个指定长度的数组。

4) 在System.arraycopy()方法中,如果目标数组dest==null,则会报NullPointerException;而Arrays.copyOf()中不会因此而报错。

5)System.arraycopy()方法中目标数组作为参数;而Arrays.copyOf()中目标数组作为返回值。

 

6) System.arraycopy()方法和Arrays.copyOf()方法均可以实现数组类型的向上转换,即子类型的数组可以复制到父类型数组中;但不可以实现向下转换,即父类型的数组不可以复制到子类型数组中。

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值