Java12常用类2:String类、StringBuffer和StringBuilder

2.String类

特点不可变性 final-操作量较少
String类、StringBuilder类、StringBuffer类是三个字符串相关类。
String类的对象代表不可变的字符序列,StringBuilder类和StringBuffer类代表可变字符序列

2.1 String类的理解

2.1.1. 类的声明

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {}

final:String类是不可以被继承的
Serializable:可序列化接口。只要是实现此接口的对象就可以通过网络 或本地流进行数据的传输;
Comparable:只要是实现此接口的类,其对象都可以比较大小。

2.1.2. 内部声明的属性

/**Jdk1.8中:
1.存储字符串数据的容器
2.final:指明此 value数组一旦初始化,其地址就不可变。 */
private final char value[];

/**Jdk1.9开始:为了节省内部空间
1.存储字符串数据的容器 */
private final byte value[];	

2.2 String的内存结构

字符串常量都存储在字符串常量池(StringTable)中;
字符串常量池:不允许存放两个相同的字符串常量
字符串常量池:在不同 Jdk 版本中存储位置不同:
(1)jdk1.7之前: 字符串常量池存放在方法区(jdk1.8称其为元空间
(2)jdk1.7之后(包括jdk1.7):存放在空间

2.2.1 不可变性的理解

package com.kString;
import org.junit.Test;
public class String0 {
    @Test
    public void test1() {
        String str1 = "hello";// 字面量的定义
        String str2 = "hello";
        // 字面量相同时使用同一个对象,即引用的地址相同
        System.out.println(str1 == str2);// 结果,true
    }

    @Test
    public void test2() {
        /**
         * String的不可变性
         * ①当对字符串变量重新赋值时,需要重新指定一个字符串常量的
         * 位置进行赋值,不能在原有的位置修改;
         */
        String str1 = "hello";
        String str2 = "hello";

        str2 = "hi";
        System.out.println(str1);// 结果,hello
        System.out.println(str2);// 结果,hi
    }

    @Test
    public void test3() {
        /**
         * String的不可变性;
         * ②在对现有字符串进行拼接操作时,需要重新开辟空间保存拼接
         * 以后的字符串,不能在原有的位置修改;
         */
        String str1 = "hello";
        String str2 = "hello";

        /* 在`堆`中开辟了一处新空间,str2指向这个新空间
        String str2 = new String();
        str2 = str2 + " hi";
         */
        str2 += " hi";
        System.out.println(str1);// 结果,hello
        System.out.println(str2);// 结果,hello hi
    }

	/**
	* String的不可变性;
	* ③当调用字符串的 replace()替换现有的字符时,需要重新开辟空间后,
 	* 保存修改后的字符串,不能在原有的位置修改;*/
    @Test
    public void test4() {
        String str1 = "hello";
        String str2 = "hello";
        /* 取代:replace(char oldChar, char newChar)
        返回的是一个新的 字符串*/
        String str3 = str2.replace('l', 'o');
        System.out.println(str1);//hello
        System.out.println(str2);//hello
        System.out.println(str3);//heooo
    }
}

不可变性练习

    String str="good";
    char[] ch = {'t','e','s','t'};
    // 传参时:基本数据类型传的是值,引用数据类型传的是地址
    public void change(String str, char[] ch){
        // 这里是 new了一个对象用来存储改变后的值
        str="test ok";
        System.out.println(str);// 结果,test ok
        ch[0]='b';
    }
    @Test
    public void test10(){
        String0 s0 = new String0();
        s0.change(s0.str,s0.ch);
        System.out.println(s0.str);// 结果,good
        System.out.println(s0.ch);// 结果,best
    }

2.2.2 String的2种实例化方式

String实例化的两种方式:
(1) String str1 = “hello”;
(2) String str2 = new String(“hello”);

package com.kString;
import org.junit.Test;
public class String0 {
    @Test
    public void test5() {
        String str1 = "hello";
        String str2 = "hello";
        String s1 = new String("hello");
        String s2 = new String("hello");

        //==:比较对象时是地址; equals比较的是内容
        System.out.println(str1 == str2);//true
        System.out.println(str1 == s1);//false
        System.out.println(s1 == s2);//false
        System.out.println(str1.equals(s1));//true
        System.out.println(s1.equals(s2));//true
    }
}

思考:String s1 = new String(“hello”); 在内存中创建了几个对象?
两个对象:①在堆空间 new的对象;②在字符串常量池中生成的字面量

package com.kString;

import org.junit.Test;

class Person{
    String name;
}z
public class String0 {
    @Test
    public void test6() {
        Person p1 = new Person();
        Person p2 = new Person();
        
        /*
        ① p1和 p2在堆中的地址不同,在常量池中指向的值都是 Tom
        ②常量池添加新的字面量 Jerry;
        ③将 p1指向的地址变为 Jerry的地址 */
        p1.name= "Tom";
        p2.name= "Tom";
        p1.name= "Jerry";
        System.out.println(p1.name);// 结果,Jerry
        System.out.println(p2.name);// 结果,Tom
    }
}

2.2.3 String的连接操作

  1. 常量 + 常量:结果仍存储在字符串常量池中,返回此字面量的地址。注:此时的常量可能是字面量,也可能是final修饰的常量;
  2. 常量 + 变量 变量 + 常量:都会通过 new的方式创建一个新的字符串,返回的是堆空间中此字符串对象的地址,和concat(String otherString)一样创建新的对象;
  3. 调用String.intern():返回的是字符串在常量池中字面量的地址,而不是对象的地址。
package com.kString;
import org.junit.Test;
/**情况Ⅰ~Ⅲ */
public class String0 {
    @Test
    public void test7() {
        String s1 = "Hello";
        String s2 = "World";

        String s3 = "HelloWorld";
        String s4 = "Hello" + "World";
        /*底层new 了一个StringBuilder调用了它的toString() 方法,
        就是在该方法中 new了一个新对象*/
        String s5 = s1 + "World";
        String s6 = "Hello" + 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(s3 == s7);// 结果,false
        System.out.println(s5 == s6);// 结果,false
        System.out.println(s5 == s7);// 结果,false
        System.out.println(s6 == s7);// 结果,false

        /*返回的是字符串在常量池中字面量的地址,
        而不是对象的地址。*/
        String s8=s5.intern();
        System.out.println(s4 == s8);// 结果,true
    }

	@Test
    public void test8() {
        final String s1 = "Hello";
        final String s2 = "World";

        String s4 = "Hello" + "World";
        String s5 = s1 + "World";
        String s6 = "Hello" + s2;
        String s7 = s1   + s2;
        System.out.println(s4 == s5);// 结果,true
        System.out.println(s4 == s6);// 结果,true
        System.out.println(s4 == s7);// 结果,true
        System.out.println(s5 == s6);// 结果,true
        System.out.println(s5 == s7);// 结果,true
        System.out.println(s6 == s7);// 结果, true
    }
}
  1. (了解):String.concat():不管是常量还是变量,参数是常量(或是变量),只要调用此方法,都返回一个 new的新对象。
package com.kString;
import org.junit.Test;
/**4. String.concat(); */
public class String0 {
    @Test
    public void test9() {
        String s1 = "Hello";
        String s2 = "World";

        String s3 = s1.concat(s2);
        String s4 = "Hello".concat("World");
        String s5 = s1.concat("World");

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

    }
}

2.3 String的常用API_1

2.3.1 String类构造器

public String(){/*初始化一个新创建的 String对象,以使其表示空字符序列 */}
public String(String original){/*初始化一个新创建的 String对象,使其表示一个与参数形同的字符序列*/}
public String(char value[]){/* 通过当前参数中的字符数组,来构建新的 String */}
public String(char value[], int offset, int count){/* 通过字符数组的一部分,来构建新的 String */}
public String(byte[] bytes){/* 通过使用平台的`默认字符集`,解码当前参数中的 字节数组来构建新的 String */}
public String(byte[] bytes,String charsetName){/* 通过使用指定的字符集,解码当前参数中的字节数组,来构建新的 String */}
	/**
     * String构造器的使用
     */
    @Test
    public void test1() {
        String s1 = new String();
        String s2 = new String("");

        String s3 = new String(new char[]{'a', 'b', 'c'});
        System.out.println(s3);// 结果,abc
    }

2.3.2 String与常见的其他结构之间的转换

(1)字符集:

  1. 在UTF-8字符集中:一个汉字占用3个字节,一个字母占1个字节
  2. 在GBK字符集中:一个汉字占用2个字节,一个字母占用1个字节
  3. utf-8和gbk 都向下兼容 ASCII码

(2)编码与解码

  1. 编码:String —> 字节或字节数组
  2. 解码:字节或字节数组 —> String
  3. 要求:解码字符集 需要和编码字符集相同,否则就会出现乱码
1. String和基本数据类型、包装类之间的转换:
  1. 基本数据类型 --> String: valueOf(num)
  2. String --> 基本数据类型: 调用包装类的parseXxx(String s)
@Test
    public void test2() {
        /* 1.基本数据类型 --> String */
        int num = 10;
        // 方式一:
        String s1 = num + "";
        // 方式二:
        String s2 = String.valueOf(num);
        System.out.println(s1.getClass());// 结果,class java.lang.String
        System.out.println(s2.getClass());// 结果,class java.lang.String

        /* 2.String --> 基本数据类型:调用包装类的parseXxx(String s) */
        int i = Integer.parseInt(s1);
        System.out.println(i);// 10
    }
2. String和 char[] 之间的转换:
  1. String —> char[],调用 String.toCharArray()
  2. char[] —> String,调用 String的构造器
@Test
    public void test3() {
        /*1.String ---> char[],调用 String.toCharArray() */
        String s1 = "Tom and Jerry";
        char[] chars = s1.toCharArray();
        for (char c : chars) {
            System.out.print(c);
        }
        System.out.println();

        /*2.char[] ---> String,调用 String的构造器 */
        String s = new String(chars);
        System.out.println("s: " + s);
    }
3. String和 byte[] 之间的转换:
  1. String 转 byte[],调用 String.getBytes()方法
  2. byte[] 转 String,调用 String的构造器
@Test
    public void test4() throws UnsupportedEncodingException {
        /* 1.String 转 byte[],调用 String.getBytes()方法 */
        String s1 = new String("c国");
        byte[] bytes = s1.getBytes();
        for (byte b : bytes) {
            System.out.println(b);// 转成ASCII码
        }

        // getBytes(String charsetName); 使用指定的字符集编码方式;
        byte[] gbkByte = s1.getBytes("gbk");
        for (byte b : gbkByte) {
            System.out.println(b);
        }

        /* 2.byte[] 转 String,调用 String的构造器 */
        String s2 = new String(bytes);// 使用默认的解码字符集:utf-8
        String s3 = new String(gbkByte,"utf-8");// 使用显示的解码字符集:utf-8
        System.out.println(s2 +"---"+s3);// 结果,c国---c��
        // 使用显示的解码字符集:gbk
        System.out.println(new String(gbkByte,"gbk"));// c国
    }

2.3.3 练习一:反转String

将一个字符串进行翻转,将字符串中指定部分进行翻转,
如:“abcdefg"反转为"abfedcg”

 	@Test
    public void test11() {
        String sc = "abcdefg";
        String s1 = new String0().reverse1(sc, 2, 5);
        String s2 = reverse2(sc, 2, 5);
        System.out.println("Method1: "+s1);
        System.out.println("Method2: "+s2);

    }

方法一:将字符串转为char[],针对数组进行相应位置的反转,然后将char[]转为 String

    public String reverse1(String str, int fromIndex, int toIndex) {
        char[] arr = str.toCharArray();
        for (int i = fromIndex, j = toIndex; i < j; i++, j--) {
            char temp = arr[i];
            arr[i] = arr[j];
            arr[j] = temp;
        }
        //使用构造器
        return new String(arr);
    }

方法二:将字符串分成三部分:对中间部分进行从后往前遍历,再拼接

    public String reverse2(String str, int fromIndex, int toIndex) {
        //1.获取 str的第一部分
        String finalStr = str.substring(0, fromIndex);
        //2.拼接第二部分
        for (int i = toIndex; i >= fromIndex; i--) {
            finalStr += str.charAt(i);
        }
        //3.拼接第三部分
        finalStr += str.substring(toIndex + 1);
        return finalStr;
    }

两种方法分析:

方法一:没有过多的占用内存,主要操作都在数组中进行的,不需要另外开辟空间;
方法二:当需要操作的字符串比较大时,会占用较多内存,效率变慢
方法三:使用StringBuffer

2.3.4 练习二:获取String出现的次数

练习二:获取一个字符串在另一个字符串中出现的次数
如:获取 “ab”,在 "serabthabwnabbyabsebab"中出现的次数

    @Test
    public void test12() {
        String s="serabthabwnabbyabsebab";
        int i = getSubStringCount(s, "ab");
        System.out.println(i);

    }
    /**
     * @description 判断 subStr 在 str中出现的次数
     * @param str
     * @param subStr 子字符串
     * @return 返回次数
     */
    public int getSubStringCount(String str, String subStr) {
        int count = 0;// 记录出现的次数
        if (str.length() >= subStr.length()){
            int index = str.indexOf(subStr);
            while (index >= 0) {
                count++;
                index = str.indexOf(subStr, index + subStr.length());
            }
        }
        return count;
    }

2.4 String的常用API_2

2.4.1 常用方法

  1. boolean isEmpty(); 判断字符串是否为空
  2. int length(); 返回字符串长度
  3. String concat(xx): 拼接字符 xx
  4. boolean equals(Object obj); 比较字符串是否相等
  5. boolean equalsIgnoreCase(Object obj); 比较字符串是否相等,不区分大小写
    String s1 = "";
    String s2 = "tom";
    String s3 = "Tom";
    @Test
    public void test1() {
        /** 1. boolean isEmpty(); 判断字符串是否为空 */
        String str1 = new String();
        String str2 = new String("");
        // s1:true; str1:true; str2:true
        System.out.println("s1:" + s1.isEmpty() + "; str1:" + str1.isEmpty() + "; str2:" + str2.isEmpty());

        String str3 = null;
        // NullPointerException 报空指针异常
//        System.out.println(str3.isEmpty());

        /** 2. int length(); 返回字符串长度 */
        System.out.println(s2.length());// 结果,3

        /**3. String concat(xx): 拼接字符 xx */
        System.out.println(s3.concat(" is in China"));//Tom is in China

        /**4. boolean equals(Object obj); 比较字符串是否相等 */
        if (s2.equals("tom")) {
            System.out.println("s2=\"tom\"");//s2="tom"
        }

        /**5. boolean equalsIgnoreCase(Object obj);比较字符串是否相等,不区分大小写*/
        if (s2.equalsIgnoreCase(s3)) {
            System.out.println("s2=s3");//s2=s3
        }
    }
  1. int compareTo(String other): 比较字符串大小,区分大小写,按照Unicode编码比较大小
  2. int compareToIgnoreCase(String other): 比较字符串大小,不区分大小写
  3. String toLowerCase(); 将字符串中大写字母转为小写字母
  4. String toUpperCase(); 将字符串中小写字母转为大写字母
  5. Stirng trim(): 去掉字符串前后空白符
  6. public String intern(): 结果在常量池中共享
    String s2 = "tom";
    String s3 = "Tom";
    String s4 = "中国";
    String s5 = " Jerry Love ";
    @Test
    public void test2() {
        /**6. int compareTo(String other): 比较字符串大小(实现了Comparable接口),
         * 区分大小写,按照`Unicode编码`比较大小*/
        System.out.println("s2-s3=" + s2.compareTo(s3));//s2-s3=32

        /**7. int compareToIgnoreCase(String other): 比较字符串大小,不区分大小写*/
        System.out.println("s2-s3=" + s2.compareToIgnoreCase(s3));//s2-s3=0

        /**8. String toLowerCase(); 将字符串中大写字母转为小写字母 */
        System.out.println(s3.toLowerCase());//tom
        System.out.println(s4.toLowerCase());//中国

        /**9. String toUpperCase(); 将字符串中小写字母转为大写字母*/
        System.out.println(s3.toUpperCase());//TOM
        System.out.println(s4.toUpperCase());//中国

        /**10. Stirng trim(): 去掉字符串前后空白符*/
        System.out.println("\""+s5.trim()+"\"");//"Jerry Love"
    }

2.4.2 查找字符串

1)查找特定字符串,返回 Boolean值
12. boolean contains(xx): 是否包含xx

