Java String 源码分析
定义
Java 8 中 String 源码
代码语言:javascript复制public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {...}
String 是final 类型不能被继承,同时实现了 java.io.serializable Comparable charSequence 三个接口。
String类 官方的说法是:
String 字符串常量,在实例化后不能被修改,但是字符串缓冲区支持可变的字符串,因为缓存区里面的不可变字符串对象可被共享。
属性
代码语言:javascript复制/** The value is used for character storage. */
private final char value[];
一个字符数组,并且是 final 类的,用于存储字符串内容。final 字符数组可看出,String 经过定义后,不能被修改。
可能会有疑问,String 初始化化之后,可以被修改啊
代码语言:javascript复制String str = "hello";
str = "World"
这里的复制并不是对 str 内容的修改,而是 str 指向了新的字符串。
代码语言:javascript复制/** use serialVersionUID from JDK 1.0.2 for interoperability */
private static final long serialVersionUID = -6849794470754667710L;
String 实现了 Serializable 接口,支持序列化和反序列化支持,Java 序列化机制通过在运行时判断 serialVersionUID 来验证版本是否一致,在进行反序列时, JVM 会把传来的字节流的 SerialVersial 与本地类中的 serialVersionUID 进行比较,如果相同就认为是一致的,可以进行反序列化,否则抛出不一致 InvalidCastException 异常。
构造方法
空构造方法
代码语言:javascript复制/**
* 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;
}
该构造方法,指挥创建空的字符串,构造方法不必要的字符串对象是不变的。
不建议使用如下方式创建对象:会产生空字符串。
代码语言:javascript复制String str = new String();
str = "Hello";
使用字符串类型对象初始化
代码语言:javascript复制 /**
* 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 的值。
使用字符串来构造
代码语言:javascript复制 /**
* 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);
}
代码语言:javascript复制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 进行相互转换。
代码语言:javascript复制public String(byte bytes[], int offset, int length) {
checkBounds(bytes, offset, length);
this.value = StringCoding.decode(bytes, offset, length);
}
如果使用 byte[] 数组构造 String 的时候,如果没有指明使用的字符集的话,那么StringCoding 的 decode 方法
代码语言:javascript复制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 。
代码语言:javascript复制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()方法
代码语言:javascript复制@Override
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}
SringBuffer 的 toString() 方法
代码语言:javascript复制public synchronized String toString() {
if (toStringCache == null) {
toStringCache = Arrays.copyOfRange(value, 0, count);
}
return new String(toStringCache, true);
}
主要方法
substring
代码语言:javascript复制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);
}
假设一个方法从某个地方取得了一个很长的字符串,然后对其提取其中的一个小段内容,代码如下:
代码语言:javascript复制String longStr = "....averylongstring";
String partStr= longStr.substring(10,30);
longStr 是临时的,要用的就是 partStr, 长度截取20个字符,但是他的内部数组是 longStr 共享的,虽然 longStr 可以被回收,但是内部数组不能释放。这样就出现了内存泄漏。Java 8 中采用的是 Array.copy 方法,避免了这个问题
代码语言:javascript复制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() 返回字符串长度
代码语言:javascript复制public int length() {
return value.length;
}
isEmpty() 返回字符为空
代码语言:javascript复制public boolean isEmpty() {
return value.length == 0;
}
chartAt 方法
charAt(int index) 返回字符串中第 index 1 个字符
代码语言:javascript复制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 方法。
代码语言:javascript复制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) 方法。
代码语言:javascript复制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 方法
代码语言:javascript复制 public boolean equalsIgnoreCase(String anotherString) {
return (this == anotherString) ? true
: (anotherString != null)
&& (anotherString.value.length == value.length)
&& regionMatches(true, 0, anotherString, 0, value.length);
}
hashcode
代码语言:javascript复制 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 区别
代码语言:javascript复制Sring replaceFirst(String replacement)
String replaceAll(String regex,String replacement)
String replace(CharSequence target,CharSequence replacement)
replace 的参数是 char,支持字符的替换,也支持字符串的替换 replaceAll 和 replaceFirst 的参数是 regex ,基于正则表达式替换 replaceAll("%d",“”) 把一个字符串所有的数字字符都换成 replace 方法
代码语言:javascript复制 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 对字符串的支持
代码语言:javascript复制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 编译之后
代码语言:javascript复制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
- 如果只需要创建一个字符串,可以使用引号的方式,如果在堆中创建一个新的对象,可以选择构造函数。
欢迎关注公众号:程序员开发者社区
微信号:程序员开发者社区
博客:CSDN 王小明
关注我们,了解更多