java const string_深入研究Java String

開始寫 Java 一年來,一直都是遇到什么問題再去解決,還沒有主動的深入的去學習過 Java 語言的特性和深入閱讀 JDK 的源碼。既然決定今后靠 Java

吃飯,還是得花些心思在上面,放棄一些打游戲的時間,系統深入的去學習。

Java String 是 Java 編程中最常用的類之一,也是 JDK 提供的最基礎的類。所以我決定先從 String 類入手,深入的研究一番來開個好頭。

類定義與類成員

打開 JDK 中的 String 源碼,最先應當關注 String 類的定義。

public final class String

implements java.io.Serializable, Comparable, CharSequence

不可繼承與不可變

寫過 Java 的人都知道, 當 final 關鍵字修飾類時,代表此類不可繼承。所以 String 類是不能被外部繼承。這時候我們可能會好奇,String 的設計者

為什么要把它設計成不可繼承的呢。我在知乎上找到了相關的問題和討論,

我覺得首位的回答已經說的很明白了。String 做為 Java 的最基礎的引用數據類型,最重要的一點就是不可變性,所以使用 final 就是為了**禁止繼承

破壞了 String 的不可變的性質**。

實現類的不可變性,不光是用 final 修飾類這么簡單,從源碼中可以看到,String 實際上是對一個字符數組的封裝,而字符數組是私有的,并且沒有提供

任何可以修改字符數組的方法,所以一旦初始化完成, String 對象便無法被修改。

序列化

從上面的類定義中我們看到了 String 實現了序列化的接口 Serializable,所以 String 是支持序列化和反序列化的。

什么是Java對象的序列化?相信很多和我一樣的 Java 菜鳥都有這樣疑問。深入分析Java的序列化與反序列化這篇文章中的這一段話

解釋的很好。

Java平臺允許我們在內存中創建可復用的Java對象,但一般情況下,

只有當JVM處于運行時,這些對象才可能存在,

即,這些對象的生命周期不會比JVM的生命周期更長。但在現實應用中,

就可能要求在JVM停止運行之后能夠保存(持久化)指定的對象,并在將來重新讀取被保存的對象。

Java對象序列化就能夠幫助我們實現該功能。

使用Java對象序列化,在保存對象時,會把其狀態保存為一組字節,在未來,再將這些字節組裝成對象。

必須注意地是,對象序列化保存的是對象的”狀態”,即它的成員變量。由此可知,對象序列化不會關注類中的靜態變量。

除了在持久化對象時會用到對象序列化之外,當使用RMI(遠程方法調用),或在網絡中傳遞對象時,都會用到對象序列化。

Java序列化API為處理對象序列化提供了一個標準機制,該API簡單易用。

在 String 源碼中,我們也可以看到支持序列化的類成員定義。

/** 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

*

* Object Serialization Specification, Section 6.2, "Stream Elements"

*/

private static final ObjectStreamField[] serialPersistentFields =

new ObjectStreamField[0];

serialVersionUID 是一個序列化版本號,Java 通過這個 UID 來判定反序列化時的字節流與本地類的一致性,如果相同則認為一致,

可以進行反序列化,如果不同就會拋出異常。

serialPersistentFields 這個定義則比上一個少見許多,大概猜到是與序列化時的類成員有關系。為了弄懂這個字段的意義,我 google 百度齊上,也

僅僅只找到了 JDK 文檔對類 ObjectStreamField的一丁點描述, `A description of a Serializable field from a Serializable class.

An array of ObjectStreamFields is used to declare the Serializable fields of a class.` 大意是這個類用來描述序列化類的一個序列化字段,

如果定義一個此類的數組則可以聲明類需要被序列化的字段。但是還是沒有找到這個類的具體用法和作用是怎樣的。后來我仔細看了一下這個字段的定義,

與 serialVersionUID 應該是同樣通過具體字段名來定義各種規則的,然后我直接搜索了關鍵字 serialPersistentFields,終于找到了它的具體作用。

即,**默認序列化自定義包括關鍵字 transient 和靜態字段名 serialPersistentFields,transient 用于指定哪個字段不被默認序列化,

serialPersistentFields 用于指定哪些字段需要被默認序列化。如果同時定義了 serialPersistentFields 與 transient,transient 會被忽略。**

