深入Java基础(二)——字符串
这段时间在准备找一份java实习工作,所以来把基础知识整理归纳一下
文章结构:
1.equals和==
2.字符串的基本知识以及字符串的源码解读;
3.字符串的注意点以及使用推荐;
一、equals和==
概述:
1、 ==对于基本类型是比较其值,对于引用类型是比较地址,地址也可以是一个基本类型的值,因此可认为就是比较值的。
2、equals只能用于对象的比较,是所有类的一个基本方法。如果用equals来比较基本类型的变量是有语法错误的,equals只是比较对象的内容。
1 public class CompareTest { 2 public static void main(String[] args){ 3 int t1=30; 4 int t2=90; 5 int t3=120; 6 int t4=120; 7 Boolean result1=(t1==t2); //验证不同值的比较是否相等 8 Boolean result2=((t1+t2)==t3); //验证基本数据类型只要数值相等即相等 9 Boolean result3=(t3==t4); //验证基本数据类型直接相等即相等 10 11 System.out.println("--【t1==t2】"+result1+"-----【(t1+t2)=t3】"+result2+"-----【t3=t4】"+result3); 12 13 //另外博主验证过了,只要在Integer缓存大小(-128-127)以内,只要数值相等,还是相等的。觉得大家应该动手试下这个就不贴太多出来了。 14 Integer s1 = Integer.valueOf(t1); //把基本数据类型传递给Integer包装类构建成对象 15 Integer s2 = Integer.valueOf(t2); 16 Integer s3 = Integer.valueOf(t3); 17 Integer s4 = Integer.valueOf(t4); 18 Integer s5 = Integer.valueOf(130); 19 Integer s6 = Integer.valueOf(130); 20 21 Boolean b1 = ((s1+s2)==s3); //验证只要数值相等,还是相等的。即使它是一个Integer对象相加 22 Boolean b2 = s3.equals(s3); //验证使用equals对象比较,因为还是在缓存区域以内,所以当然相等啦。 23 Boolean b3 = (s3==s4); //验证了Integer对象的缓存-128~127以内,值相等即可相等啦。但是只要超出缓存区域,就不相等了。 24 //以下就思考地址与对象的比较关系 25 Boolean b4 = (s5==s6); //验证啦超出缓存的对象,当然不相等。而且这个是对象地址的比较 26 Boolean b5 = s5.equals(s6); //验证比较两个拥有相同属性值的对象比较的话,就相等了。只是比较对象的值,也就是对象的内容而已。 27 28 System.out.println("---【(s1+s2)==s3】"+b1+"-----【s3.equals(s3)】"+b2+"-----【s3==s4】"+b3+"-----【s5==s6】"+b4+"-----【s5.equals(s6)】"+b5); 29 } 30 }
补充:
==”比较的是值–变量(栈)内存中存放的对象的(堆)内存地址
equals用于比较两个对象的值是否相同–不是比地址,是比较内容。
注:
Object类中的equals方法和“==”是一样的,没有区别,而String类,Integer类等等一些类,是重写了equals方法,才使得equals和“==不同”
二、字符串家族的基本知识:
(1)String:
是不可变类,即一旦String对象被创建,包含这个对象的字符序列是不可改变的。
源码:
1 //String类由final修饰,故String不能被继承。并且被标记了可序列化 2 //CharSequence 是字符串协议接口 3 public final class String 4 implements java.io.Serializable, Comparable<String>, CharSequence { 5 /** The value is used for character storage. */ 6 //看到源码也这么说了,用于字符存储,并且它是一个final修饰的属性,所以String一旦创建即不可被修改。因此所有对String字符串的修改(如追加字符串,删除部分字符串,截取字符串)都不是在原来的对象基础上修改,而是新建一个String对象修改并返回,这会造成原对象被废弃,浪费资源且性能较差(特别是追加字符串和删除部分字符串),若遇到字符串将被频繁修改的情况,建议不要使用String,改用StringBuffer或StringBuilder。 7 private final char value[]; 8 /**String类型的hash值**/ 9 private int hash; // Default to 0 10 //数了下7个构造器,分析些常用和基本类似的吧。我们在生成一个String对象的时候必须对该对象的offset、count、value三个属性进行赋值,这样我们才能获得一个完成的String类型。 11 //这个就是直接赋值给字符存储的数组嘛。默认value为一个长度为0的数组,所以为null。 12 public String() { 13 this.value = new char[0]; 14 } 15 //初始化新创建的字符串对象,它代表相同的字符序列的参数.新创建的字符串是参数字符串的副本(就是增删改string的时候的string对象),此构造函数的使用是因为字符串是不可变的。 16 public String(String original) { 17 this.value = original.value; 18 this.hash = original.hash; 19 } 20 //就是可以传入一个字符数组嘛。而且还是这个问题,string不可变,你一创建就被copy了。随后修改的字符对象已经不是原来的对象了。 21 public String(char value[]) { 22 this.value = Arrays.copyOf(value, value.length); 23 } 24 //跟上面的构造器差不多,多点规范 25 public String(char value[], int offset, int count) { 26 if (offset < 0) { 27 throw new StringIndexOutOfBoundsException(offset);//起始值下标小于0,抛异常 28 } 29 if (count < 0) { 30 throw new StringIndexOutOfBoundsException(count);//取值长度小于0,抛异常 31 } 32 // Note: offset or count might be near -1>>>1. 33 if (offset > value.length - count) { 34 throw new StringIndexOutOfBoundsException(offset + count);//起始值下标加长度大于数组长度,抛异常 35 } 36 this.value = Arrays.copyOfRange(value, offset, offset+count); 37 } 38 //在构造对象时,传入了下标以及长度 39 public String(int[] codePoints, int offset, int count) { 40 if (offset < 0) { 41 throw new StringIndexOutOfBoundsException(offset); 42 } 43 if (count < 0) { 44 throw new StringIndexOutOfBoundsException(count); 45 } 46 // Note: offset or count might be near -1>>>1. 47 if (offset > codePoints.length - count) { 48 throw new StringIndexOutOfBoundsException(offset + count); 49 } 50 //构造对象后:(步骤一)精确计算String所需要的长度。自动计算string所需长度。 51 int n = count; 52 for (int i = offset; i < end; i++) { 53 int c = codePoints[i]; 54 if (Character.isBmpCodePoint(c)) 55 continue; 56 else if (Character.isValidCodePoint(c)) 57 n++; 58 else throw new IllegalArgumentException(Integer.toString(c)); 59 } 60 //构造对象后:(步骤二)分配和填充字符对象。提取每一位的字符,并将其放入String字符串。 61 final char[] v = new char[n]; 62 63 for (int i = offset, j = 0; i < end; i++, j++) { 64 int c = codePoints[i]; 65 if (Character.isBmpCodePoint(c)) 66 v[j] = (char)c; 67 else 68 Character.toSurrogates(c, v, j++); 69 } 70 this.value = v; 71 } 72 //可以看到StringBuffer 和StringBuilder 传进来也可成为String对象。但是三者并没继承关系。 73 public String(StringBuffer buffer) { 74 //嘿嘿,神奇的一个线程关键字synchronized。一会有解析 75 synchronized(buffer) { 76 this.value = Arrays.copyOf(buffer.getValue(), buffer.length()); 77 } 78 } 79 public String(StringBuilder builder) { 80 this.value = Arrays.copyOf(builder.getValue(), builder.length()); 81 } 82 //很普通的方法啦 83 public int length() { 84 return value.length; 85 } 86 public boolean isEmpty() { 87 return value.length == 0; 88 } 89 //用于取得字符串下标为i的字符 90 public char charAt(int index) { 91 if ((index < 0) || (index >= value.length)) { 92 throw new StringIndexOutOfBoundsException(index); 93 } 94 return value[index]; 95 } 96 //返回指定索引处的字符(Unicode代码点)。该索引引用char值(Unicode代码单元),其范围从 0 到 length() - 1。就是返回一个Unicode值。 97 public int codePointAt(int index) { 98 if ((index < 0) || (index >= value.length)) { 99 throw new StringIndexOutOfBoundsException(index); 100 } 101 return Character.codePointAtImpl(value, index, value.length); 102 } 103 //上面解析过,Integer,String这些类都是重写了equals才有不同的效果。比较字符串的值是否相同 。 104 public boolean equals(Object anObject) { 105 if (this == anObject) { 106 return true;//若比较的两个对象引用地址值相同,则为同一个对象,值当然相同 107 } 108 if (anObject instanceof String) {//String与另一个对象能比较的前提是,对方也属于String类型 109 String anotherString = (String)anObject; 110 int n = value.length; 111 if (n == anotherString.value.length) {//首先直接先比较长度,若不相等,则一定不等,效率很高 112 char v1[] = value; 113 char v2[] = anotherString.value; 114 int i = 0; 115 while (n-- != 0) {//将两个String对象的值放入数组中,遍历比较,全部相同才表示相同 116 if (v1[i] != v2[i]) 117 return false; 118 i++; 119 } 120 return true; 121 } 122 } 123 return false; 124 } 125 //如果是直接String比较,很快的嘛。 126 public boolean equalsIgnoreCase(String anotherString) { 127 return (this == anotherString) ? true 128 : (anotherString != null) 129 && (anotherString.value.length == value.length) 130 && regionMatches(true, 0, anotherString, 0, value.length); 131 } 132 //比较两个字符串字典。什么意思呢?比较是基于字符串中的每个字符的Unicode值,就是遍历去比较两个字符串的每个字符Unicode值大小。其结果是负的整数,如果此String对象字典前面的参数字符串;其结果是一个正整数,如果此String对象字典如下的参数字符串;结果是零,如果两个字符串相等,CompareTo返回0时,equal(Object)方法将返回true。 133 public int compareTo(String anotherString) { 134 int len1 = value.length; 135 int len2 = anotherString.value.length; 136 int lim = Math.min(len1, len2); 137 char v1[] = value; 138 char v2[] = anotherString.value; 139 140 int k = 0; 141 while (k < lim) { 142 char c1 = v1[k]; 143 char c2 = v2[k]; 144 if (c1 != c2) { 145 return c1 - c2; 146 } 147 k++; 148 } 149 return len1 - len2; 150 } 151 //判断一个字符串是否以prefix字符串开头,toffset是相同的长度 152 public boolean startsWith (String prefix,int toffset){ 153 char ta[] = value; 154 int to = offset + toffset; 155 char pa[] = prefix.value; 156 int po = prefix.offset; 157 int pc = prefix.count; 158 // Note: toffset might be near -1>>>1. 159 if ((toffset < 0) || (toffset > count - pc)) { 160 return false; 161 } 162 while (--pc >= 0) { 163 if (ta[to++] != pa[po++]) { 164 return false; 165 } 166 } 167 return true; 168 } 169 //连接两个字符串 170 public String concat (String str){ 171 int otherLen = str.length(); 172 if (otherLen == 0) { 173 return this; 174 } 175 char buf[] = new char[count + otherLen]; 176 getChars(0, count, buf, 0); 177 str.getChars(0, otherLen, buf, count); 178 return new String(0, count + otherLen, buf);//哈哈哈,看到了吧,都是新建一个String对象,然后返回。 179 } 180 //String与基本类型的包装类转换源码啦。都是静态方法 181 public static String valueOf(boolean b) { 182 return b ? "true" : "false"; 183 } 184 //这个比较有意思,用了一个非public的构造器,不过还是直接赋值给value属性去存储而已。但前提是传入一个字节数组,而不是字节 185 public static String valueOf(char c) { 186 char data[] = {c}; 187 return new String(data, true); 188 } 189 public static String valueOf(int i) { 190 return Integer.toString(i); 191 } 192 public static String valueOf(long l) { 193 return Long.toString(l); 194 } 195 public static String valueOf(float f) { 196 return Float.toString(f); 197 } 198 //String截取方法,传入截取开始的下标 199 public String substring(int beginIndex) { 200 if (beginIndex < 0) { 201 throw new StringIndexOutOfBoundsException(beginIndex); 202 } 203 int subLen = value.length - beginIndex; 204 if (subLen < 0) { 205 throw new StringIndexOutOfBoundsException(subLen); 206 } 207 return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);//当传入的开始下标符合且不为0时,新建一个String,注意这个String的值并没有变化,只是改变了偏移量 208 //传入开始index以及结束下标去截取 209 public String substring(int beginIndex, int endIndex) { 210 if (beginIndex < 0) { 211 throw new StringIndexOutOfBoundsException(beginIndex); 212 } 213 if (endIndex > value.length) { 214 throw new StringIndexOutOfBoundsException(endIndex); 215 } 216 int subLen = endIndex - beginIndex; 217 if (subLen < 0) { 218 throw new StringIndexOutOfBoundsException(subLen); 219 } 220 return ((beginIndex == 0) && (endIndex == value.length)) ? this 221 : new String(value, beginIndex, subLen);//与上面的方法类似,没有改变String的value属性,只是而是改变了偏移量和count长度。 222 } 223 public String replace(char oldChar, char newChar) { 224 if (oldChar != newChar) { 225 int len = value.length;//替代的是整个value中的oldChar,而不是从偏移量开始替代 226 int i = -1; 227 char[] val = value; 228 229 while (++i < len) {//先遍历数组中是否有原字母,没有就无需替换,高效的设计 230 if (val[i] == oldChar) { 231 break; 232 } 233 } 234 if (i < len) {//获得需要替换的char的下标,此下表以前的char直接复制, 235 此下标以后的char才开始一个一个比较,若等于oldchar则替换,高效 236 char buf[] = new char[len]; 237 for (int j = 0; j < i; j++) { 238 buf[j] = val[j];// 239 } 240 while (i < len) { 241 char c = val[i]; 242 buf[i] = (c == oldChar) ? newChar : c; 243 i++; 244 } 245 return new String(buf, true); 246 } 247 } 248 return this; 249 } 250 } 251 }
String对象的地址问题,异同问题以及java对string的优化问题
1 public class StringTest { 2 public static void main(String[] args) { 3 String a="fuzhu"; //这样的方式也是创建一个string对象!!这样创建是放在常量池,new的方式是放在堆中。 4 String b="fuzhu"; 5 String c=new String ("fuzhu"); 6 String d=new String ("fuzhu"); 7 System.out.println(a==b);//true,fuzhu被创建在String Pool中,a和b会指向同一个对象, 8 System.out.println(a==c);//a指向的fuzhu被创建在String Pool中,而c所指向的对象被创建在heap中,两者为不同的对象,地址值不同 9 System.out.println(d==c);//c和d所指的对象都被创建在堆中,但是两个不同的对象,地址值不同 10 System.out.println("---------重要的优化问题!!!----------"); 11 String e = "ab"; 12 String f = "a" + "b"; 13 System.out.println((e == f));//答案是什么??是true!!因为什么?因为java对String有个字符串常量池,恐怖优化!下面有解析。 14 System.out.println("-----------验证什么时候是常量表达式!!--------"); 15 String g = "a1"; 16 String h = "a" + 1; 17 System.out.println((g == h)); //result = true 18 String i = "a" + true; //String i = "atrue"; 19 String j = "atrue" ; 20 System.out.println((i == j)); //result = true 21 String k = "a" + 3.4; // String k = "a3.4"; 22 String l = "a3.4" ; 23 System.out.println((k == l)); //result = true 24 System.out.println("---------验证非常量表达式的情况----------"); 25 String o = "ab"; 26 String p = "b"; 27 String q = "a" + p; 28 System.out.println((o == q)); //result = false 29 } 30 }
小结:
1. String s=”a”是一种非常特殊的形式,和new 有本质的区别。它是java中唯一不需要new 就可以产生对象的途径。以 String s=”a”;形式赋值在java中叫直接量,它是在常量池中而不是象new 一样放在压缩堆中.
2. String s=”a”这种形式的字符串,在JVM内部发生字符串拘留,即当声明这样的一个字符串后,JVM会在常量池中先查找有有没有一个值为”a”的对象,如果有,就会把它赋给当前引用.即原来那个引用和现在这个引用指点向了同一对象,如果没有,则在常量池中新创建一个”a”,下一次如果有String s2 = “1”;又会将s1指向”abcd”这个对象,即以这形式声明的字符串,只要值相等,任何多个引用都指向同一对象.
3. 解析直接创建的java对string的优化过程:编译优化+ 常量池。String b = “a” + “b”;编译器将这个”a” + “b”作为常量表达式,在编译时进行优化,直接取结果”ab”。
4. 什么时候是常量表达式,什么时候不是??
(1)String + String(这的string指的是直接量);(2)string + 基本类型;
不是的情况:两个变量啊。比如两个string变量就不行了。
(2)StringBuffer:
也代表字符串对象,与其余两个基本类似。但StringBuffer是线程安全,StringBuilder没有线程安全功能,所以StringBuilder性能略高。
StringBuffer和StringBuilder都继承了AbstractStringBuilder
AbstractStringBuilder 源码:
1 //为抽象类,主要的属性有两个,一个为value,一个为count,value用于存放值,count用于管理该类的容量 2 abstract class AbstractStringBuilder implements Appendable, CharSequence { 3 //用来存字符串 4 char[] value; 5 //用来计算存储使用的字符数量 6 int count; 7 public int length() {//length方法返回的是count的值,而不是value.length 8 return count; 9 } 10 //抽象类的构造器没啥必要看了吧 11 //这个才是返回容量,字符串总长度 12 public int capacity() { 13 return value.length; 14 } 15 //一层扣一层的封装的扩容机制: 16 public void ensureCapacity(int minimumCapacity) { 17 if (minimumCapacity > 0) 18 ensureCapacityInternal(minimumCapacity); 19 } 20 private void ensureCapacityInternal(int minimumCapacity) { 21 if (minimumCapacity - value.length > 0)//如果最小容量大于长度就要扩容了 22 expandCapacity(minimumCapacity); 23 } 24 void expandCapacity(int minimumCapacity) { 25 int newCapacity = value.length * 2 + 2;//自动扩容机制,每次扩容(value.length+1)*2 26 if (newCapacity - minimumCapacity < 0) 27 newCapacity = minimumCapacity;//若传入的参数小于0,则直接把容量设置到Integer的最 28 if (newCapacity < 0) { 29 if (minimumCapacity < 0) // overflow 30 throw new OutOfMemoryError(); 31 newCapacity = Integer.MAX_VALUE;//若扩容后的容量还是小于传入的参数,则将传入的参数设为容量 32 } 33 value = Arrays.copyOf(value, newCapacity);//当count小于value.length时,将value多余长度的值删除,这时value.length的长度等于count 34 } 35 //用于保留value的值,保留的长度为count的值,只有当count的值小于value.length时才起作用, 36 public void trimToSize() { 37 if (count < value.length) { 38 value = Arrays.copyOf(value, count); 39 } 40 } 41 public void setLength(int newLength) { 42 if (newLength < 0) 43 throw new StringIndexOutOfBoundsException(newLength); 44 ensureCapacityInternal(newLength);//当传入的值大于等于0时,需要扩容 45 46 if (count < newLength) { //当传入值大于字符统计量 47 Arrays.fill(value, count, newLength, '\0');//为新扩容的元素赋值'\0',为结束符 48 } 49 count = newLength;//排除那堆不合理参数干扰后就是那个真正的字符统计量了。 50 } 51 //append依赖的一个方法,用以添加一个字符串数组 52 public void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin) 53 { 54 if (srcBegin < 0) 55 throw new StringIndexOutOfBoundsException(srcBegin); 56 if ((srcEnd < 0) || (srcEnd > count)) 57 throw new StringIndexOutOfBoundsException(srcEnd); 58 if (srcBegin > srcEnd) 59 throw new StringIndexOutOfBoundsException("srcBegin > srcEnd"); 60 System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);//用于添加字符串,将value的值添加到dst[]中 61 } 62 //在对象后拼接字符串或其他对象,效率较高,可以观察到,在拼接时并没有创建新的对象,也没有舍弃旧的对象,相对于String的机制,性能提升相当明显 63 //根据传入参数对应不同的方法 64 public AbstractStringBuilder append(Object obj) { 65 return append(String.valueOf(obj)); 66 } 67 public AbstractStringBuilder append(String str) { 68 if (str == null) 69 return appendNull();//若传入的字符串长度为0,则默认添加null这个字符串 70 int len = str.length(); 71 ensureCapacityInternal(count + len);//扩容到这么大 72 str.getChars(0, len, value, count);//然后用getChar方法去添加字符串数组 73 count += len;//确定存储了这么多的字符 74 return this; 75 } 76 //拼接StringBuffer 也是可以的 77 public AbstractStringBuilder append(StringBuffer sb) { 78 if (sb == null) 79 return appendNull(); 80 int len = sb.length(); 81 ensureCapacityInternal(count + len); 82 sb.getChars(0, len, value, count); 83 count += len; 84 return this; 85 } 86 //这意味只要是AbstractStringBuilder 就可以拼接(暗指builder吧) 87 AbstractStringBuilder append(AbstractStringBuilder asb) { 88 if (asb == null) 89 return appendNull(); 90 int len = asb.length(); 91 ensureCapacityInternal(count + len);//直接检验容量,有需要则执行扩容 92 asb.getChars(0, len, value, count);//然后用getChar方法去添加字符串数组 93 count += len;//确定存储了这么多的字符 94 return this; 95 } 96 //添加null这个字符串 97 private AbstractStringBuilder appendNull() { 98 int c = count; 99 ensureCapacityInternal(c + 4); 100 final char[] value = this.value; 101 value[c++] = 'n'; 102 value[c++] = 'u'; 103 value[c++] = 'l'; 104 value[c++] = 'l'; 105 count = c; 106 return this; 107 } 108 //同理char的字节。就不看了,我们看下Interger等一些基本类型包装类,其余都基本类似的。 109 public AbstractStringBuilder append(int i) { 110 if (i == Integer.MIN_VALUE) { 111 append("-2147483648");//Integer最小值为特例,特殊处理 112 return this; 113 } 114 int appendedLength = (i < 0) ? Integer.stringSize(-i) + 1 115 : Integer.stringSize(i);//判断Integer的位数,负数有负号,要多加一位 116 int spaceNeeded = count + appendedLength;//确定字符串大小 117 ensureCapacityInternal(spaceNeeded);//还是扩容 118 Integer.getChars(i, spaceNeeded, value);//还是添加,不过是用Integer的静态方法 119 count = spaceNeeded; 120 return this; 121 } 122 //以删除一部分的字符,传入开始下标以及截止下标 123 public AbstractStringBuilder delete(int start, int end) { 124 //验证参数的有效性 125 if (start < 0) 126 throw new StringIndexOutOfBoundsException(start); 127 if (end > count) 128 end = count;//结束下标大于count时,将count设为结束下标 129 if (start > end) 130 throw new StringIndexOutOfBoundsException();//开始下标就大于结束下标,当然异常 131 int len = end - start; 132 if (len > 0) { 133 //System的静态方法来实现数组之间的复制。src:源数组; srcPos:源数组要复制的起始位置;dest:目的数组;destPos:目的数组放置的起始位置;length:复制的长度。注意:src and dest都必须是同类型或者可以进行转换类型的数组. 134 //这个函数可以实现自己到自己复制。解析:http://blog.csdn.net/kesalin/article/details/566354 135 System.arraycopy(value, start+len, value, start, count-end);//执行删除 136 count -= len;//重置count大小 137 } 138 return this; 139 } 140 //在对象中间插入字符串数组 141 public AbstractStringBuilder insert(int dstOffset, CharSequence s) { 142 //验证参数有效性 143 if (s == null) 144 s = "null"; 145 if (s instanceof String)//这个必须要String才准插入 146 return this.insert(dstOffset, (String)s); 147 return this.insert(dstOffset, s, 0, s.length()); 148 } 149 //传入的是插入开始下标,以及要插入的字符串。其他插入方法差不多,就不多说了。 150 public AbstractStringBuilder insert(int offset, String str) { 151 //验证参数有效性 152 if ((offset < 0) || (offset > length())) 153 throw new StringIndexOutOfBoundsException(offset); 154 if (str == null) 155 str = "null"; 156 int len = str.length(); 157 ensureCapacityInternal(count + len);//扩容 158 System.arraycopy(value, offset, value, offset + len, count - offset);//使用arraycopy方法去创建那么多长度先,就是先在value中建立起用于存放插入值的空位 159 str.getChars(value, offset);//向空位中插入str 160 count += len;//更新count值 161 return this; 162 } 163 //将对象本身的字符顺序调转后返回给原对象.。就是反转字符串 164 public AbstractStringBuilder reverse() { 165 boolean hasSurrogates = false; 166 int n = count - 1; 167 //采用从中间向两端遍历,对换对称位置上的字符 168 for (int j = (n-1) >> 1; j >= 0; j--) { 169 int k = n - j; 170 char cj = value[j];//两个暂存变量 171 char ck = value[k]; 172 value[j] = ck;//直接对应位置交换 173 value[k] = cj; 174 //验证每个字符的编码是否在范围内 175 if (Character.isSurrogate(cj) || 176 Character.isSurrogate(ck)) { 177 hasSurrogates = true; 178 } 179 } 180 if (hasSurrogates) { 181 //直接反转后,如果两字符顺序错了,就需要重新调整。考虑到存在增补字符,需要成对校验,就是超出了字符的编码范围的话就会重新翻转到原来那样 182 reverseAllValidSurrogatePairs(); 183 } 184 return this; 185 } 186 //reverse的依赖方法--重新调整字符顺序 187 private void reverseAllValidSurrogatePairs() { 188 for (int i = 0; i < count - 1; i++) { 189 char c2 = value[i]; 190 if (Character.isLowSurrogate(c2)) { 191 char c1 = value[i + 1]; 192 if (Character.isHighSurrogate(c1)) { 193 value[i++] = c1; 194 value[i] = c2; 195 } 196 } 197 } 198 } 199 //返回一个新字符串,它是此字符串的一个子字符串。该子字符串从指定的 start索引处开始,一直到索引 end- 1 处的字符。因此,该子字符串的长度为 end-start。 200 public String substring(int start, int end) { 201 if (start < 0) 202 throw new StringIndexOutOfBoundsException(start); 203 if (end > count) 204 throw new StringIndexOutOfBoundsException(end); 205 if (start > end) 206 throw new StringIndexOutOfBoundsException(end - start); 207 return new String(value, start, end - start); 208 } 209 }
StringBuilder的关键源码:
代表字符串对象。但这个是线程不安全的
1 //StringBuilder类由final修饰,不能被继承,并且继承了AbstractStringBuilder类,并完成了toString方法,同时使用了AbstractStringBuilder类中大量的方法。 2 public final class StringBuilder 3 extends AbstractStringBuilder 4 implements java.io.Serializable, CharSequence{ 5 /** 序列号, 对象序列化和反序列化需要的唯一标识版本号,基本类中,只要接入Serializable接口都会分配一个id */ 6 static final long serialVersionUID = 4383685877147921099L; 7 ///默认构造方法 8 public StringBuilder() { 9 //使用父类的构造方法,默认初始化容量为capacity = 16 。基本所有jdk中实现类涉及初始化容量的大小都为16,加上一点扩容机制 10 super(16); 11 } 12 //带一个参数的构造方法,可以指定初始化容量 13 public StringBuilder(int capacity) { 14 super(capacity); 15 } 16 //带一个参数构造方法,与前一个不同,这是指定一个String来初始化 17 public StringBuffer(String str) { 18 // 这里可以注意下,指定String初始化StringBuffer的时候指定容量大小为String的长度加上16 19 super(str.length() + 16); 20 //然后追加到value中 21 append(str); 22 } 23 //其余的构造方法就都类似的啦 24 //下面是字符串修改方法。基本都是用父类的方法的,我们刚刚就分析过了,就不多分析了。但是要注意:修改的方法全部都为线程不安全,是牺牲了安全用以实现性能。若需要考虑线程的安全性,建议使用StringBuffer。一会会看到为啥Buffer是线程安全的。 25 @Override 26 public StringBuilder append(Object obj) { 27 return append(String.valueOf(obj)); 28 } 29 30 @Override 31 public StringBuilder append(String str) { 32 super.append(str); 33 return this; 34 } 35 @Override 36 public String toString() { 37 // Create a copy, don't share the array 38 //源码所说,是new了一个String对象,返回string出去,而不是直接给Builder出去。 39 return new String(value, 0, count); 40 } 41 //将对象序列化,写入了count和value。 42 private void writeObject(java.io.ObjectOutputStream s) 43 throws java.io.IOException { 44 s.defaultWriteObject(); 45 s.writeInt(count); 46 s.writeObject(value); 47 } 48 //用于反序列化,将count和value属性读取出来 49 private void readObject(java.io.ObjectInputStream s) 50 throws java.io.IOException, ClassNotFoundException { 51 s.defaultReadObject(); 52 count = s.readInt(); 53 value = (char[]) s.readObject(); 54 } 55 } 56 1 57 2 58 3 59 4 60 5 61 6 62 7 63 8 64 9 65 10 66 11 67 12 68 13 69 14 70 15 71 16 72 17 73 18 74 19 75 20 76 21 77 22 78 23 79 24 80 25 81 26 82 27 83 28 84 29 85 30 86 31 87 32 88 33 89 34 90 35 91 36 92 37 93 38 94 39 95 40 96 41 97 42 98 43 99 44 100 45 101 46 102 47 103 48 104 49 105 50 106 51 107 52 108 53 109 54 110 55 111 (3)StringBuffer:代表一个字符序列可变的字符串。这个stringbuffer提供了一系列的修改方法去改变字符串对象序列。一旦StringBuffer生成了最终想要的对象,调用toString方法将它转换成一个String对象即可。 112 113 来观份源码,将会详细地去对比StringBuilder去讲解 114 115 public final class StringBuffer 116 extends AbstractStringBuilder 117 implements java.io.Serializable, CharSequence 118 { 119 //Builder没有的功能(一):最后一次修改后的缓存值(字符数组保存),只要修改了value,那么就会重置 120 private transient char[] toStringCache; 121 /** 序列号, 对象序列化和反序列化需要的唯一标识版本号 */ 122 static final long serialVersionUID = 3388685877147921107L; 123 //构造方法基本跟builder差不多的啦 124 public StringBuffer() { 125 super(16); 126 } 127 public StringBuffer(int capacity) { 128 super(capacity); 129 } 130 //获取字符串字符数量,效率低,对象锁,所以这样才会线程安全 131 @Override 132 public synchronized int length() { 133 return count; 134 } 135 // 获取容量,效率低--对象锁 136 @Override 137 public synchronized int capacity() { 138 return value.length; 139 } 140 //确保容量不小于minimumCapacity 141 @Override 142 public synchronized void ensureCapacity(int minimumCapacity) { 143 if (minimumCapacity > value.length) { 144 // 当最小容量值(传进来的参数值)大于value.length(这个其实就是容量),那么就直接扩容 145 expandCapacity(minimumCapacity); 146 } 147 } 148 //扩容,在它父类讲过了。 149 void expandCapacity(int minimumCapacity) { 150 int newCapacity = value.length * 2 + 2; 151 if (newCapacity - minimumCapacity < 0) 152 newCapacity = minimumCapacity; 153 if (newCapacity < 0) { 154 if (minimumCapacity < 0) // overflow 155 throw new OutOfMemoryError(); 156 newCapacity = Integer.MAX_VALUE; 157 } 158 value = Arrays.copyOf(value, newCapacity); 159 } 160 //将value数组中没有存入元素的部分去掉(类似去空格),此时容量大小和size大小相等。也就是删掉count后面的那堆空的东东 161 @Override 162 public synchronized void trimToSize() { 163 //用父类的。但线程安全 164 super.trimToSize(); 165 } 166 //setLength讲过了就省略了。注意它也是线程安全。扩充字符串容量到newLength,并且用空格填充 167 @Override 168 public synchronized void setLength(int newLength) { 169 toStringCache = null; 170 //调用父类函数 171 super.setLength(newLength); 172 } 173 //根据指定索引获取字符,效率慢,对象锁. 174 @Override 175 public synchronized char charAt(int index) { 176 if ((index < 0) || (index >= count)) 177 throw new StringIndexOutOfBoundsException(index); 178 return value[index]; 179 } 180 /** 181 * 根据索引修改字符串中某个字符值 182 */ 183 @Override 184 public synchronized void setCharAt(int index, char ch) { 185 if ((index < 0) || (index >= count)) 186 throw new StringIndexOutOfBoundsException(index); 187 toStringCache = null;//清除缓存,只要修改了value,此值就会clear 188 value[index] = ch; 189 } 190 //修改操作跟父类的唯一区别就是线程安全了:就不多贴了 191 @Override 192 public synchronized StringBuffer append(Object obj) { 193 toStringCache = null; 194 super.append(String.valueOf(obj)); 195 return this; 196 } 197 @Override 198 public synchronized StringBuffer append(String str) { 199 toStringCache = null; 200 super.append(str); 201 return this; 202 } 203 /** 204 * 不存在内存泄漏,实现了线程安全 205 */ 206 @Override 207 public synchronized String substring(int start) { 208 return substring(start, count); 209 //结果是new String(value, start, end - start);弄了个新对象的。 210 // 没有复用char[] 211 } 212 //有一套这样的同步与不同步方法 213 /** 214 * 此方法不同步, 而且也没有 toStringCache = null; 215 * 如果需要同步,那么需要将boolean b转化为specific type特定类型(String) 216 */ 217 @Override 218 public StringBuffer insert(int offset, boolean b) { 219 super.insert(offset, b); 220 return this; 221 } 222 @Override 223 public synchronized StringBuffer insert(int offset, char c) { 224 toStringCache = null; 225 super.insert(offset, c); 226 return this; 227 } 228 //toStringCache之前都不知道这个字段的含义,看到这里似乎看懂了,。是提高toString函数的效率,不用每次都是调用。也就是有做了一个缓存 229 // Arrays.copyOfRange。。。但是字符串修改后这个值需要clear 230 //线程安全 231 @Override 232 public synchronized String toString() { 233 if (toStringCache == null) { 234 toStringCache = Arrays.copyOfRange(value, 0, count); 235 } 236 return new String(toStringCache, true);//返回一个新的string对象过去 237 } 238 // 自定义序列化字段 239 //**transient 用于指定哪个字段不被默认序列化,如public transient int a; 240 //serialPersistentFields 用于指定哪些字段需要被默认序列化.如下: 241 private static final java.io.ObjectStreamField[] serialPersistentFields = 242 { 243 new java.io.ObjectStreamField("value", char[].class), 244 new java.io.ObjectStreamField("count", Integer.TYPE), 245 new java.io.ObjectStreamField("shared", Boolean.TYPE), 246 }; 247 248 /** 249 * 序列化大到ObjectOutputStream,写入了count和value、shared 250 */ 251 private synchronized void writeObject(java.io.ObjectOutputStream s) 252 throws java.io.IOException { 253 java.io.ObjectOutputStream.PutField fields = s.putFields(); 254 fields.put("value", value); 255 fields.put("count", count); 256 fields.put("shared", false); 257 s.writeFields(); 258 } 259 /** 260 * 反序列化到对象,读出count和value。 261 */ 262 private void readObject(java.io.ObjectInputStream s) 263 throws java.io.IOException, ClassNotFoundException { 264 java.io.ObjectInputStream.GetField fields = s.readFields(); 265 value = (char[])fields.get("value", null); 266 count = fields.get("count", 0); 267 } 268 }
(3)StringBuffer:
代表一个字符序列可变的字符串。这个stringbuffer提供了一系列的修改方法去改变字符串对象序列。一旦StringBuffer生成了最终想要的对象,调用toString方法将它转换成一个String对象即可。
源码
1 public final class StringBuffer 2 extends AbstractStringBuilder 3 implements java.io.Serializable, CharSequence 4 { 5 //Builder没有的功能(一):最后一次修改后的缓存值(字符数组保存),只要修改了value,那么就会重置 6 private transient char[] toStringCache; 7 /** 序列号, 对象序列化和反序列化需要的唯一标识版本号 */ 8 static final long serialVersionUID = 3388685877147921107L; 9 //构造方法基本跟builder差不多的啦 10 public StringBuffer() { 11 super(16); 12 } 13 public StringBuffer(int capacity) { 14 super(capacity); 15 } 16 //获取字符串字符数量,效率低,对象锁,所以这样才会线程安全 17 @Override 18 public synchronized int length() { 19 return count; 20 } 21 // 获取容量,效率低--对象锁 22 @Override 23 public synchronized int capacity() { 24 return value.length; 25 } 26 //确保容量不小于minimumCapacity 27 @Override 28 public synchronized void ensureCapacity(int minimumCapacity) { 29 if (minimumCapacity > value.length) { 30 // 当最小容量值(传进来的参数值)大于value.length(这个其实就是容量),那么就直接扩容 31 expandCapacity(minimumCapacity); 32 } 33 } 34 //扩容,在它父类讲过了。 35 void expandCapacity(int minimumCapacity) { 36 int newCapacity = value.length * 2 + 2; 37 if (newCapacity - minimumCapacity < 0) 38 newCapacity = minimumCapacity; 39 if (newCapacity < 0) { 40 if (minimumCapacity < 0) // overflow 41 throw new OutOfMemoryError(); 42 newCapacity = Integer.MAX_VALUE; 43 } 44 value = Arrays.copyOf(value, newCapacity); 45 } 46 //将value数组中没有存入元素的部分去掉(类似去空格),此时容量大小和size大小相等。也就是删掉count后面的那堆空的东东 47 @Override 48 public synchronized void trimToSize() { 49 //用父类的。但线程安全 50 super.trimToSize(); 51 } 52 //setLength讲过了就省略了。注意它也是线程安全。扩充字符串容量到newLength,并且用空格填充 53 @Override 54 public synchronized void setLength(int newLength) { 55 toStringCache = null; 56 //调用父类函数 57 super.setLength(newLength); 58 } 59 //根据指定索引获取字符,效率慢,对象锁. 60 @Override 61 public synchronized char charAt(int index) { 62 if ((index < 0) || (index >= count)) 63 throw new StringIndexOutOfBoundsException(index); 64 return value[index]; 65 } 66 /** 67 * 根据索引修改字符串中某个字符值 68 */ 69 @Override 70 public synchronized void setCharAt(int index, char ch) { 71 if ((index < 0) || (index >= count)) 72 throw new StringIndexOutOfBoundsException(index); 73 toStringCache = null;//清除缓存,只要修改了value,此值就会clear 74 value[index] = ch; 75 } 76 //修改操作跟父类的唯一区别就是线程安全了:就不多贴了 77 @Override 78 public synchronized StringBuffer append(Object obj) { 79 toStringCache = null; 80 super.append(String.valueOf(obj)); 81 return this; 82 } 83 @Override 84 public synchronized StringBuffer append(String str) { 85 toStringCache = null; 86 super.append(str); 87 return this; 88 } 89 /** 90 * 不存在内存泄漏,实现了线程安全 91 */ 92 @Override 93 public synchronized String substring(int start) { 94 return substring(start, count); 95 //结果是new String(value, start, end - start);弄了个新对象的。 96 // 没有复用char[] 97 } 98 //有一套这样的同步与不同步方法 99 /** 100 * 此方法不同步, 而且也没有 toStringCache = null; 101 * 如果需要同步,那么需要将boolean b转化为specific type特定类型(String) 102 */ 103 @Override 104 public StringBuffer insert(int offset, boolean b) { 105 super.insert(offset, b); 106 return this; 107 } 108 @Override 109 public synchronized StringBuffer insert(int offset, char c) { 110 toStringCache = null; 111 super.insert(offset, c); 112 return this; 113 } 114 //toStringCache之前都不知道这个字段的含义,看到这里似乎看懂了,。是提高toString函数的效率,不用每次都是调用。也就是有做了一个缓存 115 // Arrays.copyOfRange。。。但是字符串修改后这个值需要clear 116 //线程安全 117 @Override 118 public synchronized String toString() { 119 if (toStringCache == null) { 120 toStringCache = Arrays.copyOfRange(value, 0, count); 121 } 122 return new String(toStringCache, true);//返回一个新的string对象过去 123 } 124 // 自定义序列化字段 125 //**transient 用于指定哪个字段不被默认序列化,如public transient int a; 126 //serialPersistentFields 用于指定哪些字段需要被默认序列化.如下: 127 private static final java.io.ObjectStreamField[] serialPersistentFields = 128 { 129 new java.io.ObjectStreamField("value", char[].class), 130 new java.io.ObjectStreamField("count", Integer.TYPE), 131 new java.io.ObjectStreamField("shared", Boolean.TYPE), 132 }; 133 /** 134 * 序列化大到ObjectOutputStream,写入了count和value、shared 135 */ 136 private synchronized void writeObject(java.io.ObjectOutputStream s) 137 throws java.io.IOException { 138 java.io.ObjectOutputStream.PutField fields = s.putFields(); 139 fields.put("value", value); 140 fields.put("count", count); 141 fields.put("shared", false); 142 s.writeFields(); 143 } 144 /** 145 * 反序列化到对象,读出count和value。 146 */ 147 private void readObject(java.io.ObjectInputStream s) 148 throws java.io.IOException, ClassNotFoundException { 149 java.io.ObjectInputStream.GetField fields = s.readFields(); 150 value = (char[])fields.get("value", null); 151 count = fields.get("count", 0); 152 } 153 }
3.字符串的注意点以及使用推荐;
1.效率问题:效率:StringBuilder>StringBuffer>String
1 public static void main(String[] args) { 2 long start = System.currentTimeMillis(); 3 String str = null; 4 for (int i = 0; i < 20000; i++) { 5 str = str + i + ",";//因为是不断弄出一个新的对象出来的 6 } 7 System.out.println("String耗时 "+ (System.currentTimeMillis() - start)); 8 System.out.println("-------------------"); 9 10 //buffer和builder都有自动扩容机制,不像string有两大缺点:1.返回对象使用大量new操作,产生很多垃圾;2.虽然最终调用的是系统复制数组操作,但调用之前开销非常大,只能靠复制来解决拼接问题。 11 12 start = System.currentTimeMillis(); 13 StringBuffer buffer = new StringBuffer(); 14 for (int i = 0; i < 20000; i++) { 15 buffer.append(i + ",");//线程安全所以慢一点,但前提这里是单线程 16 } 17 System.out.println("StringBuffer耗时 "+(System.currentTimeMillis() - start)); 18 System.out.println("-------------------"); 19 start = System.currentTimeMillis(); 20 StringBuilder builder = new StringBuilder(); 21 for (int i = 0; i < 20000; i++) { 22 builder.append(i + ",");//线程不安全,效率最高 23 } 24 System.out.println("StringBuilder耗时 "+(System.currentTimeMillis() - start)); 25 } 26 /* 27 String耗时 2030 28 ------------------- 29 StringBuffer耗时 5 30 ------------------- 31 StringBuilder耗时 3 32 */
2.线程安全与线程不安全问题
1 public class StringBuilderTest { 2 public static void main(String[] args) { 3 /* 4 * 声明个字符串s,用下划线和井号是因为两个比较好区分。 分别实例化StringBuffer和StringBuilder两个对象 5 */ 6 String s = "####____"; 7 StringBuffer sf = new StringBuffer(s); 8 StringBuilder sd = new StringBuilder(s); 9 /* 10 * 对sf和sd各自实例化两个反转他们的类 11 */ 12 // ThreadString sfr1 = new ThreadString(sf); 13 // ThreadString sfr2 = new ThreadString(sf); 14 ThreadString sdr1 = new ThreadString(sd); 15 ThreadString sdr2 = new ThreadString(sd); 16 /* 17 * 启动这四个线程,此时sf和sd各自有两个线程在对他们进行字符串反转操作 18 */ 19 // new Thread(sfr1).start(); 20 // new Thread(sfr2).start(); 21 new Thread(sdr1).start(); 22 new Thread(sdr2).start(); 23 } 24 } 25 class ThreadString implements Runnable { 26 /* 27 * 这个类用来完成字符串的反转工作,使用了Runnable接口来实现多线程 times是用来表示循环多少次的 28 * 因为懒的再写一个变量所以用了一个Object类型的s,后面再转化 29 */ 30 public Object s = null; 31 int times = 10; 32 33 /* 34 * 两个构造方法把s传进来 35 */ 36 public ThreadString(StringBuffer s) { 37 this.s = s; 38 } 39 40 public ThreadString(StringBuilder s) { 41 this.s = s; 42 } 43 44 /* 45 * 复写run方法实现多线程 在我的电脑上大概循环十几次可以看到效果了 46 */ 47 public void run() { 48 for (int i = 0; i <= times; i++) { 49 //sleep一下让输出更加清晰 50 try { 51 Thread.sleep(1); 52 } catch (InterruptedException e) { 53 // TODO Auto-generated catch block 54 e.printStackTrace(); 55 } 56 57 if (s instanceof StringBuffer) { 58 ((StringBuffer) s).reverse();//直接调用翻转的方法 59 System.out.println("BUFFER->" + s); 60 } else if (s instanceof StringBuilder) { 61 ((StringBuilder) s).reverse();//直接调用翻转的方法 62 System.out.println(" " + s + "<-Builder"); 63 } 64 System.out.println(Thread.currentThread().getName());//输出下线程名字更加清晰啦 65 System.out.println("-------------------"); 66 } 67 } 68 } 69 /* 70 * 最后看一下控制台的输出会发现反转后出现井号和下划线交错的都是StringBuilder的输出 71 */ 72 73 /* 74 我们输出的时候会发现,Builder在一次线程操作中甚至可以这样反转,但是Buffer就可观察到没有这样的抢占修改,是十分有序地修改。 75 ------------------- 76 ####____<-builder 77 ____####<-builder 78 Thread-1 79 ------------------- 80 */
使用注意点总结:
1. 如果要操作少量的数据用 = String
2. 单线程操作字符串缓冲区下操作大量数据 –StringBuilder3
3. 多线程操作字符串缓冲区 下操作大量数据 – StringBuffer