Java常用API(五)

9.6 字符串

java.lang.String 類代表字符串。Java語言提供對象字符串的特殊支持:

  • Java程序中所有的字符串文字(例如 "abc" )都可以被看作是實現此類的實例,即所有 "" 引起來的內容都是字符串對象。
  • Java語言提供對字符串串聯符號(+),即Java重載了 + 的功能。
  • Java語言提供了將任意類型對象轉換為字符串的特殊支持(toString() 方法)。

9.6.1 字符串的特點

  1. 字符串 String 類型本身是 final 聲明的,意味著我們不能繼承 String
  2. String 對象內部是用字符數組進行保存的
    1. JDK8的 String 內部是用字符數組進行保存的

      private final char[] value;
      

      "abc" 等效于 char[] data={ 'a', 'b', 'c' }。

      例如: 
      String str = "abc";
      
      相當于: 
      char data[] = {'a', 'b', 'c'};     
      String str = new String(data);
      // String底層是靠字符數組實現的。
      
    2. JDK9之后 String 內部是用 byte 數組進行保存的

      private final byte[] value;
      private final byte coder;//新增加的字段,表示字符串采用的編碼 1是UTF-16BE,0是LATIN1
      

      定義的字符串中,如果沒有漢字,每個字符將采用 LATIN1 編碼表存儲,如果字符串中包含漢字等非 ASCII 碼表的字符,存儲的編碼就是 UTF-16BE,相比JDK1.8節(jié)約內存。

  3. 字符串的對象也是不可變對象,意味著一旦進行修改,就會產生新對象。
    1. 為什么 String 對象不可變?或者說 String 類是如何設計為對象不可變的?(面試題)

      • String 類中 value 數組是 final 修飾的,意味著這個數組地址不可變,即不能對同一個 String 對象的 value 數組進行擴容、縮容等。
      • 雖然 final 修飾的 value 數組元素值可以修改。但是由于它是 private 修飾,外部不能直接操作它,所以在 String 類外無法直接修改 value 數組的元素值(除非用反射)。
      • String 類型中提供的所有方法實現涉及到 value 數組長度需要變化,或 value 元素值需要變化,都是用新對象來表示修改后內容的。

      綜上3點保證了 String 對象的不可變。

    2. 既然 String 對象不可變,那么我們“修改” String 對象要如何操作?
      • 我們修改了字符串后,如果想要獲得新的內容,必須重新接收。
      • 如果程序中涉及到大量的字符串的修改操作,那么此時的時空消耗比較高??赡苄枰紤]使用 StringBuilderStringBuffer 的可變字符序列。
  4. 就因為字符串對象設計為不可變,所以可以共享。Java中把需要共享的字符串常量對象放在字符串常量池中。

    String s1 = "abc";
    String s2 = "abc";
    System.out.println(s1 == s2);
    // 內存中只有一個"abc"對象被創(chuàng)建,同時被s1和s2共享。
    

9.6.2 API方法

1、構造字符串對象

