Java常用類(二)String類詳解


前言

  在我們開發中經常會用到很多的常用的工具類,這里做一個總結。他們有很多的方法都是我們經常要用到的。所以我們一定要把它好好的掌握起來!

一、String簡介

1.1、String(字符串常量)概述

  在API中是這樣描述:

    String 類代表字符串。Java 程序中的所有字符串字面值(如 "abc" )都作為此類的實例實現
    字符串是常量;它們的值在創建之后不能更改。字符串緩沖區支持可變的字符串。因為 String 對象是不可變的,所以可以共享。

  java.lang.String:

    

1.2、分析String源碼

  1)String的成員變量

 /** String的屬性值 */  
    private final char value[];

    /** The offset is the first index of the storage that is used. */
    /**數組被使用的開始位置**/
    private final int offset;

    /** The count is the number of characters in the String. */
    /**String中元素的個數**/
    private final int count;

    /** Cache the hash code for the string */
   /**String類型的hash值**/
    private int hash; // Default to 0

    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    private static final long serialVersionUID = -6849794470754667710L;
    /**
     * Class String is special cased within the Serialization Stream         Protocol.
     *
     * A String instance is written into an ObjectOutputStream according to
     * <a href="{@docRoot}/../platform/serialization/spec/output.html">
     * Object Serialization Specification, Section 6.2, "Stream Elements"</a>
     */

  private static final ObjectStreamField[] serialPersistentFields =
        new ObjectStreamField[0];
String的成員變量

    從源碼看出String底層使用一個字符數組來維護的。

    成員變量可以知道String類的值是final類型的,不能被改變的,所以只要一個值改變就會生成一個新的String類型對象,存儲String數據也不一定從數組的第0個元素開始的,而是從offset所指的元素開始。

  2)String的構造方法  

String() 
          初始化一個新創建的 String 對象,使其表示一個空字符序列。 
String(byte[] bytes) 
          通過使用平台的默認字符集解碼指定的 byte 數組,構造一個新的 String。 
String(byte[] bytes, Charset charset) 
          通過使用指定的 charset 解碼指定的 byte 數組,構造一個新的 String。  
String(byte[] bytes, int offset, int length) 
          通過使用平台的默認字符集解碼指定的 byte 子數組,構造一個新的 String。 
String(byte[] bytes, int offset, int length, Charset charset) 
          通過使用指定的 charset 解碼指定的 byte 子數組,構造一個新的 String。 
String(byte[] bytes, int offset, int length, String charsetName) 
          通過使用指定的字符集解碼指定的 byte 子數組,構造一個新的 String。 
String(byte[] bytes, String charsetName) 
          通過使用指定的 charset 解碼指定的 byte 數組,構造一個新的 String。 
String(char[] value) 分配一個新的 String,使其表示字符數組參數中當前包含的字符序列。 
String(char[] value, int offset, int count) 分配一個新的 String,它包含取自字符數組參數一個子數組的字符。 
String(int[] codePoints, int offset, int count) 分配一個新的 String,它包含 Unicode 代碼點數組參數一個子數組的字符。 
String(String original) 初始化一個新創建的 String 對象,使其表示一個與參數相同的字符序列;換句話說,新創建的字符串是該參數字符串的副本。 
String(StringBuffer buffer) 
          分配一個新的字符串,它包含字符串緩沖區參數中當前包含的字符序列。 
String(StringBuilder builder) 
          分配一個新的字符串,它包含字符串生成器參數中當前包含的字符序列。 

二、創建字符串對象兩種方式的區別

2.1、直接賦值方式創建對象

  直接賦值方式創建對象是在方法區的常量池

String str="hello";//直接賦值的方式

2.2、通過構造方法創建字符串對象

  通過構造方法創建字符串對象是在堆內存

String str=new String("hello");//實例化的方式

2.3、兩種實例化方式的比較

  1)編寫代碼比較