2)查找特定字符,返回字符所在位置下标
13. int indexOf(xx); // 从前往后查找字符串中xx,如果有返回第一次出现的下标,没有找到返回 -1;
14. int indexOf(String str, int fromIndex); // 返回子字符串在此字符串中 第一次出现的下标,从指定下标处开始搜索;
15. int lastIndexOf(xx);//从后往前查找当前字符串中xx,如果有返回最后一次出现的下标,没有找到返回 -1;
16. int lastIndexOf(String str, int fromIndex); 返回子字符串在此字符串中 最后一次出现的下标,从指定下标处开始反向搜索;

    @Test
    public void test1(){
        String str= "hello,World";

        /*13. 从前往后查找字符串中xx,如果有返回第一次出现的下标,没有找到返回 -1;*/
        System.out.println(str.indexOf("Wo"));//6
        
        /*14. 返回子字符串在此字符串中 第一次出现的下标,从指定下标处开始搜索;*/
        System.out.println(str.indexOf("lo",2));//3
        
        /*15. 从后往前查找当前字符串中xx,如果有返回最后一次出现的下标,没有找到返回 -1;*/
        System.out.println(str.lastIndexOf("el"));//1
        
        /*16. 返回子字符串在此字符串中 最后一次出现的下标,从指定下标处开始反向搜索;*/
        System.out.println(str.lastIndexOf("ll", 9));//2
    }

