Jdk源码学习之:String

1、定义

Java Java 7 源码学习系列(一)——String

从该类的声明中咱们能够看出String是final类型的,表示该类不能被继承,同时该类实现了三个接口:java.io.Serializable、 Comparable、 CharSequencejava

2、属性

Java Java 7 源码学习系列(一)——String

这是一个字符数组,而且是final类型,他用于存储字符串内容,从fianl这个关键字中咱们能够看出,String的内容一旦被初始化了是不能被更改的。 虽然有这样的例子: String s = “a”; s = “b” 可是,这并非对s的修改,而是从新指向了新的字符串, 从这里咱们也能知道,String其实就是用char[]实现的。程序员

Java Java 7 源码学习系列(一)——String

缓存字符串的hash Code,默认值为 0正则表达式

Java Java 7 源码学习系列(一)——String

        由于String实现了Serializable接口,因此支持序列化和反序列化支持。Java的序列化机制是经过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体(类)的serialVersionUID进行比较,若是相同就认为是一致的,能够进行反序列化,不然就会出现序列化版本不一致的异常(InvalidCastException)。数据库


3、构造方法

String类做为一个java.lang包中比较经常使用的类,天然有不少重载的构造方法.在这里介绍几种典型的构造方法:编程

1.使用字符数组、字符串构造一个String

        咱们知道,其实String就是使用字符数组(char[])实现的。因此咱们可使用一个字符数组来建立一个String,那么这里值得注意的是,当咱们使用字符数组建立String的时候,会用到Arrays.copyOf方法和Arrays.copyOfRange方法。这两个方法是将原有的字符数组中的内容逐一的复制到String中的字符数组中。一样,咱们也能够用一个String类型的对象来初始化一个String。这里将直接将源String中的value和hash两个属性直接赋值给目标String。由于String一旦定义以后是不能够改变的,因此也就不用担忧改变源String的值会影响到目标String的值。数组

        固然,在使用字符数组来建立一个新的String对象的时候,不只可使用整个字符数组,也可使用字符数组的一部分,只要多传入两个参数int offsetint count就能够了。缓存

2.使用字节数组构造一个String

        在Java中,String实例中保存有一个char[]字符数组,char[]字符数组是以unicode码来存储的,String 和 char 为内存形式,byte是网络传输或存储的序列化形式。因此在不少传输和存储的过程当中须要将byte[]数组和String进行相互转化。因此,String提供了一系列重载的构造方法来将一个字符数组转化成String,提到byte[]和String之间的相互转换就不得不关注编码问题。String(byte[] bytes, Charset charset)是指经过charset来解码指定的byte数组,将其解码成unicode的char[]数组,够形成新的String。安全

这里的bytes字节流是使用charset进行编码的,想要将他转换成unicode的char[]数组,而又保证不出现乱码,那就要指定其解码方式网络

一样使用字节数组来构造String也有不少种形式,按照是否指定解码方式分的话能够分为两种:app

String(byte bytes[]) String(byte bytes[], int offset, int length)
String(byte bytes[], Charset charset)
String(byte bytes[], String charsetName)
String(byte bytes[], int offset, int length, Charset charset)
String(byte bytes[], int offset, int length, String charsetName)

        若是咱们在使用byte[]构造String的时候,使用的是下面这四种构造方法(带有charsetName或者charset参数)的一种的话,那么就会使用StringCoding.decode方法进行解码,使用的解码的字符集就是咱们指定的charsetName或者charset。 咱们在使用byte[]构造String的时候,若是没有指明解码使用的字符集的话,那么StringCoding的decode方法首先调用系统的默认编码格式,若是没有指定编码格式则默认使用ISO-8859-1编码格式进行编码操做。主要体现代码以下:

Java Java 7 源码学习系列(一)——String

3.使用StringBuffer和StringBuider构造一个String

做为String的两个“兄弟”,StringBuffer和StringBuider也能够被当作构造String的参数。

Java Java 7 源码学习系列(一)——String

固然,这两个构造方法是不多用到的,至少我历来没有使用过,由于当咱们有了StringBuffer或者StringBuilfer对象以后能够直接使用他们的toString方法来获得String。关于效率问题,Java的官方文档有提到说使用StringBuilder的toString方法会更快一些,缘由是StringBuffer的toString方法是synchronized的,在牺牲了效率的状况下保证了线程安全。

