美文网首页
String类详解

String类详解

作者: 流年划破容颜_cc55 | 来源:发表于2018-08-02 22:21 被阅读94次

    参考文章地址

    字符串的分类

    其实说起来会有些别扭,为什么字符串会有分类这种东西。了解的朋友可能会知道字符串的操作除了String,还有StringBuffer和StringBuilder(区别我们在下面来说)
    不可变的字符串

    String是一个奇葩。

    String对象不可变,也就是说当对象创建完毕之后,该对象的内容(字符序列)是不允许改变的,如果内容改变则会创建一个新的String对象,返回到原地址中。

    细心的朋友也许会发现,String类维护的char数组不仅被final所修饰,并且查看JDK源码你就会发现,String类中每一个看起来会修改String值得方法,实际上都是创建了一个全新的String对象,以包含修改后的字符串对象。而最初的String对象则丝毫未动。我们可以简单的来看一个实例(从替换操作中就能明显看出)

    image.png

    replace方法就是替换字符串中的内容,如果替换之后跟原来的字符串相同则返回this,如果不相同则new一个新的对象返回。这明显体现了内容改变则返回新对象而不是直接修改String对象的值。

    表面的错觉

    关于String对象是否可变,有些操作确实会给人错觉,先来看一段程序:


    image

    从结果来看,s1的值最初是“A”,经过赋值以后,变成了“C”,经过字符串连接运算并赋值以后,变成了“BC”。String对象的内容真的改变了吗?实际上,这只是错觉而已。有疑惑的朋友可以去看我的上一篇笔记,你就能知道:

    String对象“A”,“B”,“C”在全程中都没有任何改变,改变的只是引用s1所指向的内容,也就是s1的值。

    String对象的创建

    有两种方式:

    // 第一种:直接赋一个字面量
    String str1 = "ABCD";
    // 第二种:通过构造器创建
    String str2 = new String("ABCD");

    那么这两种方式有什么不同呢?这里可能会涉及到一个面试题:

    上述的两种方法分别创建了几个String对象?

    回答这个问题也特别简单,首先你需要直到JVM的内存模型是怎样的,在上一篇笔记中也有简单提到,这里需要补充的是:常量池(专门存储常量的地方,都指的是方法区中)分为编译常量池(不研究,存储字节码的相关信息)和运行常量池(存储常量数据)。

    image.png

    当执行第一句话的时候,会在常量池中添加一个新的ABCD字符,str1指向常量池的ABCD

    当执行第二句话的时候,因为有new操作符,所以会在堆空间新开辟一块空间用来存储新的String对象,因为此时常量池中已经有了ABCD字符,所以堆中的String对象指向常量池中的ABCD,而str2则指向堆空间中的String对象。

    所以结论:
    String str1 = "ABCD";
    最多创建一个String对象,最少不创建String对象.如果常量池中,存在”ABCD”,那么str1直接引用,此时不创建String对象.否则,先在常量池先创建”ABCD”内存空间,再引用.
    String str2 = new String("ABCD");
    最多创建两个String对象(当常量池里面没有“ABCD”时就会想在常量池里面创建,然后在内存空间里面创建一个),至少创建一个String对象。new关键字绝对会在堆空间创建一块新的内存区域,所以至少创建一个String对象。

    String对象的空值

    一种是表示引用为空(null)的空值:

    String str1 = null; // 没有初始化,没有分配内存空间

    另外一种表示内容为空的空值:

    String str2 = "; // 分配有内存空间,有内容。

    所以当你需要判断字符串是否为空的时候,实际上应该这样:


    image.png

    字符串的比较

    image.png

    从上图可以明显看出,使用“==”,只能比较引用的内存地址是否相同,而使用“equals”方法,则比较的是字符串的内容。

    我们可以跟到String类的equals方法:


    image.png

    这里补充一下instanceof这个关键字的作用:判断其左边对象是否为其右边类的实例,返回boolean类型的数据。可以用来判断继承中的子类的实例是否为父类的实现(简单的讲,就是一个类跟另外的一个类有亲戚关系,从object到最后的子类,只有你左边的类属于这个亲戚链子里面。就会返回true

    “+”号是怎么来连接字符串的

    先来直接看一个简单的例子,程序中创建了三个String对象,str是hello和wrold两个字符串连接赋值后的对象,程序的结果很明显,但我们关心的是,hello和world是怎样连接起来的呢?


    image.png

    我们在XJad(Java反编译程序,把生成的class反编译成java)中打开刚刚生成的class文件会发现:


    image.png

    编译器自动引入了一个java.lang.StringBuilder类。虽然我们在源代码中并没有使用StringBuilder类,但是编译器却自作主张地使用了它,因为它更高效。
    在这个例子中,编译器创建了一个StringBuilde对象,用以构造最终的String,并为每个字符串调用了一次StringBuilder的append()方法,总计两次。最后调用toString()生成结果。这是编译器自动优化的结果,包括自动生成的Tester()无参数默认的构造函数也是。

    现在,你也许会觉得可以随意使用String对象,反正编译器会为你自动地优化性能。可是在这之前,我们先要看看编译器究竟能给我们优化到什么程度(下面再详细介绍StringBuilder)。

    可变的字符串

    StringBuilder/StringBuffer:当对象创建完毕之后,该对象的内容可以发生改变,当内容发生改变的时候,对象保持不变。

    接着上面的问题,我们继续来看一个例子:


    image.png

    程序的结果显而易见,我们来看看反编译之后的代码:


    image.png

    可以看到,对比两个对象,后者的循环部分的代码更简短、更简单,而且它只生成了一个StringBuilder对象。

    结论是:如果字符串操作比较简单,那就可以信赖编译器,它会为你合理地构造最终的字符串结果。但如果你还使用循环,多次地改变字符串的内容,那就更适合StringBuilder对象。

    但是如果你想要走捷径,例如append(a+":"+c),则编译器就会调入陷阱,从而为你另外创建一个StringBuilder对象处理括号内的字符串操作


    image

    String对象的比较

    StringBuilder是Java SE5引入的,在这之前Java用的是StringBuffer。后者线程安全(只需要了解,该对象方法中所有的方法都是用了synchronized修饰符),因此开销也会大。有没有用synchronized修饰符,就是这两者唯一的区别。我们可以简单地来比较一下这三个String对象在拼接字符串中的性能:

    创建好三个方法,分别测试三个类型的对象的拼接效率: image.png

    面试题

    最后再有一个String的面试题:

    说说下面的String对象,彼此之间是否相等


    image.png

    如果你自己写几个判断相等的语句,分别判断str1和另外五个是否相等,则会发现:
    str1和str2/str3相等,和另外几个都不相等。我们先来看一下反编译之后的代码:

    image.png

    知识点(纯干货):

    1 单独使用""引号创建的字符串都是直接量,编译期就已经确定存储到常量池中;
    2 使用new String("")创建的对象会存储到堆内存中,是运行期才创建;
    3 使用只包含直接量的字符串连接符如"aa" + "bb"创建的也是直接量编译期就能确定,已经确定存储到常量池中(str2和str3);
    4 使用包含String直接量(无final修饰符)的字符串表达式(如"aa" + s1)创建的对象是运行期才创建的,存储在堆中;
    5 通过变量/调用方法去连接字符串,都只能在运行时期才能确定变量的值和方法的返回值,不存在编译优化操作.

    相关文章

      网友评论

          本文标题:String类详解

          本文链接:https://www.haomeiwen.com/subject/pmbsvftx.html