跟我一起读源码(java)—String、StringBuffer、StringBuilder

写在前面:之所以写这个系列,是因为实习期间相对还算不太忙,想看看源码,学习学习,仅此而已,写的不对的地方,还请大家多多指正(博主用的版本是1.8.0)。。。

我不知道如何学习源码,网上源码谈的比较多的都是一些比较牛逼的人,而我,仅仅一个小白,太复杂的注定看不懂,所以只能找自己的方法去学习,想用自己的路线慢慢去消化、了解,我的办法很直白,就从我们最初学习java开始一步步深入去了解我们一路上所接触到的源码,好了,毋庸置疑,我们大部分人接触到的第一个java小例子估计就是下面这段小程序了吧:

public class Main {
public static void main(String[] args) {
System.out.println("Hello,word!");
}
}

这段代码大家都很熟悉,也没有多少说的,那么我想大家也肯定知道我要先从谁说起了,对,没错,就是String类!

String类:
首先看看String类的定义:

public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {...}

这一段很简洁的给我们交待了String类几个比较重要的特性:
1)String类是final的,也就是说String类不允许被继承、不能被修改(在Java中,被 final类型修饰的类不允许被其他类继承,被 final修饰的变量不允许被修改)
2)String类实现了三个接口:Serializable Comparable CharSequence,展开来说,
a、实现了IO流的Serializable接口,表明String类的对象可被序列化和反序列化。Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体(类)的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常(InvalidCastException),所以在开发中,我们一定记得显式的声明UID。。。。
b、实现Comparable接口,表明String类的对象进行整体排序,可以进行自定义的字符串比较,定义的泛型为String类,说明给Comparable的数据类型只能是String类
c、实现CharSequence接口,用于表明char值得一个可读序列,此接口对许多不同种类的 char 序列提供统一的只读访问。

往下看是String类的一些属性:

/** The value is used for character storage. */
private final char value[]; // char[] 用来存放String类型的字符数组,注意的是这里value是final的,所以赋值后不能被修改,这是字符串不能被修改的原因!

/** Cache the hash code for the string */
private int hash; // Default to 0 表示String字符的hash值 默认为0

/** use serialVersionUID from JDK 1.0.2 for interoperability */
private static final long serialVersionUID = -6849794470754667710L; // 序列化serialVersionUID

紧接着,我们可以看到String类的构造方法:

//默认无参构造方法,调用这个构造方法可以构造一个空的字符串对象 没有多大意义(因为字符串值是final的,不可变)
public String() {
this.value = "".value;
}
//有参构造方法,接收一个字符串对象orginal作为参数,并用它初始化新创建的字符串对象,使新创建的字符串是该参数字符串的副本,除非需要original的显式副本,否则没必要使用此构造函数。
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
//使用一个字符数组创建一个新的字符串以表示字符数组参数中当前包含的字符序列,字符数组value的内容已被复制到字符串对象中,所以后续对字符数组的修改不会影响到已经创建好的字符串。
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
//该构造方法会分配一个新字符串,初始值取自字符数组value,offset参数是子数组第一个字符的索引,count参数指定子数组的长度。当count=0且offset<=value.length时,会返回一个空的字符串。
public String(char value[], int offset, int count) {
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
if (count <= 0) {
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
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);
}
this.value = Arrays.copyOfRange(value, offset, offset+count);
}
//这个方法只是单纯的进行边界检查,length、offset不能小于零,而且offset+lenght不能超出字节数组的长度。
/* Common private utility method used to bounds check the byte array
* and requested offset & length values used by the String(byte[],..)
* constructors.
*/
private static void checkBounds(byte[] bytes, int offset, int length) {
if (length < 0)
throw new StringIndexOutOfBoundsException(length);
if (offset < 0)
throw new StringIndexOutOfBoundsException(offset);
if (offset > bytes.length - length)
throw new StringIndexOutOfBoundsException(offset + length);
}
//这两个构造函数使用指定的字符集解码字节数组,构造一个新的字符串。解码的字符集可以使用字符集名指定或者直接将字符集传入。注意,如果给定的字符集无效,构造函数的行为没有指定。 
public String(byte bytes[], int offset, int length, String charsetName)
throws UnsupportedEncodingException {
if (charsetName == null)
throw new NullPointerException("charsetName");
checkBounds(bytes, offset, length);
this.value = StringCoding.decode(charsetName, bytes, offset, length);
}

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);
}
//这个decode方法属于Stringcoding类 使用的解码的字符集就是我们指定的charsetName或者charset。 我们在使用byte[]构造String的时候,如果没有指明解码使用的字符集的话,那么StringCoding的decode方法首先调用系统的默认编码格式,如果没有指定编码格式则默认使用ISO-8859-1编码格式进行编码操作。
static char[] decode(String charsetName, byte[] ba, int off, int len)
throws UnsupportedEncodingException
{
StringDecoder sd = deref(decoder);
String csn = (charsetName == null) ? "ISO-8859-1" : charsetName;
if ((sd == null) || !(csn.equals(sd.requestedCharsetName())
|| csn.equals(sd.charsetName()))) {
sd = null;
try {
Charset cs = lookupCharset(csn);
if (cs != null)
sd = new StringDecoder(cs, csn);
} catch (IllegalCharsetNameException x) {}
if (sd == null)
throw new UnsupportedEncodingException(csn);
set(decoder, sd);
}
return sd.decode(ba, off, len);
}
//除了前面所示的,可以从字符串、字符数组、代码点数组、字节数组构造字符串外,也可以使用StringBuffer和StringBuilder构造字符串。
public String(StringBuffer buffer) {
synchronized(buffer) {
this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
}
}

