Java常用类:字符串相关的类

本文详细讲解了Java中的String类特性、实例化、拼接技巧,以及StringBuffer和StringBuilder的区别与使用场景,包括内存分配、算法题解等内容,适合理解字符串操作和并发编程的读者。
摘要由CSDN通过智能技术生成

String类

1.String的特性

String,代表字符串。是特殊的引用类型,因为它可以为常量:String s = "hello";

String类:jdk8源码:

//jdk8中源码:
public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    
    private final char value[];
    
    ...
}

String类的特性

  1. 它代表一个不可变的字符序列(不可变性)。体现
    1. 是一个final类:不可被继承。
    2. String对象的字符内容是存储在一个字符数组value[]中的。且这个字符数组为final的。
    3. 具体说明:
      1. 当对字符串重新赋值时,需要重新指定内存区域赋值,不能使用原有的value[]进行赋值
      2. 当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值
      3. 当调用String 的 replace()方法修改指定字符或字符串时,也需要重新指定内存区域赋值
      4. 通过字面量的方式(区别于new)给一个字符串赋值,此时的字符串值声明在方法区的字符串常量池中,字符串常量池中是不会存储相同内容的字符串的。
  2. 实现了Serializable接口:表示字符串支持序列化的。
  3. 实现了Comparable接口:表示String可以比较大小。
  4. String类型的变量可以存储三种类型:
    1. 数据值:String s = "abc";(也称abc为字面量)(内存:方法区常量池)
    2. 地址值:String s = new String();(内存:堆空间)
    3. null:String s = null(内存:不是方法区,也不是堆空间)

2.String的实例化

创建String的方式

  1. 使用一对"":此时创建的是常量

    String s = 4;//编译报错
    
    String s = "";//编译通过,可以为空
    
    String str = "hello";
    
    • 内存解析
      在这里插入图片描述

      字符串常量池中是不会存储相同内容的字符串的。

  2. 通过new调用String类的构造器 :此时创建的是对象

    String s1 = new String(); //底层上 this.value = new char[0]
    
    String s2 = new String(String original); // this.value = original.value
    
    String s3 = new String(char[] a);  //this.value = Arrays.copyOf(value,value.length);
    
    String s4 = new String(char[] a, int startIndex, int count);
    
    ...其他重载的构造器
    
    1. 内存解析
      在这里插入图片描述

两方式的内存对比:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

JVM中字符串常量池的位置:

  • jdk6:方法区(永久代)
  • jdk7:堆空间
  • jdk8:方法区(元空间)

面试题:

  1. String s = new String("abc");这样创建对象,在内存中创建了几个对象?

    答:两个。一个是堆空间中new的对象;另一个是char[]对应的常量池中的数据:“abc”(当然,如果常量池已有“abc”,则不会再创建)。