我自己也測試了一下,確實是這個效果。

知道了 serialPersistentFields 的作用以后,問題又來了,既然這個靜態字段是用來定義參與序列化的類成員的,那為什么在 String 中這個數組的長度定義為0?

經過一番搜索查找資料以后,還是沒有找到一個明確的解釋,期待如果有大佬看到能解答一下。

可排序

String 類還實現了 Comparable 接口,Comparable接口只有一個方法 public int compareTo(T o),實現了這個接口就意味著該類支持排序,

即可用 Collections.sort 或 Arrays.sort 等方法對該類的對象列表或數組進行排序。

在 String 中我們還可以看到這樣一個靜態變量,

public static final Comparator CASE_INSENSITIVE_ORDER

= new CaseInsensitiveComparator();

private static class CaseInsensitiveComparator

implements Comparator, java.io.Serializable {

// use serialVersionUID from JDK 1.2.2 for interoperability

private static final long serialVersionUID = 8575799808933029326L;

public int compare(String s1, String s2) {

int n1 = s1.length();

int n2 = s2.length();

int min = Math.min(n1, n2);

for (int i = 0; i < min; i++) {

char c1 = s1.charAt(i);

char c2 = s2.charAt(i);

if (c1 != c2) {

c1 = Character.toUpperCase(c1);

c2 = Character.toUpperCase(c2);

if (c1 != c2) {

c1 = Character.toLowerCase(c1);

c2 = Character.toLowerCase(c2);

if (c1 != c2) {

// No overflow because of numeric promotion

return c1 - c2;

}

}

}

}

return n1 - n2;

}

/** Replaces the de-serialized object. */

private Object readResolve() { return CASE_INSENSITIVE_ORDER; }

}

從上面的源碼中可以看出,這個靜態成員是一個實現了 Comparator 接口的類的實例,而實現這個類的作用是比較兩個忽略大小寫的 String 的大小。

那么 Comparable 和 Comparator 有什么區別和聯系呢?同時 String 又為什么要兩個都實現一遍呢?

第一個問題這里就不展開了,總結一下就是,Comparable 是類的內部實現,一個類能且只能實現一次,而 Comparator 則是外部實現,可以通過不改變

類本身的情況下,為類增加更多的排序功能。

所以我們也可以為 String 實現一個 Comparator使用,具體可以參考Comparable與Comparator的區別這篇文章。

String 實現了兩種比較方法的意圖,實際上是一目了然的。實現 Comparable 接口為類提供了標準的排序方案,同時為了滿足大多數排序需求的忽略大小寫排序的情況,

String 再提供一個 Comparator 到公共靜態類成員中。如果還有其他的需求,那就只能我們自己實現了。

類方法

String 的方法大致可以分為以下幾類。

構造方法

功能方法

工廠方法

intern方法

關于 String 的方法的解析,這篇文章已經解析的夠好了,所以我這里也不再重復的說一遍了。不過

最后的 intern 方法值得我們去研究。

intern方法

字符串常量池

String 做為 Java 的基礎類型之一,可以使用字面量的形式去創建對象,例如 String s = "hello"。當然也可以使用 new 去創建 String 的對象,

但是幾乎很少看到這樣的寫法,久而久之我便習慣了第一種寫法,但是卻不知道背后大有學問。下面一段代碼可以看出他們的區別。

public class StringConstPool {

public static void main(String[] args) {

String s1 = "hello world";

String s2 = new String("hello world");

String s3 = "hello world";

String s4 = new String("hello world");

String s5 = "hello " + "world";

String s6 = "hel" + "lo world";

String s7 = "hello";

String s8 = s7 + " world";

System.out.println("s1 == s2: " + String.valueOf(s1 == s2) );

System.out.println("s1.equals(s2): " + String.valueOf(s1.equals(s2)));

System.out.println("s1 == s3: " + String.valueOf(s1 == s3));

System.out.println("s1.equals(s3): " + String.valueOf(s1.equals(s3)));

System.out.println("s2 == s4: " + String.valueOf(s2 == s4));

System.out.println("s2.equals(s4): " + String.valueOf(s2.equals(s4)));

System.out.println("s5 == s6: " + String.valueOf(s5 == s6));

System.out.println("s1 == s8: " + String.valueOf(s1 == s8));

}

}

/* output

s1 == s2: false

s1.equals(s2): true

s1 == s3: true

s1.equals(s3): true

s2 == s4: false

s2.equls(s4): true

s5 == s6: true

s1 == s8: false

*/

從這段代碼的輸出可以看到,equals 比較的結果都是 true,這是因為 String 的 equals 比較的值( Object 對象的默認 equals 實現是比較引用,

String 對此方法進行了重寫)。== 比較的是兩個對象的引用,如果引用相同則返回 true,否則返回 false。s1==s2: false和 s2==s4: false

說明了 new 一個對象一定會生成一個新的引用返回。s1==s3: true 則證明了使用字面量創建對象同樣的字面量會得到同樣的引用。

s5 == s6 實際上和 s1 == s3 在 JVM 眼里是一樣的情況,因為早在編譯階段,這種常量的簡單運算就已經完成了。我們可以使用 javap 反編譯一下 class 文件去查看

編譯后的情況。

? ~ javap -c StringConstPool.class

Compiled from "StringConstPool.java"

public class io.github.jshanet.thinkinginjava.constpool.StringConstPool {

public io.github.jshanet.thinkinginjava.constpool.StringConstPool();

Code:

0: aload_0

1: invokespecial #1 // Method java/lang/Object."":()V

4: return

public static void main(java.lang.String[]);

Code:

0: ldc #2 // String hello world

2: astore_1

3: return

}

看不懂匯編也沒關系,因為注釋已經很清楚了......

s1 == s8 的情況就略復雜,s8 是通過變量的運算而得,所以無法在編譯時直接算出其值。而 Java 又不能重載運算符,所以我們在 JDK 的源碼里也

找不到相關的線索。萬事不絕反編譯,我們再通過反編譯看看實際上編譯器對此是否有影響。

public class io.github.jshanet.thinkinginjava.constpool.StringConstPool {

public io.github.jshanet.thinkinginjava.constpool.StringConstPool();

Code:

0: aload_0

1: invokespecial #1 // Method java/lang/Object."":()V

4: return

public static void main(java.lang.String[]);

Code:

0: ldc #2 // String hello

2: astore_1

3: new #3 // class java/lang/StringBuilder

6: dup

7: invokespecial #4 // Method java/lang/StringBuilder."":()V

10: aload_1

11: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

14: ldc #6 // String world

16: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

19: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;

22: astore_2

23: return

}

通過反編譯的結果可以發現,String 的變量運算實際上在編譯后是由 StringBuilder 實現的,s8 = s7 + " world" 的代碼等價于

(new StringBuilder(s7)).append(" world").toString()。Stringbuilder 是可變的類,通過 append 方法 和 toString 將兩個 String 對象聚合

成一個新的 String 對象,所以到這里就不難理解為什么 s1 == s8 : false 了。

之所以會有以上的效果,是因為有字符串常量池的存在。字符串對象的分配和其他對象一樣是要付出時間和空間代價,而字符串又是程序中最常用的對象,JVM

為了提高性能和減少內存占用,引入了字符串的常量池,在使用字面量創建對象時, JVM 首先會去檢查常量池,如果池中有現成的對象就直接返回它的引用,如果

沒有就創建一個對象,并放到池里。因為字符串不可變的特性,所以 JVM 不用擔心多個變量引用同一個對象會改變對象的狀態。同時運行時實例創建的全局

字符串常量池中有一個表,總是為池中的每個字符串對象維護一個引用,所以這些對象不會被 GC 。

intern 方法的作用

上面說了很多都沒有涉及到主題 intern 方法,那么 intern 方法到作用到底是什么呢?首先查看一下源碼。

/**

* Returns a canonical representation for the string object.

*

* A pool of strings, initially empty, is maintained privately by the

* class {@code String}.

*

* When the intern method is invoked, if the pool already contains a

* string equal to this {@code String} object as determined by

* the {@link #equals(Object)} method, then the string from the pool is

* returned. Otherwise, this {@code String} object is added to the

* pool and a reference to this {@code String} object is returned.

*

* It follows that for any two strings {@code s} and {@code t},

* {@code s.intern() == t.intern()} is {@code true}

* if and only if {@code s.equals(t)} is {@code true}.

*

* All literal strings and string-valued constant expressions are

* interned. String literals are defined in section 3.10.5 of the

* The Java? Language Specification.

*

* @return a string that has the same contents as this string, but is

* guaranteed to be from a pool of unique strings.

*/

public native String intern();

Oracle JDK 中,intern 方法被 native 關鍵字修飾并且沒有實現,這意味著這部分到實現是隱藏起來了。從注釋中看到,這個方法的作用是如果常量池

中存在當前字符串,就會直接返回當前字符串,如果常量池中沒有此字符串,會將此字符串放入常量池中后再返回。通過注釋的介紹已經可以明白這個方法的作用了,

再用幾個例子證明一下。

public class StringConstPool {

public static void main(String[] args) {

String s1 = "hello";

String s2 = new String("hello");

String s3 = s2.intern();

System.out.println("s1 == s2: " + String.valueOf(s1 == s2));

System.out.println("s1 == s3: " + String.valueOf(s1 == s3));

}

}

/* output

s1 == s2: false

s1 == s3: true

*/

這里就很容易的了解 intern 實際上就是把普通的字符串對象也關聯到常量池中。

當然 intern 的實現原理和最佳實踐等也是需要理解學習的,美團技術團隊的這篇深入解析String#intern

很深入也很詳細,推薦閱讀。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/378794.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/378794.shtml
英文地址,請注明出處:http://en.pswp.cn/news/378794.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

python 示例_帶有示例的Python字典update()方法

python 示例字典update()方法 (Dictionary update() Method) update() method is used to update the dictionary by inserting new items to the dictionary. update()方法用于通過將新項目插入字典來更新字典。 Syntax: 句法&#xff1a; dictionary_name.setdefault(itera…

Rsync 使用指南

Rsync是個相當棒的同步工具&#xff0c;比如&#xff1a;1. 如何做本地兩個目錄之間的同步&#xff1f;rsync -av --delete --force ~/Desktop/Miscs/ /media/disk/DesktopMiscs 這樣就可以做~/Desktop/Miscs目錄的鏡像了。/media/disk是我的移動硬盤的掛載點。這里關鍵有個問題…

C++——統計多行單個字符類型個數

鍵盤輸入n個字符&#xff0c;請分別統計大寫字母、小寫字母、數字、其他字符的個數并輸出&#xff1b;還需要輸出所有數字字符之和 【輸入形式】 第一行為一個整數n(100 > n > 0)&#xff0c;接下來n行每行一個字符 【輸出形式】 輸出第1行為4個整數&#xff0c;分別…

安卓項目4

經歷兩天的琢磨&#xff0c;終于把android連接服務器端php&#xff0c;讀取mysql這一塊弄好了。 先說說這幾天遇到的問題。 http://wenku.baidu.com/view/87ca3bfa700abb68a982fbca.html 這是我參照的資料&#xff0c;原先我一度認為是不能實例化ServiceLink類&#xff0c;后來…

system getenv_Java System類getenv()方法及示例

system getenv系統類getenv()方法 (System class getenv() method) getenv() method is available in java.lang package. getenv()方法在java.lang包中可用。 getenv() method is used to return an unmodifiable Map of the current environment variable in key-value pairs…

用ASP獲取客戶端IP地址的方法

要想透過代理服務器取得客戶端的真實IP地址&#xff0c;就要使用 Request.ServerVariables("HTTP_X_FORWARDED_FOR") 來讀取。不過要注意的事&#xff0c;并不是每個代理服務器都能用 Request.ServerVariables("HTTP_X_FORWARDED_FOR") 來讀取客戶端的真實…

C++——已知a+b、 a+c、b+c、 a+b+c,求a、b、 c

有三個非負整數a、b、 C,現按隨機順序給出它們的兩兩和以及總和4個整數&#xff0c;即ab、 ac、bc、 abc, 注意,給出的4個數的順序是隨機的&#xff0c;請根據這四個數求出a、b、c是多少? [輸入形式] 輸入為一-行4個正整數, x1、 x2、x3、 x4 (0≤xi≤10^9) &#xff0c;表示…

DDD:DomainEvent、ApplicationEvent、Command

Command&#xff1a;縱向傳遞&#xff0c;跨分層&#xff0c;在控制器層和應用層之間傳遞。 DomainEvent&#xff1a;橫向傳遞&#xff0c;跨聚合&#xff0c;在一個DLL中。 ApplicationEvent&#xff1a;橫向傳遞&#xff0c;跨模塊&#xff0c;在不同的DLL中。轉載于:https:/…

表示和描述-邊界追蹤

邊界追蹤目標&#xff1a; 輸入&#xff1a;某一區域的點 輸出&#xff1a;這一區域的點的坐標序列&#xff08;順時針或逆時針&#xff09; Moore邊界追蹤法&#xff1a; 兩個前提條件&#xff1a; 1、圖像為二值化后的圖像&#xff08;目標為1&#xff0c;背景為0&#xff0…

視頻的讀取與處理

讀取本地視頻&#xff0c;以灰度視頻輸出 import cv2vc cv2.VideoCapture(E:\Jupyter_workspace\study\data/a.mp4)#視頻路徑根據實際情況而定#檢查是否打開正確 if vc.isOpened():open,fream vc.read()#read()返回兩個參數&#xff0c;第一個參數為打開成功與否True or Fal…

更靈活的定位內存地址的方法05 - 零基礎入門學習匯編語言36

第七章&#xff1a;更靈活的定位內存地址的方法05 讓編程改變世界 Change the world by program 問題7.8 [codesyntax lang"asm"] assume cs:codesg,ds:datasg datasg segment db ibm db dec db dos db vax …

nextgaussian_Java Random nextGaussian()方法與示例

nextgaussian隨機類nextGaussian()方法 (Random Class nextGaussian() method) nextGaussian() method is available in java.util package. nextGaussian()方法在java.util包中可用。 nextGaussian() method is used to generate the next pseudo-random Gaussian double valu…

Java PriorityQueue clear()方法與示例

PriorityQueue類clear()方法 (PriorityQueue Class clear() method) clear() method is available in java.util package. clear()方法在java.util包中可用。 clear() method is used to remove all the objects from this PriorityQueue. clear()方法用于從此PriorityQueue中刪…

圖像分割-邊緣連接

三種基本方法&#xff1a; 1&#xff1a;局部處理 2&#xff1a;區域處理 3&#xff1a;使用霍夫變換的全局處理 局部處理 根據預定的規則&#xff0c;將所有相似點連接起來。 用于確定邊緣像素相似性的兩個主要性質&#xff1a;1、梯度向量的幅度2、梯度向量的角度 由于要…

01-圖像ROI區域獲取

截取部分圖像數據 import cv2 def cv_show(name,img):cv2.imshow(name,img)cv2.waitKey(0)cv2.destroyAllWindows()img2 cv2.imread("E:\Jupyter_workspace\study\data/cat.png")#讀取照片&#xff0c;第二個參數若為0&#xff0c;則灰度圖&#xff1b;若不填或者1…

如何編寫測試計劃

有以下幾個方面需要作考慮&#xff1a; 1. 測試的范圍。要測試什么&#xff0c;這是肯定要明確的&#xff0c;即使你知道&#xff0c;你也要寫出來&#xff0c;讓看這份文檔的人知道測試的范圍。在確定測試內容的時候&#xff0c;還可以做一個優先級的區分&#xff0c;這樣能保…

java clone 序列化_關于Java對象深度Clone以及序列化與反序列化的使用

? 我們可以利用clone方法來實現對象只見的復制&#xff0c;但對于比較復雜的對象(比如對象中包含其他對象&#xff0c;其他對象又包含別的對象.....)這樣我們必須進行層層深度clone&#xff0c;每個對象需要實現 cloneable接口&#xff0c;比較麻煩&#xff0c;那就繼續…

java enummap_Java EnumMap containsKey()方法與示例

java enummapEnumMap類containsKey()方法 (EnumMap Class containsKey() method) containsKey() method is available in java.util package. containsKey()方法在java.util包中可用。 containsKey() method is used to check whether this map has values for the given key e…

02-對圖像進行邊界填充

import cv2 import matplotlib.pyplot as pltimg2 cv2.imread("E:\Jupyter_workspace\study\data/cat.png")#讀取照片&#xff0c;第二個參數若為0&#xff0c;則灰度圖&#xff1b;若不填或者1則彩色圖或本身圖top_size,bottom_size,left_size,right_size (50,50,…