除了直接 "" 構建字符串之外,還可以通過以下4個方式:構造器、valueOf 方法、+toString 方法

  1. 使用構造方法

    • public String() :初始化新創(chuàng)建的 String 對象,以使其表示空字符序列。
    • String(String original): 初始化一個新創(chuàng)建的 String 對象,使其表示一個與參數相同的字符序列;換句話說,新創(chuàng)建的字符串是該參數字符串的副本。
    • public String(char[] value) :通過當前參數中的字符數組來構造新的 String。
    • public String(char[] value,int offset, int count) :通過字符數組的一部分來構造新的 String。
    • public String(byte[] bytes) :通過使用平臺的默認字符集解碼當前參數中的字節(jié)數組來構造新的 String。
    • public String(byte[] bytes,String charsetName) :通過使用指定的字符集解碼當前參數中的字節(jié)數組來構造新的 String。

    構造舉例,代碼如下:

    //字符串常量對象
    String str = "hello";
    
    // 無參構造
    String str1 = new String();
    
    //創(chuàng)建"hello"字符串常量的副本
    String str2 = new String("hello");
    
    //通過字符數組構造
    char chars[] = {'a', 'b', 'c','d','e'};     
    String str3 = new String(chars);
    String str4 = new String(chars,0,3);
    
    // 通過字節(jié)數組構造
    byte bytes[] = {97, 98, 99 };     
    String str5 = new String(bytes);
    String str6 = new String(bytes,"GBK");
    
  2. 使用靜態(tài)方法 valueOfcopyValueOf

    • static String copyValueOf(char[] data): 返回指定數組中表示該字符序列的 String
    • static String copyValueOf(char[] data, int offset, int count):返回指定數組中表示該字符序列的 String
    • static String valueOf(char[] data) : 返回指定數組中表示該字符序列的 String
    • static String valueOf(char[] data, int offset, int count) : 返回指定數組中表示該字符序列的 String
    • static String valueOf(xx value):xx支持各種數據類型,返回各種數據類型的 value 參數的字符串表示形式。
     public static void main(String[] args) {
            char[] data = {'h','e','l','l','o','j','a','v','a'};
            String s1 = String.copyValueOf(data);
            String s2 = String.copyValueOf(data,0,5);
            int num = 123456;
            String s3 = String.valueOf(num);
            System.out.println(s1);
            System.out.println(s2);
            System.out.println(s3);
        }
    
  3. 任意類型與字符串 +

    任意數據類型與 "字符串" 進行拼接,結果都是字符串類型

     public static void main(String[] args) {
            int num = 123456;
            String s = num + "";
            System.out.println(s);
            
            Student stu = new Student();
            String s2 = stu + "";//自動調用對象的toString(),然后與""進行拼接
            System.out.println(s2);
        }
    
  4. 任意對象.toString

    Object類中聲明了 toString() 方法,因此任意對象都可以調用 toString 方法,轉為字符串類型。如果沒有重寫 toString 的話,返回的默認是“對象的運行時類型@對象的 hashCode 值的十六進制值”

    public static void main(String[] args) {
        LocalDate today = LocalDate.now();
        String str = today.toString();
        System.out.println(str);
    }
    

2、求字符串的長度

  1. int length():返回字符串的長度

        @Test
        public void test1(){
            System.out.println("hello".length());
        }
    

3、轉大小寫

  1. String toLowerCase():將字符串中大寫字母轉為小寫
  2. String toUpperCase():將字符串中小寫字母轉為大寫
    @Test
    public void test2(){
        System.out.println("Hello".toLowerCase());
        System.out.println("Hello".toUpperCase());
    }

4、字符串對象的比較

