一句题外话

面试刚入行的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. Struts2学习第七课 OGNL

    request变成了struts重写的StrutsRequestWrapper 关于值栈: helloWorld时,${productName}读取productName值,实际上该属性并不在requ ...

  2. 【转】log4j.properties 详解与配置步骤 - edward0830ly的专栏 - 博客频道 - CSDN.NET

    一.log4j.properties 的使用详解 1.输出级别的种类 ERROR.WARN.INFO.DEBUGERROR 为严重错误 主要是程序的错误WARN 为一般警告,比如session丢失IN ...

  3. shell之hello world

    [root@localhost sh]#vi hello.sh //编辑 .sh 文件 #The first program #!/bin/bash echo 'hello world' [root@ ...

  4. 对各种lca算法的理解

    1.RMQ+ST 首先注意这个算法的要素:结点编号,dfs序,结点深度. 首先dfs,求出dfs序,同时求出每个结点的深度.然后st算法,维护深度最小的结点编号(dfs序也可以,因为他们俩可以互相转换 ...

  5. 2017-10-2 清北刷题冲刺班a.m

    一道图论神题 (god) Time Limit:1000ms   Memory Limit:128MB 题目描述 LYK有一张无向图G={V,E},这张无向图有n个点m条边组成.并且这是一张带权图,只 ...

  6. centos 7 安装python3

    centos系统默认已安装python2.7,python3需要手动安装.以上是安装步骤 一.备份原来的2.7版本 首先看一下默认的python2.7在哪里 [root@apple ~]# cd / ...

  7. Nacos-spring-samples解析

    小白们在看这个用例的时候得注意,这个东东不知道他是为了让大家能够快速体验还是怎么的, 反正我一开始没整明白,有点想当然的去理解了: 我一直以为这个Nacos-spring-samples只是一个简单的 ...

  8. Java基础笔记(十六)——继承

    继承 提取出一些共性特征,作为父类,子类就可以继承父类的这些开放成员,子类再添加自己独有的属性和方法.如果再有类具有这些共同特征,也可继承这个父类. 特点:1.利于代码复用     2.缩短开发周期 ...

  9. linux限制内存和磁盘使用

    一.如何限制用户的磁盘空间 1. 查看系统中所有用户的磁盘空间配额 sudo repquota /dev/vda1 2. 查看某个用户的磁盘空间配额 sudo edquota user_name 要想 ...

  10. 【外部节点】json判断@表示正在处理的当前数组项或对象。过滤器还可用于$引用当前对象之外的属性

    $.store.book[?(@.price < $.expensive)] { "category" : "reference", "auth ...