Java Java 7 源码学习系列(一)——String

4.一个特殊的保护类型的构造方法

String除了提供了不少公有的供程序员使用的构造方法之外,还提供了一个保护类型的构造方法(Java 7),咱们看一下他是怎么样的:

Java Java 7 源码学习系列(一)——String

        从代码中咱们能够看出,该方法和 String(char[] value)有两点区别,第一个,该方法多了一个参数: boolean share,其实这个参数在方法体中根本没被使用,也给了注释,目前不支持使用false,只使用true。那么能够判定,加入这个share的只是为了区分于String(char[] value)方法,不加这个参数就没办法定义这个函数,只有参数不能才能进行重载。那么,第二个区别就是具体的方法实现不一样。咱们前面提到过,String(char[] value)方法在建立String的时候会用到 会用到Arrays的copyOf方法将value中的内容逐一复制到String当中,而这个String(char[] value, boolean share)方法则是直接将value的引用赋值给String的value。那么也就是说,这个方法构造出来的String和参数传过来的char[] value共享同一个数组。 那么,为何Java会提供这样一个方法呢? 首先,咱们分析一下使用该构造函数的好处:

首先,性能好,这个很简单,一个是直接给数组赋值(至关于直接将String的value的指针指向char[]数组),一个是逐一拷贝。固然是直接赋值快了。

其次,共享内部数组节约内存。

可是,该方法之因此设置为protected,是由于一旦该方法设置为公有,在外面能够访问的话,那就破坏了字符串的不可变性。例如以下YY情形:

Java Java 7 源码学习系列(一)——String

若是构造方法没有对arr进行拷贝,那么其余人就能够在字符串外部修改该数组,因为它们引用的是同一个数组,所以对arr的修改就至关于修改了字符串。

因此,从安全性角度考虑,他也是安全的。对于调用他的方法来讲,因为不管是原字符串仍是新字符串,其value数组自己都是String对象的私有属性,从外部是没法访问的,所以对两个字符串来讲都很安全。

在Java 7 之有不少String里面的方法都使用这种“性能好的、节约内存的、安全”的构造函数。好比:substring、replace、concat、valueOf等方法(实际上他们使用的是public String(char[], int, int)方法,原理和本方法相同,已经被本方法取代)。

可是在Java 7中,substring已经再也不使用这种“优秀”的方法了,为何呢? 虽然这种方法有不少优势,可是他有一个致命的缺点,对于sun公司的程序员来讲是一个零容忍的bug,那就是他颇有可能形成内存泄露。 看一个例子,假设一个方法从某个地方(文件、数据库或网络)取得了一个很长的字符串,而后对其进行解析并提取其中的一小段内容,这种状况常常发生在网页抓取或进行日志分析的时候。下面是示例代码。

Java Java 7 源码学习系列(一)——String

在这里aLongString只是临时的,真正有用的是aPart,其长度只有20个字符,可是它的内部数组倒是从aLongString那里共享的,所以虽然aLongString自己能够被回收,但它的内部数组却不能(以下图)。这就致使了内存泄漏。若是一个程序中这种状况常常发生有可能会致使严重的后果,如内存溢出,或性能降低。

Java Java 7 源码学习系列(一)——String

新的实现虽然损失了性能,并且浪费了一些存储空间,但却保证了字符串的内部数组能够和字符串对象一块儿被回收,从而防止发生内存泄漏,所以新的substring比原来的更健壮。

额、、、扯了好远,虽然substring方法已经为了其鲁棒性放弃使用这种share数组的方法,可是这种share数组的方法仍是有一些其余方法在使用的,这是为何呢?首先呢,这种方式构造对应有不少好处,其次呢,其余的方法不会将数组长度变短,也就不会有前面说的那种内存泄露的状况(内存泄露是指不用的内存没有办法被释放,好比说concat方法和replace方法,他们不会致使元数组中有大量空间不被使用,由于他们一个是拼接字符串,一个是替换字符串内容,不会将字符数组的长度变得很短!)。


4、其余方法

