最近以爲本身學的東西經常會忘記,因而就想寫一下筆記。總結一下String相關的一些內容,若是有什麼理解有誤的話歡迎你們指出。java
         計算機誕生的時候,老美爲了存儲他們的英文單詞和一些符號,與計算機有一套約定,整數45表示百分號%,整數50表明數字2,51表示數字3,這些約定成爲ASCII
,他跟計算機約定好了從整數0-127所對應的字符。即1個字節就能夠保存下來了。正則表達式
         後來計算機被世界普遍應用,因此各個國家都須要與計算機作好約定,好比咱們大中國也須要跟計算機約定好,可是128個字符明顯就容不下咱們中國五千年來的文化積累呀,因而咱們和計算機說好,中文要佔3個字節,好比0xB6A001
表示中文字「丁」,這種數值與中文的約定成爲GBK
,那日本也須要與計算機作約定,韓國也須要。世界上那麼多種語言,計算機在識別字符的時候還要先看看是什麼國家跟他作了約定,很是麻煩。數組
         因而Unicode對這些編碼進行了大一統,用6個字節表示全部的字符,範圍從0
到0x10FFFF
,其中經常使用的字符放在0
到0xFFFF
的位置,每個字符對應的數字叫作碼點,安全
         後來人們發現,其實經常使用的字符用0
