Java String 源码分析
定义
Java 8 中 String 源码
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {...}
String 是final 类型不能被继承,同时实现了 java.io.serializable Comparable charSequence 三个接口。
String类 官方的说法是:
String 字符串常量,在实例化后不能被修改,但是字符串缓冲区支持可变的字符串,因为缓存区里面的不可变字符串对象可被共享。
属性
/** The value is used for character storage. */ private final char value[];
一个字符数组,并且是 final 类的,用于存储字符串内容。final 字符数组可看出,String 经过定义后,不能被修改。
可能会有疑问,String 初始化化之后,可以被修改啊
String str = "hello"; str = "World"
这里的复制并不是对 str 内容的修改,而是 str 指向了新的字符串。
/** use serialVersionUID from JDK 1.0.2 for interoperability */ private static final long serialVersionUID = -6849794470754667710L;
String 实现了 Serializable 接口,支持序列化和反序列化支持,Java 序列化机制通过在运行时判断 serialVersionUID 来验证版本是否一致,在进行反序列时, JVM 会把传来的字节流的 SerialVersial 与本地类中的 serialVersionUID 进行比较,如果相同就认为是一致的,可以进行反序列化,否则抛出不一致 InvalidCastException 异常。
构造方法
空构造方法
/** * Initializes a newly created {@code String} object so that it represents * an empty character sequence. Note that use of this constructor is * unnecessary since Strings are immutable. */ public String() { this.value = "".value; }
该构造方法,指挥创建空的字符串,构造方法不必要的字符串对象是不变的。
不建议使用如下方式创建对象:会产生空字符串。
String str = new String(); str = "Hello";
使用字符串类型对象初始化
/** * Initializes a newly created {@code String} object so that it represents * the same sequence of characters as the argument; in other words, the * newly created string is a copy of the argument string. Unless an * explicit copy of {@code original} is needed, use of this constructor is * unnecessary since Strings are immutable. * * @param original * A {@code String} */ public String(String original) { this.value = original.value; this.hash = original.hash; }
这里将源 String 中的 value 和 hash 两个属性直接赋值给目标 String . 因为 String 一旦定义之后是不可变的,所以也就不用担心,改变源 String 的值会影响到目标 String 的值。
使用字符串来构造
/** * Allocates a new {@code String} so that it represents the sequence of * characters currently contained in the character array argument. The * contents of the character array are copied; subsequent modification of * the character array does not affect the newly created string. * * @param value * The initial value of the string */ public String(char value[]) { this.value = Arrays.copyOf(value, 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); }
使用字符数组构建字符串时,会用到 Arrays.copyOf 方法,或者使用 Arrays.copyOfRange() 方法,这两个方法会将原有的字符串中的字符串数组中的内容赋值到 String 中的字符数组中,会创建一个新的字符串对象。随后修改字符数组不影响新创建的字符串。
使用字节数组来构建 String
Java 中,String 实例中报错一个字符数组,char[] 字符数组时以 unicode 码来存储的。
byte 是网络传输或者存储的序列化形式,在很多传输和存储过程中将 byte[] 数组和 String 进行相互转换。
public String(byte bytes[], int offset, int length) { checkBounds(bytes, offset, length); this.value = StringCoding.decode(bytes, offset, length); }
如果使用 byte[] 数组构造 String 的时候,如果没有指明使用的字符集的话,那么StringCoding 的 decode 方法
public String(byte bytes[]) { this(bytes, 0, bytes.length); } public String(byte bytes[], String charsetName) throws UnsupportedEncodingException { this(bytes, 0, bytes.length, charsetName); } 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); }
使用 byte[] 构造 String , 如果没有指定编码格式,默认使用 ISO-8895-1 编码。
使用 StringBuffer 和 StringBuilder 构造一个 String 。
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()); }
关于效率问题,Java 官方文档中有提到 StringBuilder 的 toString 方法会更快一些,原因是 StringBuffer 中的toString 方法是 synchronized,有同步的开销。
StringBuilder 的 toString()方法
@Override public String toString() { // Create a copy, don't share the array return new String(value, 0, count); }
SringBuffer 的 toString() 方法
public synchronized String toString() { if (toStringCache == null) { toStringCache = Arrays.copyOfRange(value, 0, count); } return new String(toStringCache, true); }
主要方法
substring
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); }
假设一个方法从某个地方取得了一个很长的字符串,然后对其提取其中的一个小段内容,代码如下:
String longStr = "....averylongstring"; String partStr= longStr.substring(10,30);
longStr 是临时的,要用的就是 partStr, 长度截取20个字符,但是他的内部数组是 longStr 共享的,虽然 longStr 可以被回收,但是内部数组不能释放。这样就出现了内存泄漏。Java 8 中采用的是 Array.copy 方法,避免了这个问题
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() 返回字符串长度
public int length() { return value.length; }
isEmpty() 返回字符为空
public boolean isEmpty() { return value.length == 0; }
chartAt 方法
charAt(int index) 返回字符串中第 index+1 个字符
public char charAt(int index) { if ((index < 0) || (index >= value.length)) { throw new StringIndexOutOfBoundsException(index); } return value[index]; }
char[] toCharArray() 转化为字符数组
trim() 去掉两端空格
toUpperCase() 转换为大写
toLowerCase() 转换为小写
String concat(String str) //拼接字符串
String replace(char oldChar, char newChar) //将字符串中的oldChar字符换成newChar字符
boolean matches(String regex) //判断字符串是否匹配给定的regex正则表达式
boolean contains(CharSequence s) //判断字符串是否包含字符序列s
String[] split(String regex, int limit) 按照字符regex将字符串分成limit份
String[] split(String regex) 按照字符regex将字符串分段
getBytes
在创建 String 的时候,可以使用 byte[] 数组,将一个字节数组转换成字符串,同样,可以将一个字符串转换成字节数组,那么 String 提供了多种重载 getBytes 方法。
String s = "Hello World!"; byte[] bytes = s.getBytes();
上面这段代码没有指定编码方式,在该方法对字符串进行编码的时候默认使用系统编码,中文操作系统中可能会使用 GBK,英文操作系统中使用 ISO-8859-1.
boolean equals(ObjectanObject);
boolean contentEquals(StringBuffersb);
boolean contentEquals(CharSequencecs);
boolean equalsIgnoreCase(StringanotherString);
int compareTo(StringanotherString);
int compareToIgnoreCase(Stringstr);
boolean regionMatches(inttoffset,Stringother,intooffset,intlen) //局部匹配
boolean regionMatches(booleanignoreCase,inttoffset,Stringother,intooffset,intlen) //局部匹配
contentEquals
StringBuffer 考虑线程安全问题,加锁之后再调用 contentEquals(CharSequence sb) 方法。
public boolean contentEquals(CharSequence cs) { // Argument is a StringBuffer, StringBuilder if (cs instanceof AbstractStringBuilder) { if (cs instanceof StringBuffer) { synchronized(cs) { return nonSyncContentEquals((AbstractStringBuilder)cs); } } else { return nonSyncContentEquals((AbstractStringBuilder)cs); } } // Argument is a String if (cs instanceof String) { return equals(cs); } // Argument is a generic CharSequence char v1[] = value; int n = v1.length; if (n != cs.length()) { return false; } for (int i = 0; i < n; i++) { if (v1[i] != cs.charAt(i)) { return false; } } return true; }
equalsIgnoreCase 方法
public boolean equalsIgnoreCase(String anotherString) { return (this == anotherString) ? true : (anotherString != null) && (anotherString.value.length == value.length) && regionMatches(true, 0, anotherString, 0, value.length); }
hashcode
public int hashCode() { 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 = h; } return h; }
数学公式:s[0]*31^(n-1) + s[1]*31^(n-2) + … + s[n-1]
replaceFirst、replaceAll replace 区别
Sring replaceFirst(String replacement) String replaceAll(String regex,String replacement) String replace(CharSequence target,CharSequence replacement)
replace 的参数是 char,支持字符的替换,也支持字符串的替换 replaceAll 和 replaceFirst 的参数是 regex ,基于正则表达式替换 replaceAll("%d",“”) 把一个字符串所有的数字字符都换成
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; }
intern 方法
方法返回一个字符串对象的内部引用。
String 类维护一个初始为空的字符串的常量池,当intern 被调用时,如果对象池中已经包含这一个相等的字符串则返回对象池中的实例,否则添加字符串到对象池并返回字符串引用。
switch 对字符串的支持
public class switchDemoString { public static void main(String[] args) { String str = "world"; switch (str) { case "hello": System.out.println("hello"); break; case "world": System.out.println("world"); break; default: break; } } }
javap 编译之后
public static void main(String args[]) { String str = "world"; String s; switch ((s = str).hashCode()) { case 99162322: if (s.equals("hello")) System.out.println("hello"); break; case 113318802: if (s.equals("world")) System.out.println("world"); break; default: break; } }
字符串的switch 是通过equals 和 hashCode 方法来实现的,switch 支持整型 byte ,short char int, 也可以看到 hashcode 返回的是int 。
为啥 String 定义成 final 的?
- 为了线程安全
字符串不可变,所以是多线程安全,同一个字符串实例可以被多个线程共享,这样不会因为线程安全问题而使用同步,字符串便是线程安全的。 - 为实现 String 可以创建 hashcode 不可变
字符串不可变,在创建的时候 hashCode 被缓存了,不㔿重新机损这样可以使得字符串作为 Map的键,字符串处理快。 - 为了实现字符串常量池
字符串不可变,可以放在字符串常量池中,因为不同的字符串遍历都可以指向池中的同一个字符串,如果字符串可变,那么 String intern 不能实现。
总结
- string 对象在内存对中被创建后,就无法修改 - 如果需要一个可修改的字符串,应该使用 StringBuffer 或者 StringBuilder
- 如果只需要创建一个字符串,可以使用引号的方式,如果在堆中创建一个新的对象,可以选择构造函数。