一句题外话

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

从一类面试题说起

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

 public class StringFirst {
public static void main(String[] args) {
String s1 = "123java";
String s2 = "123" + "java";
String s3 = 123 + "java";
String s4 = '1' + 23 + "java";
String s5 = "123ja" + 'v' + 'a';
String s6 = new String("123java");
String s7 = new String("123" + "java");
String s8 = s6.intern();
String s9 = "123";
String s10 = "java";
String s11 = s9 + s10;
String s12 = s7.intern();
String s13 = s11.intern(); System.out.println(s1 == s2); //true
System.out.println(s1 == s3); //true
System.out.println(s1 == s4); //false
System.out.println(s1 == s5); //true
System.out.println(s1 == s6); //false
System.out.println(s6 == s7); //false
System.out.println(s1 == s7); //false
System.out.println(s6 == s8); //false
System.out.println(s1 == s8); //true
System.out.println(s1 == s11); //false
System.out.println(s8 == s12); //true
System.out.println(s12 == s13); //true
}
}

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

透过现象看本质

本节对上面所涉及到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、不变长字符串常量池

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

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

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核心技术  卷一》 

逐步解读String类(一)的更多相关文章

  1. Java问题解读系列之String相关---String类为什么是final的?

    今天看到一篇名为<Java开发岗位面试题归类汇总>的博客,戳进去看了一下题目,觉得有必要夯实一下基本功了,所以打算边学边以博客的形式归纳总结,每天一道题, 并将该计划称为java问题解读系 ...

  2. java.lang.String 类源码解读

    String类定义实现了java.io.Serializable, Comparable<String>, CharSequence 三个接口:并且为final修饰. public fin ...

  3. JDK常用类解读--String

    一.字符串的不变性: 文章使用的源码是jdk1.8的.(下同) 1.首先可以看到`String`是`final`类,说明该类不可继承,保证不会被子类改变语义 2.String的值实际上就是一个字符数组 ...

  4. java问题解读,String类为什么是final的

    一.理解final 望文生义,final意为“最终的,最后的”,我理解为“不能被改变的”,它可以修饰类.变量和方法. 所以我是否可以理解为被它所修饰的类.变量和方法都不能被改变呢?答案是”是“,因为有 ...

  5. Java问题解读系列之String相关---String类的常用方法?

    今天的题目是:String类的常用方法? 首先,我们在eclipse中定义一个字符串,然后使用alt+/就会出现String类的所有方法,如下图所示: 下面我就挑选一些常用的方法进行介绍: 首先定义两 ...

  6. 【Java】整理关于java的String类,equals函数和比较操作符的区别

    初学 Java 有段时间了,感觉似乎开始入了门,有了点儿感觉但是发现很多困惑和疑问而且均来自于最基础的知识折腾了一阵子又查了查书,终于对 String 这个特殊的对象有了点感悟大家先来看看一段奇怪的程 ...

  7. 2、String类

    String类 String 对象用于保存字符串,也就是一组字符序列 字符串常量对象是用双引号括起来的字符序列,例如:"你好"."12.07"."bo ...

  8. 标准库String类

    下面的程序并没有把String类的所有成员方法实现,只参考教程写了大部分重要的成员函数. [cpp] view plain copy #include<iostream> #include ...

  9. 自己实现简单的string类

    1.前言 最近看了下<C++Primer>,觉得受益匪浅.不过纸上得来终觉浅,觉知此事须躬行.今天看了类类型,书中简单实现了String类,自己以前也学过C++,不过说来惭愧,以前都是用C ...

随机推荐

  1. C# 开发网页的打印版

    在项目中,有一个需求时是需要打印产品页面.但是打印出来的版本和网页上的版本不太一致,有些图片不需要,网页上以tab选项卡显示的内容,都需要在打印页面中看到..等等 CSS针对这种需求,引入了一个@me ...

  2. iis应用程序池没有fromwork 4.0-----安装iis

    找到已经安装的目录  C:\Windows\Microsoft.NET\Framework\v4.0.30319  以管理员身份运行一下就ok 安装iis 控制面板-程序与功能-打开与关闭window ...

  3. iscsi使用教程(上)

    服务端 服务器环境 已经安装过qemu-img的32位ubuntu $ uname -a Linux ubuntu-virtual-machine 3.13.0-46-generic #76-Ubun ...

  4. 教大家一个看电视局免广告的方法--由UWP想到的

    将近一年(10个月)来一直在学习.NET技术,这其中包括C#.WPF.WCF和ASP.NET MVC,目前学习即将结束. 本人在学习WPF的过程中,也了解到有UWP这门技术,UWP技术目前来说主要是应 ...

  5. (PHP)redis Zset(有序集合 sorted set)操作

    /** * * Zset操作 * sorted set操作 * 有序集合 * sorted set 它在set的基础上增加了一个顺序属性,这一属性在修改添加元素的时候可以指定,每次指定后,zset会自 ...

  6. Baidu - Echarts 地图实例测试,并绘制平滑圆弧路径

    百度Echarts实例地址: http://echarts.baidu.com/examples.html 同事想做一个地图,地图上的几个点通过动态的线连接起来.但是在实例里没找到类似的. 然后仔细分 ...

  7. HDP3.1 中配置 YARN 的 timeline server 使用外部的 HBase

    HDP3.1 中的 YARN 的 timeline server 默认使用内置的 HBase,不知道为什么,总是过几天就挂掉,暂时还没找到原因.后来想着让它使用外部的 HBase 看看会不会还有此问题 ...

  8. msyql分区与分库分表

    分区 工作原理 对用户而言,分区表是一个独立的逻辑表,但是底层MySQL将其分成多个物理子表,这对用户来说是透明的,每一个分区表都会使用一个独立的表文件. 如果数据量比较大,可以进行分区.分区对PHP ...

  9. 简谈react中的虚拟DOM

    相信你在看到此篇前也翻阅大量的对DOM的文章讲解和介绍 react中的虚拟DOM 此篇我尽量说人话(大白话),不然想必你在看到别的大神的文章早就懂了. 不说废话了,上干货. 1.首先简单对Html中的 ...

  10. thinkphp5使用uploadify

    uploadify flash版本下载地址:http://www.uploadify.com/wp-content/uploads/files/uploadify.zip 将解压后的文件放入项目公共文 ...