国内最全IT社区平台 联系我们 | 收藏本站
华晨云阿里云优惠2
您当前位置:首页 > php开源 > php教程 > Java String源码解析

Java String源码解析

来源:程序员人生   发布时间:2016-06-16 08:52:31 阅读次数:2055次

String类概要

  • 所有的字符串字面量都属于String类,String对象创建后不可改变,因此可以缓存同享,StringBuilder,StringBuffer是可变的实现
  • String类提供了操作字符序列中单个字符的方法,比如有比较字符串,搜索字符串等
  • Java语言提供了对字符串联接运算符的特别支持(+),该符号也可用于将其他类型转换成字符串。
  • 字符串的连接实际上是通过StringBuffer或StringBuilder的append()方法来实现的
  • 1般情况下,传递1个空参数在这类构造函数或方法会致使NullPointerException异常被抛出。
  • String表示1个字符串通过UTF⑴6(unicode)格式,补充字符通过代理对表示。索引值参考字符编码单元,所以补充字符在String中占两个位置。

String是不可变的

  • String是常量,1旦被创建就不可被改变,因此可以用来同享

从String的奇异现象讲起

String是不是相等

==判断的是对象的内存起始地址是不是相同,equals判断自定义的语义是不是相同

  • JVM为了提高内存效力,将所有不可变的字符串缓存在常量池中,当有新的不可变的字符串需要创建时,如果常量池中存在相等的字符串就直接将援用指向已有的字符串常量,而不会创建新对象
  • new创建的对象存储在堆内存,不可能与常量区的对象具有相同地址
  • 直接用字面量初始化String要比用new 关键字创建String对象效力更高
public class Demo { public static void main(String[] args) throws Exception { String s = "abc"; String s1 = "abc"; String s2 = "a" + "bc"; final String str1 = "a"; final String str2 = "bc"; String s3 = str1 + str2; String s4 = new String("abc"); System.out.println(s == s1); System.out.println(s == s2); System.out.println(s == s3); System.out.println(s == s4); } } //结果:true true true false

为何String不可变

final修饰变量,如果是基本类型那末内容运行期间不可变,如果是援用类型那末援用的对象(包括数组)运行期地址不可变,但是对象(数组)的内容是可以改变的

  • final只是保证value不会指向其他的数组,但不保证数组内容不可修改
  • private属性保证了不可以在类外访问数组,也就不能改变其内容
  • String内部没有改变value内容的函数,所以String就不可变了
  • String声明为final杜绝了通过继承的方法添加新的函数
  • 基于数组的构造方法,会拷贝数组元素,从而避免了通过外部援用修改value的情况
  • 用String构造其他可变对象时,触及的数组只是返回的数组的拷贝而不是原数组,例如 new StringBuilder(str),会把str数组进行拷贝后传递给StringBuilder而不是传递原数组

固然只要类库设计人愿意,只要增加1个类似的setCharAt(index)的接口,String就变成可变的了

private final char value[]; private int hash; // Default to 0 public String(char value[]) { this.value = Arrays.copyOf(value, value.length); }

通过反射改变String

  • final 只在编译器有效,在运行期间无效,因此可以通过反射改变value援用的对象
  • s与str始终具有相同的内存地址,反射改变了s的内容,并没有新创建对象
  • s 与 s1对应常量池中的两个对象,所以即使通过反射修改了s的内容,他们两个的内存地址还是不同的
public class Demo { public static void main(String[] args) throws Exception { String s = "abc"; String str = s; String s1 = "bbb"; System.out.println(str == s); Field f = s.getClass().getDeclaredField("value"); f.setAccessible(true); f.set(s, new char[]{'b', 'b', 'b'}); System.out.println(str + " " + s); System.out.println(s == str); System.out.println(s == s1); } } //结果:bbb bbb true false

String的HashCode

s的内容改变了但是hashCode值并没有改变,虽然s与s1的内容是相同的但是他们hashCode值其实不相同

  • Object的hashCode方法返回的是16进制内存地址,String类重写了hashCode的,hashCode值的计算是基于字符串内容的
  • String的hashCode值初始为0,由于String是不可变的,当第1次运行完hashCode方法后String类对HashCode值进行了缓存,下1次在调用时直接返回hash值