涉及到以下5個方式方法:==equals、equalsIgnoreCasecompareTo、compareToIgnoreCaseCollator

  1. ==

    == 運算符:比較是兩個字符串對象的地址

        @Test
        public void test1(){
            String str1 = "hello";
            String str2 = "hello";
            System.out.println(str1 == str2);//true,說明str1和str2指向同一個字符串對象
    
            String str3 = new String("hello");
            String str4 = new String("hello");
            System.out.println(str1 == str4); //false
    
            System.out.println(str3 == str4); //false
        }
    
  2. equals 方法比較

    boolean equals(Object obj) 方法:比較是兩個字符串對象的內容,因為 String 類型重寫 equalsequals 方法比較字符串內容時嚴格區(qū)分大小寫。

        @Test
        public void test2(){
            String str1 = "hello";
            String str2 = "hello";
            System.out.println(str1.equals(str2));//true
    
            String str3 = new String("hello");
            String str4 = new String("hello");
            System.out.println(str1.equals(str3));//true
            System.out.println(str3.equals(str4));//true
        }
    
  3. equalsIgnoreCase 方法

    boolean equalsIgnoreCase(String str) 方法:比較是兩個字符串對象的內容,并且不區(qū)分大小寫。

        @Test
        public void test3(){
            String str1 = new String("hello");
            String str2 = new String("HELLO");
            System.out.println(str1.equalsIgnoreCase(str2)); //true
        }
    
        @Test
        public void test04(){
            //隨機生成驗證碼,驗證碼由0-9,A-Z,a-z的4位字符組成
            char[] array = new char[26*2+10];
            for (int i = 0; i < 10; i++) {
                array[i] = (char)('0' + i);
            }
            for (int i = 10,j=0; i < 10+26; i++,j++) {
                array[i] = (char)('A' + j);
            }
            for (int i = 10+26,j=0; i < array.length; i++,j++) {
                array[i] = (char)('a' + j);
            }
            char[] code = new char[4];
            Random rand = new Random();
            for (int i = 0; i < 4; i++) {
                code[i]= array[rand.nextInt(array.length)];
            }
            String codeString = new String(code);
            System.out.println("驗證碼:" + codeString);
            
            //將用戶輸入的單詞全部轉為小寫,如果用戶沒有輸入單詞,重新輸入
            Scanner input = new Scanner(System.in);
            System.out.print("請輸入驗證碼:");
            String inputCode = input.nextLine();
            
            if(!codeString.equalsIgnoreCase(inputCode)){
                System.out.println("驗證碼輸入不正確");
            }else{
                System.out.println("驗證碼輸入正確");
            }
            
            input.close();
        }
    
  4. compareTo 方法

    int compareTo(String str) 方法:String 類型實現了 java.lang.Comparable 接口,重寫了 Comparable 接口的抽象方法,即 String 對象支持自然排序,該方法按照字符的 Unicode 編碼值進行比較大小的,嚴格區(qū)分大小寫。

        @Test
        public void test5(){
            String str1 = "hello";
            String str2 = "world";
            String str3 = "HELLO";
            System.out.println(str1.compareTo(str2));//-15
            System.out.println(str1.compareTo(str3));//32
        }
    
        @Test
        public void test6(){
            String[] arr = {"hello","java","chai","Jack","hi"};
            System.out.println(Arrays.toString(arr));
            Arrays.sort(arr);
            System.out.println(Arrays.toString(arr));
        }
    
  5. compareToIgnoreCase 方法

    int compareToIgnoreCase(String str)String 類型支持不區(qū)分大小寫比較字符串大小。具體原理是先統一大小寫再比較大小。

        @Test
        public void test7(){
            String str1 = "hello";
            String str2 = "world";
            String str3 = "HELLO";
            System.out.println(str1.compareToIgnoreCase(str2));//-15
            System.out.println(str1.compareToIgnoreCase(str3));//0
        }
    
        @Test
        public void test8(){
            String[] arr = {"hello","java","chai","Jack","Hi"};
            System.out.println(Arrays.toString(arr));
            Arrays.sort(arr, new Comparator() {
                @Override
                public int compare(Object o1, Object o2) {
                    String s1 = (String) o1;
                    String s2 = (String) o2;
                    return s1.compareToIgnoreCase(s2);
                }
            });
            System.out.println(Arrays.toString(arr));
        }
    
  6. 區(qū)分語言環(huán)境的 String 比較

    java.text.Collator 類執(zhí)行區(qū)分語言環(huán)境的 String 比較。

        @Test
        public void test9(){
            String[] arr = {"張三","李四","柴林燕","王五","尚硅谷"};
            System.out.println(Arrays.toString(arr));
            Arrays.sort(arr, new Comparator() {
                @Override
                public int compare(Object o1           , Object o2) {
                    String s1 = (String) o1;
                    String s2 = (String) o2;
                    Collator collator = Collator.getInstance(Locale.CHINA);
                    return collator.compare(s1,s2);
                }
            });
            System.out.println(Arrays.toString(arr));
        }
    

5、去掉前后空白

  1. String trim():去掉字符串前后空白符

        @Test
        public void test3(){
            System.out.println("[" + "    hello   world    ".trim() + "]");
        }
        @Test
        public void test04(){
            //將用戶輸入的單詞轉為小寫,如果用戶沒有輸入單詞,重新輸入
            Scanner input = new Scanner(System.in);
            String word;
            while(true){
                System.out.print("請輸入單詞:");
                word = input.nextLine();
                if(word.trim().length()!=0){
                    word = word.toLowerCase();
                    break;
                }
            }
            System.out.println("你輸入的單詞是:" + word);
            input.close();
        }
    

