Java之String重点解析

String s = new String("abc")这段代码创建了几个对象呢?s=="abc"这个判断的结果是什么?s.substring(0,2).intern()=="ab"这个的结果是什么呢?

s.charAt(index) 真的能表示出所有对应的字符吗?

"abc"+"gbn"+s直接的字符串拼接是否真的比使用StringBuilder的性能低?

前言

很高兴遇见你~

Java中的String对象特性,与c/c++语言是很不同的,重点在于其不可变性。那么为了服务字符串不可变性的设计,则衍生出非常多的相关问题:为什么要保持其不可变?底层如何存储字符串?如何进行字符串操作才拥有更好的性能?等等。此外,字符编码的相关知识也是非常重要;毕竟,现在使用emoij是再正常不过的事情了。

文章的内容围绕着不可变 这个重点展开:

分析String对象的不可变性;

常量池的存储原理以及intern方法的原理

字符串拼接的原理以及优化

代码单元与代码点的区别

总结

那么,我们开始吧~

不可变性

理解String的不可变性,我们可以简单看几行代码:

String string = "abcd"; String string1 = string.replace("a","b"); System.out.println(string); System.out.println(string1); 输出: abcd bbcd

string.replace("a","b")这个方法把"abcd"中的a换成了b。通过输出可以发现,原字符串string并没有发生任何改变,replace方法构造了一个新的字符串"bbcd"并赋值给了string1变量。这就是String的不可变性。

再举个栗子:把"abcd"的最后一个字符d改成a,在c/c++语言中,直接修改最后一个字符即可;而在java中,需要重新创建一个String对象:abca,因为"abcd"本身是不可变的,不能被修改。

String对象值是不可变的,一切操作都不会改变String的值,而是通过构造新的字符串来实现字符串操作。

很多时候很难理解,为什么Java要如此设计,这样不是会导致性能的下降吗?回顾一下我们日常使用String的场景,更多的时候并没有直接去修改一个string,而是使用一次,则被抛弃。但下次,很可能,又再一次使用到相同的String对象。例如日志打印:

Log.d("MainActivity",string);

前面的"MainActivity"我们并不需要去更改他,但是却会频繁使用到这个字符串。Java把String设计为不可变,正是为了保持数据的一致性,使得相同字面量的String引用同个对象。例如:

String s1 = "hello"; String s2 = "hello";

s1与s2引用的是同个String对象。如果String可变,那么就无法实现这个设计了。因此,我们可以重复利用我们创建过的String对象,而无需重新创建他。

基于重复使用String的情况比更改String的场景更多的前提下,Java把String设计为不可变,保持数据一致性,使得同个字面量的字符串可以引用同个String对象,重复利用已存在的String对象。

在《Java编程思想》一书中还提到另一个观点。我们先看下面的代码:

public String allCase(String s){ return string.toUpperCase(); }

allCase方法把传入的String对象全部变成大写并返回修改后的字符串。而此时,调用者的期望是传入的String对象仅仅作为提供信息的作用,而不希望被修改,那么String不可变的特性则非常符合这一点。

使用String对象作为参数时,我们希望不要改变String对象本身,而String的不可变性符合了这一点。

存储原理

由于String对象的不可变特性,在存储上也与普通的对象不一样。我们都知道对象创建在 上,而String对象其实也一样,不一样的是,同时也存储在 常量池 中。处于堆区中的String对象,在GC时有极大可能被回收;而常量池中的String对象则不会轻易被回收,那么则可以重复利用常量池中的String对象。也就是说, 常量池是String对象得以重复利用的根本原因

常量池不轻易垃圾回收的特性,使得常量池中的String对象可以一直存在,重复被利用。

往常量池中创建String对象的方式有两种: 显式使用双引号构造字符串对象、使用String对象的intern()方法 。这两个方法不一定会在常量池中创建对象,如果常量池中已存在相同的对象,则会直接返回该对象的引用,重复利用String对象。其他创建String对象的方法都是在堆区中创建String对象。举个栗子吧。

当我们通过new String()的方法或者调用String对象的实例方法,如string.substring()方法,会在堆区中创建一个String对象。而当我们使用双引号创建一个字符串对象,如String s = "abc",或调用String对象的intern()方法时,会在常量池中创建一个对象,如下图所示:

image.png

还记得我们文章开头的问题吗?

String s = new String("abc"),这句代码创建了几个对象?"abc"在常量池中构造了一个对象,new String()方法在堆区中又创建了一个对象,所以一共是两个。

s=="abc"的结果是false。两个不同的对象,一个位于堆中,一个位于常量池中。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wssdsx.html