String、Integer、Long、Enum、BigDecimal、ThreadLocal、ClassLoader & URLClassLoader、ArrayList & LinkedList、 HashMap & LinkedHashMap & TreeMap & CouncurrentHashMap、HashSet & LinkedHashSet & TreeSet
转:https://www.cnblogs.com/ouym/p/8981074.html
1类签名与注释
public final class String implements java.io.Serializable, Comparable, CharSequence
String类被定义为final类型的,所以String对象一旦创建了,就是不可变的。
String类实现了Serializable接口,表示可以序列化。
String类实现了Comparable
String类实现了CharSequence接口,CharSequence是char值的可读序列。
String类代表字符串。 Java程序中的所有字符串文字(例如
"abc"
)都被实现为此类的实例。Strings是不可变的,它们的值在创建后不能被更改。字符串缓冲区支持可变字符串。
Java语言为字符串连接运算符(+)提供特殊支持,并为其他对象转换为字符串。 字符串连接是通过StringBuilder
(或StringBuffer
)类及其append
方法实现的。 字符串转换是通过toString
方法来实现,该方法定义在Object类中。
2基本属性
private final char value[]; private int hash; // Default to 0
String用字符数组存储值,内部还保留了自己的hash值。为什么要将hash值作为成员属性呢?我们先看一下String的hashCode方法
1 public int hashCode() { 2 int h = hash; 3 if (h == 0 && value.length > 0) { 4 char val[] = value; 5 6 for (int i = 0; i < value.length; i++) { 7 h = 31 * h + val[i]; 8 } 9 hash = h; 10 } 11 return h; 12 }
当调用String的hashCode方法时,首先会从hash属性取值,当hash属性值为0时(第一次调用该方法),才会重新计算hash值。hash值得计算如line7所示。计算后会将值赋给hash属性。这里保证只在第一次调用hashCode方法的时候计算hash值,之后直接从hash属性取值。这种优化只有当String是final的时候才可以,若String可变的话,hash值也会变化。
3构造方法
String的构造方法有很多,下面是eclipse中outline的所有构造方法的截图。
红色方框部分表示废弃方法(我的版本JDK1.8)。通过outline可以看出,String可以通过String、字符数组、int数组、字节数组、StringBuffer/StringBuilder构造。下面贴出几个常用构造方法的代码。
//1构造空String public String() { this.value = "".value; } //2通过String构造 public String(String original) { this.value = original.value; this.hash = original.hash; } //3通过char数组构造 public String(char value[]) { this.value = Arrays.copyOf(value, value.length); } //4通过byte数组构造 public String(byte bytes[]) { this(bytes, 0, bytes.length); } public String(byte bytes[], int offset, int length) { checkBounds(bytes, offset, length); this.value = StringCoding.decode(bytes, offset, length); } //5通过字符串缓冲区构造 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()); }
这里有几点自己的理解:
(1)在通过char数组构造的时候,是通过Arrays的copyOf复制了一个新的数组(开辟了新的数组存储空间),这里不是通过简单的引用传递。
(2)byte数组不能直接变成char数组,需要通过StringCoding的decode方法。
(3)注意通过StringBuffer构造的时候加了同步,因为StringBuffer是线程安全的,这里复制的时候加锁也要保证线程安全。
4常用方法
(1)equals方法
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; }
只有满足类型一样,长度相等,char数组所有的值都相等时equals方法才返回true。
(2)compareTo方法
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; }
挨个比较字符数组的值,当较短的串所有字符都和较长串的相等时返回串的长度差。当compareTo方法的返回值等于0时,表示被比较的字符串和当前字符串相等,返回值大于0时表示当前字符串较大,返回值小于0表示当前字符串较小。
(3)startsWith和endsWith
//startsWith public boolean startsWith(String prefix) { return startsWith(prefix, 0); } 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; // Note: toffset might be near -1>>>1. if ((toffset < 0) || (toffset > value.length - pc)) { return false; } while (--pc >= 0) { if (ta[to++] != pa[po++]) { return false; } } return true; } //endsWith public boolean endsWith(String suffix) { return startsWith(suffix, value.length - suffix.value.length); }
starsWhith方法是从偏移量开始,挨个比较自身字符数组的值和参数对应的字符数组的值。endsWith用长度差当作偏移量。
(4)indexOf(String)方法
1 public int indexOf(String str) { 2 return indexOf(str, 0); 3 } 4 5 public int indexOf(String str, int fromIndex) { 6 return indexOf(value, 0, value.length, 7 str.value, 0, str.value.length, fromIndex); 8 } 9 10 static int indexOf(char[] source, int sourceOffset, int sourceCount, 11 String target, int fromIndex) { 12 return indexOf(source, sourceOffset, sourceCount, 13 target.value, 0, target.value.length, 14 fromIndex); 15 } 16 17 static int indexOf(char[] source, int sourceOffset, int sourceCount, 18 char[] target, int targetOffset, int targetCount, 19 int fromIndex) { 20 if (fromIndex >= sourceCount) { 21 return (targetCount == 0 ? sourceCount : -1); 22 } 23 if (fromIndex < 0) { 24 fromIndex = 0; 25 } 26 if (targetCount == 0) { 27 return fromIndex; 28 } 29 30 char first = target[targetOffset]; 31 int max = sourceOffset + (sourceCount - targetCount); 32 33 for (int i = sourceOffset + fromIndex; i <= max; i++) { 34 /* Look for first character. */ 35 if (source[i] != first) { 36 while (++i <= max && source[i] != first); 37 } 38 39 /* Found first character, now look at the rest of v2 */ 40 if (i <= max) { 41 int j = i + 1; 42 int end = j + targetCount - 1; 43 for (int k = targetOffset + 1; j < end && source[j] 44 == target[k]; j++, k++); 45 46 if (j == end) { 47 /* Found whole string. */ 48 return i - sourceOffset; 49 } 50 } 51 } 52 return -1; 53 }
主要实现在line17-53,这里就是挨个对比匹配。通过indexOf(String str)调用line17的方法是没有什么问题,因为sourceOffset偏移量总是为0,但是看源码的时候总感觉max(line31)的逻辑会导致数组越界问题。举个例子如下:
public class Test { public static void main(String[] args) { String a = "abc1234"; char[] aa = {'a','b','c','1','2','3','4'}; String b = "25"; char[] bb = {'2','5'}; System.out.println(indexOf(aa,2,aa.length, bb,0,bb.length,0)); } static int indexOf(char[] source, int sourceOffset, int sourceCount, char[] target, int targetOffset, int targetCount, int fromIndex) { //省略... } }
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 7 at com.ouym.test.Test.indexOf(Test.java:44) at com.ouym.test.Test.main(Test.java:19)
(5)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); } 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); }
String的subString方法是通过构造方法String(value, beginIndex, subLen)来实现的,截取的字串包含beginIndex,不包含endIndex。举个例子
String a = "0123456"; System.out.println(a.substring(2,4)); 输出:23
(6)concat
public String concat(String str) { int otherLen = str.length(); if (otherLen == 0) { return this; } int len = value.length; //新建字符数组buf,并将主调String对象的value数组复制到buf char buf[] = Arrays.copyOf(value, len + otherLen); //将参数str的字符数组填充到buf的剩余空间 str.getChars(buf, len); return new String(buf, true); }
(7)replace
1 public String replace(char oldChar, char newChar) { 2 if (oldChar != newChar) { 3 int len = value.length; 4 int i = -1; 5 char[] val = value; /* avoid getfield opcode */ 6 7 while (++i < len) { 8 if (val[i] == oldChar) { 9 break; 10 } 11 } 12 if (i < len) { 13 char buf[] = new char[len]; 14 for (int j = 0; j < i; j++) { 15 buf[j] = val[j]; 16 } 17 while (i < len) { 18 char c = val[i]; 19 buf[i] = (c == oldChar) ? newChar : c; 20 i++; 21 } 22 return new String(buf, true); 23 } 24 } 25 return this; 26 }
replace方法主要分四步:首先,找到oldChar的下标index。然后将index之前的所有字符复制到新的char数组。接下来将oldchar的值变成newchar。最后将剩下的复制到char数组并返回构造的新String。
注意:replace方法是通过数组复制并构造新的String实现的。因为String是final的,内部的char数组也是final的。
5一些问题
(1)codePoint(代码点)
代码点是指一个编码表中的某个字符对应的代码值,也就是Unicode编码表中每个字符对应的数值。
举个例子:
"sfdg".codePointAt(1); 返回结果是102,就是ASCII码中的小写字母 f