天天看點

《JavaSE-第十二章》之String

部落格首頁:​​KC老衲愛尼姑的部落格首頁​​

​​部落客的github,平常所寫代碼皆在于此​​

​​刷題求職神器​​

共勉:talk is cheap, show me the code

作者是爪哇島的新手,水準很有限,如果發現錯誤,一定要及時告知作者哦!感謝感謝!

刷題求職神器

在下給諸位推薦一款巨好用的刷題求職神器,如果還有小夥伴沒有注冊該網站,可以點選下方連結直接注冊,注冊完後就可以立即刷題了。

《JavaSE-第十二章》之String

傳送門:​​牛客網​​

文章目錄

  • ​​1.String概述​​
  • ​​2.String類常用的構造方法​​
  • ​​3.字元串方法​​
  • ​​4.什麼是池?​​
  • ​​4.1字元串常量池​​
  • ​​4.2再談String對象建立​​
  • ​​4.3intern方法​​
  • ​​5.字元串的不可變性​​
  • ​​6字元串修改​​
  • ​​7.StringBuilder和StringBuffffe​​
  • ​​8.面試題​​

1.String概述

String是Java中的引用類型,位于java.lang下,該類所定義的變量可用于指向字元串對象,然後來操作該字元串。

String既然是一個類,那麼可以從該類的屬性以及構造方法出發,去認識該類。

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

通過源碼可知,String實作了三個接口,首先java.io.Serializable是一個空接口,作用就是辨別該類,說明此類可以被序列化,Comparable接口是用于比較大小的接口,最後一個CharSequence接口,該接口是char值的可讀序列, 該接口為其實作類提供統一的,隻讀通路許多不同類型的char序列。

2.String類常用的構造方法

String類提供了許多的構造方法,但是最常用有以下幾種。

@Test
    public  void testString(){
        //使用常量串構造
        String  s  = "hello";
        System.out.println(s);
        //newString對象
        String s2 = new String("world");
        System.out.println(s2);
        //使用字元數組進行構造
        char [] arr ={'a','b','c'};
        String s3 = new String(arr);
        System.out.println(s3);
    }      

通過源碼可以看到String的底層是一個被private以及final修飾的字元數組

private final char value[];

 private int hash; // Default to 0      

通過調試也能證明底層确實是一個數組,隻是它的組成部分還有hash。

1.直接使用常量串構造詳解

《JavaSE-第十二章》之String

2.newString詳解

《JavaSE-第十二章》之String

3.使用字元數組進行構造詳解

《JavaSE-第十二章》之String

當傳入字元數組時 ,底層會拷貝一份字元數組并将拷貝後數組的引用給字元串對象的value。

傳入字元數組時String的構造方法

public String(char value[]) {
        this.value = Arrays.copyOf(value, value.length);
    }      

3.字元串方法

String對象的比較

字元串的對象的比較分為以下4中

1.== 比較是否引用同一個對象

@Test
    public void testString2(){
        String s1 = new String("hmr");
        String s2 = new String("hmr");
        String s3 = new String("yzq");
        String s4 = s1;
        System.out.println(s1==s3);//fasle
        System.out.println(s1 == s2);//false
        System.out.println(s4==s1);//true
    }      

2.boolean equals(Object anObject) 方法:按照字典序比較

String 重寫了Object中的equals方法,因為Object中的equals方法預設按照==來比較,String類重寫後會按照字典序來比較

String重寫後equals

public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }      

示例

@Test
    public void testString3(){
        String s1 = new String("hmr");
        String s2 = new String("hmr");
        String s3 = new String("yzq");
        String s4 = s1;
        System.out.println(s1.equals(s3));//false
        System.out.println(s1 .equals(s2));//true
        System.out.println(s4.equals(s1));//true
    }      

3.int compareTo(String s) 方法 按照字典序進行比較

與equals不同的是,equals傳回的是boolean類型,而compareTo傳回的是int類型。具體比較方式:

  1. 先按照字典次序大小比較,如果出現不等的字元,直接傳回這兩個字元的大小內插補點
  2. 如果前k個字元相等(k為兩個字元長度最小值),傳回值兩個字元串長度內插補點

