深入理解Java的String
type
status
date
slug
summary
tags
category
icon
password
Java
的字符串就是Unicode
字符序列,Java
并没有内置字符串类型,而是在Java
库中提供了预定义类String
,每个用双引号扩起来的字符串都是String
类的一个实例。String的成员属性
String
是一个final
类也就是说String
是不可被继承的,并且它的成员方法默认为final
方法,Java
中被final
修饰的类默认是不可继承的,final
类的成员方法都默认为final
方法。
- 从
String
类的成员属性可以看出String
是通过Char
数组存储字符串。
子串
String
类中substring
方法可以从一个字符串中提取子串- 在
substring
中从0开始计数,直到2为止,但是不包含2.
substring
的工作方式有一个优点:容易计算子串的长度。字符串s.substring(a,b)
的长度为b-a
。
- String的substring源码
拼接
Java提供了和其他语言一样同样支持+号进行拼接但是效率低稍后会说到。
将一个字符串与一个非字符串进行拼接时,后者被转换成字符串。
不可变字符串
String
类没有提供用于修改字符串的方法,String
使用private final char value[]来实现字符串的储存,也就是说String
对象创建后,就不能在修改此对象中储存的字符串内容,就是因为如此,才说String
类型是不可变的(immutable
)。我们不能对以创建的不可变对象进行修改。我们自己也可以创建不可变对象,只要在接口中不提供修改数据的方法就可以。String类对象确实有编辑字符串的功能,比如replace()。这些编辑功能是通过创建一个新的对象来实现的,而不是在原有的对象进行修改。
上面
s.replace()
的调用将创建一个新的字符串“Hello ShellMing”
,并返回该对象的引用。通过赋值,引用s将指向新的字符串。如果没有其他引用指向原有字符串“Hello World”
,原字符串对象将被垃圾回收。引用变量与对象
String a;
以上语句中String的引用变量是a而对象一般通过new的方式来创建。所以a就是一个引用变量,不是对象。- 创建字符串的方式
- 使用“”引号来创建字符串对象;
- 使用new关键字来创建字符串对象;
那么以上这两种有什么区别呢!
- 单独使用双引号创建的字符串都是常量,编译期就已经存储到字符串常量池当中
- 使用new关键字来创建的字符串对象会储存在堆内存中,是运行期创建的。
- 但值得注意的是new关键字创建字符串对象时首先查看字符串常量池中是否有相同值的字符串,
- 如果有,则拷贝一份到堆内存中,然后将堆内存的地址返回。
- 如果常量池中没有,则在堆内存中创建一份,然后返回堆内存地址。
- 但是在堆内存中创建的字符串对象不会在复制到字符串常量池中,以避免不必要的常量池空间的浪费。
- 只包含常量的字符串连接符如
“a” + “a”
创建的也是常量,编译期就能确定,已经确定储存到字符串常量池中。 - 使用包含变量的字符串链接符如
“a” + str
创建的对象是运行期才创建的,储存在堆内存中。 - 这就导致了使用String不一定创建对象但是new String 一定创建对象。
关于intern()
方法
- 简介
一个初始化为空的字符串池,它由
String
独自维护。当调用intern
方法时,如果池已经包含String
对象的字符串(使用equals
方法确定)则返回池中的字符串,否则将此String
对象添加到池中,并返回此String
对象的引用。- 执行规则
它遵循以下规则:对于任意两个字符串
s
和 t
,当且仅当 s.equals(t)
为 true
时,s.intern() == t.intern()
才为 true
。- 扩展
存在于
.class
文件中的常量池,在运行期间被jvm
装载,并且可以扩充。String
的intern()
方法就是扩充常量池的一个方法;当一个String
实例str
调用intern()
方法时,Java
查找常量池中是否有相同Unicode
的字符串常量,如果有,则返回其引用,如果没有,则在常量池中增加一个unicode
等于str
的字符串并返回它的引用。检测字符串是否相等
对于==如果作用于基本类型的变量(byte,short,char,int,long,float,doble,boolen)则直接比较储存的值是否相等注意这里提到的值指的是数值,如果作用于引用类型的变量则比较的是所指向的对象的地址就是判断是否指向同一个对象。
- String中的equals方法
equals
方法是基类Object
中的方法,因此对于所由继承于Object
的类都会有该方法。在Object
类中,equals
方法是用来比较两个对象的引用是否相等。即是否指向同一个对象。对于
equals
方法,equals
方法不能用于几本数据类型的变量。如果没有对equals
方法进行重写。则比较的引用类型的变量所指向的地址,而String
类对equals
方法进行了重写,用来比较指向的字符串所储存的字符串是否相等。其他的一写诸如Double
,Date
,Integer
等都对equals
方法进行了重写用来比较指向所储存的内容是否相等。String连接符"+"的详解
- 编译运行后的字节码
String
中使用+号进行字符串连接时对不同的字符串,连接操作最开始时如果都是字符串常量,编译后将尽可能多的直接将字符串常量连接起来,形成新的字符串常量参与后续连接。
- 接下来的字符串连接是从左向右依次进行,首先以最左边的字符串参数创建
StringBuilder
,然后依次对右边进行append
操作,最后将StringBuilder
对象通过toString()
方法转换成String
对象注意中间的多个字符串常量不会自动拼接。
- 也就是说
String c = "xx" + "yy " + a + "zz" + "mm" + b;
实质上的实现过程是:
由此得出结论:当使用+进行多个字符串连接时,实际上是产生了一个
StringBuilder
对象和一个String
对象。String不可变性导致字符串连接的代价
变量
s
的创建等价于String s = “abc”;
由上面的例子可知编译器进行了优化,这里创建了一个对象。有上面例子也可以知道s4
不能在编译期进行优化,其对象的创建。由上面分析的结果,就不难推断出
String
采用连接符效率低下的原因的代码分析每做一次
+
就产生了一个StringBuilder
对象,然后append
后就扔掉。下次再循环的时候重新产生个StringBuilder
对象,然后append
字符串,如此循环直至结束。如果我们直接采用StringBuilder
对象进行append
的话我们可以节省N-1
次创建和销毁对象的时间。所以对于循环中要进行字符串连接的应用一般都是使用StringBuilder
或者StringBuffer
对象来进行append
操作。String中的final用法和理解
可见,final只对引用的"值"(即内存地址)有效,它迫使引用只能指向初始指向的那个对象,改变它的指向会导致编译期错误至于它所指向的对象的变化,final是不负责的。
关于String、StringBuffer和StringBuilder
- 可变与不可变:
String
是不可变字符串对象,StringBuilder
和StringBuffer
是可变字符串对象(其内部的字符数组长度可变)。- 是否多线程安全:
String
中的对象是不可变的,也就可以理解为常量,显然线程安全。StringBuffer
与 StringBuilder
中的方法和功能完全是等价的,只是StringBuffer
中的方法大都采用了synchronized
关键字进行修饰,因此是线程安全的,而 StringBuilder
没有这个修饰,可以被认为是非线程安全的。- 三者的执行效率:
StringBuilder
> StringBuffer
> String
当然这个是相对的,不一定在所有情况下都是这样。比如String str = "hello"+ "world"
的效率就比 StringBuilder st = new StringBuilder().append("hello").append("world")
要高。因此,这三个类是各有利弊,- 不同的情况的使用:
当字符串相加操作或者改动较少的情况下,建议使用
String str="hello"
这种形式;当字符串相加操作较多的情况下,建议使用StringBuilder
,如果采用了多线程,则使用StringBuffer
。字符串池的优缺点
字符串池的优点就是避免了相同内容的字符串的创建,节省了内存,省去了创建相同字符串的时间,同时提升了性能;另一方面,字符串池的缺点就是牺牲了
JVM
在常量池中遍历对象所需要的时间,不过其时间成本相比而言比较低。场景总结
Loading...