length() 返回字符串长度
isEmpty() 返回字符串是否为空
charAt(int index) 返回字符串中第(index+1)个字符
char[] toCharArray() 转化成字符数组
trim() 去掉两端空格
toUpperCase() 转化为大写
toLowerCase() 转化为小写
String concat(String str) //拼接字符串
String replace(char oldChar, char newChar) //将字符串中的oldChar字符换成newChar字符
//以上两个方法都使用了String(char[] value, boolean share);
boolean matches(String regex) //判断字符串是否匹配给定的regex正则表达式
boolean contains(CharSequence s) //判断字符串是否包含字符序列s
String[] split(String regex, int limit) 按照字符regex将字符串分红limit份。
String[] split(String regex)

Java Java 7 源码学习系列(一)——String

一、getBytes

在建立String的时候,可使用byte[]数组,将一个字节数组转换成字符串,一样,咱们能够将一个字符串转换成字节数组,那么String提供了不少重载的getBytes方法。可是,值得注意的是,在使用这些方法的时候必定要注意编码问题。好比:

Java Java 7 源码学习系列(一)——String

这段代码在不一样的平台上运行获得结果是不同的。因为咱们没有指定编码方式,因此在该方法对字符串进行编码的时候就会使用系统的默认编码方式,好比在中文操做系统中可能会使用GBK或者GB2312进行编码,在英文操做系统中有可能使用iso-8859-1进行编码。这样写出来的代码就和机器环境有很强的关联性了,因此,为了不没必要要的麻烦,咱们要指定编码方式。如使用如下方式:

Java Java 7 源码学习系列(一)——String

二、比较方法

Java Java 7 源码学习系列(一)——String

字符串有一系列方法用于比较两个字符串的关系。 前四个返回boolean的方法很容易理解,前三个比较就是比较String和要比较的目标对象的字符数组的内容,同样就返回true,不同就返回false,核心代码以下:

Java Java 7 源码学习系列(一)——String

v1 v2分别表明String的字符数组和目标对象的字符数组。 第四个和前三个惟一的区别就是他会将两个字符数组的内容都使用toUpperCase方法转换成大写再进行比较,以此来忽略大小写进行比较。相同则返回true,不想同则返回false 。

在这里,看到这几个比较的方法代码,有不少编程的技巧咱们应该学习。咱们看equals方法:

Java Java 7 源码学习系列(一)——String

该方法首先判断this == anObject ?,也就是说判断要比较的对象和当前对象是否是同一个对象,若是是直接返回true,如不是再继续比较,而后在判断anObject是否是String类型的,若是不是,直接返回false,若是是再继续比较,到了能终于比较字符数组的时候,他仍是先比较了两个数组的长度,不同直接返回false,同样再逐一比较值。 虽然代码写的内容比较多,可是能够很大程度上提升比较的效率。值得学习~~!!!

contentEquals有两个重载,StringBuffer须要考虑线程安全问题,再加锁以后调用contentEquals((CharSequence) sb)方法。contentEquals((CharSequence) sb)则分两种状况,一种是cs instanceof AbstractStringBuilder,另一种是参数是String类型。具体比较方式几乎和equals方法相似,先作“宏观”比较,在作“微观”比较。

下面这个是equalsIgnoreCase代码的实现:

Java Java 7 源码学习系列(一)——String

看到这段代码,眼前为之一亮。使用一个三目运算符和&&操做代替了多个if语句。

三、hashCode

hashCode的实现其实就是使用数学公式:

Java Java 7 源码学习系列(一)——String

s[i]是string的第i个字符,n是String的长度。那为何这里用31,而不是其它数呢? 计算机的乘法涉及到移位计算。当一个数乘以2时,就直接拿该数左移一位便可!选择31缘由是由于31是一个素数!

所谓素数: 质数又称素数。指在一个大于1的天然数中,除了1和此整数自身外,无法被其余天然数整除的数。 素数在使用的时候有一个做用就是若是我用一个数字来乘以这个素数,那么最终的出来的结果只能被素数自己和被乘数还有1来整除!如:咱们选择素数3来作系数,那么3*n只能被3和n或者1来整除,咱们能够很容易的经过3n来计算出这个n来。这应该也是一个缘由! (本段表述有问题,感谢 @沉沦 的提醒)