public class Demo { public static void main(String[] args) throws Exception { String s = "abc"; String s1 = "bbb"; System.out.println(s.hashCode()); Field f = s.getClass().getDeclaredField("value"); f.setAccessible(true); f.set(s, new char[]{'b', 'b', 'b'}); System.out.println(s + " "+ s1); System.out.println(s.hashCode() +" " +s1.hashCode()); } } //结果:96354 bbb bbb 96354 97314

String 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; }

toString方法中的this

  • Java为String类重载了“+”操作符,String类与其他类对象进行连接时会调用其他类的toString方法
  • 如果在其他类的toString方法中用“+”对this进行连接就会出现无穷递归调用而出现栈溢出毛病
  • 解决方法将this换做super.this
public class Demo { @Override public String toString() { //会造成递归调用 // return "address"+super.toString(); return "address"+super.toString(); } public static void main(String[] args) { System.out.println(new Demo()); } }

CodePoints与CodeUnit

String的length表示的是代码单元的个数,而不是字符的个数

  • codePoints是代码点, 表示的是例如’A’, ‘王’ 这类字符,每种字符都有1个唯1的数字编号,这个数字编号就叫unicode code point。目前code point的数值范围是0~0x10FFFF。
  • codeUnit是代码单元, 它根据编码不同而不同, 可以理解为是字符编码的基本单元,java中的char是两个字节, 也就是16位的。这样也反应了1个char只能表示从u+0000~u+FFFF范围的unicode字符, 在这个范围的字符也叫BMP(basic Multiligual Plane ), 超越这个范围的叫增补字符,增补字符占用两个代码单元。
public class Demo { public static void main(String[] args) { String s = "\u1D56B"; System.out.println(s); System.out.println(s.length()); } }

我们看看String是怎样处理增补字符的

  • 首先value字符数组的长度是根据代码单元来定的,每出现1个Surrogate字符数组长度在count的基础上加1
  • BMP字符直接存储,增补字符的用两个char分别存储高位和低位
public String(int[] codePoints, int offset, int count) { if (offset < 0) { throw new StringIndexOutOfBoundsException(offset); } if (count < 0) { throw new StringIndexOutOfBoundsException(count); } // Note: offset or count might be near ⑴>>>1. if (offset > codePoints.length - count) { throw new StringIndexOutOfBoundsException(offset + count); } final int end = offset + count; // Pass 1: Compute precise size of char[] 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)); } // Pass 2: Allocate and fill in char[] 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; } static void toSurrogates(int codePoint, char[] dst, int index) { // We write elements "backwards" to guarantee all-or-nothing dst[index+1] = lowSurrogate(codePoint); dst[index] = highSurrogate(codePoint); }

源码解析

声明

  • String类可序列化,可比较,实现CharSequence接口提供了对字符的基本操作
  • String内部使用final字符数组进行存储,触及value数组的操作都使用了拷贝数组元素的方法,保证了不能在外部修改字符数组
  • String重写了Object的hashCode函数使hash值基于字符数组内容,但是由于String缓存了hash值,所以即使通过反射改变了字符数组内容,hashhashCode返回值不会自动更新
  • serialVersionUID 用来肯定类的版本是不是正确,如果不是同1个类会抛出InvalidCastException异常
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { private final char value[]; private static final long serialVersionUID = -6849794470754667710L; /** Cache the hash code for the string */ private int hash; // Default to 0 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; }

构造函数

  • String主要提供了通过String,StringBuilder,StringBuffer,char数组,int数组(CodePoint),byte数组(需要指定编码)进行初始化
  • 当通过字符串初始化字符串时,并没有履行value数组拷贝,由于original的value数组是不可以在外部修改的,也就保证了新String对象的不可修改
  • 通过字符数组,StringBuffer,StringBuilder进行初始化时,就要履行value数组元素的拷贝,创建新数组,避免外部对value内容的改变
  • 通过byte数组进行初始化,需要指定编码,或使用默许编码(ISO⑻859⑴),否则没法正确解释字节内容
  • 通过Unicode代码点进行的初始化,可能会包括非BMP字符(int值大于65535),这时候候字符串的长度可能会擅长int数组的长度,(见本文前面增补字符处理部份)
public String(String original) { this.value = original.value; this.hash = original.hash; } 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()); } 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) { throw new StringIndexOutOfBoundsException(count); } // Note: offset or count might be near ⑴>>>1. if (offset > value.length - count) { throw new StringIndexOutOfBoundsException(offset + count); } this.value = Arrays.copyOfRange(value, offset, offset+count); } 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[], int offset, int length) { checkBounds(bytes, offset, length); this.value = StringCoding.decode(bytes, offset, length); } static char[] decode(byte[] ba, int off, int len) { String csn = Charset.defaultCharset().name(); try { // use charset name decode() variant which provides caching. return decode(csn, ba, off, len); } catch (UnsupportedEncodingException x) { warnUnsupportedCharset(csn); } try { return decode("ISO⑻859⑴", ba, off, len); } catch (UnsupportedEncodingException x) { // If this code is hit during VM initialization, MessageUtils is // the only way we will be able to get any kind of error message. MessageUtils.err("ISO⑻859⑴ charset not available: " + x.toString()); // If we can not find ISO⑻859⑴ (a required encoding) then things // are seriously wrong with the installation. System.exit(1); return null; } }

内部构造函数

使用外部数组来初始化String内部数组只有保证传入的数组不可能被改变才能保证String的不可变性,例如用String初始化String对象时