6、空字符串的判斷

  1. isEmpty()
  2. isBlank()

        @Test
        public void test3()throws Exception{
            String s = "";
            System.out.println(s.isEmpty());
            System.out.println(s.length()==0);
            System.out.println(s.equals(""));
            System.out.println("".equals(""));
            
            System.out.println(s.isBlank());
            System.out.println(s.trim().length()==0);
        }
    

7、字符串開頭與結尾的判斷

  1. boolean startsWith(xx):是否以xx開頭

        @Test
        public void test4(){
            String name = "張三";
            System.out.println(name.startsWith("張"));
        }
    
  2. boolean endsWith(xx):是否以xx結尾

        @Test
        public void test5(){
            String file = "Hello.txt";
            if(file.endsWith(".java")){
                System.out.println("Java源文件");
            }else if(file.endsWith(".class")){
                System.out.println("Java字節(jié)碼文件");
            }else{
                System.out.println("其他文件");
            }
        }
    

8、字符串內容查找

  1. boolean contains(xx):是否包含xx
  2. int indexOf(xx):從前往后找當前字符串中xx,即如果有返回第一次出現的下標,要是沒有返回-1
  3. int lastIndexOf(xx):從后往前找當前字符串中xx,即如果有返回最后一次出現的下標,要是沒有返回-1

     @Test
        public void test05(){
            String str = "尚硅谷是一家靠譜的培訓機構,尚硅谷可以說是IT培訓的小清華,JavaEE是尚硅谷的當家學科,尚硅谷的大數據培訓是行業(yè)獨角獸。尚硅谷的前端和運維專業(yè)一樣獨領風騷。";
            System.out.println("是否包含清華:" + str.contains("清華"));
            System.out.println("培訓出現的第一次下標:" + str.indexOf("培訓"));
            System.out.println("培訓出現的最后一次下標:" + str.lastIndexOf("培訓"));
        }
    

9、字符串截取

  1. String substring(int beginIndex) :返回一個新的字符串,它是此字符串的從 beginIndex 開始截取到最后的一個子字符串。
  2. String substring(int beginIndex, int endIndex) :返回一個新字符串,它是此字符串從 beginIndex 開始截取到 endIndex (不包含)的一個子字符串。

     @Test
        public void test06(){
            String str = "helloworldjavaatguigu";
            String sub1 = str.substring(5);
            String sub2 = str.substring(5,10);
            System.out.println(sub1);
            System.out.println(sub2);
        }
    
        @Test
        public void test07(){
            String fileName = "快速學習Java的秘訣.dat";
            //截取文件名
            System.out.println("文件名:" + fileName.substring(0,fileName.lastIndexOf(".")));
            //截取后綴名
            System.out.println("后綴名:" + fileName.substring(fileName.lastIndexOf(".")));
        }
    

10、獲取char和char[]

  1. char charAt(index):返回 [index] 位置的字符
  2. char[] toCharArray(): 將此字符串轉換為一個新的字符數組返回

    將字符數組轉為 String 對象,可以使用之前介紹過的構造器和靜態(tài)方法 valueOfcopyValueOf

    • String(char[] value):返回指定數組中表示該字符序列的 String。
    • String(char[] value, int offset, int count):返回指定數組中表示該字符序列的 String
    • static String copyValueOf(char[] data): 返回指定數組中表示該字符序列的 String
    • static String copyValueOf(char[] data, int offset, int count):返回指定數組中表示該字符序列的 String
    • static String valueOf(char[] data, int offset, int count) : 返回指定數組中表示該字符序列的 String
    • static String valueOf(char[] data) :返回指定數組中表示該字符序列的 String
     @Test
        public void test08(){
            //將字符串中的字符按照大小順序排列
            String str = "helloworldjavaatguigu";
            char[] array = str.toCharArray();
            Arrays.sort(array);
            str = new String(array);
            System.out.println(str);
        }
        
        @Test
        public void test09(){
            //將首字母轉為大寫
            String str = "jack";
            str = Character.toUpperCase(str.charAt(0))+str.substring(1);
            System.out.println(str);
        }
    