示例

@Test
    public void testString4(){
        String s1 = new String("hmr");
        String s2 = new String("hmr");
        String s3 = new String("yzq");
        String s4 = s1;
        System.out.println(s1.compareTo(s2));//0
        System.out.println(s2 .compareTo(s3));//-17
        System.out.println(s4.compareTo(s1));//0
    }      

4.int compareToIgnoreCase(String str) 方法:與compareTo方式相同,但是忽略大小寫比較。

示例

@Test
    public void testString5(){
        String s1 = new String("HMR");
        String s2 = new String("hmr");
        String s3 = new String("YZQ");
        String s5 = new String("yzq");
        String s4 = s1;
        System.out.println(s1.compareToIgnoreCase(s2));//0
        System.out.println(s2 .compareToIgnoreCase(s3));//-17
        System.out.println(s4.compareToIgnoreCase(s1));//0
        System.out.println(s5.compareToIgnoreCase(s3));//0
}      

4.什麼是池?

由于我們經常對這些字元串常量(常用資源)進行操作,而每次使用時都會開辟相應的記憶體,為了是程式運作的速度加快,就以空間來換時間,即事先将要頻繁使用的資源放入空間中,當我們需要操作時直接從空間來拿使用就行了,這個空間就是池。這就好比張三家裡沒有冰箱,那麼想吃冰棒得去小賣部,張三每天都出去覺得太麻煩了,于是自己買了個冰箱,在向冰箱裡屯了許多冰棒,以後向吃就可以随時吃,就節約了大量的時間。

4.1字元串常量池

字元串常量池在JVM中是StringTable類,實際是一個固定大小的HashTable(數組+連結清單(val為字元串對象))。不同版本的jdk下的字元串常量池的位置和大小的是不同的再次讨論的是jdk8下的字元串常量池,jdk8中該池位于堆記憶體中,池的大小可以設定,其最小值是1009。

4.2再談String對象建立

1.直接使用字元串常量進行指派

@Test
    public void testString6(){
       String s1 = "hello";
       String s2 = "hello";
        System.out.println(s1==s2);//true
}      

2.通過new建立String類對象

@Test
    public void testString6(){
      String s3 = new String("hello");
      String s4 =new String ("hello");
      System.out.println(s3==s4);//false
    }      
《JavaSE-第十二章》之String

當直接使用字元串常量進行指派時,在加載位元組碼檔案時,“hello”在字元串中已經建立好并儲存在字元串常量池中,當代碼走到String s1 = “hello”;建立對象時,會優先在字元串常量池中查找是否有該字元串,當找到了該字元串則将該字元串的引用指派給s1,如果沒有則建立新的字元串對象并入池。通過new建立的字元串類對象,首先會在堆記憶體開辟一個String對象,然後向字元串常量池中查找該字元是否存在,如若存在則将字元數組的引用指派給字元串對象的value,反之則直接建立新的字元串對象。

通過上述,可以得出new的對象是唯一,并且使用常量串建立的String類型對象的效率更高,更節約空間。

4.3intern方法

intern 是一個native方法(Native方法指:底層使用C++實作的,看不到其實作的源代碼),該方法的作用是手動将建立的String對象添加到常量池中。

代碼示例

@Test
    public void testString7(){
        char [] arr = new char[]{'a','b','c'};
        String s1 = new String(arr);
        String s2 = "abc";
        System.out.println(s1==s2);//false
    }

    @Test
    public void testString7(){
        char [] arr = new char[]{'a','b','c'};
        String s1 = new String(arr);
        s1.intern();
        String s2 = "abc";
        System.out.println(s1==s2);//true
    }      

當s1調用ntern()方法後,會将s1對象放入到字元串常量池,故s1==s2。

5.字元串的不可變性