public String(StringBuilder builder) {
this.value = Arrays.copyOf(builder.getValue(), builder.length());
}

​这里提到StringBuffer,我们进入这个类可以看到如下主要代码,具体不多说,值得一提的是StringBuffer给每个方法上都利用Synchronized关键字进行了同步操作,其中StringBuilder类和StringBuffer大致一样,只是没有加Synchronized关键字,所以没有实现同步,在单线程时效率比较高所以这里不重复书写代码了!

public final class StringBuffer extends AbstractStringBuilder
implements java.io.Serializable, CharSequence{...}
//默认为16个字符
public StringBuffer() {
super(16);
}
//以指定capacity大小初始化
public StringBuffer(int capacity) {
super(capacity);
}
【 这里super 调用父类进行初始化 这里是按照指定容量创建字符数组  其他的不多说 参照这个即可。
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}
】
//初始化为内容为指定str,初始化容量为str的大小+16
public StringBuffer(String str) {
super(str.length() + 16);
append(str);
}
//以下操作方法都利用了Synchronized关键字
@Override
public synchronized int length() {
return count;
}
@Override
public synchronized int capacity() {
return value.length;
}
@Override
public synchronized void ensureCapacity(int minimumCapacity) {
super.ensureCapacity(minimumCapacity);
}
@Override
public synchronized void trimToSize() {
super.trimToSize();
}
/**
* @throws IndexOutOfBoundsException {@inheritDoc}
* @see #length()
*/
@Override
public synchronized void setLength(int newLength) {
toStringCache = null;
super.setLength(newLength);
}
/**
* @throws IndexOutOfBoundsException {@inheritDoc}
* @see #length()
*/
@Override
public synchronized char charAt(int index) {
if ((index < 0) || (index >= count))
throw new StringIndexOutOfBoundsException(index);
return value[index];
}

接下来还需要看下StringBuffer中的append方法,这里就举其中一个方法为例,其他重载方法还有Object、String、AbstractStringBuilder等类型的,具体的话,大家自行查看,代码比较简单 就不一一解释了 大家知道怎么添加的就好,下面示例代码是按照在类中调用顺序依次查看的。。。