11、字符串的編碼與解碼

  1. byte[] getBytes():編碼,把字符串變?yōu)樽止?jié)數組,按照平臺默認的字符編碼方式進行編碼

    byte[] getBytes(字符編碼方式):按照指定的編碼方式進行編碼

  2. new String(byte[] )new String(byte[], int, int):解碼,按照平臺默認的字符編碼進行解碼

    new String(byte[],字符編碼方式 )new String(byte[], int, int,字符編碼方式):解碼,按照指定的編碼方式進行解碼

    ==(編碼方式見附錄10.7.1)==

    1559746309093-17212813118171.png

        @Test
        public void test10()throws Exception{
            byte[] data = {(byte)0B01100001, (byte)0B01100010, (byte)0B01100011, (byte)0B01100100};
            System.out.println(new String(data,"ISO8859-1"));
            System.out.println(new String(data,"GBK"));
            System.out.println(new String(data,"UTF-8"));
        }
    
        @Test
        public void test11()throws Exception{
            byte[] data = {(byte)0B01100001, (byte)0B01100010, (byte)0B01100011,(byte)0B11001001,(byte)0B11010000};
            System.out.println(new String(data,"ISO8859-1"));
            System.out.println(new String(data,"GBK"));
            System.out.println(new String(data,"UTF-8"));
        }
    
        @Test
        public void test12()throws Exception{
            byte[] data = {(byte)0B01100001,(byte)0B11100101, (byte)0B10110000, (byte)0B10011010, (byte)0B11000111, (byte)0B10101011};
            System.out.println(new String(data,"ISO8859-1"));
            System.out.println(new String(data,"GBK"));
            System.out.println(new String(data,"UTF-8"));
        }
    
        @Test
        public void test13() throws Exception {
            String str = "中國";
            System.out.println(str.getBytes("ISO8859-1").length);// 2
            // ISO8859-1把所有的字符都當做一個byte處理,處理不了多個字節(jié)
            System.out.println(str.getBytes("GBK").length);// 4 每一個中文都是對應2個字節(jié)
            System.out.println(str.getBytes("UTF-8").length);// 6 常規(guī)的中文都是3個字節(jié)
    
            /*
             * 不亂碼:(1)保證編碼與解碼的字符集名稱一樣(2)不缺字節(jié)
             */
            System.out.println(new String(str.getBytes("ISO8859-1"), "ISO8859-1"));// 亂碼
            System.out.println(new String(str.getBytes("GBK"), "GBK"));// 中國
            System.out.println(new String(str.getBytes("UTF-8"), "UTF-8"));// 中國
        }
    

12、字符串匹配正則表達式

  1. boolean matches(正則表達式):判斷當前字符串是否匹配某個正則表達式。==(正則表達式詳細見附錄)==

     @Test
        public void test16(){
            //簡單判斷是否全部是數字,這個數字可以是1~n位
            String str = "12a345";
            
            //正則不是Java的語法,它是獨立與Java的規(guī)則
            //在正則中\(zhòng)是表示轉義,
            //同時在Java中\(zhòng)也是轉義
            boolean flag = str.matches("\\d+");
            System.out.println(flag);
        }
        
        @Test
        public void test17(){
            String str = "123456789";
            
            //判斷它是否全部由數字組成,并且第1位不能是0,長度為9位
            //第一位不能是0,那么數字[1-9]
            //接下來8位的數字,那么[0-9]{8}+
            boolean flag = str.matches("[1-9][0-9]{8}+");
            System.out.println(flag);
        }
    
        @Test
        public void test18(){
            //密碼要求:必須有大寫字母,小寫字母,數字組成,6位
            System.out.println("Cly892".matches("^(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9])[A-Za-z0-9]{6}$"));//true
            System.out.println("1A2c45".matches("^(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9])[A-Za-z0-9]{6}$"));//true
            System.out.println("Clyyyy".matches("^(?=.*[A-Z])(?=.*[0-9])[A-Za-z0-9]{6}$"));//false
            /*
            (1)密碼的長度為6,且只能有[A-Za-z0-9]組成。
            (2)另外,三個非捕獲組都能匹配到自己的值。
            (?=.*[A-Z]):匹配值  C
            (?=.*[a-z]):匹配值  Clyya
            (?=.*[0-9]):匹配值  Clyya1
            三個非捕獲組都有值,即都匹配上了就行。
            非捕獲組是只匹配不捕獲。
             */
        }
    