3.String的拼接

  • 一个字符串可以串接另一个字符串,也可以直接串接8种基本数据类型的数据。换句话说:做 + 运算(拼接运算),运算的结果都是String类型,例:

    str = str + "xyz";
    int n = 100;
    str = str + n;//xyz100
    
    boolean b1 = true;
    String str2 = b1 + str;//truexyz100
    
  • 注意点:

    • 注意拼接运算不要和基本数据类型的加法运算搞混。
    练习1
    char c = 'a';
    int num = 10;
    String str = "hello";
    System.out.println(c + num + str);//107hello
    System.out.println(c + str + num);//ahello10
    System.out.println(c + (num + str);//a10hello
    System.out.println((c + num) + str);//107hello
    System.out.println(str + num + c);//hello10a
                       
    练习2
    System.out.println("*	*");//*	  *
    System.out.println('*' + '\t' + '*');//93
    System.out.println('*' + "\t" + '*');//*	*
    System.out.println('*' + '\t' + "*");//51*
    System.out.println('*' + ('\t' + "*"));//*	  *
    
    • 一般转换不用拼接的方式,因为效率低。
  • String不同拼接方式的对比

String s1 = "javaEE";
String s2 = "hadoop";

String s3 = "javaEEhadoop";
String s4 = "javaEE" + "hadoop";
String s5 =  s1 + "hadoop";
String s6 = "javaEE" + s2;
String s7 = s1 + s2;

System.out.println(s3 == s4);//true
System.out.println(s3 == s5);//false
System.out.println(s3 == s6);//false
System.out.println(s5 == s6);//false
System.out.println(s3 == s7);//false
System.out.println(s6 == s7);//false

String s8 = s5.intern();
/**
intern() --->  是一个本地方法(由Java语言外的语言编写)。
使用intern(),当常量池中不存在“javaEEhadoop”这个字符串的引用,将这个字符串加入常量池,返回这个字符串的引用。当常量池存在“javaEEhadoop”这个字符串的引用时,返回这个字符串的引用。
*/
System.out.println(s3 == s8);//true
  • 内存解析:
    在这里插入图片描述

    • 结论

      1. 常量与常量的拼接结果在常量池。且常量池中不会存在相同内容的常量。
      2. 只要其中有一个是变量,结果就在堆中
      3. 如果拼接的结 果调用intern()方法,返回值就在常量池中
    • 例题:

      1. String s1 = "javaEEhadoop";
        String s2 = "javaEE";
        String s3 = s2 + "hadoop";
        System.out.println(s1 == s3);//false
        
        final String s4 = "javaEE";//s4:常量。
        String s5 = s4 + "hadoop";//此时相当于 s5 = "javaEE" + "hadoop"; 指向常量池
        System.out.println(s1 == s5);//true
        

4.String例题

String使用陷阱:

  • String s1 = “a”;

说明:在字符串常量池中创建了一个字面量为"a"的字符串。

  • s1 = s1 + “b”;

说明:实际上原来的“a”字符串对象已经丢弃了,现在在堆空间中产生了一个字符串s1+“b”(也就是"ab")。如果多次执行这些改变串内容的操作,会导致大量副本字符串对象存留在内存中,降低效率。如果这样的操作放到循环中,会极大影响程序的性能。

  • String s2 = “ab”;

说明:直接在字符串常量池中创建一个字面量为"ab"的字符串。

  • String s3 = “a” + “b”;

说明:s3指向字符串常量池中已经创建的"ab"的字符串。

  • String s4 = s1.intern();

说明:堆空间的s1对象在调用intern()之后,会将常量池中已经存在的"ab"字符串赋值给s4。

面试题:

下列程序运行的结果:

public class StringTest {
    String str = new String("good");
    char[] ch = { 't', 'e', 's', 't' };
    public void change(String str, char ch[]) {
        str = "test ok";
        ch[0] = 'b'; 
    }
    public static void main(String[] args) {
        StringTest ex = new StringTest();
        ex.change(ex.str, ex.ch);
        System.out.print(ex.str + " and ");//good and
        /*
        涉及到值传递:
        注意实参ex.str和形参str是两个不同的变量,只不过ex.str存的地址引用传给了str,所以它们存的是同一份地址。方法中,str更改其存的值为“test ok",并没有改变地址对应的对象的内容。所以ex.str并没有受影响。
        */
        System.out.println(ex.ch);//best
    } 
}

5.String类的常用方法

  1. int length():返回字符串的长度: return value.length

  2. char charAt(int index):返回某索引处的字符return value[index]

  3. boolean isEmpty():判断是否是空字符串:return value.length == 0

  4. String toLowerCase():使用默认语言环境,将 String 中的所有字符转换为小写

  5. String toUpperCase():使用默认语言环境,将 String 中的所有字符转换为大写

  6. String trim():返回字符串的副本,忽略前导空白和尾部空白

  7. boolean equals(Object obj):比较字符串的内容是否相同

  8. boolean equalsIgnoreCase(String anotherString):与equals方法类似,忽略大小写

  9. String concat(String str):将指定字符串连接到此字符串的结尾。 等价于用“+”

  10. int compareTo(String anotherString):比较两个字符串的大小

  11. String substring(int beginIndex):返回一个新的字符串,它是此字符串的从beginIndex开始截取到最后的一个子字符串。

  12. String substring(int beginIndex, int endIndex) **:**返回一个新字符串,它是此字符串从beginIndex开始截取到endIndex(不包含)的一个子字符串。

  13. boolean endsWith(String suffix):测试此字符串是否以指定的后缀结束

  14. boolean startsWith(String prefix):测试此字符串是否以指定的前缀开始

  15. boolean startsWith(String prefix, int toffset):测试此字符串从指定索引开始的子字符串是否以指定前缀开始

  16. boolean contains(CharSequence s):当且仅当此字符串包含指定的 char 值序列时,返回 true

    1. String s = "hello";
      s.contains("he");
      
  17. **int indexOf(String str):**返回指定子字符串在此字符串中第一次出现处的索引

  18. int indexOf(String str, int fromIndex):返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始

  19. **int lastIndexOf(String str):**返回指定子字符串在此字符串中最右边出现处的索引

  20. int lastIndexOf(String str, int fromIndex):返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索

    • 注:indexOf和lastIndexOf方法如果未找到都是返回-1

    • 什么情况下,indexOf(str)lastIndexOf(str)返回值相同?
          答:①存在唯一的一个str。② 不存在str
      
  21. String replace(char oldChar, char newChar):返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。

  22. String replace(CharSequence target, CharSequence replacement):使用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串。

  23. String replaceAll(String regex, String replacement) 使 用 给 定 的replacement 替换此字符串所有匹配给定的正则表达式的子字符串。

    • String str = "12hello34world5java7891mysql456";
      //把字符串中的数字替换成,,如果结果中开头和结尾有,的话去掉
      String string = str.replaceAll("\\d+", ",").replaceAll("^,|,$", "");
      System.out.println(string);
      
  24. String replaceFirst(String regex, String replacement) 使 用 给 定 的replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。

  25. boolean matches(String regex):告知此字符串是否匹配给定的正则表达式。

    • String str = "12345";
      //判断str字符串中是否全部有数字组成,即有1-n个数字组成
      boolean matches = str.matches("\\d+");
      System.out.println(matches);
      String tel = "0571-4534289";
      //判断这是否是一个杭州的固定电话
      boolean result = tel.matches("0571-\\d{7,8}");
      System.out.println(result);
      
  26. **String[] split(String regex):**根据给定正则表达式的匹配拆分此字符串。

    • String str = "hello|world|java";
      String[] strs = str.split("\\|");
      for (int i = 0; i < strs.length; i++) {
      	System.out.println(strs[i]);
      }
      System.out.println();
      String str2 = "hello.world.java";
      String[] strs2 = str2.split("\\.");
      for (int i = 0; i < strs2.length; i++) {
      	System.out.println(strs2[i]);
      }
      
  27. **String[] split(String regex, int limit):**根据匹配给定的正则表达式来拆分此字符串,最多不超过limit个,如果超过了,剩下的全部都放到最后一个元素中。

6.String与其他类型的转换

  1. 字符串 ——》 基本数据类型、包装类

    1. Integer包装类的public static int parseInt(String s):可以将由“数字”字符组成的字符串转换为整型。

    2. 类似地,使用java.lang包中的Byte、Short、Long、Float、Double类调相应的类方法可以将由“数字”字符组成的字符串,转化为相应的基本数据类型。

  2. 基本数据类型、包装类 ——》 字符串

    1. 调用String类的public String valueOf(int n)可将int型转换为字符串
  3. 调用String类相应的valueOf(byte b)、valueOf(long l)、valueOf(float f)、valueOf(double d)、valueOf(boolean b)可由参数的相应类型到字符串的转换

  4. 使用拼接运算(+

  5. 字符数组 ——》 字符串

    1. String 类的构造器:String(char[]) 和 String(char[],int offset,int length) 分别用字符数组中的全部字符和部分字符创建字符串对象。
  6. 字符串 ——》 字符数组

    1. public char[] toCharArray():将字符串中的全部字符存放在一个字符数组中的方法。

    2. public void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin):提供了将指定索引范围内的字符串存放到数组中的方法。

  7. 字节数组 ——》 字符串

    1. String(byte[]):通过使用平台的默认字符集解码指定的 byte 数组,构造一个新的 String。

    2. String(byte[],int offset,int length) **:**用指定的字节数组的一部分,即从数组起始位置offset开始取length个字节构造一个字符串对象。

  8. 字符串 ——》 字节数组

    • 编码:字符串 ——> 字节
    • 解码:字节 ——> 字符串
    1. public byte[] getBytes()**:**使用平台的默认字符集将此 String 编码为byte 序列,并将结果存储到一个新的 byte 数组中。
    2. public byte[] getBytes(String charsetName) **:**使用指定的字符集将 此 String 编码到 byte 序列,并将结果存储到新的 byte 数组。
    String str1 = "我爱你中国";
    byte[] gbks = null;
    try {
    	gbks = str1.getBytes("gbk");
    } catch (UnsupportedEncodingException e) {
    	e.printStackTrace();
    }//使用gbk字符集进行编码。
    System.out.println(Arrays.toString(gbks));
    System.out.println("************************");
    String str2 = new String(gbks);//使用默认是字符集,进行解码
    System.out.println(str2);
    System.out.println("***************************");
    byte[] b = str1.getBytes();//使用默认字符集编码
    String str3 = new String(b);
    System.out.println(str3);
    /*
    运行结果:
    [-50, -46, -80, -82, -60, -29, -42, -48, -71, -6]
    ************************
    �Ұ����й�
    ***************************
    我爱你中国
    */
    //解码时,要求解码使用的字符集必须与编码时使用的字符集一致,否则出现乱码。
    

7.常见算法题目

  1. 模拟一个trim方法,去除字符串两端的空格。

    1. 	public String myTrim(String str) {
         		if (str != null) {
         			int start = 0;// 用于记录从前往后首次索引位置不是空格的位置的索引
         			int end = str.length() - 1;// 用于记录从后往前首次索引位置不是空格的位置的索引
         
         			while (start < end && str.charAt(start) == ' ') {
         				start++;
         			}
         
         			while (start < end && str.charAt(end) == ' ') {
         				end--;
         			}
         			if (str.charAt(start) == ' ') {
         				return "";
         			}
         
         			return str.substring(start, end + 1);
         		}
         		return null;
         	}
      
  2. 将一个字符串进行反转。将字符串中指定部分进行反转。比如“abcdefg”反转为”abfedcg”

    1. 	// 方式一:
         	public String reverse1(String str, int start, int end) {// start:2,end:5
         		if (str != null) {
         			// 1.
         			char[] charArray = str.toCharArray();
         			// 2.
         			for (int i = start, j = end; i < j; i++, j--) {
         				char temp = charArray[i];
         				charArray[i] = charArray[j];
         				charArray[j] = temp;
         			}
         			// 3.
         			return new String(charArray);
         
         		}
         		return null;
         
         	}
         
         	// 方式二:
         	public String reverse2(String str, int start, int end) {
         		// 1.
         		String newStr = str.substring(0, start);// ab
         		// 2.
         		for (int i = end; i >= start; i--) {
         			newStr += str.charAt(i);
         		} // abfedc
         			// 3.
         		newStr += str.substring(end + 1);
         		return newStr;
         	}
         
         	// 方式三:推荐 (相较于方式二做的改进)
         	public String reverse3(String str, int start, int end) {// ArrayList list = new ArrayList(80);
         		// 1.
         		StringBuffer s = new StringBuffer(str.length());
         		// 2.
         		s.append(str.substring(0, start));// ab
         		// 3.
         		for (int i = end; i >= start; i--) {
         			s.append(str.charAt(i));
         		}
         
         		// 4.
         		s.append(str.substring(end + 1));
         
         		// 5.
         		return s.toString();
         
         	}
         
         	@Test
         	public void testReverse() {
         		String str = "abcdefg";
         		String str1 = reverse3(str, 2, 5);
         		System.out.println(str1);// abfedcg
         
         	}
      
  3. 获取一个字符串在另一个字符串中出现的次数。比如:获取“ ab”在 “abkkcadkabkebfkabkskab” 中出现的次数

    1. 	// 判断str2在str1中出现的次数
         	public int getCount(String mainStr, String subStr) {
         		if (mainStr.length() >= subStr.length()) {
         			int count = 0;
         			int index = 0;
         			// while((index = mainStr.indexOf(subStr)) != -1){
         			// count++;
         			// mainStr = mainStr.substring(index + subStr.length());
         			// }
         			// 改进:
         			while ((index = mainStr.indexOf(subStr, index)) != -1) {
         				index += subStr.length();
         				count++;
         			}
         
         			return count;
         		} else {
         			return 0;
         		}
         
         	}
         
         	@Test
         	public void testGetCount() {
         		String str1 = "cdabkkcadkabkebfkabkskab";
         		String str2 = "ab";
         		int count = getCount(str1, str2);
         		System.out.println(count);
         	}
         
         	@Test
         	public void testMyTrim() {
         		String str = "   a   ";
         		// str = " ";
         		String newStr = myTrim(str);
         		System.out.println("---" + newStr + "---");
         	}
      
  4. 获取两个字符串中最大相同子串。比如:str1 = "abcwerthelloyuiodef“;str2 = “cvhellobnm”。提示:将短的那个串进行长度依次递减的子串与较长的串比较。

    1. 	// 如果只存在一个最大长度的相同子串
         	public String getMaxSameSubString(String str1, String str2) {
         		if (str1 != null && str2 != null) {
         			String maxStr = (str1.length() > str2.length()) ? str1 : str2;
         			String minStr = (str1.length() > str2.length()) ? str2 : str1;
         
         			int len = minStr.length();
         
         			for (int i = 0; i < len; i++) {// 0 1 2 3 4 此层循环决定要去几个字符
         
         				for (int x = 0, y = len - i; y <= len; x++, y++) {
         
         					if (maxStr.contains(minStr.substring(x, y))) {
         
         						return minStr.substring(x, y);
         					}
         
         				}
         
         			}
         		}
         		return null;
         	}
         
         	// 如果存在多个长度相同的最大相同子串
         	// 此时先返回String[],后面可以用集合中的ArrayList替换,较方便
         	public String[] getMaxSameSubString1(String str1, String str2) {
         		if (str1 != null && str2 != null) {
         			StringBuffer sBuffer = new StringBuffer();
         			String maxString = (str1.length() > str2.length()) ? str1 : str2;
         			String minString = (str1.length() > str2.length()) ? str2 : str1;
         
         			int len = minString.length();
         			for (int i = 0; i < len; i++) {
         				for (int x = 0, y = len - i; y <= len; x++, y++) {
         					String subString = minString.substring(x, y);
         					if (maxString.contains(subString)) {
         						sBuffer.append(subString + ",");
         					}
         				}
         				System.out.println(sBuffer);
         				if (sBuffer.length() != 0) {
         					break;
         				}
         			}
         			String[] split = sBuffer.toString().replaceAll(",$", "").split("\\,");
         			return split;
         		}
         
         		return null;
         	}
         	// 如果存在多个长度相同的最大相同子串:使用ArrayList
      //	public List<String> getMaxSameSubString1(String str1, String str2) {
      //		if (str1 != null && str2 != null) {
      //			List<String> list = new ArrayList<String>();
      //			String maxString = (str1.length() > str2.length()) ? str1 : str2;
      //			String minString = (str1.length() > str2.length()) ? str2 : str1;
      //
      //			int len = minString.length();
      //			for (int i = 0; i < len; i++) {
      //				for (int x = 0, y = len - i; y <= len; x++, y++) {
      //					String subString = minString.substring(x, y);
      //					if (maxString.contains(subString)) {
      //						list.add(subString);
      //					}
      //				}
      //				if (list.size() != 0) {
      //					break;
      //				}
      //			}
      //			return list;
      //		}
      //
      //		return null;
      //	}
      
      	@Test
      	public void testGetMaxSameSubString() {
      		String str1 = "abcwerthelloyuiodef";
      		String str2 = "cvhellobnmiodef";
      		String[] strs = getMaxSameSubString1(str1, str2);
      		System.out.println(Arrays.toString(strs));
      	}
      
  5. 对字符串中字符进行自然顺序排序。提示:

    1. 字符串变成字符数组。
    2. 对数组排序,选择,冒泡,(Arrays.sort()😉
    3. 将排序后的数组变成字符串。
    	public void testSort() {
    		String str = "abcwerthelloyuiodef";
    		char[] arr = str.toCharArray();
    		Arrays.sort(arr);
    
    		String newStr = new String(arr);
    		System.out.println(newStr);
    	}
    

StringBuffer、StringBuilder类

  • 概念:

    • String是代表不可变的字符序列,jdk也提供了可变的字符序列,可以对字符串内容进行增删,此时不会产生新的对象。即 jdk1.0开始有StringBuffer,jdk5.0新增了StringBuilfer。
  • 特点:

    1. StringBuffer、StringBuilder有共同的父类AbstractStringBuilder。AbstractStringBuilder是一个抽象类,内部真正提供了 可变的字符序列的实现,但我们只能通过使用它的子类来使用它。

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XbRhzQGM-1627114825359)(D:\笔记\typora_images\image-20210420134134757.png)]

    2. ,它的两个子类的方法都是通过super使用父类的实现,结构基本一致。只不过StringBuffer类将这些方法都声明为 synchronized 的,保证了线程安全,但不支持多线程,某些程度上降低了效率。而 StringBuilder 没有这么做,所以它的线程不安全的,多线程时效率相对StringBuffer较高。总的来说,考虑线程安全,建议使用StringBuffer,不考虑线程安全,建议使用StringBuilder

      1. //StringBuffer类中
        public synchronized StringBuffer append(String str) {
            toStringCache = null;
            super.append(str);//真正功能实现都写在父类(AbstractStringBuilder)中。
            return this;
        }
        
    3. 很多方法与String相同

    4. 不同于String,它们对象必须使用构造器生成。以StringBuffer为例有三个构造器:

      1. StringBuffer()**:**初始容量为16的字符串缓冲区。通俗讲:底层创建了长度为16的char[] value
      2. StringBuffer(int size):构造指定容量的字符串缓冲区。通俗讲:底层创建指定容量的字符数组
      3. StringBuffer(String str):将内容初始化为指定字符串内容。底层创建的数组长度为str.length + 16
  • 常用方法:(方法基本都在父类实现)

    1. append(xxx):重载了很多的append()方法,用于进行字符串拼接
    2. delete(int start,int end):删除指定位置的内容
    3. replace(int start, int end, String str):把[start,end)位置替换为str
    4. insert(int offset, xxx):在指定位置插入xxx
    5. reverse() :把当前字符序列逆转
    6. public int indexOf(String str)
    7. public String substring(int start,int end)
    8. public int length():返回字符串的长度,即返回count。注意不是返回底层数组value的长度。
    9. public char charAt(int n )
    10. public void setCharAt(int n ,char ch)
  • 可变的字符序列的体现:

    1. 底层用 char[] value 存储,声明在它的抽象父类 AbstractStringBuilder中。不同于String,这里的value是可变的。

      abstract class AbstractStringBuilder implements Appendable, CharSequence {
          /**
           * The value is used for character storage.
           */
          char[] value; //没有加final,可以扩容
      
          /**
           * The count is the number of characters used.
           */
          int count; //记录字符个数(字符串长度)
      
      • 当append()和insert()时,如果原来value数组长度不够,可扩容。
    2. 对于字符串的操作的方法支持方法链操作。

      • 方法链的原理:

        [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MVISdX2O-1627114825359)(D:\笔记\typora_images\image-20210420135010942.png)]

  • 扩容机制原理

    • 若新增的字符串长度,超过底层数组的剩余容量,则扩容:默认情况下扩容长度至原来数组的2倍加2,如果还不满足则扩容为原来数组的长度+新增字符串的长度。如果扩容后容量超出Integer.MAX_VALUE则报异常。
      原有数组的元素会复制到扩容后的数组中,扩容后数组继续执行添加操作。
    • 底层代码:jdk8,以 append(String str) 为例
    1. //AbstractStringBuilder类中,真正提供实现
      public AbstractStringBuilder append(String str) {
              if (str == null)
                  return appendNull();//处理str为空的情况
              int len = str.length();
              ensureCapacityInternal(count + len);//保证内部数组的容量足够存储str
              str.getChars(0, len, value, count);
              count += len;
              return this;
      }
      
      private AbstractStringBuilder appendNull() {//处理字符串为空的情况:拼接一个 null
              int c = count;//count是类的属性,用于记录字符串的字符个数
              ensureCapacityInternal(c + 4);
              final char[] value = this.value;
              value[c++] = 'n';
              value[c++] = 'u';
              value[c++] = 'l';
              value[c++] = 'l';
              count = c;
              return this;
      }	
      
      private void ensureCapacityInternal(int minimumCapacity) {
              // overflow-conscious code
              if (minimumCapacity - value.length > 0) {//与底层数组长度比较,如果超出,则创建新的长度的数组覆盖原来的数组。(原来数组的数据会复制到新的数组中)
                  value = Arrays.copyOf(value,
                          newCapacity(minimumCapacity));
              }
      }
      
      private int newCapacity(int minCapacity) {//扩容后数组的长度
              // overflow-conscious code
              int newCapacity = (value.length << 1) + 2;//扩容为原来的二倍加2
              if (newCapacity - minCapacity < 0) {//如果还不够,则扩容为存的字符串的长度+原来数组的长度
                  newCapacity = minCapacity;
              }
          	
          	//如果扩容后数值超过MAX_ARRAY_SIZE,或者<<运算溢出导致结果为负数。
              return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
                  ? hugeCapacity(minCapacity)
                  : newCapacity;
      }
      
      private int hugeCapacity(int minCapacity) {//确定最大容量
              if (Integer.MAX_VALUE - minCapacity < 0) { // overflow //长度超过int型的最大值,报异常
                  throw new OutOfMemoryError();
              }
              return (minCapacity > MAX_ARRAY_SIZE) //长度超过MAX_ARRAY_SIZE,以该长度(存的字符串的长度)为数值的长度,否则以MAX_ARRAY_SIZE为数组的长度。
                  ? minCapacity : MAX_ARRAY_SIZE;
      }
      
      private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
      
    • 指导意义:
      1. 开发中推荐大家使用指定容量的构造器创建StringBuffer或StringBuilder,避免使用过程中执行多次扩容机制,浪费资源。