2.4.3 截取字符串

截取字符串,返回截取后的字符串(截取内容,前包后不包)
17. String substring(int beginIndex);
String substring(int beginIndex, int endIndex); 注意取值范围是:[ beginIndex , endIndex )
String lastIndexOf(int ch);//从特定字符处截取字符串,返回后面的字符串

package com.kString;

import java.util.UUID;
/** 2.查找特定字符,返回字符所在位置下标*/
public class String2 {
    public static void main(String[] args) {
        String str= "hello,World";
        
        /** 17.截取字符串,返回截取后的字符串(截取内容,前包后不包)*/
        System.out.println(str.substring(1));//从指定下标截取字符串
        System.out.println(str.substring(2,5)+"---0b001");//截取指定下标处的字符串
        
        /*文件重命名*/
        String id= UUID.randomUUID().toString();//生成随机ID
        System.out.println(id);
        String file = "1.2.3.4.jpg";
        
        //截取并生成新的文件名
        System.out.println(id+file.substring(file.lastIndexOf('.')));
        System.out.println(file.substring(file.lastIndexOf('.')));
        System.out.println(file.substring(file.lastIndexOf('4')));
    }
}

2.4.4 字符/字符数组相关

  1. char charAt(index); 返回 [index]位置的字符
  2. char toCharArray(): 将此字符串转换为一个新的字符数组返回
  3. static String valueOf(char[] date): 返回指定数组中表示该字符序列的 String
  4. static String valueOf(char[] date, int offset, int count): 返回同上
  5. static String copyValueOf(char[] date): 返回同上
  6. static String copyValueOf(char[] date, int offset, int count): 返回同上
    @Test
    public void test2(){
    	String s="每日新冠核酸阳性4000人以上";
        /*18. char charAt(index);  返回 [index]位置的字符 */
        System.out.println(s.charAt(5));//酸

        /*19. char toCharArray(): 将此字符串转换为一个新的字符数组返回*/
        char[] chars = s.toCharArray();
        System.out.println(chars.getClass());//class [C

        /*20. 返回指定数组中表示该字符序列的 String*/
        String valueOf = String.valueOf(new char[]{'t', 'o', 'm'});
        System.out.println(valueOf);//tom

        /*22. static String copyValueOf(char[] date): 返回同上*/
        String copyValue = String.copyValueOf(new char[]{'t', 'o', 'm'});
        System.out.println(copyValue);//tom
        System.out.println(valueOf == copyValue);//false
        
    }

