逐步解读String类(一)
一句题外话
面试刚入行的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);是hello
String类的实例,属于对象,从另一方面说明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类(一)的更多相关文章
- Java问题解读系列之String相关---String类为什么是final的?
今天看到一篇名为<Java开发岗位面试题归类汇总>的博客,戳进去看了一下题目,觉得有必要夯实一下基本功了,所以打算边学边以博客的形式归纳总结,每天一道题, 并将该计划称为java问题解读系 ...
- java.lang.String 类源码解读
String类定义实现了java.io.Serializable, Comparable<String>, CharSequence 三个接口:并且为final修饰. public fin ...
- JDK常用类解读--String
一.字符串的不变性: 文章使用的源码是jdk1.8的.(下同) 1.首先可以看到`String`是`final`类,说明该类不可继承,保证不会被子类改变语义 2.String的值实际上就是一个字符数组 ...
- java问题解读,String类为什么是final的
一.理解final 望文生义,final意为“最终的,最后的”,我理解为“不能被改变的”,它可以修饰类.变量和方法. 所以我是否可以理解为被它所修饰的类.变量和方法都不能被改变呢?答案是”是“,因为有 ...
- Java问题解读系列之String相关---String类的常用方法?
今天的题目是:String类的常用方法? 首先,我们在eclipse中定义一个字符串,然后使用alt+/就会出现String类的所有方法,如下图所示: 下面我就挑选一些常用的方法进行介绍: 首先定义两 ...
- 【Java】整理关于java的String类,equals函数和比较操作符的区别
初学 Java 有段时间了,感觉似乎开始入了门,有了点儿感觉但是发现很多困惑和疑问而且均来自于最基础的知识折腾了一阵子又查了查书,终于对 String 这个特殊的对象有了点感悟大家先来看看一段奇怪的程 ...
- 2、String类
String类 String 对象用于保存字符串,也就是一组字符序列 字符串常量对象是用双引号括起来的字符序列,例如:"你好"."12.07"."bo ...
- 标准库String类
下面的程序并没有把String类的所有成员方法实现,只参考教程写了大部分重要的成员函数. [cpp] view plain copy #include<iostream> #include ...
- 自己实现简单的string类
1.前言 最近看了下<C++Primer>,觉得受益匪浅.不过纸上得来终觉浅,觉知此事须躬行.今天看了类类型,书中简单实现了String类,自己以前也学过C++,不过说来惭愧,以前都是用C ...
随机推荐
- 做c语言的码农专业发展方向
写了几年C语言代码,最近在思索,何去何从比较好? 搜索了一下,发现几个答案: 2015年10月编程语言排行榜 丢开C语言在教学应用外.在目前C语言的实际应用中.常见的应用的情景有如下: 内核/驱动,b ...
- supervisor启动worker源码分析-worker.clj
supervisor通过调用sync-processes函数来启动worker,关于sync-processes函数的详细分析请参见"storm启动supervisor源码分析-superv ...
- day1_2_3
DD烧写命令(mfgtools-without-rootfs.tar.gz) ubuntu minicom svn 应用层进程阻塞调试 多机共享 securecrt的远程登录以及调试 tengxunt ...
- 35.windows提权总结
本文参考自冷逸大佬的博客,源地址在这里:https://lengjibo.github.io/windows%E6%8F%90%E6%9D%83%E6%80%BB%E7%BB%93/ windows提 ...
- cookie 、Session 和自定义分页
cookie cookie的由来 大家都知道Http协议是无状态的. 无状态的意思 是每次请求都是独立的,它的执行情况和结果与前面的请求和之后的请求都无直接关系, 他不会受前面的请求响应情况直接影响, ...
- 整数划分——真正的递归经典例题(NYOJ——90)
先注明学习博客的地址:(http://www.cnblogs.com/hoodlum1980/archive/2008/10/11/1308493.html) 题目描述:任何正整数n都可以写成n=n1 ...
- Protocol Buffers官方文档(proto3语言指南)
本文是对官方文档的翻译,大部分内容都是引用其他一些作者的优质翻译使文章内容更加通俗易懂(自己是直译,读起来有点绕口难理解,本人英文水平有限),参考的文章链接在文章末尾 这篇指南描述如何使用protoc ...
- Sequence( 分块+矩阵快速幂 )
题目链接 #include<bits/stdc++.h> using namespace std; #define e exp(1) #define pi acos(-1) #define ...
- js index of()用法
含义: indexOf() 方法可返回某个指定的字符串值在字符串中首次出现的位置.(工作中常用) 提示和注释: 注释:indexOf() 方法对大小写敏感! 注释:如果要检索的字符串值没有出现,则该方 ...
- IE8浏览器总是无响应或卡死崩溃怎么办
IE8浏览器总是无响应或卡死崩溃怎么办 2016-05-11 11:22:31 来源:百度经验 作者:qq675495787 编辑:Jimmy51 我要投稿 IE在打开某些网页的时候经常崩溃或无响应, ...