面试题

String , StringBuffer , StringBuilder的异同

    • 都表示字符串
    1. String(jdk1.0):不可变的字符序列,底层用char[]存储并且是final的
    2. StringBuffer(jdk1.0):可变的字符序列,线程安全的,效率低些(对于多线程操作),底层用char[]存储且不是final
    3. StringBuilder(jdk5.0):可变的字符序列,JDK 5.0新增,线程不安全的,效率高些((对于多线程操作)),底层用char[]存储且不是final的
  • 注意:作为参数传递的话,方法内部String不会改变其值,StringBuffer和StringBuilder会改变其值。

  • 使用建议:

    • 当使用多变的字符串时,建议使用可变字符序列,字符串不要求多变时,建议使用不可变字符序列
  • 使用可变字符序列:

    • 当作为共享数据被多线程操作时,建议使用 StringBuffer
    • 当不作为多线程操作的共享数据时,建议使用 StringBuilder
  • 对比三者的效率:从高到低

    • StringBuilder > StringBuffer > String

    • 三者的效率测试:

      //初始设置
      long startTime = 0L;
      long endTime = 0L;
      String text = "";
      StringBuffer buffer = new StringBuffer("");
      StringBuilder builder = new StringBuilder("");
      //开始对比
      startTime = System.currentTimeMillis();
      for (int i = 0; i < 20000; i++) {
      	buffer.append(String.valueOf(i));
      }
      endTime = System.currentTimeMillis();
      System.out.println("StringBuffer的执行时间:" + (endTime - startTime));
      startTime = System.currentTimeMillis();
      for (int i = 0; i < 20000; i++) {
      	builder.append(String.valueOf(i));
      }
      endTime = System.currentTimeMillis();
      System.out.println("StringBuilder的执行时间:" + (endTime - startTime));
      startTime = System.currentTimeMillis();
      for (int i = 0; i < 20000; i++) {
      	text = text + i; 
      }
      endTime = System.currentTimeMillis();
      System.out.println("String的执行时间:" + (endTime - startTime));
      