2.4.5 开头和结尾

  1. boolean startsWith(xx): 测试此字符串是否以 指定前缀xx 开始
  2. boolean startsWith(String prefix, int toffset): 测试从特定下标处,此字符串是否以 指定前缀开始
  3. boolean endsWith(xx): 测试此字符串是否以 指定后缀xx 结束

2.4.7 String在内存中的地址和指向

package com.lCommonClasses.fString;
/**
 * String在内存中的地址和指向
 */
public class String1 {
    public static void main(String[] args) {
        /**1 常量池中的引用
        1. 栈中开辟一块空间存放的是 引用str1的地址,
        2. String池中开辟一块空间,存放String常量"abc",
        3. 引用str1指向池中String常量"abc",
        4. str1所指代的地址即常量"abc"所在地址 */
        String str1 = "abc";
        System.out.println(str1 == "abc");// 结果,true

        /**2 堆中的引用
         1. 栈中开辟一块空间存放的是 引用str2的地址,
         2. 堆中开辟一块空间存放一个新建的String对象,内容是"abc",
         3. 引用str2指向堆中的新建的String对象"abc",
         4. str2所指代的对象地址为堆中地址,而常量"abc"地址在池中 */
        String str2 = new String("abc");
        System.out.println(str2 == "abc");// 结果,false

        /**3 两个堆中的引用
         1. 栈中开辟一块空间存放的是 引用str3的地址,
         2. 堆中开辟一块新空间存放另外一个(不同于str2所指)新建的String对象,
         3. 引用str3指向另外新建的那个String对象
         4. str3和str2指向堆中不同的String对象,地址也不相同 */
        String str3 = new String("abc");
        System.out.println(str3 == str2);// 结果,false

        /** 4.编译器有合并已知量的优化功能
         1. 栈中开辟一块空间存放引用str4,
         2. 根据编译器合并已知量的优化功能,池中开辟一块空间,
            存放合并后的String常量"ab",
         3. 引用str4指向池中常量"ab",
         4. str4所指即池中常量"ab" */
        String str4 = "a" + "b";
        System.out.println(str4 == "ab");// 结果,true
        /** 5.和4相同*/
        final String s = "a";
        String str5 = s + "b";
        System.out.println(str5 == "ab");// 结果,true

        /** 6 toString()方法,还原一个新的String对象
         1. 栈中开辟一块空间存放引用s1,s1指向池中String常量"a",
         2. 栈中开辟一块空间存放引用s2,s2指向池中String常量"b",
         3. 栈中开辟一块空间存放引用str6,
         4. s1+s2通过 StringBuilder的最后一步toString()方法
            还原一个新的String对象"ab",因此堆中开辟一块空间存放此对象,
         5. 引用str6指向堆中(s1 + s2)所还原的新String对象,
         6. str6指向的对象在堆中,而常量"ab"在池中 */
        String s1 = "a";
        String s2 = "b";
        String str6 = s1 + s2;
        System.out.println(str6 == "ab");// 结果,false

        /**7 编译器有合并已知量的优化功能
         1.栈中开辟一块空间存放引用str7,
         2.toUpperCase()方法还原一个新的String对象"ABC",
            池中并未开辟新的空间存放String常量"ABC",
         3.引用str7指向堆中的新String对象
         */
        String str7 = "abc".toUpperCase();
        String str8 = "abc".toLowerCase();
        System.out.println(str7 == "ABC");// 结果,false
        System.out.println(str8 == "abc");// 结果,true
    }
}