13、字符串內容的替換

  1. String replace(xx,xx):不支持正則
  2. String replaceFirst(正則,value):替換第一個匹配部分
  3. String replaceAll(正則, value):替換所有匹配部分

        @Test
        public void test19(){
            String str = "hello244world.java;887";
    
            String s1 = str.replace("244","");
            System.out.println("s1 = " + s1);
    
            String s2 = str.replaceFirst("\\d+","");
            System.out.println("s2 = " + s2);
    
    
            //把其中的非字母去掉
            String s3 = str.replaceAll("[^a-zA-Z]", "");
            System.out.println("s3 = " + s3);
        }
    

14、字符串拆分

  1. String[] split(正則):按照某種規(guī)則進行拆分

     @Test
        public void test20(){
            String str = "Hello World java atguigu";
            String[] all = str.split(" ");
            for (int i = 0; i < all.length; i++) {
                System.out.println(all[i]);
            }
        } 
    
        @Test
        public void test21(){
            String str = "1Hello2World3java4atguigu";
            str = str.replaceFirst("\\d", "");
            System.out.println(str);
            String[] all = str.split("\\d");
            for (int i = 0; i < all.length; i++) {
                System.out.println(all[i]);
            }
        }
        
        @Test
        public void test23(){
            String str = "張三.23|李四.24|王五.25";
            //|在正則中是有特殊意義,我這里要把它當做普通的|
            String[] all = str.split("\\|");
            
            //轉成一個一個學生對象
            Student[] students = new Student[all.length];
            for (int i = 0; i < students.length; i++) {
                //.在正則中是特殊意義,我這里想要表示普通的.
                String[] strings = all[i].split("\\.");//張三,  23
                String name = strings[0];
                int age = Integer.parseInt(strings[1]);
                students[i] = new Student(name,age);
            }
            
            for (int i = 0; i < students.length; i++) {
                System.out.println(students[i]);
            }
        }
    
    class Student {
        private String name;
        private int age;
    
        public Student(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        @Override
        public String toString() {
            return "Student{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
    

15、字符串拼接方法

兩種方式: 字符串1.concat(字符串2):只要拼接的不是空字符串,每次都new一個String 字符串1 + 字符串2: ""常量拼接,編譯器直接優(yōu)化為拼接后的字符串常量值 非""常量拼接,編譯器優(yōu)化為StringBuilder的append,然后再把結果toString。

package com.atguigu.string;

import org.junit.Test;

public class TestStringConcat {
    @Test
    public void test1(){
        String s1 = "hello";
        String s2 = "world";
        String s3 = s1 + s2;
        String s4 = s1.concat(s2);
        System.out.println("s3 = " + s3);//helloworld
        System.out.println("s4 = " + s4);//helloworld
    }

}
class Test{
    public void test1() {
        String s1 = "hello" + "world";
    }
    public void test2() {
        String s1 = "hello";
        String s2 = s1 + "world";
    }
    public void test3() {
        String s1 = "hello";
        String s2 = "world";
        String s3 = s1 + s2;
    }
}

image-20221102140637953-17212813118182.png

image-20221102140830458-17212813118183.png

JDK1.9及以后的版本看到的是

image-20240718132149290.png

動態(tài)指令invokedynamic指令會調用makeConcatWithConstants方法進行字符串的連接。該方法位于java.lang.invoke.StringConcatFactory類中,如果繼續(xù)往下跟蹤,最終還是調用StringBuilder類append方法,篇幅有限,就不一一展示了:

image-20240718132417051.png