逐步解读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 ...
随机推荐
- Entity Framework Code-First(3):Setup Environment
Setup Development Environment for EF Code-First: Let's setup the development environment for Code-Fi ...
- 《鸟哥的Linux私房菜》读书笔记2
1. 压缩后缀与压缩程序: *.Z compress 程序压缩的档案; *.bz2 bzip2 程序压缩的档案; *.gz gzip 程序压缩的档案; *.tar tar 程序打包的数据,并没有压缩过 ...
- 关于layui弹出层的使用
Jquery必须大于1.83 layui必须是all,否则不显示 <script src="../js/jquery-1.8.3.min.js"></script ...
- CEPH安装教程(中)
NTP服务配置 NTP客户端配置 # vim /etc/ntp.conf server 92.0.0.250 ### 手动同步下时间 # ntpdate -u 92.0.0.250 ### 启动服务 ...
- LOJ6053 简单的函数(min_25筛)
题目链接:LOJ 题目大意:从前有个积性函数 $f$ 满足 $f(1)=1,f(p^k)=p\oplus k$.(异或)求其前 $n$ 项的和对 $10^9+7$ 取模的值. $1\le n\le 1 ...
- CoinEye PRIVACY POLICY
PRIVACY POLICY First, welcome to use the app Thank you for using our products and services ("Se ...
- day20模块作业
1.模块化作业 1.回顾文件递归遍历. 默写一遍. 入口在: 当文件是个文件夹的时候 出口在: 文件是一个文件 2.计算时间差(用户输入起始时间和结束时间. 计算时间差(小时), 例如, 用户输入20 ...
- ch8 -- directMethod
稀疏直接法 主要用的g2o的方法.自己定义了一个新的一元边.边的误差项是测量值和由估计得来的x,y对应的灰度值之间的误差.导数为灰度对像素坐标的导数乘以像素坐标对yi*李代数的导数的负数.灰度对于像素 ...
- Jmeter3.2源码编译环境搭建(转)
1.下载jmeter3.2源码 https://github.com/apache/jmeter/tree/v3_2 https://blog.csdn.net/fly_to_higher/artic ...
- POJ3974 Palindrome Manacher 最长回文子串模板
这道题可以$O(nlogn)$,当然也可以$O(n)$做啦$qwq$ $O(nlogn)$的思路是枚举每个回文中心,通过哈希预处理出前缀和后缀哈希值备用,然后二分回文串的长度,具体的就是判断在长度范围 ...