public class TestString {
    public static void main(String[] args) {
        String str1 = "Lance";
        String str2 = new String("Lance");
        String str3 = str2; //引用傳遞,str3直接指向st2的堆內存地址
        String str4 = "Lance";
        /**
         *  ==:
         * 基本數據類型:比較的是基本數據類型的值是否相同
         * 引用數據類型:比較的是引用數據類型的地址值是否相同
         * 所以在這里的話:String類對象==比較,比較的是地址,而不是內容
         */
         System.out.println(str1==str2);//false
         System.out.println(str1==str3);//false
         System.out.println(str3==str2);//true
         System.out.println(str1==str4);//true
    }

}

  2)內存圖分析

    

    可能這里還是不夠明顯,構造方法實例化方式的內存圖:String str = new String("Hello");

    首先:

      

    當我們再一次的new一個String對象時:

      

    3)字符串常量池

      在字符串中,如果采用直接賦值的方式(String str="Lance")進行對象的實例化,則會將匿名對象“Lance”放入對象池,每當下一次對不同的對象進行直接賦值的時候會直接利用池中原有的匿名對象

      這樣,所有直接賦值的String對象,如果利用相同的“Lance”,則String對象==返回true;

      比如:對象手工入池

public class TestString {
    public static void main(String args[]){
     String str =new String("Lance").intern();//對匿名對象"hello"進行手工入池操作
     String str1="Lance";
     System.out.println(str==str1);//true
    }
}

    4)總結:兩種實例化方式的區別

      1)直接賦值(String str = "hello"):只開辟一塊堆內存空間,並且會自動入池,不會產生垃圾。

      2)構造方法(String str=  new String("hello");):會開辟兩塊堆內存空間,其中一塊堆內存會變成垃圾被系統回收,而且不能夠自動入池,需要通過public  String intern();方法進行手工入池。

        在開發的過程中不會采用構造方法進行字符串的實例化

    5)避免空指向

      首先了解: == 和public boolean equals()比較字符串的區別

      ==在對字符串比較的時候,對比的是內存地址,而equals比較的是字符串內容,在開發的過程中,equals()通過接受參數,可以避免空指向

      舉例:

      String str = null;
      if(str.equals("hello")){//此時會出現空指向異常
        ...
      }
      if("hello".equals(str)){//此時equals會處理null值,可以避免空指向異常
         ...
      }

     6)String類對象一旦聲明則不可以改變;而改變的只是地址,原來的字符串還是存在的,並且產生垃圾

       

三、String常用的方法

  

3.1、String的判斷功能

  1)常用方法

  boolean equals(Object obj):比較字符串的內容是否相同
  boolean equalsIgnoreCase(String str): 比較字符串的內容是否相同,忽略大小寫
  boolean startsWith(String str): 判斷字符串對象是否以指定的str開頭
  boolean endsWith(String str): 判斷字符串對象是否以指定的str結尾

  2)代碼測試

 
public class TestString {
    public static void main(String[] args) {
           // 創建字符串對象
        String s1 = "hello";
        String s2 = "hello";
        String s3 = "Hello";
 
        // boolean equals(Object obj):比較字符串的內容是否相同
        System.out.println(s1.equals(s2));
        System.out.println(s1.equals(s3));
        System.out.println("-----------");
 
        // boolean equalsIgnoreCase(String str):比較字符串的內容是否相同,忽略大小寫
        System.out.println(s1.equalsIgnoreCase(s2));
        System.out.println(s1.equalsIgnoreCase(s3));
        System.out.println("-----------");
 
        // boolean startsWith(String str):判斷字符串對象是否以指定的str開頭
        System.out.println(s1.startsWith("he"));
        System.out.println(s1.startsWith("ll"));
    }

}
測試

    結果:

    

3.2、String類的獲取功能

  1)常用方法

  int length():獲取字符串的長度,其實也就是字符個數
  char charAt(int index):獲取指定索引處的字符 int indexOf(String str):獲取str在字符串對象中第一次出現的索引 String substring(int start):從start開始截取字符串 String substring(int start,int end):從start開始,到end結束截取字符串。包括start,不包括end

  2)代碼測試

public class TestString {
    public static void main(String[] args) {
        String str1 = "Lance";
        String str2 = new String("Lance");
        String str3 = "LANCE";
        
        // boolean equals(Object obj):比較字符串的內容是否相同
        System.out.println(str1.equals(str2));
        System.out.println(str1.equals(str3));
        System.out.println("-----------------------");
        
        // boolean equalsIgnoreCase(String str):比較字符串的內容是否相同,忽略大小寫
        System.out.println(str1.equalsIgnoreCase(str3));
        System.out.println("-----------------------");
        
        // boolean startsWith(String str):判斷字符串對象是否以指定的str開頭
        // boolean endsWith(String str): 判斷字符串對象是否以指定的str結尾
        System.out.println(str1.startsWith("La"));
        System.out.println(str3.endsWith("CE"));
        
    }

}
  結果:

    

