相信做为 JAVAER
,平时编码时使用最多的必然是 String
字符串,而相信应该存在很多人对于 String
的 api
很熟悉了,但没有看过其源码实现,其实我我的以为对于 api 的使用,最开始的阶段是看其官方文档,而随着开发经验的积累,应当尝试去看源码实现,这对自身能力的提高是相当重要的。当你理解了源码以后,后面对于 api 的使用也会更加驾轻就熟!java
备注:如下记录基于 jdk8 环境api
String
其实只是一个类,咱们大体能够从如下几个角度依次剖析它:数组
从 IDEA
自带插件导出 String
的 UML 类图以下:安全
从图中立刻能够看出,String
实现了接口 Serializable
,Comparable
,CharSequence
,简单介绍一下这三个接口的做用:数据结构
Serializable
:实现该接口的类将具有序列化的能力,该接口没有任何实现,仅仅是一直标识做用。Comparable
:实现此接口的类具有比较大小的能力,好比实现此接口的对象的列表(和数组)能够由 Collections
类的静态方法 sort
进行自动排序。CharSequence
:字符序列统一的我接口。提供字符序列通用的操做方法,一般是一些只读方法,许多字符相关的类都实现此接口,以达到对字符序列的操做,好比:String
,StringBuffer
等。String
类定义以下:ui
public final class String implements java.io.Serializable, Comparable<String>, CharSequence{
...
}
复制代码
由 final
修饰符可知, String
类是没法被继承,不可变类。this
这里主要介绍最关键的一个成员变量 value[]
,其定义以下:编码
/** The value is used for character storage. */
private final char value[];
复制代码
String
是一个字符串,由字符 char
所组成,所以实际上 String
内部其实就是一个字符数组,用 value[]
表示,注意这里的 value[] 是用 final 修饰的,表示该值是不容许修改的。spa
String
有不少重载的构造方法,介绍以下:插件
String = ""
就会初始化一个空字符串的 String
对象,而此构造方法,也是把空字符的 value[]
拷贝一遍而已,源码实现以下:public String() {
this.value = "".value;
}
复制代码
String
对象,实际上 将形参的 value
和 hash
赋值给实例对象做为初始化,至关于深拷贝了一个形参String
对象,源码以下:public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
复制代码
String
对象,这里使用 Arrays.copyOf
方法拷贝字符数组public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
复制代码
String
对象。public String(char value[], int offset, int count) {
//若是偏移量小于0,则抛出越界异常
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
if (count <= 0) {
//若是字符数量小于0,则抛出越界异常
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
//在截取的字符数量为0的状况下,偏移量在字符串长度范围内,则返回空字符
if (offset <= value.length) {
this.value = "".value;
return;
}
}
// Note: offset or count might be near -1>>>1.
//若是偏移量大于字符总长度-截取的字符长度,则抛出越界异常
if (offset > value.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
//使用Arrays.copyOfRange静态方法,截取必定范围的字符数组,从offset开始,长度为offset+count,赋值给当前实例的字符数组
this.value = Arrays.copyOfRange(value, offset, offset+count);
}
复制代码
String
对象。这里的整数数组表示字符对应的ASCII整数值public String(int[] codePoints, int offset, int count) {
//若是偏移量小于0,则抛出越界异常
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
if (count <= 0) {
//若是字符数量小于0,则抛出越界异常
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
//在截取的字符数量为0的状况下,偏移量在字符串长度范围内,则返回空字符
if (offset <= codePoints.length) {
this.value = "".value;
return;
}
}
// Note: offset or count might be near -1>>>1.
若是偏移量大于字符总长度-截取的字符长度,则抛出越界异常
//if (offset > codePoints.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
final int end = offset + count;
// 这段逻辑是计算出字符数组的精确大小n,过滤掉一些不合法的int数据
int n = count;
for (int i = offset; i < end; i++) {
int c = codePoints[i];
if (Character.isBmpCodePoint(c))
continue;
else if (Character.isValidCodePoint(c))
n++;
else throw new IllegalArgumentException(Integer.toString(c));
}
// 按照上一步骤计算出来的数组大小初始化数组
final char[] v = new char[n];
//遍历填充字符数组
for (int i = offset, j = 0; i < end; i++, j++) {
int c = codePoints[i];
if (Character.isBmpCodePoint(c))
v[j] = (char)c;
else
Character.toSurrogates(c, v, j++);
}
//赋值给当前实例的字符数组
this.value = v;
}
复制代码
String
实例,同时能够指定字符编码。public String(byte bytes[], int offset, int length, String charsetName) throws UnsupportedEncodingException {
//字符编码参数为空,抛出空指针异常
if (charsetName == null)
throw new NullPointerException("charsetName");
//静态方法 检查字节数组的索引是否越界
checkBounds(bytes, offset, length);
//使用 StringCoding.decode 将字节数组按照必定范围解码为字符串,从offset开始截取length个长度
this.value = StringCoding.decode(charsetName, bytes, offset, length);
}
复制代码
Charset
类型public String(byte bytes[], int offset, int length, Charset charset) {
if (charset == null)
throw new NullPointerException("charset");
checkBounds(bytes, offset, length);
this.value = StringCoding.decode(charset, bytes, offset, length);
}
复制代码
public String(byte bytes[], String charsetName) throws UnsupportedEncodingException {
this(bytes, 0, bytes.length, charsetName);
}
复制代码
public String(byte bytes[], Charset charset) {
this(bytes, 0, bytes.length, charset);
}
复制代码
String
实例,与第六个构造器不一样的是,使用系统默认字符编码public String(byte bytes[], int offset, int length) {
//检查索引是否越界
checkBounds(bytes, offset, length);
//使用系统默认字符编码解码字节数组为字符数组
this.value = StringCoding.decode(bytes, offset, length);
}
复制代码
public String(byte bytes[]) {
this(bytes, 0, bytes.length);
}
复制代码
StringBuffer
构建成一个新的String
,比较特别的就是这个方法有synchronized
锁 同一时间只容许一个线程对这个 buffer
构建成String
对象,是线程安全的public String(StringBuffer buffer) {
//对当前 StringBuffer 对象加同步锁
synchronized(buffer) {
//拷贝 StringBuffer 字符数组给当前实例的字符数组
this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
}
}
复制代码
StringBuilder
构建成一个新的String
,与第12个构造器不一样的是,此构造器不是线程安全的public String(StringBuilder builder) {
this.value = Arrays.copyOf(builder.getValue(), builder.length());
}
复制代码
获取字符串长度,其实是获取字符数组长度
public int length() {
return value.length;
}
复制代码
判断字符串是否为空,其实是盼复字符数组长度是否为0
public boolean isEmpty() {
return value.length == 0;
}
复制代码
根据索引参数获取字符
public char charAt(int index) {
//索引小于0或者索引大于字符数组长度,则抛出越界异常
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
//返回字符数组指定位置字符
return value[index];
}
复制代码
根据索引参数获取指定字符ASSIC码(int类型)
public int codePointAt(int index) {
//索引小于0或者索引大于字符数组长度,则抛出越界异
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
//返回索引位置指定字符ASSIC码(int类型)
return Character.codePointAtImpl(value, index, value.length);
}
复制代码
返回index位置元素的前一个元素的ASSIC码(int型)
public int codePointBefore(int index) {
//得到index前一个元素的索引位置
int i = index - 1;
//检查索引是否越界
if ((i < 0) || (i >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return Character.codePointBeforeImpl(value, index, 0);
}
复制代码
方法返回的是代码点个数,是实际上的字符个数,功能相似于length(),对于正常的String来讲,length方法和codePointCount没有区别,都是返回字符个数。但当String是Unicode类型时则有区别了。例如:String str = “/uD835/uDD6B” (即便 'Z' ), length() = 2 ,codePointCount() = 1
public int codePointCount(int beginIndex, int endIndex) {
if (beginIndex < 0 || endIndex > value.length || beginIndex > endIndex) {
throw new IndexOutOfBoundsException();
}
return Character.codePointCountImpl(value, beginIndex, endIndex - beginIndex);
}
复制代码
也是相对Unicode字符集而言的,从index索引位置算起,偏移codePointOffset个位置,返回偏移后的位置是多少,例如,index = 2 ,codePointOffset = 3 ,maybe返回 5
public int offsetByCodePoints(int index, int codePointOffset) {
if (index < 0 || index > value.length) {
throw new IndexOutOfBoundsException();
}
return Character.offsetByCodePointsImpl(value, 0, value.length,
index, codePointOffset);
}
复制代码
这是一个不对外的方法,是给String内部调用的,由于它是没有访问修饰符的,只容许同一包下的类访问 参数:dst[]是目标数组,dstBegin是目标数组的偏移量,既要复制过去的起始位置(从目标数组的什么位置覆盖) 做用就是将String的字符数组value整个复制到dst字符数组中,在dst数组的dstBegin位置开始拷贝
void getChars(char dst[], int dstBegin) {
System.arraycopy(value, 0, dst, dstBegin, value.length);
}
复制代码
获得char字符数组,原理是getChars() 方法将一个字符串的字符复制到目标字符数组中。 参数:srcBegin是原始字符串的起始位置,srcEnd是原始字符串要复制的字符末尾的后一个位置(既复制区域不包括srcEnd) dst[]是目标字符数组,dstBegin是目标字符的复制偏移量,复制的字符从目标字符数组的dstBegin位置开始覆盖。
public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
if (srcBegin < 0) {
throw new StringIndexOutOfBoundsException(srcBegin);
}
if (srcEnd > value.length) {
throw new StringIndexOutOfBoundsException(srcEnd);
}
if (srcBegin > srcEnd) {
throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
}
System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
}
复制代码
获取字符串的字节数组,按照指定字符编码将字符串解码为字节数组
public byte[] getBytes(String charsetName)
throws UnsupportedEncodingException {
if (charsetName == null) throw new NullPointerException();
return StringCoding.encode(charsetName, value, 0, value.length);
}
复制代码
获取字符串的字节数组,按照指定字符编码将字符串解码为字节数组
public byte[] getBytes(Charset charset) {
if (charset == null) throw new NullPointerException();
return StringCoding.encode(charset, value, 0, value.length);
}
复制代码
获取字符串的字节数组,按照系统默认字符编码将字符串解码为字节数组
public byte[] getBytes() {
return StringCoding.encode(value, 0, value.length);
}
复制代码
String
被 修饰符 final
修饰,是没法被继承的,不可变类String
实现 Serializable
接口,能够被序列化String
实现 Comparable
接口,能够用于比较大小String
实现 CharSequence
接口,表示一直有序字符序列,实现了通用的字符序列方法String
是一个字符序列,内部数据结构实际上是一个字符数组,全部的操做方法都是围绕这个字符数组的操做。String
中频繁使用到了 System
类的 arraycopy
方法,目的是拷贝字符数组因为篇幅缘由,String
第一篇总结先到这里,后续部分将写另外写一遍记录,会第一时间推送公众号【张少林同窗】,欢迎关注!