到0xFFFF
就能夠表示了,後面那些字符用的不多,不必每次都弄3個字節來存儲,因而咱們制定了可變長的UTF-8
和·UTF16
,在存儲過程當中,若是字符在0
到0xFFFF
之間就用2個字節,恰好一個char來存儲,若是大於0xFFFF
的就要用4個字節存儲,恰好2個char。在java中就是以這種方式存儲字符的。app
/** 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;
serialVersionUID
這個與序列化有關,之後再總結默認構造函數函數
public String() { this.value = "".value; }
傳入String性能
public String(String original) { this.value = original.value; this.hash = original.hash; }
傳入char數組學習
public String(char value[]) { this.value = Arrays.copyOf(value, value.length); }
傳入byte數組,須要傳入字符集優化
public String(byte bytes[], String charsetName) throws UnsupportedEncodingException { this(bytes, 0, bytes.length, charsetName); }
平時咱們的轉碼就須要依靠這個構造方法啦
傳入StringBuffer
ui
public String(StringBuffer buffer) { synchronized(buffer) { this.value = Arrays.copyOf(buffer.getValue(), buffer.length()); } }
這裏上了鎖,因此是線程安全滴
傳入StringBuilder
public String(StringBuilder builder) { this.value = Arrays.copyOf(builder.getValue(), builder.length()); }
關於StringBuilder
和StringBuffer
等等會講
equals
據說這個經常會被問到,原本equals方法是Object類裏面的,String把他重寫了一下,用來判斷兩字符串是否相等
public boolean equals(Object anObject) { //判斷是否是同一個引用 if (this == anObject) { return true; } //判斷是否爲String類 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; }
寫的很精煉,裏面有些地方值得我去學習的,看到instanceof我忽然想起反射裏面的一個小知識點
equalsIgnoreCase
忽略大小寫比較兩字符串是否相等
public boolean equalsIgnoreCase(String anotherString) { return (this == anotherString) ? true : (anotherString != null) && (anotherString.value.length == value.length) && regionMatches(true, 0, anotherString, 0, value.length); }
這裏調用了一個叫regionMatches
的方法,我也把他沾上來
public boolean regionMatches(boolean ignoreCase, int toffset, String other, int ooffset, int len) { char ta[] = value; int to = toffset; char pa[] = other.value; int po = ooffset; // Note: toffset, ooffset, or len might be near -1>>>1. if ((ooffset < 0) || (toffset < 0) || (toffset > (long)value.length - len) || (ooffset > (long)other.value.length - len)) { return false; } while (len-- > 0) { char c1 = ta[to++]; char c2 = pa[po++]; if (c1 == c2) { continue; } if (ignoreCase) { // If characters don't match but case may be ignored, // try converting both characters to uppercase. // If the results match, then the comparison scan should // continue. char u1 = Character.toUpperCase(c1); char u2 = Character.toUpperCase(c2); if (u1 == u2) { continue; } // Unfortunately, conversion to uppercase does not work properly // for the Georgian alphabet, which has strange rules about case // conversion. So we need to make one last check before // exiting. if (Character.toLowerCase(u1) == Character.toLowerCase(u2)) { continue; } } return false; } return true; }
他在比較的時候還先轉大寫再轉小寫,爲何要這麼作你們看他的註釋把我其實沒看懂。
我想了一下,String不是帶有一個toUppaerCase
的方法嘛?若是讓我去寫這個方法我可能就直接這樣寫了
s2.equals(s1.toUppaerCase());
不過看了看源碼,我這樣寫Java會對字符串遍歷兩次,效率會比較低吧。
而後我又看了一下toUpperCase()
方法裏面的代碼
public String toUpperCase(Locale locale) { if (locale == null) { throw new NullPointerException(); } int firstLower; final int len = value.length; /* Now check if there are any characters that need to be changed. */ scan: { for (firstLower = 0 ; firstLower < len; ) { int c = (int)value[firstLower]; int srcCount; if ((c >= Character.MIN_HIGH_SURROGATE) && (c <= Character.MAX_HIGH_SURROGATE)) { c = codePointAt(firstLower); srcCount = Character.charCount(c); } else { srcCount = 1; } int upperCaseChar = Character.toUpperCaseEx(c); if ((upperCaseChar == Character.ERROR) || (c != upperCaseChar)) { break scan; } firstLower += srcCount; } return this; }
值得注意的是其中兩行代碼
c = codePointAt(firstLower); srcCount = Character.charCount(c);
在Java中有兩個獲取下表字符的Unicode編碼,
charAt
獲取的是0
-0xFFFFFF
的碼點,3個字節,保存在int中codePointAt
獲取的是0
-0xFFFF
的碼點,2個字節,保存在int中你們順着代碼看,大概也能猜出來Character.charCount()
實際上是計算這個字符究竟是佔2個字節的仍是3個字節的。
順便提一下,String的length
方法和codePointCount
方法也是同樣的道理
length
返回的是字符串的長度,若是有超出0xFFFF
的字符會算兩codePointCount
返回真正的字符個數,一個字符對應一個碼點因此Java在大小寫轉換的時候還要考慮編碼問題,學就是了。
compareTo
也是比較兩個字符串,不過他與equals
不一樣,equals
傳入的是Object,返回的是布爾值,compareTo
傳入的是字符串,返回的是整型
public int compareTo(String anotherString) { int len1 = value.length; int len2 = anotherString.value.length; int lim = Math.min(len1, len2); char v1[] = value; char v2[] = anotherString.value; int k = 0; while (k < lim) { char c1 = v1[k]; char c2 = v2[k]; if (c1 != c2) { return c1 - c2; } k++; } return len1 - len2; }
返回值表示第一個出現不一樣的字符的編碼差值。若是是相同的字符串則返回0。你們回憶一下equals
方法,他第一步是判斷兩個變量是否爲同一個引用,這裏爲何不去判斷呢?解決這個問題咱們要想一下字符串在內存中是怎麼存儲的。
indexOf
可傳入字符、字符數組,從左向右對入參進行索引,找不到返回-1lastIndexOf
從右向左對入參進行索引,找不到返回-1contains
判斷是否包含子字符串,內部調用indexOf
toUpperCase
將字符串轉換成大寫toLowerCase
將字符串轉換成小寫trim
去除字符串先後的空格replace
替換字符串,可傳入正則表達式,內部真正作替換的是replaceAll
join
鏈接字符串,能夠傳入String
、StringBuffer
等
注意 : 字符串是不可變的,操做完必定要拿個新的字符串存於下返回值,例如·
trim
函數,他會新建一個char數組存放結果
public String trim() { int len = value.length; int st = 0; char[] val = value; /* avoid getfield opcode */ while ((st < len) && (val[st] <= ' ')) { st++; } while ((st < len) && (val[len - 1] <= ' ')) { len--; } return ((st > 0) || (len < value.length)) ? substring(st, len) : this; }
==
和 equals
有什麼區別final
修飾的好處StringBuffer
和StringBuilder
==
和 equals
有什麼區別==
對於基本類型來講,是直接比較值的大小,而對於引用來講是比較地址是否相等,而String
的·equals
是比較兩個字符串的內容是否相同。
能夠發現Object
也有equals
方法,內部實際上是調用==
public boolean equals(Object obj) { return (this == obj); }
String
用final
修飾兩個目的,安全 和 高效
看下圖,如今有兩個引用指向字符串,咱們的字符串是存儲在常量池中的,若是字符串設計成可變的話,s1
不當心對字符串的內容修改了,咱們用s2
取值的時候發現他變了,會引發不堪設想的災難,關於常量池
的內容晚點我也總結一下。
String
、StringBuffer
和StringBuilder
的區別SringBuffer
和StringBuilder
主要用於字符串的拼接。
字符串的拼接方法有不少,下面咱們來分析一下:
+
:
因爲String
的不可變性,在循環體中對字符串拼接的時候每次都要建立一個對象
String s = ""; for(int i = 0; i < 100000; i++) { s = s + "test" }
每次都須要在建立新的字符串變量,因此·+
適用於常量字符串的拼接,編譯器會在編譯的時候幫咱們拼接好。
String
的join
方法其實內部是使用了StringJoin
方法實現的,StringJoiner
內部實際上是使用StringBuilder
的。
StringBufer
咱們能夠看看他的append
方法
public synchronized StringBuffer append(CharSequence s) { toStringCache = null; super.append(s); return this; }
加了同步鎖,因此是線程安全的,返回的是this,因此支持鏈式操做
因爲加了同步鎖,性能相對沒有
StringBuilder
高
StringBuilder
public StringBuilder append(String str) { super.append(str); return this; }
沒有帶鎖,線程不安全,可是效率會皮較高,關於線程安全的知識我稍後也會總結
總結:所以,若是是常量或者一兩個變量之間的拼接咱們能夠直接使用+
,若是是須要重複拼接的話,不考慮線程安全就是用StringBuilder
,考慮則使用StringBuffer
.
StringJoiner
有點像Python裏面的join
,它能夠幫咱們在拼接的時候加入分隔符、前綴和後綴
構造函數
public StringJoiner(CharSequence delimiter, CharSequence prefix, CharSequence suffix) { Objects.requireNonNull(prefix, "The prefix must not be null"); Objects.requireNonNull(delimiter, "The delimiter must not be null"); Objects.requireNonNull(suffix, "The suffix must not be null"); // make defensive copies of arguments this.prefix = prefix.toString(); this.delimiter = delimiter.toString(); this.suffix = suffix.toString(); this.emptyValue = this.prefix + this.suffix; }
能夠傳入分隔符、前綴和後綴
兩個StringJoiner
可使用merge
拼接
public StringJoiner merge(StringJoiner other) { Objects.requireNonNull(other); if (other.value != null) { final int length = other.value.length(); // lock the length so that we can seize the data to be appended // before initiate copying to avoid interference, especially when // merge 'this' StringBuilder builder = prepareBuilder(); builder.append(other.value, other.prefix.length(), length); } return this; }
String有兩種建立方式
String s1 = "Rhythm";
s1
在建立時,會在常量池中看看有沒有這個字符串,有就直接返回句柄(地址),沒有就建立在返回句柄(地址)
String s2 = new String("rHYTHM");
直接在堆中建立,調用intern
方法能夠將其存入常量池
在JDB1.7
後的版本中,編譯器會對字符串進行優化
String s1 = "AAA" + "BBB"; String s2 = "AAABBB"; System.out.println(s1 == s2); //true
返回的是true,你們平時能夠稍加註意一下
第一次寫文章,寫得不咋地,你們見諒,下一篇打算寫一下JVM的內存模型,順便交代一下String是怎麼存儲的。