004 Java字符串的几个特性

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了004 Java字符串的几个特性相关的知识,希望对你有一定的参考价值。

在本系列教材的上一篇(003 Java字符串)中,对Java语言中String类的一些基本情况和整体架构进行了讲解和分析,相信大家已经很好地掌握了。本篇教程主要是补充一些String类的重要特性,帮助大家避免掉使用String过程中的一些陷阱。

        首先,补充一个在JDK中使用非常频繁的概念:不可变类。所谓的不可变类是指该类的对象在生成以后就不会被改变了,关于不可变类的优点、缺点,特别是在Java并发编程时的优势,此处暂时略过不讲。那么如何定义一个不可变类呢?如果你有仔细观察String类的源码,你肯定能发现所有String类的所有可能修改字符串对象的方法都新建了一个字符串对象,而不是在原有的字符串对象上做修改。所以String类就是一个非常标准的不可变类。

        如果曾经做过Java的面试题,那么你对下面的题目不会陌生:

String a = "test";

String b = "te" + "st";

 

System.out.println(a == b);

System.out.println(a.equals(b));

强烈建议大家先自己思考一下,得出答案后再接着往下看。正确答案是true和true,恭喜你答对了。答案虽然单纯,背后的机制却并不简单,导致此种结果的主要原因有三:一是通过双引号定义的字符串是字符串常量,存储在JVM内存模型中的方法区(常量区);二是String是一个不可变类,JVM对字符串常量进行了特殊处理:缓存处理,也就是说一个字符串常量在整个JVM中只存在一份;三是Java编译器对编译时就能确定的字符串常量进行了一定的优化,也就是自动进行了合并操作。我绘制了一个字符串在简化版的JVM内存模型中的布局情况简图:

技术分享

从此图中可以看出,对于字符串常量,JVM将其存储于方法区。

    ?    ?接下来,再看看如下的代码:

String a = "test";

String b = new String(a);

String c = new String(a);

 

System.out.println(a == b);

System.out.println(b == c);

同样建议大家先自己思考得出答案。如果的答案不是false和false,那么接下来的内容你需要边看边思考了。我们发现此处使用new运算法来生成两个对象,它们就不再符合上面说的字符串常量的情况,它们在JVM简化内存模型中的布局如下图所示:

技术分享

从此图可以得出,如果使用new运算符来生成String对象,那么该String对象就一定是作为一个新生成的对象,存储于堆空间内。

    ?    ?关于String对象的内存布局情况,我们再来看一下String类中的intern函数:

public native String intern();

如果你的英文水平不差,建议仔细看一遍该方法的注释,从中可以获得该方法的大部分特性。看完之后并思考之后,可以通过下面这个代码来测试一下是否完全理解,代码如下:

String a = "test";

String b = new String(a);

 

System.out.println(a == a.intern());

System.out.println(b == b.intern());

System.out.println(a == b);

System.out.println(a.intern() == b.intern());

如果你给出的答案是true、false、false和true,那么恭喜你,以后关于String内存布局的笔试面试题都难不住你了。如果答案没有完全匹配,请仔细查看我给出JVM内存布局模型简图并思考:

技术分享

intern这个本地方法,对于存储于堆内存和方法区中的字符串对象,该方法都是获得字符串在JVM中的实际存储位置。一个Java应用中使用到的所有字符串值,实际上都只在JVM方法区的常量区中存储,堆内存中的String对象实际上并没有真正存储字符串值。如果理解了这个,上述代码的答案就显而易见了。

    ?    ?最后,大家需要注意一下String类中trim()方法,源码如下:

 public String trim() {

        int len = value.length;

        int st = 0;

        char[] val = value;    /* avoid getfield opcode */

 

        while ((st < len) && (val[st] <= ‘ ‘)) {

            st++;

        }

        while ((st < len) && (val[len - 1] <= ‘ ‘)) {

            len--;

        }

        return ((st > 0) || (len < value.length)) ? substring(st, len) : this;

    }

我相信大部分Java程序员学的第一门编程语言都是C\\C++,那么自然就以为String类的trim()方法也是用来去除字符串的前后空白字符的。当仔细看一下trim()方法的源码,你可能会发现Java版本trim()方法去除的不仅仅是空白字符(空格、换行、制表符),而是所有Unicode码小于等于32(空格的Unicode码)的字符。

    ?    ?通过本篇和上一篇教程对String类的讲解,相信你对JDK1.7中String类的构造结构和基本特性已经有了比较深入的理解。总结起来,String类中最关键的两个难点是字符编码和内存布局,相信各位读者都已经很好地掌握了。如果还有疑问和建议,欢迎给我留言。

  本系列文档会在本人的微信公众号发布,欢迎大家扫码关注。

                技术分享

以上是关于004 Java字符串的几个特性的主要内容,如果未能解决你的问题,请参考以下文章

java 数据库连接的几个步骤

java 12-5 StringBuffer的几个案例

java 19 - 5 Throwable的几个常见方法

javaFX的几个新特性,让swing彻底过时

需要将字节数组/二进制消息从 UDP DatagramPacket 转换为 java 中的几个字段

拼凑字符串时,去除末尾多余字符的几个方法