3.StringBuffer、StringBuilder

3.1 String/StringBuffer/StringBuilder区别:

String:不可变的字符序列,拼接时需要创建新对象后再拼接
StringBuffer:JDK1.0提供的类,可变的字符序列,不需要创建新对象-可变长,线程安全(synchronized修饰方法),做线程同步检查,效率较低;
StringBuilder:JDK1.5提供的类,可变的字符序列,不需要创建新对象-可变长,线程不安全,不做线程同步检查,因此效率较高,建议使用

3.2 String、StringBuffer、StringBuilder

3.2.1 String、StringBuffer、StringBuilder底层

  1. String :(jdk1.8及之前:)底层使用char[];(jdk1.9之后:)底层使用byte[]
  2. StringBuffer(jdk1.8及之前:)底层使用char[];(jdk1.9之后:)底层使用byte[]
  3. StringBuilder (jdk1.8及之前:)底层使用char[];(jdk1.9之后:)底层使用byte[]

2)StringBuffer和 StringBuilder方法一样,参考StringBuilder的代码

3.2.2 String、StringBuilder源码分析

  1. String:源码分析
    String s1 = new String();// char[] value = new char[0];
    String:String s2 = new String(“abc”);// char[] value = new char[]{‘a’,‘b’,‘c’};
  2. StringBuilder:可变性源码分析(继承了AbstractStringBuilder,其属性有)
    char[] value;// 存储字符序列(没有用 final修饰)
    int count;// 实际存储字符的个数(没有用 final修饰)
