【Java】【基礎類】詳解String類
簡介
String類是干什么用的?為什么需要出現String這個類?
String類是我們日常最常用的字符串操作類了,因此String類作為Java語言的核心類,String類位于java.lang包下面,String類提供了很多操作字符串的方法,比如字符串的比較、查找、截取、大小寫轉換等操作,還提供了“+”連接符(字符串連接符)和對象轉換為字符串的支持,也就是說字符串對象可以使用“+”連接其他對象。就像我們看到的“A”、"B"等字面值,都是String類的實例對象;例如String str="A"和String str=new String("A"),都是Stirng實例對象,只不過它們的實現方式有點不同:
前者是默認調用String.valueOf()靜態方法來返回一個String對象的(valueOf方法有很多種,具體看實際):
public static String valueOf(char c) {
char data[] = {c};
return new String(data, true);
}
String(char[] value, boolean share) {
// assert share : "unshared not supported";
this.value = value;
}
而后者是直接通過構造器new出來的一個String對象(其實是先通過上面的方式創建一個字符串“A”的String實例,然后把該實例對象當作構造器參數傳進去的):
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
最后會將其值(“A”)保存到String類的char數組中:
/** The value is used for character storage. */
private final char value[];
我們可以從源碼角度去分析String類,獲取更多的信息。
提前說一下,String類是一個不可變類,稱為不可變類需要滿足的條件為:
使用private和final修飾該類的成員變量
提供帶參數的構造器用于對成員變量進行初始化
僅為該類提供getter方法,不提供setter方法,因為普通方法無法修改final修飾的成員變量
一、String類結構圖
可以看出String類是一個不可變類,使用final修飾,表示該類不可以被繼承,同時實現了三個接口:
Serializable 接口是為了實現類對象的序列化,主要是能把堆內存中的對象的生命周期延長,做持久化操作。當下次再需要這個對象的時候,我們不用 new了,直接從硬盤中讀取就可以了。
Comparable 接口強行對實現它的每個類的對象進行整體排序。此排序被稱為該類的自然排序 ,類的 compareTo 方法被稱為它的自然比較方法 。實現此接口的對象列表(和數組)可以通過 Collections.sort(和 Arrays.sort)進行自動排序。實現此接口的對象可以用作有序映射表中的鍵或有序集合中的元素,無需指定比較器。后續會講解 String 類中的 compareTo 方法。
CharSequence 是一個接口,它只包括 length(), charAt(int index), subSequence(int start, int end)這幾個 API 接口。除了 String 實現了 CharSequence 之外,StringBuffer 和 StringBuilder 也實現了 CharSequence 接口。CharSequence 就是字符序列,String, StringBuilder 和 StringBuffer 本質上都是通過字符數組實現的!
二、源碼注釋
/**
1.Stirng類表示字符串,所有Java程序中的字符串字面量,如"ABC"都是都是該類的一個實例
* The {@code String} class represents character strings. All
* string literals in Java programs, such as {@code "abc"}, are
* implemented as instances of this class.
*
2.Stirng字符串字面量是一個常量,它的值一旦被創建就不能被改變,StringBuffer是支持多次修改字符串的,String對象不支持修改,因此可以被共享。
* Strings are constant; their values cannot be changed after they
* are created. String buffers support mutable strings.
* Because String objects are immutable they can be shared.
3.String類包含了字符序列的很多方法,像比較字符串、搜索字符串、截取字符串、復制字符串,或者字符串大小寫轉換等等方法,遵循unicode編碼標準
* The class {@code String} includes methods for examining
* individual characters of the sequence, for comparing strings, for
* searching strings, for extracting substrings, and for creating a
* copy of a string with all characters translated to uppercase or to
* lowercase. Case mapping is based on the Unicode Standard version
* specified by the {@link java.lang.Character Character} class.
*
4.String支持Java連接符"+"操作,轉換成其他的字符串對象,其實是通過StringBuilder或者StringBuffer類的append方法拼接實現的。最后會調用Object的toString()方法返回拼接好的String對象。
但是需要注意的是,由于使用連接符+,jvm會隱式創建StringBuiller對象來進行對象的拼接,如果頻繁大量的使用連接符+,就會造成創建大量的StringBuilder對象在堆內存中,造成GC的頻繁回收工作,這樣很影響工作效率。所以需要注意這一點。例如:
String s = "abc";
for (int i=0; i<10000; i++) {
s += "abc";
}
這樣就會很影響效率,我們可以這樣做:
String s ;
StringBuilder ss=new StringBuilder(s);
for (int i=0; i<10000; i++) {
ss += "abc";
}
s=ss.toString();
三、字段屬性
稱為不可變類的必要條件之一就是成員屬性都是私有private的,如下:
/** The value is used for character storage. */
1.String底層是有char數組構成的,為private final修飾,是不可變的,不可以被繼承,通過char數組來保存數據的。數組大小在初始化就 確定了,不可變。
private final char value[];
/** Cache the hash code for the string */
2.用來保存String對象的hashCode值,默認為0。
private int hash; // Default to 0
/** use serialVersionUID from JDK 1.0.2 for interoperability */
3.因為String實現了Serializable接口,所以支持序列化和反序列化。Java的序列化機制是通過在運行時判斷類的serialVersionUID來驗證版本一致性的。在進行反序列化時,JVM會把傳來的字節流中的serialVersionUID與本地相應實體(類)的serialVersionUID進行比較,如果相同就認為是一致的,可以進行反序列化,否則就會出現序列化版本不一致的異常(InvalidCastException)。
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];
四、構造器
String類的構造器有很多:
1.無參的
public String() {
this.value = "".value;
}
2.字符串參數
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
3.char數組參數
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
4.帶char數組范圍偏移量參數
public String(char value[], int offset, int count) {
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
if (count <= 0) {
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
if (offset <= value.length) {
this.value = "".value;
return;
}
}
// Note: offset or count might be near -1>>>1.
if (offset > value.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
this.value = Arrays.copyOfRange(value, offset, offset+count);
}
5.參數為int數組并帶范圍偏移量參數
其中int數組需要滿足Unicode編碼點條件(通過codePoint >>> 16驗證):
作為參數的 int 數組中值,至少需要滿足“大寫字母(A-Z):65 (A)~ 90(Z);小寫字母(a-z):97(a) ~ 122(z);字符數字(‘0’ ~ ‘9’):48(‘0’) ~ 57(‘9’)”的條件。當數組中值為其他值時,得到的字符串結果可能為空或特殊符號。
public String(int[] codePoints, int offset, int count) {
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
if (count <= 0) {
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
if (offset <= codePoints.length) {
this.value = "".value;
return;
}
}
// Note: offset or count might be near -1>>>1.
if (offset > codePoints.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
final int end = offset + count;
// Pass 1: Compute precise size of char[]
int n = count;
for (int i = offset; i < end; i++) {
int c = codePoints[i];
if (Character.isBmpCodePoint(c))
continue;
else if (Character.isValidCodePoint(c))
n++;
else throw new IllegalArgumentException(Integer.toString(c));
}
// Pass 2: Allocate and fill in char[]
final char[] v = new char[n];
for (int i = offset, j = 0; i < end; i++, j++) {
int c = codePoints[i];
if (Character.isBmpCodePoint(c))
v[j] = (char)c;
else
Character.toSurrogates(c, v, j++);
}
this.value = v;
}
6.參數為字節數組或偏移量或編碼方式
Jdk1.1版本:
public String(byte bytes[], int offset, int length, String charsetName)
throws UnsupportedEncodingException {
if (charsetName == null)
throw new NullPointerException("charsetName");
//檢查字節數組偏移量相關邊界問題
checkBounds(bytes, offset, length);
//根據編碼方式給value賦值
this.value = StringCoding.decode(charsetName, bytes, offset, length);
}
Jdk1.6版本:
public String(byte bytes[], int offset, int length, Charset charset) {
if (charset == null)
throw new NullPointerException("charset");
checkBounds(bytes, offset, length);
this.value =? StringCoding.decode(charset, bytes, offset, length);
}
public String(byte bytes[], String charsetName)
throws UnsupportedEncodingException {
this(bytes, 0, bytes.length, charsetName);
}
public String(byte bytes[], Charset charset) {
this(bytes, 0, bytes.length, charset);
}
public String(byte bytes[], int offset, int length) {
checkBounds(bytes, offset, length);
this.value = StringCoding.decode(bytes, offset, length);
}
public String(byte bytes[]) {
this(bytes, 0, bytes.length);
}
7.參數為StringBuffer或StringBuilder
public String(StringBuffer buffer) {
synchronized(buffer) {
this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
}
}
public String(StringBuilder builder) {
this.value = Arrays.copyOf(builder.getValue(), builder.length());
}
五、常用方法
length()?返回字符串長度
isEmpty()?返回字符串是否為空
charAt(int?index)?返回字符串中第(index+1)個字符(數組索引)
char[]?toCharArray()?轉化成字符數組
trim()去掉兩端空格
toUpperCase()轉化為大寫
toLowerCase()轉化為小寫
boolean?matches(String?regex)?判斷字符串是否匹配給定的regex正則表達式
boolean?contains(CharSequence?s)?判斷字符串是否包含字符序列?s
String[]?split(String?regex,?int?limit)?按照字符?regex將字符串分成?limit?份
String[]?split(String?regex)?按照字符?regex?將字符串分段
1.連接函數concat
public String concat(String str) {
int otherLen = str.length();
if (otherLen == 0) {
return this;
}
int len = value.length;
char buf[] = Arrays.copyOf(value, len + otherLen);
str.getChars(buf, len);
return new String(buf, true);
}
會新new一個String對象對原來的沒有影響
2.getBytes()方法
public byte[] getBytes(Charset charset) {
if (charset == null) throw new NullPointerException();
return StringCoding.encode(charset, value, 0, value.length);
}
public byte[] getBytes(String charsetName)
throws UnsupportedEncodingException {
if (charsetName == null) throw new NullPointerException();
return StringCoding.encode(charsetName, value, 0, value.length);
}
這兩種方法其實都是根據編碼方法來解碼返回字節數組
3.equals和hashCode方法
String類重寫的Object類的equals方法和hashCode方法,我們先看一下Object類的equals方法和hashCode方法:
public boolean equals(Object obj) {
return (this == obj);
}
public native int hashCode();
它的equals方法是是直接使用==來判斷兩個對象是否相等的(基本類型比較的它們的值,引用類型比較的是它們的引用地址),hashCode方法是本地Native方法
我們再來看一下String類重寫的equals方法和hashCode方法:
public boolean equals(Object anObject) {
//1.兩個字符串對象相同(即引用地址相同),則返回true
if (this == anObject) {
return true;
}
//2.長度一致且類型相同時,比較兩個對象的內容是否相等
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;
}
//hashCode的計算公式為s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
為什么要使用這個公式,就是在存儲數據計算 hash 地址的時候,我們希望盡量減少有同樣的 hash 地址。如果使用相同 hash 地址的數據過多,那么這些數據所組成的 hash 鏈就更長,從而降低了查詢效率。
所以在選擇系數的時候要選擇盡量長的系數并且讓乘法盡量不要溢出的系數,因為如果計算出來的 hash 地址越大,所謂的“沖突”就越少,查找起來效率也會提高。
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
4.其他的方法還有很多,用到的時候再回來分析寫上去
Java 數據結構
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。