3.2、String類的獲取功能

  1)常用方法

 int length():獲取字符串的長度,其實也就是字符個數
 char charAt(int index):獲取指定索引處的字符
 int indexOf(String str):獲取str在字符串對象中第一次出現的索引
 String substring(int start):從start開始截取字符串
 String substring(int start,int end):從start開始,到end結束截取字符串。包括start,不包括end
  2)測試代碼

public class TestString {
    public static void main(String[] args) {
          // 創建字符串對象
        String s = "helloworld";
 
        // int length():獲取字符串的長度,其實也就是字符個數
        System.out.println(s.length());
        System.out.println("--------");
 
        // char charAt(int index):獲取指定索引處的字符
        System.out.println(s.charAt(0));
        System.out.println(s.charAt(1));
        System.out.println("--------");
 
        // int indexOf(String str):獲取str在字符串對象中第一次出現的索引
        System.out.println(s.indexOf("l"));
        System.out.println(s.indexOf("owo"));
        System.out.println(s.indexOf("ak"));
        System.out.println("--------");
 
        // String substring(int start):從start開始截取字符串
        System.out.println(s.substring(0));
        System.out.println(s.substring(5));
        System.out.println("--------");
 
        // String substring(int start,int end):從start開始,到end結束截取字符串
        System.out.println(s.substring(0, s.length()));
        System.out.println(s.substring(3, 8));
        
    }

}
測試

  結果:

    

3.3、String的轉換功能

  1)常用方法

  char[] toCharArray():把字符串轉換為字符數組
  String toLowerCase():把字符串轉換為小寫字符串
  String toUpperCase():把字符串轉換為大寫字符串

  2)核心代碼

public class TestString {
    public static void main(String[] args) {
         // 創建字符串對象
        String s = "abcde";
 
        // char[] toCharArray():把字符串轉換為字符數組
        char[] chs = s.toCharArray();
        for (int x = 0; x < chs.length; x++) {
            System.out.println(chs[x]);
        }
 
        System.out.println("-----------");
 
        // String toLowerCase():把字符串轉換為小寫字符串
        System.out.println("HelloWorld".toLowerCase());
        // String toUpperCase():把字符串轉換為大寫字符串
        System.out.println("HelloWorld".toUpperCase());
        
    }

}
測試

 

  結果:

    

  注意:  

    字符串的遍歷有兩種方式:一是ength()加上charAt()。二是把字符串轉換為字符數組,然后遍歷數組。

3.4、其他常用方法

  1)常用方法

  去除字符串兩端空格:String trim()
  按照指定符號分割字符串:String[] split(String str)

  2)核心代碼

public class TestString {
    public static void main(String[] args) {
          // 創建字符串對象
        String s1 = "helloworld";
        String s2 = " helloworld ";
        String s3 = " hello world ";
        System.out.println("---" + s1 + "---");
        System.out.println("---" + s1.trim() + "---");
        System.out.println("---" + s2 + "---");
        System.out.println("---" + s2.trim() + "---");
        System.out.println("---" + s3 + "---");
        System.out.println("---" + s3.trim() + "---");
        System.out.println("-------------------");
 
        // String[] split(String str)
        // 創建字符串對象
        String s4 = "aa,bb,cc";
        String[] strArray = s4.split(",");
        for (int x = 0; x < strArray.length; x++) {
            System.out.println(strArray[x]);
        }
    }

}
測試

 

  結果:

      

四、String的不可變性

當我們去閱讀源代碼的時候,會發現有這樣的一句話:

  

意思就是說:String是個常量,從一出生就注定不可變

我想大家應該就知道為什么String不可變了,String類被final修飾,官方注釋說明創建后不能被改變,但是為什么String要使用final修飾呢? 

4.1、前言

  了解一個經典的面試題:

public class Apple {
    public static void main(String[] args) {
        String a = "abc";
        String b = "abc";
        String c = new String("abc");
        System.out.println(a==b);  //true
        System.out.println(a.equals(b));  //true
        System.out.println(a==c);  //false
        System.out.println(a.equals(c));  //true
    }
}

  內存圖:

    