在存储数据计算hash地址的时候,咱们但愿尽可能减小有一样的hash地址,所谓“冲突”。若是使用相同hash地址的数据过多,那么这些数据所组成的hash链就更长,从而下降了查询效率!因此在选择系数的时候要选择尽可能长的系数而且让乘法尽可能不要溢出的系数,由于若是计算出来的hash地址越大,所谓的“冲突”就越少,查找起来效率也会提升。 31能够 由i*31== (i<<5)-1来表示,如今不少虚拟机里面都有作相关优化,使用31的缘由多是为了更好的分配hash地址,而且31只占用5bits!

在java乘法中若是数字相乘过大会致使溢出的问题,从而致使数据的丢失.

而31则是素数(质数)并且不是很长的数字,最终它被选择为相乘的系数的缘由不过与此!

在Java中,整型数是32位的,也就是说最多有2^32= 4294967296个整数,将任意一个字符串,通过hashCode计算以后,获得的整数应该在这4294967296数之中。那么,最多有 4294967297个不一样的字符串做hashCode以后,确定有两个结果是同样的, hashCode能够保证相同的字符串的hash值确定相同,可是,hash值相同并不必定是value值就相同。


四、substring

Java Java 7 源码学习系列(一)——String

前面咱们介绍过,java 7 中的substring方法使用String(value, beginIndex, subLen)方法建立一个新的String并返回,这个方法会将原来的char[]中的值逐一复制到新的String中,两个数组并非共享的,虽然这样作损失一些性能,可是有效地避免了内存泄露。

五、replaceFirst、replaceAll、replace区别

Java Java 7 源码学习系列(一)——String

1)replace的参数是char和CharSequence,便可以支持字符的替换,也支持字符串的替换 2)replaceAll和replaceFirst的参数是regex,即基于规则表达式的替换,好比,能够经过replaceAll(“\d”, “*”)把一个字符串全部的数字字符都换成星号; 相同点是都是所有替换,即把源字符串中的某一字符或字符串所有换成指定的字符或字符串, 若是只想替换第一次出现的,可使用 replaceFirst(),这个方法也是基于规则表达式的替换,但与replaceAll()不一样的是,只替换第一次出现的字符串; 另外,若是replaceAll()和replaceFirst()所用的参数据不是基于规则表达式的,则与replace()替换字符串的效果是同样的,即这二者也支持字符串的操做;

六、copyValueOf 和 valueOf

String的底层是由char[]实现的:经过一个char[]类型的value属性!早期的String构造器的实现呢,不会拷贝数组的,直接将参数的char[]数组做为String的value属性。而后test[0] = 'A';将致使字符串的变化。为了不这个问题,提供了copyValueOf方法,每次都拷贝成新的字符数组来构造新的String对象。可是如今的String对象,在构造器中就经过拷贝新数组实现了,因此这两个方面在本质上已经没区别了。

valueOf()有不少种形式的重载,包括:

Java Java 7 源码学习系列(一)——String

能够看到这些方法能够将六种基本数据类型的变量转换成String类型。

七、intern()方法

Java Java 7 源码学习系列(一)——String

该方法返回一个字符串对象的内部化引用。 众所周知:String类维护一个初始为空的字符串的对象池,当intern方法被调用时,若是对象池中已经包含这一个相等的字符串对象则返回对象池中的实例,不然添加字符串到对象池并返回该字符串的引用。

八、String对“+”的重载

咱们知道,Java是不支持重载运算符,String的“+”是java中惟一的一个重载运算符,那么java使如何实现这个加号的呢?咱们先看一段代码:

Java Java 7 源码学习系列(一)——String

而后咱们将这段代码反编译:

Java Java 7 源码学习系列(一)——String

看了反编译以后的代码咱们发现,其实String对“+”的支持其实就是使用了StringBuilder以及他的append、toString两个方法。

九、String.valueOf和Integer.toString的区别

接下来咱们看如下这段代码,咱们有三种方式将一个int类型的变量变成呢过String类型,那么他们有什么区别?

Java Java 7 源码学习系列(一)——String

一、第三行和第四行没有任何区别,由于String.valueOf(i)也是调用Integer.toString(i)来实现的。 二、第二行代码实际上是String i1 = (new StringBuilder()).append(i).toString();,首先建立一个StringBuilder对象,而后再调用append方法,再调用toString方法。  

相关文章
相关标签/搜索