zoukankan      html  css  js  c++  java
  • 逐步解读String类(一)

    一句题外话

    面试刚入行的Java新手,侧重基础知识;面试有多年工作经验的老鸟,多侧重对具体问题的解决策略。

    从一类面试题说起

    考察刚入行菜鸟对基础知识的掌握程度,面试官提出关于String类的内容挺常见的。

     1 public class StringFirst {
     2     public static void main(String[] args) {
     3         String s1 = "123java";
     4         String s2 = "123" + "java";
     5         String s3 = 123 + "java";
     6         String s4 = '1' + 23 + "java";
     7         String s5 = "123ja" + 'v' + 'a';
     8         String s6 = new String("123java");
     9         String s7 = new String("123" + "java");
    10         String s8 = s6.intern();
    11         String s9 = "123";
    12         String s10 = "java";
    13         String s11 = s9 + s10;
    14         String s12 = s7.intern();
    15         String s13 = s11.intern();
    16         
    17         System.out.println(s1 == s2);   //true
    18         System.out.println(s1 == s3);   //true
    19         System.out.println(s1 == s4);   //false
    20         System.out.println(s1 == s5);   //true
    21         System.out.println(s1 == s6);   //false
    22         System.out.println(s6 == s7);   //false
    23         System.out.println(s1 == s7);   //false
    24         System.out.println(s6 == s8);   //false
    25         System.out.println(s1 == s8);   //true
    26         System.out.println(s1 == s11);  //false
    27         System.out.println(s8 == s12);  //true
    28         System.out.println(s12 == s13); //true
    29     }
    30 }

    这些题你都做对了吗?如果你不是靠蒙全做对了,我相信你一定是能够清楚地说出这些问题背后的原理了,可以跳过本文余下内容。

    透过现象看本质

    本节对上面所涉及到Java编译器优化、不变长字符串常量池、intern方法等内容讨论片刻。

    1、Java编译器优化

    1.1)Java跨平台性:

    Java为实现跨平台,使用的是虚拟机技术(软件)。只要针对不同的平台使用不同的虚拟机,通过层间接口屏蔽操作系统底层的细节,就可以使得建立在虚拟机上层的所有内容几乎具有平台一致性。

    1.2)Java编译器、解释器简介:

    Java编译器、解释器本质上是一种类的包装(wrapper),它们都是由很多相关的类组装而成的。JVM真正运行的是由Java解释器加载,经Java编译器优化然后编译而成字节码,因此执行的结果可能会与Java源代码的逻辑有所出入。

     1.3)编译器优化:

    Java相对C/C++这类语言,运行代码的速度较为缓慢,因此设计人员设计了很多优化措施来提高JVM跑Java程序的速度,其中编译器优化是极其重要的一环。例如:

    String s2 = "123" + "java";   被编译器优化成  String s2 = "123java";
    String s7 = new String("123" + "java");   被编译器优化成  String s7 = new String("123java");

    事实上编译器对字符串的优化策略:如果字符串是拼接而成的,都会被编译器优化拼接成一个字符串对象。至于是不是同一个字符串对象,即能够共享字符串还是一个值得推敲的问题。

    2、不变长字符串常量池

    略微改变上面题目中的字符串顺序,这样解释编译器对字符串的优化更加具有代表性。

     1 public class StringFirst {
     2     public static void main(String[] args) {
     3         String s1 = 123 + "java";    // 编译器先对s1=123+"java"优化,优化后的结果是 s1="123java",并在字节码中指明在常量池中创建对象"123java"
     4         String s2 = "123java";       // 编译器在编译s2后发现,其s2="123java",因此做了s2=s1(让s2也指向了"123java"对象),并没有创建对象
     5         String s3 = "123" + "java";  // 同s2
     6         String s4 = '1' + 23 + "java";    // 编译器优化后s4=('1'+23)+"java"="72java",并在字节码中指明在常量池中创建对象"72java"
     7         String s5 = "123ja" + 'v' + 'a';  // 同s2
     8         String s6 = new String("123java");  // 在堆中创建一个新的内容为"123java"的对象  注:new出来的必须创建新对象
     9         String s7 = new String("123" + "java");  // 编译器先优化,然后再在堆中创建一个新的内容为"123java"的对象
    10         String s8 = s6.intern();   // 关于intern内容见下文
    11         String s9 = "123";    
    12         String s10 = "java";
    13         String s11 = s9 + s10;   // 编译器先对s11优化,结果为s11="123java",但是编译器并没有将s11也指向原来在堆中保存的共用的"123java"   
    14         String s12 = s7.intern();  
    15         String s13 = s11.intern();
    16         
    17         System.out.println(s1 == s2);
    18         System.out.println(s1 == s3);
    19         System.out.println(s1 == s4);
    20         System.out.println(s1 == s5);
    21         System.out.println(s1 == s6);
    22         System.out.println(s6 == s7);
    23         System.out.println(s1 == s7);
    24         System.out.println(s6 == s8);
    25         System.out.println(s1 == s8);
    26         System.out.println(s1 == s11);
    27         System.out.println(s8 == s12);
    28         System.out.println(s12 == s13);
    29     }
    30 }

    3、intern方法

    intern方法用native修饰,说明这个方法涉及到Java虚拟机底层用C/C++写的代码,在虚拟机底层上执行一些操纵,返回值为一个String引用。关于这个方法的文档说明如下。

     3.1)文档说明翻译

    String intern() 方法返回字符串对象的规范化表示形式。

    字符串池在程序开始运行时是空的,并由String类私有维护。

    当调用intern()方法时,如果通过equals(Object)方法判断出字符串池中已经包含另外一个与想要创建的String对象有相同内容的String对象,那么就返回这个字符串对象。否则,将这个想要创建的对象添加到字符串池中,然后返回这个想要创建的对象的引用。

    对于任意两个字符串t、s,当且仅当s.equals(t)返回true,有s.intern() == t.intern()返回true。

    所有的字符串字面量和值是字符串的常量表达式都是被拘禁的。

    3.2)intern方法行为

    intern方法用于在程序运行时将字符串强制拘禁在运行时常量区,统一由String类私有维护,非String类对象无法访问。只要在运行时的字符串常量区存在与需求内容相同字符串常量,则会直接将该字符串对象的引用返回;否则,就会在字符串常量区拘禁一个所需内容的字符串常量,然后返回字符串对象的引用。

    这样就能够解释为什么会出现题目中,使用intern方法后都返回true的问题了。

    还有些不得不说清楚的问题

    1、Java没有内置的字符串类型,而是在标准Java库类库中提供了一个预定义的String类,这个想法源自C++。初学时,容易认为hello.equals(str); 这种代码莫名其妙,但是随着积累的代码量增多,我们能够体会到这正是说明helloString类的实例,属于对象,从另一方面说明String是引用数据类型,非Java语言内置的基本数据类型。

    2、String类没有提供修改字符串的方法。如果确实需要修改,可以间接地用substring函数提取部分字符串内容,然后再拼接上需要改成的内容。但是这样的修改方式显然不方便,并且也不推荐使用这种方法,可以采用StringBuilder或者StringBuffer类进行方便操作。正是由于不能直接修改Java字符串中的单个字符,所有在Java官方文档中将String类对象称为不可变字符串

    3、String类的实例放置在位置不一定是堆(heap)。如果是String str = Hello World!;先是在编译期被放入到String类常量池,然后在运行时被装在人方法区的运行时常量池(注:Hotspot的实现不同,可能字符串常量池也在堆中,但可以确定的是字符串常量池必定在方法区中);而String str = new String(Hello World!); 则一直放堆区。

    4、String类源代码分析:

      public final class String

      implements java.io.Serializable, Comparable<String>, CharSequence

      {

        private final char value[];    //   String底层用字符数组实现。字符数组用final修饰,

                      //   说明String的确是不可变字符串(字符串对象的内部字符不可修改)

      }

      String实现序列化接口。字符串往往作为一个整体输入输出,因此有必要对象序列化。

    5、String使用unicode编码,更加准确来说,是使用UTF-16编码。不得不提一句,unicode标准后来扩充了UTF-8和UTF-32。

    String设计思想

    字符是我们经常操作的内容,如果只是使用类似C语言的字符数组,那么对于使用者来说,每次操作都是一个噩梦。于是,C++封装了String,使得对字符串的操作不需要再写一个字符数组。Java基本继承了C++的String串,区别是C++是可以操作String内单个字符,而Java是不可修改String内容的。

    String类在Java标准类库中被设计成不可变字符串的形式,其主要目的是用于共享。修改字符串的任务被分配到了另外的两个字符串标准类StringBuilder、StringBuffer中,这样两者各司其职,大大方便了使用者。

    Java的设计者认为字符串共享带来的高效率远远胜过于提取、拼接字符串所带来的所有低效率。因为写程序的时候,我们除了会对来自键盘或文件的单个字符或较短字符串汇集成字符串,其它的情况很少需要修改字符串,往往仅是对字符串进行比较。 

    字符串不可变与共享的思考

    不可变与共享有时候是相互映衬的关系,很可能不可变就代表共享,共享就意味着不可变。当然,这不是放之四海皆准的标准,只是将不可变与共享等效起来的这种想法在某些领域有一定的市场。

    举个大家都能够接受的例子。天猫服务器提供服务的那台电脑不能够改成动态IP,必须是静态IP;而访问天猫服务器的客户端电脑无所谓是静态还是动态IP。因为天猫服务器那台电脑的IP必须是大家都能够访问到的,也就是被互联网上所有电脑访问共享的。

    注:虽然想表达那个意思,但是实际上计算机之间的通信指的是进程之间的通信,而进程之间的通信靠套接字(socket::=(ip,port)),所以上述表述存在理论错误,请包涵。但是阐述的思想确实合理的。

    正是基于这种思想,Java编译器有条件地实现了让不变长字符串共享(可以笼统地将编译器提供的这种共享方法看成具有一个储存公共字符串对象的缓冲池),进一步提高了Java的执行性能。

    参考文献:《Java核心技术  卷一》 

  • 相关阅读:
    JAVA获取微信小程序openid和获取公众号openid,以及通过openid获取用户信息
    微信公众平台开发教程Java版(三) 消息接收和发送
    在JAVA中怎么比较Double类型数据的大小
    Java利用FastJson一行代码转List<Map>为List<Bean>
    关于java的Synchronized,你可能需要知道这些(上)
    Mybatis传参- 被逗号分割的字符串
    算法疑难(js实现)---8、二叉树的创建(先序遍历)
    算法疑难(js实现)---7、二叉树的创建(层次遍历)
    心得体悟帖---200522(学习方法)
    心得体悟帖---200522(什么都不想,是一种非常好的解决问题的方案)
  • 原文地址:https://www.cnblogs.com/forget406/p/5698731.html
Copyright © 2011-2022 走看看