StringBuilder s1 = new StringBuilder(); // char[] value = new char[16];
StringBuilder s2 = new StringBuilder("abc"); // char[] value = new char[16 + "abc".length];

s1.append("ac"); // value[0] = 'a'; value[1] = 'c';
s1.append("b"); // value[2] = 'b'; 

3.2.3 AbstractStringBuilder扩容机制

StringBuffer和 StringBuilder都继承了 AbstractStringBuilder抽象类。

不断添加字符时,一旦超过 value.length时,就需要扩容。
默认扩容为原有容量的 2倍+ 2,并将原有 value数组中的元素复制到新的 char数组中。
特殊情况:原来容量的 2倍+ 2,还不满足时,需要多大容量就新建多大容量的数组

3.2.4 源码启示

  1. 开发中经常对字符进行字符串的增、删、改操作,建议使用StringBuffer 或StringBuilder替换String,因为String效率低,进行这些操作时 它的空间占用率较高;
  2. 开发中,不涉及线程安全问题时,建议使用 StringBuilder 替换StringBuffer。因为StringBuilder效率更高;
  3. 开发中若可以大体确定要操作的字符个数,建议使用带int capacity参数的 构造器。因为可以避免底层多次扩容操作,性能会更好。

3.3 StringBuilder常用方法:

1)增:

  1. StringBuilder append(xx);// 提供了很多 append()方法,用于进行字符串追加的拼接方式
  2. StringBuilder insert(int index, xx);// 在[ index] 处插入字符 xx

2)删:

  1. StringBuilder delete(int start, int end);// 删除[ start, end)之间的字符
  2. StringBuilder deleteCharAt(int index);// 删除[ index] 处的字符

3)改:

  1. void setCharAt(int index, char c);// 用 c替换[ index] 处的字符
  2. StringBuilder replace(int start, int end, String str);// 用 str替换[ start, end) 范围内的字符

4)查:

  1. char charAt(int index);查找[ index] 处的字符
  2. StringBuilder reverse();将数组反转
  3. int length() : 返回实际存储字符数据的长度
package com.kString;

public class StringBuilder1 {
    public static void main(String[] args) {
        StringBuilder builder = new StringBuilder("builder");
        System.out.println(builder);
        /** 一.字符串的增加 */
        // 1.拼接字符串,并返回自身
        System.out.println(builder.append("-hello"));
        // 2.添加字符串(在指定位置)
        System.out.println(builder.insert(5, " String "));
        // 3.拼接文件内容
        System.out.println(builder.append(new Text()));

        /** 二.字符串的删除 */
        // 4.删除这个区间里的字符串
        System.out.println(builder.delete(20,30));
        // 5.删除指定的字符
        builder.deleteCharAt(1);
        System.out.println(builder);

        /** 三.字符串的更新 */
        // 6.替换某个字符
        builder.setCharAt(1,'神');
        System.out.println(builder);
        // 7.替换指定位置的字符串
        System.out.println(builder.replace(20,30,"~-S吧-~"));

        /** 四.字符串的查询 */
        // 8.获取指定字符
        System.out.println(builder.charAt(1));
        // 9.反转字符串:从后往前读取原来的字符串
        System.out.println(builder.reverse());


        /** 五.String 和StringBuilder 的相互转换*/
        // 10.StringBuilder转String
        String str = builder.toString();
        System.out.println(str);
        // 11.String转StringBuilder
        StringBuilder buil = new StringBuilder(str);
        System.out.println(buil);
    }
}
class Text{
    StringBuilder builder = new StringBuilder(
            "Text:StringBuilder -可变长,JDK1.5提供的类," +
            "线程不安全,不做线程同步检查,因此效率较高,建议使用");
    Text(){
        System.out.println(builder);
    }
}
  1. 方法链的调用
  2. length()
  3. setLength(int newLength);