public synchronized StringBuffer append(Object obj) {
toStringCache = null;
super.append(String.valueOf(obj));
return this;
}
//StringBuffer继承自AbstractStringBuilder类
abstract class AbstractStringBuilder implements Appendable, CharSequence {
char[] value;//这里我们看到这个字符数组value没有背final修饰 与String类中不同 所以可以修改他们的引用变量的值 即可以引用到新的数组对象 所以其对象是可变的 就是从这里得出的
int count;//这个count用来记录字符个数
.... }
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
//这里如果count+len>len 则进行扩容,所以指定合适大小很重要,因为能减少扩容次数(要知道创建字符数组对象和复制都是要开销的!)
private void ensureCapacityInternal(int minimumCapacity) {
if (minimumCapacity - value.length > 0) {
value = Arrays.copyOf(value,
newCapacity(minimumCapacity));
}
}
//Arrays.copyOf(value,newCapacity)复制指定数组,截取或用null字符填充以使副本具有指定长度。
public static char[] copyOf(char[] original, int newLength) {
char[] copy = new char[newLength];
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}

public synchronized void getChars(int srcBegin, int srcEnd, char[] dst,
int dstBegin)
{
super.getChars(srcBegin, srcEnd, dst, dstBegin);
}
//将字符从此字符串复制到目标字符数组dst中,第一二个参数截取要添加字符串的长度,第三个为目标字符数组,第四个为字符串要添加到数组的开始位置
public void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin)
{
if (srcBegin < 0)
throw new StringIndexOutOfBoundsException(srcBegin);
if ((srcEnd < 0) || (srcEnd > count))
throw new StringIndexOutOfBoundsException(srcEnd);
if (srcBegin > srcEnd)
throw new StringIndexOutOfBoundsException("srcBegin > srcEnd");
System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
}

PS:这里顺带总结下String、StringBuffer、StringBuilder:
1)可变与不可变
  String类中使用字符数组private final char value[]保存字符串,因为有“final”修饰符,所以可以知道string对象是不可变的。
  StringBuilder与StringBuffer都继承自AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存字符串,char[] value;可知这两种对象都是可变的。
2)是否多线程安全
  String中的对象是不可变的,也就可以理解为常量,显然线程安全。
  AbstractStringBuilder是StringBuilder与StringBuffer的公共父类,定义了一些字符串基本操作,如append、insert、indexOf等公共方法。
  StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。
  StringBuilder并没有对方法进行加同步锁,所以是非线程安全的。
3)StringBuilder与StringBuffer共同点
  StringBuilder与StringBuffer有公共父类AbstractStringBuilder(抽象类)。
  StringBuilder、StringBuffer的方法都会调用AbstractStringBuilder中的公共方法,如super.append(…)。只是StringBuffer会在方法上加synchronized关键字,进行同步。
  PS:如果程序非多线程,那么StringBuilder效率高于StringBuffer,三者在执行速度方面的比较:StringBuilder > StringBuffer > String
具体总结可以参考:点这里查看详细总结

接下来继续回到String类,读到length()方法:

public int length() { //length()方法返回字符串的长度,即字符串中Unicode代码单元的数量。
return value.length;
}
public boolean isEmpty() {  // 判断字符串是否为空。
return value.length == 0;
}

//返回指定索引处的字符,索引范围为从0到lenght()-1。如果索引指定的char值是代理项,则返回代理项值。
public char charAt(int index) {
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return value[index];
}

public int codePointAt(int index) {  // 返回索引index处的代码点
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return Character.codePointAtImpl(value, index, value.length);
}

intern 方法,
public native String intern();
在intern方法中看到,这个方法是一个 native 的方法,但注释写的非常明了。“如果常量池中存在当前字符串, 就会直接返回当前字符串. 如果常量池中没有此字符串, 会将此字符串放入常量池中后, 再返回”。

还有例如getChar()方法之前在SringBuffer中有提及,就不多加解释了。。。
另外还有几个getBytes()方,平时用的不多也不贴出来了,这三个方法都是将String编码为byte序列存储到byte数组后返回,都是调用StringCode.encode方法实现。方法的不同之处是字符集指定方式,分别是字符集名字、字符集和平台默认字符集,大家自行查看源码即可。。。
​最后我们着重来看几个常用的方法:

equals()方法:

public boolean equals(Object anObject) {
if (this == anObject) { //如果是同一个对象,直接返回true
return true;
}
if (anObject instanceof String) { //如果是一个String类的实例 则继续向下比较
String anotherString = (String)anObject; // 转换为String类型
int n = value.length;
if (n == anotherString.value.length) { // 如果长度相等 再向下比较
char v1[] = value; // 转化为字符数组
char v2[] = anotherString.value;// 转换为字符数组 对比相应位置的元素
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i]) //比较每个字符是否相等
return false;
i++;
}
return true;//只有类型 长度 元素值都相等的情况才返回true
}
}
return false;
}
另外有一个不管大小写的比较:
public boolean equalsIgnoreCase(String anotherString) {
return (this == anotherString) ? true
: (anotherString != null)
&& (anotherString.value.length == value.length)
&& regionMatches(true, 0, anotherString, 0, value.length);
}

最后再读一个compareTo()方法,String实现了Comparable接口:

public int compareTo(String anotherString) {
int len1 = value.length;//当前字符串对象的长度
int len2 = anotherString.value.length;//当前参数字符串对象的长度
int lim = Math.min(len1, len2);//设置最大比较次数
char v1[] = value;
char v2[] = anotherString.value;//转换为字符数组

int k = 0;//从第一位开始比较
while (k < lim) {//小于最大比较次数 则继续比较
char c1 = v1[k];
char c2 = v2[k];
if (c1 != c2) {
return c1 - c2;//返回Unicode值的差
}
k++;
}
return len1 - len2;//当前n个字符都相同时,返回两个字符串的长度的差  
}

Hashcode()方法

public int hashCode() { //采用乘法hash算法,字符串相同hash值一定相同,hash值相同,不一定字符串相同
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;

for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i]; // 每一次hash值*31+该字符
}
hash = h;
}
return h;
}

这里顺道总结下equals和hashcode:
Java中equals()和hashCode()有一个契约:
1. 如果两个对象相等的话,它们的hashcode必须相等;
2. 但如果两个对象的hashcode相等的话,这两个对象不一定相等。
3.
讲到这,突然想起另外几个方法:replace和substring ,另外还有几个简单的不列出源代码了:split、toString、format、indexof之类的
replace:旧值替换成新值。。。

public String replace(char oldChar, char newChar) {
if (oldChar != newChar) {
int len = value.length;
int i = -1;
char[] val = value; /* avoid getfield opcode */

while (++i < len) {
if (val[i] == oldChar) {
break;
}
}
if (i < len) {
char buf[] = new char[len];
for (int j = 0; j < i; j++) {
buf[j] = val[j];
}
while (i < len) {
char c = val[i];
buf[i] = (c == oldChar) ? newChar : c;
i++;
}
return new String(buf, true);
}
}
return this;
}
// 这个是支持正则表达式替换的
public String replaceAll(String regex, String replacement) {
return Pattern.compile(regex).matcher(this).replaceAll(replacement);
}

str=str.substring(int beginIndex);截取掉str从首字母起长度为beginIndex的字符串,将剩余字符串赋值给str;
str=str.substring(int beginIndex,int endIndex);截取str中从beginIndex开始至endIndex结束时的字符串,并将其赋值给str,这里尤其要记住含前不含后;

public String substring(int beginIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
int subLen = value.length - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}

public String substring(int beginIndex, int endIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > value.length) {
throw new StringIndexOutOfBoundsException(endIndex);
}
int subLen = endIndex - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return ((beginIndex == 0) && (endIndex == value.length)) ? this
: new String(value, beginIndex, subLen);
} 

本来通过这个例子是还要把System类和println方法也拿出来讲讲的,由于事情缘故,就先写这么些,回头拿这个例子接着读。。。。。
代码格式有点蹩脚 不处理了。。。。
——by白附子(小仇哥)

猜你喜欢

转载自blog.csdn.net/xiaozhouchou/article/details/78686543