  • 这类方法使用同享value数组的方法避免了数组的拷贝,提高了效力
  • 上面分析指出如果直接使用外部传入的数组不能保证String的不可变性,这个方法只在String的内部使用,不能由外部调用
  • 添加share参数,只是为了重载构造函数,share必须为true
  • 该函数只用在不能缩短String长度的函数中,如concat(str1,str2),如果用在缩短String长度的函数如subString中会造成内存泄漏
String(char[] value, boolean share) { // assert share : "unshared not supported"; this.value = value; } public String concat(String str) { int otherLen = str.length(); if (otherLen == 0) { return this; } int len = value.length; char buf[] = Arrays.copyOf(value, len + otherLen); str.getChars(buf, len); return new String(buf, true); } // 使用了Arrays.copyof方法来构造新的数组,拷贝元素,而不是共用数组 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(value,share)可以在外部使用,就能够改变字符串内容

public class Demo { public static void main(String[] args) { char[] arr = new char[] {'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd'}; String s = new String(arr,true); arr[0] = 'a'; System.out.println(s); } }

aLongString 已不用了,但是由于其与aPart同享value数组,所以不能被回收,造成内存泄漏

public String subTest(){ String aLongString = "...a very long string..."; String aPart = aLongString.substring(20, 40); return aPart; }

主要方法

其他主要方法

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)

重载的valueOf方法

可以看到主要是调用构造函数或是调用对应类型的toString完成到字符串的转换

public static String valueOf(boolean b) { return b ? "true" : "false"; } public static String valueOf(char c) { char data[] = {c}; return new String(data, true); } public static String valueOf(int i) { return Integer.toString(i); } public static String valueOf(long l) { return Long.toString(l); } public static String valueOf(float f) { return Float.toString(f); } public static String valueOf(double d) { return Double.toString(d); } public static String valueOf(char data[], int offset, int count) { return new String(data, offset, count); } public static String copyValueOf(char data[], int offset, int count) { // All public String constructors now copy the data. return new String(data, offset, count); }

字符串查找算法 indexOf

可以看到String的字符串匹配算法使用的是朴素的匹配算法,即前向匹配,当遇到不匹配字符时,主串从下1个字符开始,字串从开始位置开始
其他相干字符串匹配算法

static int indexOf(char[] source, int sourceOffset, int sourceCount, char[] target, int targetOffset, int targetCount, int fromIndex) { if (fromIndex >= sourceCount) { return (targetCount == 0 ? sourceCount : -1); } if (fromIndex < 0) { fromIndex = 0; } if (targetCount == 0) { return fromIndex; } char first = target[targetOffset]; int max = sourceOffset + (sourceCount - targetCount); for (int i = sourceOffset + fromIndex; i <= max; i++) { /* Look for first character. */ if (source[i] != first) { while (++i <= max && source[i] != first); } /* Found first character, now look at the rest of v2 */ if (i <= max) { int j = i + 1; int end = j + targetCount - 1; for (int k = targetOffset + 1; j < end && source[j] == target[k]; j++, k++); if (j == end) { /* Found whole string. */ return i - sourceOffset; } } } return -1; }

编码问题 getBytes

  • 字符串终究都是使用机器码以字节存储的,当我们将字符串转换为字节的时候也需要给定编码,同1个字符不同的编码就对应不同的字节
  • 如不指定编码,就会使用默许的编码ISO⑻859⑴进行编码
  • 编码时为了不平台编码的干扰,应当指定肯定的编码
String s = "你好,世界!"; byte[] bytes = s.getBytes("utf⑻"); public byte[] getBytes(String charsetName) throws UnsupportedEncodingException { if (charsetName == null) throw new NullPointerException(); return StringCoding.encode(charsetName, value, 0, value.length); } static byte[] encode(String charsetName, char[] ca, int off, int len) throws UnsupportedEncodingException { StringEncoder se = deref(encoder); String csn = (charsetName == null) ? "ISO⑻859⑴" : charsetName; if ((se == null) || !(csn.equals(se.requestedCharsetName()) || csn.equals(se.charsetName()))) { se = null; try { Charset cs = lookupCharset(csn); if (cs != null) se = new StringEncoder(cs, csn); } catch (IllegalCharsetNameException x) {} if (se == null) throw new UnsupportedEncodingException (csn); set(encoder, se); } return se.encode(ca, off, len); }

比较方法

  • 所有比较方法都是比较对应的字符数组的内容,后两个比较方法用来进行区段比较
  • 在进行数组比较时,如果可以通太长度进行初步判断,1般可以提高效力
boolean equals(Object anObject); boolean contentEquals(StringBuffer sb); boolean contentEquals(CharSequence cs); boolean equalsIgnoreCase(String anotherString); int compareTo(String anotherString); int compareToIgnoreCase(String str); boolean regionMatches(int toffset, String other, int ooffset,int len) //局部匹配 boolean regionMatches(boolean ignoreCase, int toffset,String other, int ooffset, int len) //局部匹配 public boolean equals(Object anObject) { if (this == anObject) { return true; } if (anObject instanceof String) { String anotherString = (String) anObject; 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; } } return false; }

替换函数 replace

  • 单字符替换会替换所有特定字符的出现
  • replace为普通(literal)替换,不用正则表达式
  • replaceFirst与replaceAll都使用了正则表达式
public String replace(CharSequence target, CharSequence replacement) { return Pattern.compile(target.toString(), Pattern.LITERAL).matcher( this).replaceAll(Matcher.quoteReplacement(replacement.toString())); } public String replaceFirst(String regex, String replacement) { return Pattern.compile(regex).matcher(this).replaceFirst(replacement); } public String replaceAll(String regex, String replacement) { return Pattern.compile(regex).matcher(this).replaceAll(replacement); } 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; }

常量池相干方法