package com.kString;
import org.junit.Test;
public class StringBuilderPro1 {
    @Test
    public void test1() {
        StringBuilder builder = new StringBuilder();
        // 1.方法链的调用
        builder.append("abc").append("123").append("@#$");
        System.out.println(builder);//abc123@#$
        // 2.length();实际存储字符的个数
        System.out.println(builder.length());// 结果,9

        // 3.setLength(int newLength);将数组的 count值设置为 3,
        builder.setLength(3);
        System.out.println(builder);// 结果,abc
        builder.setLength(6);
        System.out.println("\""+builder+"\"");// 结果,"abc   "
        //设置的长度 6>3,后面的值已经用 0填充了
        System.out.println(builder.charAt(5) == 0);//true

        builder.append("D");
        System.out.println("\""+builder+"\"");//"abc   D"
    }
}

3.4 其他 API

  1. int indexOf(String str): 在当前字符序列中查询 str第一次出现的下标;
  2. int indexOf(String str, int fromIndex): 在当前字符序列中,从[fromIndex,之后]查询 str第一次出现的下标;
  3. int lastIndexOf(String str): 在当前字符序列中查询 str最后一次出现的下标;
  4. int lastIndexOf(String str, int fromIndex): 在当前字符序列中,从[fromIndex,之前]查询 str最后一次出现的下标;
  5. String substring(int start): 从[ start] 截取当前字符序列,到最后
  6. String substring(int start, int end): 截取[ start, end] 范围内的字符序列
  7. String toString(): 返回此序列中数据的字符串表示形式;
  8. void setLength(int newLength): 设置当前字符序列长度为 newLength .
