String系列01 - String 60%
1.String简介
- String类实现CharSequence接口。(同时,实现Serializable, Comparable 接口)。
- String类使用final修饰符,为final类,不可被继承,同样的,其方法自然就不可被覆盖。
- String 内部通过数组来实现。
- Java 语言提供对字符串串联符号("+")以及将其他对象转换为字符串的特殊支持。字符串串联是通过
StringBuilder
(或StringBuffer
)类及其append
方法实现的。 - 字符串在java内存中总是按unicode编码存储的。
2.CharSequence接口定义
public interface CharSequence { int length(); /*@throws IndexOutOfBoundsException*/ char charAt(int index); /* @throws IndexOutOfBoundsException */ CharSequence subSequence(int start, int end); public String toString(); }
已知的实现类有 CharBuffer, Segment, String, StringBuffer, StringBuilder。
3.String类详解
- 变量
// 类静态常量
public static final Comparator<String> CASE_INSENSITIVE_ORDER = new CaseInsensitiveComparator(); private static final int HASHING_SEED; // 在statick块中初始化了 private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0]; private static final long serialVersionUID = -6849794470754667710L;
// 成员变量 private final char value[];// 用于存储字符串 private int hash;// 存储String的hash值 private transient int hash32 = 0;// Cached value of the alternative hashing algorithm result
- 构造函数
public String() public String(String original) public String(char[] value) public String(char[] value, int offset, int count) public String(byte[] bytes) public String(byte[] bytes, int offset, int length) public String(byte[] ascii, int hibyte) public String(byte[] ascii, int hibyte, int offset, int count) public String(byte[] bytes, String charsetName) public String(byte[] bytes, int offset, int length, String charsetName) public String(byte[] bytes, Charset charset) public String(byte[] bytes, int offset, int length, Charset charset) public String(int[] codePoints, int offset, int count) public String(StringBuffer buffer) public String(StringBuilder builder)
// 部分构造函数代码
// 思考:为什么一般不用这个构造方法? 因为String是不可变类,使用此构造方法没什么用。 public String(){ this.value = new char[0]; } public String(String original) { this.value = original.value; this.hash = original.hash; } public String(char value[]) { this.value = Arrays.copyOf(value, value.length); }
// 通过使用平台的默认字符集解码指定的 byte 子数组,构造一个新的String
// 扩展学习1:平台默认字符集 指的是操作系统默认的字符集,同时可通过 -Dfile.encoding="GBK"来设置jvm字符集 public String(byte bytes[], int offset, int length) { checkBounds(bytes, offset, length); this.value = StringCoding.decode(bytes, offset, length); }
// 通过使用指定的charset解码指定的 byte 子数组,构造一个新的String
。 public String(byte bytes[], int offset, int length, String charsetName) throws UnsupportedEncodingException { if (charsetName == null) throw new NullPointerException("charsetName"); checkBounds(bytes, offset, length); this.value = StringCoding.decode(charsetName, bytes, offset, length); } 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内存中总是按unicode编码存储的!! System.out.println("平台默认字符集 = " + Charset.defaultCharset()); // 在eclipse中 可将 text-file-encoding 改为其它试试看 String str1 = "中文"; // 中文 unicode编码 byte[] b1 = str1.getBytes("unicode"); System.out.print("unicode编码 = "); for(byte b : b1){ System.out.print(Integer.toHexString(b & 0xFF) + " "); } System.out.println(); String str2 = new String(b1, "unicode"); System.out.println(str2); // 中文utf-8编码 utf8中部分汉字占3个字节,有21000多个(大部分是原GBK中的字符); 部分汉字占4个字节 中日韩超大字符集里面的汉字,有 5 万多个 b1 = str1.getBytes(); System.out.print(Charset.defaultCharset() + "编码 = "); for(byte b : b1){ System.out.print(Integer.toHexString(b & 0xFF) + " "); } System.out.println(); // 中文gbk编码 b1 = str1.getBytes("gbk"); System.out.print("gbk编码 = "); for(byte b : b1){ System.out.print(Integer.toHexString(b & 0xFF) + " "); } System.out.println(); // 中文iso8859-1编码 b1 = str1.getBytes("iso8859-1"); System.out.print("iso8859-1编码 = "); for(byte b : b1){ System.out.print(Integer.toHexString(b & 0xFF) + " "); } System.out.println(); // 中文 在ios8859-1编码下打印出来为乱码 String str5 = new String(b1, "iso8859-1"); System.out.println(str5); // unicode编码 String str13 = new String(new int[] {0x4e2d, 0x6587}, 0, 2); System.out.println(str13); // utf-8编码 0xE4B8AD 0xE69687 String str09 = new String(new byte[]{ (byte)0xE4, (byte)0xB8, (byte)0xAD, (byte)0xE6, (byte)0x96, (byte)0x87},0, 6, "utf-8"); System.out.println(str09); // GBK编码 0xD6D0 0xCEC4 String str12 = new String(new byte[]{ (byte)0xD6,(byte)0xD0, (byte)0xce,(byte)0xc4}, 0, 4,"GBK"); System.out.println(str12);
jvm 对 String的优化
// “中文”的unicode编码 = 0x4e2d 0x6587 char[] v1 = {0x4e2d, 0x6587}; String str14 = new String(v1); // new,存放在堆中 String str15 = "中文"; // 存放在常量池中(常量池自jdk7后移入堆中) System.out.println(str15 == str14); String str16 = "中文"; // 此处常量池中已有“中文”,不再创建新的对象, System.out.println(str15 == str16); String str17 = "中" + "文"; // 编译时优化, 在编译时,即将其转为“中文” System.out.println(str15 == str17); String w = "文"; String str18 = "中" + w; // 有变量w, 编译期间不能确定其值,无法合并, 运行时会new一个新的对象 System.out.println(str15 == str18); String str19 = new String("中文"); System.out.println(str15 == str19); String str20 = str14.intern(); // intern(), 在常量池通过equals方法寻找“中文”,此时,常量池中已有str15,则将常量池中“中文”对象的引用 System.out.println(str20 == str15); // true System.out.println(Integer.toHexString(str15.hashCode())); System.out.println(Integer.toHexString(str20.hashCode()));
部分函数实现
public char charAt(int index) public int compareTo(String anotherString) public boolean endsWith(String suffix) public boolean equals(Object anObject) public String intern() public String replace(char oldChar, char newChar) public boolean startsWith(String prefix, int toffset) public boolean startsWith(String prefix) public String substring(int beginIndex) public String substring(int beginIndex, int endIndex) public String trim()
/**
*1.若长度不相等,返回长度差值;
2.若长度相等,则依次比较相同位置char, 若某个char不相等,则返回char差值;
由此可见,若结果大于0,则当前字符串大; 若结果等于0,则两字符串相等; 若结果小于0,则参数中的字符串大。
*/
1 public int compareTo(MyString anString){ 2 int len1 = value.length; 3 int len2 = anString.value.length; 4 5 int limin = Math.min(len1, len2); 6 7 char v1[] = value; 8 char v2[] = anString.value; 9 10 int k = 0; 11 while(k < limin){ 12 char c1 = v1[k]; 13 char c2 = v2[k]; 14 if(c1 != c2) 15 return c1 - c2; 16 k++; 17 } 18 return len1 - len2; 19 }
/*
* 1. == 号比较,(两个变量在栈中存放的内容),若相等, true
2. 若参数不属于String类型,false
3. 若长度不等,false
4. 若长度相等,则从后向前,依次比较char,若出现不等, false
*/
1 public boolean equals(Object anObject){ 2 if(this == anObject){ 3 return true; 4 } 5 if(anObject instanceof String){ 6 String anotherString = (String) anObject; 7 int n = value.length; 8 if(n == anotherString.length()){ 9 char v1[] = this.value; 10 char v2[] = anotherString.value; 11 int i = 0; 12 while(n-- != 0){ 13 if (v1[i] != v2[i]) 14 return false; 15 i++; 16 } 17 return true; 18 } 19 } 20 return false; 21 }
/* * replace方法并没有一上来就创建新数组,循环复制; * 而是 * 第一步 找出oldChar第一次出现的位置【index】 * 第二步 创建新数组,将【index】之前的char复制到新数组 * 第三步 再从index的位置开始循环比较, 将剩余的char添加到新数组 * 代码妙处:只有原串中出现了 oldChar, 这时才创建新数组,赋值;既没有重复比较操作,又避免了从头到尾的零差异复制操作。 */ public MyString replace(char oldChar, char newChar){ if(oldChar != newChar){ int len = value.length; char val[] = value; int i = -1; 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[i]; } while (i < len){ char c = val[i]; buf[i] = (c == oldChar) ? newChar : c; i++; } return new MyString(buf,true); } } return this; }
public String trim(){ int len = value.length; int st = 0; char[] val = value; /* avoid getfield opcode */ 扩展学习2: getfield指令 及 String此处的优化在哪里 while((st < len ) && (val[st] <= ' ')){ st ++; } while((st < len) && (val[len - 1] <= ' ')){ len --; } return ((len < value.length) || (st>0)) ? subString(st, len) : this; }
1 public boolean startsWith(MyString prefix){ 2 return startsWith(prefix,0); 3 } 4 5 public boolean endsWith(MyString suffix) { 6 return startsWith(suffix, value.length - suffix.value.length); 7 } 8 9 public boolean startsWith(MyString prefix, int toffset){ 10 char[] ta = value; 11 int to = toffset; 12 char[] pa = prefix.value; 13 int po = 0; 14 int pc = prefix.value.length; 15 // Note: toffset might be near -1>>>1. 16 /* 17 * 扩展学习3:逻辑右移或叫无符号右移运算符“>>>“只对位进行操作,没有算术含义,它用0填充左侧的空 18 * -1 >>> 1 = int型最大值 2147483647 19 *-1 二进制 11111111 11111111 11111111 11111111 20 *-1 >>> 1 二进制 1111111 11111111 11111111 11111111 21 */ 22 if((toffset < 0) || (toffset > value.length - pc)){ 23 return false; 24 } 25 while(--pc >= 0){ 26 if(ta[to++] != pa[po++]){ 27 return false; 28 } 29 } 30 return true; 31 }
1 @Override 2 public CharSequence subSequence(int start, int end) { 3 return this.subString(start, end); 4 } 5 6 public MyString substring(int beginIndex) { 7 if (beginIndex < 0) { 8 throw new StringIndexOutOfBoundsException(beginIndex); 9 } 10 int subLen = value.length - beginIndex; 11 if (subLen < 0) { 12 throw new StringIndexOutOfBoundsException(subLen); 13 } 14 return (beginIndex == 0) ? this : new MyString(value, beginIndex, subLen); 15 } 16 17 public MyString subString(int beginIndex, int endIndex){ 18 if(beginIndex < 0){ 19 throw new StringIndexOutOfBoundsException(beginIndex); 20 } 21 if(endIndex > value.length){ 22 throw new StringIndexOutOfBoundsException(endIndex); 23 } 24 int subLen = endIndex - beginIndex; 25 if(subLen < 0){ 26 throw new StringIndexOutOfBoundsException(subLen); 27 } 28 29 return ((beginIndex == 0) && (endIndex == value.length)) ? this 30 : new MyString(value, beginIndex, subLen); 31 }
1 public native String intern(); 2 //intern方法是Native调用,它的作用是在常量池里通过equals方法寻找等值的对象,如果没有找到则在常量池中开辟一片空间存放字符串并返回该对应String的引用, 3 否则直接返回常量池中已存在String对象的引用