源码分析

//String:不可变的字符序列
String str = new String();//底层:new char[0]
String srt1 = new String("abc"); //底层:char[] value = new char[]{'a','b','c'};

//StringBuffer、StringBuilder:可变的字符序列。两者的底层原理是一致的,下面以StringBuffer为例:
StringBuffer sb1 = new StringBuffer();//char[] value = new char[16]; 底层默认创建了一个长度是16的数组
sb1.append('a');//value[0] = 'a';
sb1.append('b');//value[1] = 'b';

StringBuffer sb2 = new StringBuffer("abc");//底层:char[] value = new char["abc".length() + 16];

/** 
问题1:System.out.println(sb2.length());//3
答:内部有属性 count,用于记录字符串的长度,length()返回count,而不是底层数组value的长度。
*/
/**
问题2:扩容问题:如果要添加的数据底层数组盛不下了,则需要扩容底层的数组
答:默认情况下,扩容创建新的数组为原来容量的2倍+2,同时将原有数组中的元素复制到新的数组中
*/

题目

  1. 程序输出

    String str = null;
    StringBuffer sb = new StringBuffer();
    sb.append(str);
    System.out.println(sb.length());//
    System.out.println(sb);//
    StringBuffer sb1 = new StringBuffer(str);
    System.out.println(sb1);//
    
  2. 程序输出

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值