  • 每当定义1个字符串字面量,字面量进行字符串联接,或final的String字面量初始化的变量的连接的变量时都会检查常量池中是不是有对应的字符串,如果有就不创建新的字符串,而是返回指向常量池对应字符串的援用
  • 所有通过new String(str)方式创建的对象都会存在与堆区,而非常量区
  • 普通变量的连接,由于不能在编译期肯定下来,所以不会存储在常量区
public native String intern();

运算符的重载

  • String对“+”运算符进行了重载,通过反编译我们看到重载是通过StringBuilder的append方法,及String的valueOf方法实现的
  • int值转String进程中(”“+i)这类方法实际为(new StringBuilder()).append(i).toString();,而另外两种都是调用Integer的静态方法Integer.toString完成
// int转String的方法比较 public class Demo { public static void main(String[] args) throws Exception { int i = 5; String i1 = "" + i; String i2 = String.valueOf(i); String i3 = Integer.toString(i); } } // 原始代码 public class Demo { public static void main(String[] args) throws Exception { String string="hollis"; String string2 = string + "chuang"; } } //反编译代码 public class Demo { public static void main(String[] args) throws Exception { String string = "hollis"; String string2 = (new StringBuilder(String.valueOf(string))).append("chuang").toString(); } }
生活不易,码农辛苦
如果您觉得本网站对您的学习有所帮助,可以手机扫描二维码进行捐赠
程序员人生
------分隔线----------------------------
分享到:
------分隔线----------------------------
关闭
程序员人生