String是一種不可變對象. 字元串中的内容是不可改變。字元串不可被修改,是因為:

  1. String類在設計時就是不可變的,
  2. String類中的字元實際是在内部的value字元數組中,通過源碼可知String是被final修飾,不能被繼承,同時把value被final以及private修飾表明value本身的值是不能修改的,也是就是不能引用其它數組,但是對于一個數組是可以通過下标通路修改其數組對應的值,而此時在String類外壓根拿不到value故字元串不可變。
  3. 是以涉及到可能修飾字元串内容的操作都是建立一個新的對象,改變的新的對象,源碼如下。
public String replace(char oldChar, char newChar) {
        if (oldChar != newChar) {
            int len = value.length;
            int i = -1;
            char[] val = value; /* avoid getfield opcode */

            while (++i < len) {
                if (val[i] == oldChar) {
                    break;
                }
            }
            if (i < len) {
                char buf[] = new char[len];
                for (int j = 0; j < i; j++) {
                    buf[j] = val[j];
                }
                while (i < len) {
                    char c = val[i];
                    buf[i] = (c == oldChar) ? newChar : c;
                    i++;
                }
                return new String(buf, true);
            }
        }
        return this;
    }      

6字元串修改

錯誤的使用String進行拼接

public class Test {
    public static void main(String[] args) {
        String s = "";
        for (int i = 0;i<10_000;i++) {
            s+= i;
        }
        System.out.println(s);
    }
}      

使用String進行字元串拼接效率極其低下,之是以速度慢,可以通過檢視Test的彙編代碼來究其本質

《JavaSE-第十二章》之String

通過彙編得知每次進行字元串拼接時都會new一個StringBuilder對象,這也意味着會程式的運作速度是非常低下的,是以盡量不使用String直接拼接字元串,可以使用StringBuilder或者StringBuffer。

7.StringBuilder和StringBuffffe

由于String類型的字元串不可更改,為了可以高效的進行字元串修改,Java提供了StringBuffer和StringBuilder類,這兩個類大同小異,這裡就介紹幾個常用的API更多的方法,更多需求自行檢視 StringBuildre線上文檔。

1.使用StringBuilder進行拼接字元串

@Test
    public void testString9(){
        long start = System.currentTimeMillis();
        StringBuffer s = new StringBuffer("");
        for (int i = 0;i<10_0000;i++) {
           s.append(i);
        }
        long end = System.currentTimeMillis();
        System.out.println(end-start);//18
    }
    
    @Test
    public void testString8(){
        long start = System.currentTimeMillis();
        String s = "";
        for (int i = 0;i<10_0000;i++) {
            s+= i;
        }
        long end = System.currentTimeMillis();//32139
        System.out.println(end-start);
    }      

從上述代碼的運作時間上來看,StringBulder比String速度快了n倍。

2.反轉一個字元串

@Test
    public  void testStringBuilder(){
        StringBuilder s= new StringBuilder("hello world");
        System.out.println(s.reverse().toString());
    }      

注意:String和StringBuilder類不能直接轉換。如果需要轉換可以采用一下方式:

  1. String轉StringBuilder:利用StringBuilder的構造方法或者append()方法。
  2. StringBuilder變成Sring:調用toString()方法。

8.面試題

1,String、StringBuffffer、StringBuilder的差別

  • String的内容不可修改,StringBuffffer與StringBuilder的内容可以修改。
  • StringBuffffer采用同步處理,屬于線程安全操作;而StringBuilder未采用同步處理,屬于線程不安全操

    作。

  1. 以下總共建立了多少個String對象【前提不考慮常量池之前是否存在】

1.String str = “hello”;

隻會開辟一塊堆記憶體空間,儲存在字元串常量池中,然後str共享常量池中的String對象(1個)

2.String str = new String(“hello”)

會開辟兩塊堆記憶體空間,字元串"hello"儲存在字元串常量池中,然後用常量池中的String對象給新開辟

的String對象指派。(2個)

3.String str = new String(new char[]{‘h’, ‘e’, ‘l’, ‘l’, ‘o’})

先在堆上建立一個String對象,然後利用copyof将重新開辟數組空間,将參數字元串數組中内容拷貝到

String對象中(三個)