深入Java基础——字符串

Posted huster-田浪

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入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

 

以上是关于深入Java基础——字符串的主要内容,如果未能解决你的问题,请参考以下文章

《Java架构筑基》从Java基础讲起——String类深入理解

深入Java基础——基本数据类型及其包装类

片段(Java) | 机试题+算法思路+考点+代码解析 2023

Java线程池详解

《Java架构筑基》从Java基础讲起——深入理解Static

Java详解:从基础到深入案例