### 6.3 字符拼接
```java
package com.kString;

public class StringBuilder3 {
    public static void main(String[] args) {
        String str1 = "a" + 1 + 2;//a12
        int str2 = 'a'+ 1 + 2;//整数:100,对'a'进行转义
        String str3 =  1 + 2 + "a" ;//3a
        //Str1:a12; str2:100; str3:3a
        System.out.println("Str1:"+str1+"; str2:"+str2+"; str3:"+str3);

        String t="t";
        String o="o";
        String m="m";
        String str= "tom";
        //字符串拼接如果都是常量那么就和直接写一个整体效果是一样的
        String str4="a"+"b"+"c";//创建了1个对象String
        String str5=t+o+m;//创建了1个对象StringBuilder
        System.out.println(str5);//tom
        System.out.println(str==str4);//true
        System.out.println(str4==str5);//false
        /*`String str5=t+o+m;`在虚拟机优化时会变成,
        只要含有 连续的字符串拼接 就会这样优化*/
        StringBuilder builder = new StringBuilder();
        builder.append(t);
        builder.append(o);
        builder.append(m);
        String str6= builder.toString();
        System.out.println(str6);//tom
    }
}

3.5 三者效率比较

测试String、StringBuilder、StringBuilder的执行速度

package com.kString;
public class TestSpeed {
    public static void main(String[] args) {
        String s1= "";
        StringBuffer s2 = new StringBuffer();//JDK1.0
        StringBuilder s3 = new StringBuilder();//JDK1.5

        /**1.测试 String用时,十万次*/
        long firstTime = System.currentTimeMillis();//获取系统时间
        for (int i = 0; i < 10_0000; i++) {
            s1=s1.concat("拼接");
        }
        long lastTime = System.currentTimeMillis();
        System.out.println("String用时:"+(lastTime-firstTime));

        /**2.测试 StringBuffer用时,一千万次*/
        firstTime = System.currentTimeMillis();
        for (int i = 0; i < 1000_0000; i++) {
            s2.append("拼接");
        }
        lastTime = System.currentTimeMillis();
        System.out.println("JDK1.0_StringBuffer用时:"+(lastTime-firstTime));

        /**3.测试 StringBuilder用时,一千万次*/
        firstTime = System.currentTimeMillis();
        for (int i = 0; i < 1000_0000; i++) {
            s3.append("拼接");
        }
        lastTime = System.currentTimeMillis();
        System.out.println("JDK1.5_StringBuilder用时:"+(lastTime-firstTime));
    }
}
/*
String用时:4074
JDK1.0_StringBuffer用时:277
JDK1.5_StringBuilder用时:144
*/

3.6 Interview 面试:地址指向问题

3.6.1 问题一:StringBuffer地址指向问题

package com.kString;

/**
 * 场景一:StringBuffer对象,地址指向问题
 */
public class StringInterview1 {
    public static void main(String[] args) {
        StringBuffer a = new StringBuffer("A");
        StringBuffer b = new StringBuffer("B");

        operate(a, b);
        System.out.println(a+", "+b);// ABxy, B
    }

    private static void operate(StringBuffer x, StringBuffer y) {
        x.append(y);
        // 将 x指向的地址给了 y
        y=x;// 此时 x和 y都指向了 x的地址
        y.append('x');
        x.append("y");
    }
}

3.6.2 问题二:String、StringBuffer地址传递问题

package com.kString;
public class StringInterview2 {
    // Replace:取代
    public static void main(String[] args) {
        String testString = new String("java");
        StringBuffer testBuffer = new StringBuffer("java");

        stringReplace(testString);
        bufferReplace(testBuffer);

        System.out.println(testString);// 结果,java
        System.out.println(testBuffer);// 结果,javaC
    }
    public static void stringReplace(String text){
        // 对参数进行了替换操作,底层新建了一个对象
        text=text.replace('j','i');
        // 形参的值改变了,但是改变前后是两个对象,地址也不一样
        System.out.println("textString: "+text);// 结果,textString: iava
    }
    public static void bufferReplace(StringBuffer text){
        // 这是对同一个对象进行的操作
        text.append("C");
        // 新建了一个对象,地址空间和之前的不是一个
        text = new StringBuffer("hello");
        text.append("World!");
        System.out.println("testBuffer: "+text);// 结果,testBuffer: helloWorld!
    }
}

3.6.3 问题三:String、StringBuffer地址指向问题

package com.kString;

public class StringInterview3 {
    public static void main(String[] args) {
        String s = "bbbb";
        StringBuffer buff = new StringBuffer("bbbb");
        change(s, buff);
        System.out.println(s + "\"" + buff + "\"");// 结果,bbbb"aaaa"
    }
    private static void change(String s, StringBuffer buff) {
        s = "aaaa";
        /*这里没有新建StringBuffer,所以这里对 buff的操作 
        都是直接对引用地址里内容 的操作*/
        buff.setLength(0);
        System.out.println("\"" + buff + "\"");// 结果,""
        buff.append("aaaa");
        System.out.println(buff.length());// 结果,4
        System.out.println("\"" + buff + "\"");// 结果,"aaaa"
    }
}

3.6.4 问题四:null的问题

String、StringBuffer关于 null的问题

package com.kString;
public class StringInterview4 {
    public static void main(String[] args) {
        String str = null;
        StringBuffer buff = new StringBuffer();
        // append方法底层存放了一个字符串 null
        buff.append(str);
        System.out.println(buff.length());// 结果,4
        System.out.println("-" + buff);// 结果,-null

        StringBuffer buff1 = new StringBuffer(str);
        System.out.println("--" + buff1);// 结果,空指针异常
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java常用String是一个用于表示字符串的String是不可变的,意味着一旦创建了一个String对象,它的值就不能改变。String提供了许多方法来处理字符串,包括字符串的拼接、截取、替换、查找等操作。 常用String方法包括: 1. 字符串拼接:可以使用"+"操作符或者String的concat()方法将两个字符串连接起来。例如,str1 + str2 或者 str1.concat(str2)。 2. 字符串长度:使用String的length()方法可以获取字符串的长度。例如,str.length()。 3. 字符串查找:使用String的indexOf()方法可以在字符串中查找指定字符或子字符串的位置。例如,str.indexOf('a') 或者 str.indexOf("abc")。 4. 字符串截取:使用String的substring()方法可以从一个字符串中截取指定位置的子字符串。例如,str.substring(3) 或者 str.substring(0, 5)。 5. 字符串转换:String提供了许多方法来进行字符串与其他数据型之间的转换。例如,将一个字符串转换为整数可以使用Integer的parseInt()方法,将一个整数转换为字符串可以使用String的valueOf()方法。 6. 字符串比较String提供了equals()方法用于比较两个字符串是否相等。例如,str1.equals(str2)。 7. 字符串替换:使用String的replace()方法可以将字符串中的指定字符或子字符串替换为新的字符或字符串。例如,str.replace('a', 'b') 或者 str.replace("abc", "def")。 8. 字符串大小写转换:使用String的toLowerCase()和toUpperCase()方法可以将字符串转换为全小写或全大写形式。例如,str.toLowerCase() 或者 str.toUpperCase()。 这些是String的一些常用方法,可以满足大多数字符串处理的需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值