java.lang.String是使用频率非常高的类。要想更好的使用java.lang.String类,了解其源代码实现是非常有必要的
一、String类
String类是被final所修饰的,所以不允许被继承和修改,String类实现了Serializable、Comparable、CharSequence这三个接口,Serializable接口使得String可序列化;Comparable为String提供了比较器,使其可进行排序;CharSequence接口有length(),charAt(int index),subSequence(int start,int end)方法。
public final class String implements java.io.Serializable, Comparable<String>, CharSequence
二、String属性
String声明了4个变量
//存储字符串的字符数组。该数组为final变量,一旦赋值,将不会更改。 private final char value[]; //一个int型的变量hash用来存放计算后的该String的哈希值 private int hash; // Default to 0 //提供序列化的ID private static final long serialVersionUID = -6849794470754667710L; //声明了一个可序列化的字段 private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0];
三、String构造方法
java.lang.String对象构造方法比较多,列举如下:
public String() public String(String original) public String(char value[]) public String(char value[], int offset, int count) public String(int[] codePoints, int offset, int count) @Deprecated public String(byte ascii[], int hibyte, int offset, int count) @Deprecated public String(byte ascii[], int hibyte) public String(byte bytes[], int offset, int length, String charsetName) throws UnsupportedEncodingException public String(byte bytes[], int offset, int length, Charset charset) public String(byte bytes[], String charsetName) throws UnsupportedEncodingException public String(byte bytes[], Charset charset) public String(byte bytes[], int offset, int length) public String(byte bytes[]) public String(StringBuffer buffer)
在 public String(StringBuffer buffer) 中,传入形参为StringBuffer,StringBuffer为线程安全类。则在此构造方法内部进行了synchronized关键字锁同步。代码如下:
public String(StringBuffer buffer) { synchronized(buffer) { this.value = Arrays.copyOf(buffer.getValue(), buffer.length()); } }
在 public String(StringBuilder builder) 中,传入形参为StringBuilder,StringBuilder为非线程安全类。则在此构造方法内部内部未做同步处理,对比 public String(StringBuffer buffer) 。代码如下:
public String(StringBuilder builder) { this.value = Arrays.copyOf(builder.getValue(), builder.length()); }
四、String常用方法
java.lang.String对象中封装方法非常多,仅针对常用方法源代码进行分析。如:equals(),replace(), indexOf(),startsWith(),compareTo(),regionMathes(),hashCode()。
public boolean equals(Object anObject)
用于比较两对象存储内容是否相同。采用比较巧妙的方式进行排除比较:(1)先“==”比较两对象是否是同一对象,若是,直接返回true, 否则进一步判断;(2)判断待比较对象类型是否是java.lang.String,若不是,直接返回false,否则进一步判断;(3)判断两字符串长度是否相等,若不是直接返回false,否则进一步判断;(4)从字符数组中第一个字符开始,依次进行比较,一旦发现不相同字符直接返回false,若所在字符均相同则返回true。对字符数组中字符依次进行比较是一件非常耗时的操作,将此操作放在最后执行,先利用其它条件进行对其进行判断。
public boolean equals(Object anObject) { //如果引用的是同一个对象,返回真 if (this == anObject) { return true; } //如果不是String类型的数据,返回假 if (anObject instanceof String) { String anotherString = (String)anObject; int n = value.length; //如果char数组长度不相等,返回假 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; }
public String replace(char oldChar, char newChar)
将字符串中指定字符替换为新的字符。(1)先判断待替换字符和新字符是否相同,若相同,则直接返回原字符串,若不同,则继续执行;(2)找出第一次出现待替换字符位置i,创建新的等长字符数组,将该位置之前的字符依次放入新的字符数组中;(3)从位置i处依次遍历比较原字符数组中字符是否是待替换字符,若是,则将新字符放入新字符数组对应位置,若不是,则将原字符数组中字符放入对应位置。巧妙做了一个小优化,直接找出第一次出现待替换字符的位置,再从此处开始遍历,提高效率。
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 replace(CharSequence target, CharSequence replacement)
该方法是我们通常意义所用到的 public String replace(String target, String replacement) ,java.lang.String实现了java.lang.CharSequence接口。方法内部调用正则表达式匹配替换来实现。
public String replace(CharSequence target, CharSequence replacement) { return Pattern.compile(target.toString(), Pattern.LITERAL).matcher( this).replaceAll(Matcher.quoteReplacement(replacement.toString())); }
public int indexOf(String str)
该方法是找出目标字符串是第一次出现指定子字符串的位置,若不存在,则返回-1,若存在,则返回位置坐标。具体实现是调用 static int indexOf(char[] source, int sourceOffset, int sourceCount, char[] target, int targetOffset, int targetCount, int fromIndex) 方法。先对目标字符串中出现子字符串的位置可能范围,然后在此范围中遍历找出与子字符串第一个字符相同的位置,并对后面字符进行比较分析。
public int indexOf(String str) { return indexOf(str, 0); } public int indexOf(String str, int fromIndex) { return indexOf(value, 0, value.length, str.value, 0, str.value.length, fromIndex); } 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; }
public int compareTo(String anotherString)
该方法是对字符串集合进行排序的基础,通过此方法可比较两字符串大小,原理很简单,源代码如下:
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; } k++; } return len1 - len2; }
public boolean startsWith(String prefix)
判断目标字符串是否以指定字符子串开关,该方法内部是调用 public boolean startsWith(String prefix, int toffset) 方法实现,原理很简单,代码如下:
public boolean startsWith(String prefix, int toffset) { char ta[] = value; int to = toffset; char pa[] = prefix.value; int po = 0; int pc = prefix.value.length; /*如果开始查找的位置小于0或大于当前字符串长度与指定前缀长度的差值,则返回false*/ if ((toffset < 0) || (toffset > value.length - pc)) { return false; } //从此字符串的指定索引开始比较是否与指定前缀相等 while (--pc >= 0) { if (ta[to++] != pa[po++]) { //不相等返回false return false; } } //相等返回true return true; }
public int hashCode()
其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; }
public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin)
将一个String字符串,按照给定的参数复制到目标字符数组的方法。其中传入4个参数:int类型的srcBegin为字符串中要复制的第一个字符的索引;int类型的srcEnd为字符串中要复制的最后一个字符之后的索引(要复制的最后一个字符位于索引 srcEnd-1 处);char类型的数组dst[]为目标数组;int类型的desBegin为目标数组中的起始偏移量。
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 String concat(String str)
将指定字符串连接到此字符串的结尾,如果参数字符串的长度为 0,则返回此 String 对象。否则,创建一个新的 String 对象,用来表示由此 String 对象表示的字符序列和参数字符串表示的字符序列连接而成的字符序列。以下是源代码:
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); }
public String trim()
返回字符串的副本,忽略前导空白和尾部空白。这在开发中也是很常用的方法,源代码如下:
public String trim() { int len = value.length; int st = 0; char[] val = value; /* avoid getfield opcode */ //找到字符串前段没有空格的位置 while ((st < len) && (val[st] <= ' ')) { st++; } //找到字符串末尾没有空格的位置 while ((st < len) && (val[len - 1] <= ' ')) { len--; } //如果前后都没有出现空格,返回字符串本身 return ((st > 0) || (len < value.length)) ? substring(st, len) : this; }
public String substring(int beginIndex, int endIndex)
返回一个新字符串,它是此字符串的一个子字符串。该子字符串从指定的 beginIndex 处开始,直到索引 endIndex - 1 处的字符。因此,该子字符串的长度为 endIndex-beginIndex。该方法包含两个int类型的参数,分别是: beginIndex - 起始索引(包括),endIndex - 结束索引(不包括)。源码如下:
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); } /*如果起始索引为0并且结束索引为此字符串的长度则返回此字符串,否则创建新的字符串*/ return ((beginIndex == 0) && (endIndex == value.length)) ? this : new String(value, beginIndex, subLen); }