4.2、分析

  因為String太過常用,JAVA類庫的設計者在實現時做了個小小的變化,即采用了享元模式,每當生成一個新內容的字符串時,他們都被添加到一個共享池中,當第二次再次生成同樣內容的字符串實例時,

  就共享此對象,而不是創建一個新對象,但是這樣的做法僅僅適合於通過=符號進行的初始化。  

  需要說明一點的是,在object中,equals()是用來比較內存地址的,但是String重寫了equals()方法,用來比較內容的,即使是不同地址,只要內容一致,也會返回true,這也就是為什么a.equals(c)返回true的原因了。

4.3、String不可變的好處

  可以實現多個變量引用堆內存中的同一個字符串實例,避免創建的開銷

  我們的程序中大量使用了String字符串,有可能是出於安全性考慮

  大家都知道HashMap中key為String類型,如果可變將變的多么可怕

  當我們在傳參的時候,使用不可變類不需要去考慮誰可能會修改其內部的值,如果使用可變類的話,可能需要每次記得重新拷貝出里面的值,性能會有一定的損失

五、字符串常量池

5.1、字符串常量池概述

  1)常量池表(Constant_Pool table)

    Class文件中存儲所有常量(包括字符串)的table。
    這是Class文件中的內容,還不是運行時的內容,不要理解它是個池子,其實就是Class文件中的字節碼指令。

  2)運行時常量池(Runtime Constant Pool) 

    JVM內存中方法區的一部分,這是運行時的內容
    這部分內容(絕大部分)是隨着JVM運行時候,從常量池轉化而來,每個Class對應一個運行時常量池
    上一句中說絕大部分是因為:除了 Class中常量池內容,還可能包括動態生成並加入這里的內容

  3)字符串常量池(String Pool)

    這部分也在方法區中,但與Runtime Constant Pool不是一個概念,String Pool是JVM實例全局共享的,全局只有一個
    JVM規范要求進入這里的String實例叫“被駐留的interned string”,各個JVM可以有不同的實現,HotSpot是設置了一個哈希表StringTable來引用堆中的字符串實例,被引用就是被駐留。

5.2、亨元模式

  其實字符串常量池這個問題涉及到一個設計模式,叫“享元模式”,顧名思義 - - - > 共享元素模式
  也就是說:一個系統中如果有多處用到了相同的一個元素,那么我們應該只存儲一份此元素,而讓所有地方都引用這一個元素
  Java中String部分就是根據享元模式設計的,而那個存儲元素的地方就叫做“字符串常量池 - String Pool”

5.3、詳細分析

  舉例:

int x  = 10;
String y = "hello";

  1)首先,10"hello"會在經過javac(或者其他編譯器)編譯過后變為Class文件中constant_pool table的內容

  2)當我們的程序運行時,也就是說JVM運行時,每個Classconstant_pool table中的內容會被加載到JVM內存中的方法區中各自Class的Runtime Constant Pool

  3)一個沒有被String Pool包含的Runtime Constant Pool中的字符串(這里是"hello")會被加入到String Pool中(HosSpot使用hashtable引用方式),步驟如下:   

    一是:在Java Heap中根據"hello"字面量create一個字符串對象
    二是:將字面量"hello"與字符串對象的引用在hashtable中關聯起來,鍵 - 值 形式是:"hello" = 對象的引用地址。

   另外來說,當一個新的字符串出現在Runtime Constant Pool中時怎么判斷需不需要在Java Heap中創建新對象呢?

  策略是這樣:會先去根據equals來比較Runtime Constant Pool中的這個字符串是否和String Pool中某一個是相等的(也就是找是否已經存在),如果有那么就不創建,直接使用其引用;反之,如上3

  如此,就實現了享元模式,提高的內存利用效率。

  舉例:

      使用String s = new String("hello");會創建幾個對象

      會創建2個對象

      首先,出現了字面量"hello",那么去String Pool中查找是否有相同字符串存在,因為程序就這一行代碼所以肯定沒有,那么就在Java Heap中用字面量"hello"首先創建1個String對象

      接着,new String("hello"),關鍵字new又在Java Heap中創建了1個對象,然后調用接收String參數的構造器進行了初始化。最終s的引用是這個String對象.

      

喜歡就點個“推薦”!         


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM