真的懂Java的String嗎?
1.String的特性
1.1不變性
我們常常聽人說,HashMap 的 key 建議使用不可變類,比如說 String 這種不可變類。這裡說不可變指的是類值一旦被初始化,就不能再被改變了,如果被修改,將會是新的類,我們寫個demo 來演示一下。
public
class
test
{
public
static
void
main
(
String
[
] args
)
{
String str
=
"hello"
;
str
=str
+
"world"
;
}
}
從程式碼上來看,s 的值好像被修改了,但從 debug 的日誌來看,其實是 s 的記憶體地址已經被修了,也就說 s =“world” 這個看似簡單的賦值,其實已經把 s 的引用指向了新的 String,debug 截圖顯示記憶體地址已經被修改,兩張截圖如下,我們可以看到標紅的地址值已經修改了。
用示意圖來表示堆記憶體,即見下圖。
我們可以看下str的地址已經改了,說了生成了兩個字串,String類的官方註釋為 Strings are constant; their values cannot be changed after they are created. 簡單翻譯下為 字串是常量;它們的值在建立後不能更改。
下面為String的相關程式碼,如下程式碼,我們可以看到:
1. String 被 final 修飾,說明 String 類絕不可能被繼承了,也就是說任何對 String 的操作方法,都不會被繼承覆寫,即可保證雙親委派機制,保證基類的安全性。
2. String 中儲存資料的是一個 char 的陣列 value。我們發現 value 也是被 final 修飾的,也就是說 value 一旦被賦值,記憶體地址是絕對無法修改的,而且 value 的許可權是 private 的,外部絕對訪問不到,String沒有開放出可以對 value 進行賦值的方法,所以說 value 一旦產生,記憶體地址就根本無法被修改。
/** The value is used for character storage. */
private final char value
[
]
;
/** Cache the hash code for the string */
private int hash
;
// Default to 0
/** use serialVersionUID from JDK 1.0.2 for interoperability */
private
static final long serialVersionUID
=
-
6849794470754667710L
;
1.2相等判斷
相等判斷邏輯寫的很清楚明瞭,如果有人問如何判斷兩者是否相等時,我們可以從兩者的底層結構出發,這樣可以迅速想到一種貼合實際的思路和方法,就像 String 底層的資料結構是 char 的陣列一樣,判斷相等時,就挨個比較 char 陣列中的字元是否相等即可。
(這裡先挖個坑,攜程問過類似題目)
public boolean
equals
(
Object anObject
)
{
//如果地址相等,則直接返回true
if
(
this
== anObject
)
{
return
true
;
}
//如果為String字串,則進行下面的邏輯判斷
if
(anObject
instanceof
String
)
{
//將物件轉化為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迴圈挨個比較每個char
while
(
n
--
!=
0
)
{
if
(v1
[i
]
!= v2
[i
]
)
return
false
;
i
++
;
}
return
true
;
}
}
return
false
;
}
相等邏輯的流程圖如下,我們可以看到整個流程還是很清楚的。
1.3替換操作
替換在平時工作中也經常使用,主要有 replace 替換所有字元、replaceAll 批量替換字串、replaceFirst這三種場景。
下面寫了一個 demo 演示一下三種場景:
public
static
void
main
(
String
[
] args
)
{
String str
=
"hello word !!"
;
System
.out
.
println
(
"替換之前 :"
+ str
)
;
str
= str
.
replace
(
'l'
,
'd'
)
;
System
.out
.
println
(
"替換所有字元 :"
+ str
)
;
str
= str
.
replaceAll
(
"d"
,
"l"
)
;
System
.out
.
println
(
"替換全部 :"
+ str
)
;
str
= str
.
replaceFirst
(
"l"
,
""
)
;
System
.out
.
println
(
"替換第一個 l :"
+ str
)
;
}
輸出的結果是:
這邊要注意一點是 replace和 replaceAll的區別, 不是替換和替換所有的區別哦。
而是replaceAll支援 正規表示式,因此會對引數進行解析(兩個引數均是),如replaceAll("\\d", "*"),而replace則不會,replace("\\d","*")就是替換"\\d"的字串,而不會解析為正則。
1.4 intern方法
String.intern() 是一個 Native 方法,即是c和c++與底層互動的程式碼,它的作用(在
JDK1.6和1.7操作不同
)是:
如果執行時常量池中已經包含一個等於此 String 物件內容的字串,則直接返回常量池中該字串的引用;
如果沒有, 那麼
在jdk1.6中,將此String物件新增到常量池中,然後返回這個String物件的引用(此時引用的串在常量池)。
在jdk1.7中,放入一個引用,指向堆中的String物件的地址,返回這個引用地址(此時引用的串在堆)。
/**
* Returns a canonical representation for the string object.
* <p>
* A pool of strings, initially empty, is maintained privately by the
* class {@code String}.
* <p>
* 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.
* <p>
* 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}.
* <p>
* All literal strings and string-valued constant expressions are
* interned. String literals are defined in section 3.10.5 of the
* <cite>The Java™ Language Specification</cite>.
*java學習交流:737251827 進入可領取學習資源及對十年開發經驗大佬提問,免費解答!
* @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
(
)
;
如果看上面看不懂,我們來看下一下具體的例子,並來分析下。
public
static
void
main
(
String
[
] args
)
{
String s1
=
new
String
(
"學習Java"
)
;
s1
.
intern
(
)
;
String s2
=
"學習Java"
;
System
.out
.
println
(s1
== s2
)
;
String s3
=
new
String
(
"學習Java"
)
+
new
String
(
"test"
)
;
s3
.
intern
(
)
;
String s4
=
"學習Javatest"
;
System
.out
.
println
(s3
== s4
)
;
}
我們來看下結果,實際的列印資訊如下。
為什麼顯示這樣的結果,我們來看下。所以在 jdk7 的版本中,字串常量池已經從方法區移到正常的堆 區域了。
-
第一個false: 第一句程式碼String s1 = new String("學習Java");生成了2個物件。常量池中的“學習Java” 和堆中的字串物件。
s1.intern();
這一句是 s1 物件去常量池中尋找後,發現 “學習Java的小姐姐” 已經在常量池裡了。接下來String s2 = "
學習Java的";
這句程式碼是生成一個 s2的引用指向常量池中的“學習Java”物件。 結果就是 s 和 s2 的引用地址明顯不同,所以為列印結果是false。 -
第二個true:先看 s3和s4字串。
String s3 = new String("學習Java") + new String("test");
,這句程式碼中現在生成了3個物件,是字串常量池中的“學習Java” ,"test"和堆 中的 s3引用指向的物件。此時s3引用物件內容是”學習Javatest”,但此時常量池中是沒有 “學習Javatest”物件的,接下來s3.intern();
這一句程式碼,是將 s3中的“學習Javatest”字串放入 String 常量池中,因為此時常量池中不存在“學習Javatest”字串,常量池不需要再儲存一份物件了,可以直接儲存堆中的引用。這份引用指向 s3 引用的物件。 也就是說引用地址是相同的。最後String s4 = "
學習Javatest";
這句程式碼中”學習Javatest”是顯示宣告的,因此會直接去常量池中建立,建立的時候發現已經有這個物件了,此時也就是指向 s3 引用物件的一個引用。所以 s4 引用就指向和 s3 一樣了。因此最後的比較s3 == s4
是 true。
我們再看下,如果把上面的兩行程式碼調整下位置,列印結果是不是不同。
public
static
void
main
(
String
[
] args
)
{
String s1
=
new
String
(
"學習Java"
)
;
String s2
=
"學習Java"
;
s1
.
intern
(
)
;
System
.out
.
println
(s1
== s2
)
;
String s3
=
new
String
(
"學習Java"
)
+
new
String
(
"test"
)
;
String s4
=
"學習Javatest"
;
s3
.
intern
(
)
;
System
.out
.
println
(s3
== s4
)
;
//java學習交流:737251827 進入可領取學習資源及對十年開發經驗大佬提問,免費解答!
}
第一個false: s1 和 s2 程式碼中,
s1.intern();
,這一句往後放也不會有什麼影響了,因為物件池中在執行第一句程式碼
String s = new String("學習Java");
的時候已經生成“
學習Java
”物件了。下邊的s2宣告都是直接從常量池中取地址引用的。 s 和 s2 的引用地址是不會相等的。
**第二個false:**與上面唯一的區別在於
s3.intern();
的順序是放在
String s4 = "學習Javatest";
後了。這樣,首先執行
String s4 = "學習Javatest";
宣告 s4 的時候常量池中是不存在“
學習Javatest
”物件的,執行完畢後,“
學習Javatest
“物件是 s4 宣告產生的新物件。然後再執行
s3.intern();
時,常量池中“
學習Javatest
”物件已經存在了,因此 s3 和 s4 的引用是不同的。
2. String、StringBuilder和StringBuffer
2.1 繼承結構
2.2 主要區別
1)String是不可變字元序列,StringBuilder和StringBuffer是可變字元序列。
2)執行速度StringBuilder > StringBuffer > String。
3)StringBuilder是非執行緒安全的,StringBuffer是執行緒安全的。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70010294/viewspace-2846286/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- TCP|你真的懂 HTTP 嗎?TCPHTTP
- 你真的懂函式嗎?函式
- 你真的懂C++嗎?C++
- 你真的懂JavaScript的正則嗎?JavaScript
- 你真的懂Android的TextView嗎?AndroidTextView
- 你真的懂Python命名嗎?Python
- 騰訊面試,你真的懂HTTP嗎?面試HTTP
- 你真的懂 Java 的記憶體管理和引用型別嗎?Java記憶體型別
- 你真的懂 == 和 equals 的區別嗎?
- RabbitMQ的佇列模式你真的懂嗎MQ佇列模式
- 你真的懂JavaScript計時器嗎?JavaScript
- 分頁查詢,你真的懂嗎?
- ava String 物件,你真的瞭解了嗎?物件
- 你真的懂JavaScript基礎型別嗎JavaScript型別
- 你真的懂Redis的5種基本資料結構嗎?Redis資料結構
- Spring 系列(三):你真的懂@RequestMapping嗎?SpringAPP
- 細說 C# 中的 IEnumerable,你真的懂 foreach 嗎?C#
- 【前端詞典】繼承(一) - 原型鏈你真的懂嗎?前端繼承原型
- 你真的懂模組化嗎?教你CommonJS實現JS
- 你真的懂js獲取可視區寬高嗎JS
- Java內部類你真的會嗎?Java
- java基礎真的不重要嗎?Java
- 你真的懂synchronized鎖?synchronized
- 你真的懂HTML嗎-從”最新快閃記憶體”說起HTML記憶體
- Java隨談(六)## 我們真的理解 Java 裡的整型嗎?Java
- Go 和 Java 的效能對比,真的如此嗎?GoJava
- 你真的會閱讀Java的異常資訊嗎?Java
- 您真的懂fragment的onResume,setUserVisibleHint,onHiddenChanged,isVisible方法嗎!Fragment
- 你常說遊戲需要公平,又真的懂遊戲平衡性嗎?遊戲
- 參加java培訓真的能學到有用的嗎Java
- Java最大的優勢真的在於跨平臺嗎?Java
- 你真的會用 Java 中的三目運算子嗎?Java
- 瞎搞!你真的懂什麼是ERP、中臺和低程式碼嗎?
- 細思極恐 - 你真的會寫 Java 嗎?Java
- 細思極恐-你真的會寫java嗎?Java
- 在Java中,你真的會日期轉換嗎Java
- 解答:Java就業薪資真的很高嗎?Java就業
- java開發真的需要那麼複雜嗎?Java