java的map 使用string数组多了双引号_奥奥奥利给!!!再也不怕面试官问我String源码了!来吧...
簡述
字符串廣泛應用 在 Java 編程中,在 Java 中字符串屬于對象,Java 提供了String 類來創建和操作字符串。字符串緩沖區支持可變字符串。因為String對象是不可變的,因此可以共享它們。
String類代表字符串,Java程序中的所有字符串字面值如"abc"都是這個類的實例對象。String 類是不可改變的,所以你一旦創建了 String 對象,那它的值就無法改變了。如果需要對字符串做很多修改,那么應該選擇使用StringBuilder或者StringBuffer。
最簡單的創建字符串的方式:String qc = "qiu chan"編譯器會使用該值創建一個 對象。我們也可以使用關鍵字New創建String對象。
String類型的常量池比較特殊。它的主要使用方法有兩種:
直接使用雙引號聲明出來的String對象會直接存儲在常量池中。
如果不是用雙引號聲明的String對象,可以使用String提供的intern方法。intern 方法會從字符串常量池中查詢當前字符串是否存在,若不存在就會將當前字符串放入常量池中。
繼承/實現關系
public final class String implements java.io.Serializable, Comparable, CharSequence { // 省略}String是final修飾的不能夠被繼承和修改。
源碼
String的底層使用的是char數組用于存儲。
private final char value[];緩存字符串的哈希碼默認值為0
private int hash;無參數構造函數
public String() { this.value = "".value;}解析:初始化一個新創建的String對象,使其代表一個空字符序列。 注意,由于String是不可變的,所以不需要使用這個構造函數。
參數為字符串的構造函數
public String(String original) { this.value = original.value; this.hash = original.hash;}解析:初始化一個新創建的String對象,使其代表與參數相同的字符序列。換句話說,新創建的字符串是參數字符串的副本。除非需要參數字符串的顯式拷貝,否則不需要使用這個構造函數,因為String是不可變的。
參數為char數組的構造函數
public String(char value[]) { this.value = Arrays.copyOf(value, value.length);}解析:分配一個新的String,使其代表當前字符數組參數中包含的字符序列。使用Arrays.copyOf方法進行字符數組的內容被復制。字符數組的后續修改不會影響新創建的字符串。
參數為char數組并且帶有偏移量的構造方法
// value[]:作為字符源的數組,offset:偏移量、下標從0開始并且包括offset,count:從數組中取到的元素的個數。public String(char value[], int offset, int count) { // 如果偏移量小于0拋出IndexOutOfBoundsException異常 if (offset < 0) { throw new StringIndexOutOfBoundsException(offset); } // 判斷要取的元素的個數是否小于等于0 if (count <= 0) { // 要取的元素的個數小于0,拋出IndexOutOfBoundsException異常 if (count < 0) { throw new StringIndexOutOfBoundsException(count); } // 在要取的元素的個數等于0的情況下,判斷偏移量是否小于等于數組的長度 if (offset <= value.length) { // 偏移量小于等于數組的長度,返回一個空字符串數組的形式 this.value = "".value; return; } } // 如果偏移量的值大于數組的長度減去取元素的個數拋出IndexOutOfBoundsException異常 if (offset > value.length - count) { throw new StringIndexOutOfBoundsException(offset + count); } // 復制元素 this.value = Arrays.copyOfRange(value, offset, offset+count);}解析:分配一個新的Sting,來源于給定的char數組中的字符。offset參數是子數組中第一個字符的索引,count參數指定子數組的長度。子數組被被復制以后,對字符數組的修改不會影響新創建的字符串。
參數為StringBuffer的構造方法
public String(StringBuffer buffer) { // 這里對StringBuffer進行了加鎖,然后再進行拷貝操作。這里對其進行加鎖正是為了保證在多線程環境下只能有一個線程去操作StringBuffer對象。 synchronized(buffer) { this.value = Arrays.copyOf(buffer.getValue(), buffer.length()); }}解析:分配一個新的字符串,該字符串包含當前字符串緩沖區參數中包含的字符序列。Arrays.copyOf方法進行字符串緩沖區中內容的復制。這里對StringBuffer進行了加鎖,然后再進行拷貝操作。這里對其進行加鎖正是為了保證在多線程環境下只能有一個線程去操作StringBuffer對象。
參數為StringBuilder的構造方法
public String(StringBuilder builder) { this.value = Arrays.copyOf(builder.getValue(), builder.length());}解析:參數是StringBuilder,這個是線程不安全的,但是性能相對于StringBuffer有很大的提升,源碼的注釋中說通過toString方法從字符串構建器中獲取字符串可能會運行得更快,通常是首選。
length方法
public int length() { // 查看源碼發現,這個value是一個char數組,本質獲取的是字符串對應的char數組的長度。 return value.length; }解析:返回此字符串的長度。查看源碼發現,這個value是一個char數組,本質獲取的是字符串對應的char數組的長度。
isEmpty方法
public boolean isEmpty() { // 底層的char數組的長度是否為0進行判斷 return value.length == 0;}//舉例@Testpublic void test_string_isEmpty(){ System.out.println(" ".isEmpty());// true System.out.println("".isEmpty());// false}解析:判斷給定的字符串是否為空,底層實現是根據char數組的長度是否為0進行判斷。
charAt方法
public char charAt(int index) { // 給定的索引小于0或者給定的索引大于這個字符串對應的char數組的長度拋出角標越界異常 if ((index < 0) || (index >= value.length)) { throw new StringIndexOutOfBoundsException(index); } // 獲取當前的指定位置的char字符 return value[index];}解析:根據給定的索引獲取當前的指定位置的char字符。如果給定的索引否小于0,或者給定的索引是大于這個字符串對應的char數組的長度拋出角標越界異常。index是從0開始到length-1結束。序列的第一個char值在索引0處,下一個在索引1處,依此類推,與數組索引一樣。
getChars方法
// srcBegin:要復制的字符串中第一個字符的索引【包含】。srcEnd:要復制的字符串中最后一個字符之后的索引【不包含】。dst[]:目標數組。dstBegin:目標數組中的起始偏移量。public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) { // 校驗起始索引小于0拋出角標越界異常 if (srcBegin < 0) { throw new StringIndexOutOfBoundsException(srcBegin); } // 校驗結束索引大于原始字符串的長度拋出角標越界異常 if (srcEnd > value.length) { throw new StringIndexOutOfBoundsException(srcEnd); } // 校驗結束索引大于起始索引拋出角標越界異常 if (srcBegin > srcEnd) { throw new StringIndexOutOfBoundsException(srcEnd - srcBegin); } // 數組的拷貝 System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);}// 案例@Testpublic void test_string_codePointAt(){ // 原始字符串 String h = "ahelloworld"; // 目標char數組 char[] data = new char[4]; // 執行拷貝 h.getChars(2, 6, data, 0); System.out.println(data);}解析:將字符串中的字符復制到目標字符數組中。索引包含srcBegin,不包含srcEnd。
equals方法
// anObject:與此String進行比較的對象。public boolean equals(Object anObject) { // 引用相同直接返回true if (this == anObject) { return true; } // 判斷給定的對象是否是String類型的 if (anObject instanceof String) { // 給定的對象是字符串類型的轉換為字符串類型 String anotherString = (String)anObject; // 獲取當前字符串的長度 int n = value.length; // 判斷給定字符串的長度是否等于當前字符串的長度 if (n == anotherString.value.length) { // v1[]代表當前字符串對應的char數組 char v1[] = value; // v2[]代表給定的字符串對應的char數組 char v2[] = anotherString.value; // 遍歷原始char數組,并且與給定的字符串對應的數組進行比較 int i = 0; while (n-- != 0) { if (v1[i] != v2[i]) // 任意一個位置上不相等返回false return false; i++; } // 都相等返回true return true; } } // 不是String類型,或者長度不一致返回false return false;}解析:這個方法重寫了Object中的equals方法。方法中的將此字符串與指定對象進行比較。接下來附贈一個手寫的String字符串equals方法。
手寫equals方法
private boolean mineEquals(String srcObject, Object anObject){ // 比較引用是否相同 if (srcObject == anObject){ return true; } // 引用不相同比較內容 if (anObject instanceof String){ String ans = (String) anObject; char[] srcChar = srcObject.toCharArray(); char[] anChar = ans.toCharArray(); int n = srcChar.length; if (n == anChar.length){ int i = 0; while (n-- != 0){ if (srcChar[i] != anChar[i]) return false; i++; } return true; } } return false;}// 測試我們自己寫的equals方法 @Test public void test_string_mine(){ String s = new String("aaa"); // 走的是引用的比較 System.out.println(s.equals(s));// true boolean b = mineEquals(s, s); System.out.println(b);// true }equalsIgnoreCase方法
public boolean equalsIgnoreCase(String anotherString) { // 引用相同返回true。引用不相同進行長度、各個位置上的char是否相同 return (this == anotherString) ? true : (anotherString != null) && (anotherString.value.length == value.length) && regionMatches(true, 0, anotherString, 0, value.length);}解析:將此字符串與另一個字符串進行比較,而忽略大小寫注意事項。regionMatches方法的源碼很有趣的,源碼里面有一個while循環,先進行未忽略大小的判斷,然后進行忽略大小的判斷,在忽略大小的判斷中,先進行的是大寫的轉換進行比較,但是可能會失敗【這種字體Georgian alphabet】。所以在大寫轉換以后的比較失敗,進行一次小寫的轉換比較。
startsWith方法
// 判斷是否以指定的前綴開頭public boolean startsWith(String prefix) { // 0代表從開頭進行尋找 return startsWith(prefix, 0);}endsWith方法
// 判斷是否以指定的前綴結尾public boolean endsWith(String suffix) { // 從【value.length - suffix.value.length】開始尋找,這個方法調用的還是startsWith方法 return startsWith(suffix, value.length - suffix.value.length);}startsWith和endsWith最終的實現方法
// prefix: 測試此字符串是否以指定的前綴開頭。toffset: 從哪里開始尋找這個字符串。public boolean startsWith(String prefix, int toffset) { // 原始的字符串對應的char[] char ta[] = value; // 開始尋找的位置 int to = toffset; // 獲取指定的字符串對應的char[] char pa[] = prefix.value; int po = 0; // 獲取指定的字符串對應的char[]長度 int pc = prefix.value.length; // 開始尋找的位置小于0,或者起始位置大于要查找的長度【value.length - pc】返回false。 if ((toffset < 0) || (toffset > value.length - pc)) { return false; } // 比較給定的字符串的char[]里的每個元素是否跟原始的字符串對應的char數組的元素相同 while (--pc >= 0) { if (ta[to++] != pa[po++]) { // 有一個char不相同返回false return false; } } // 相同返回true return true;}substring方法
// 返回一個字符串,該字符串是該字符串的子字符串。beginIndex開始截取的索引【包含】。public String substring(int beginIndex) { // 校驗指定的索引,小于0拋出角標越界 if (beginIndex < 0) { throw new StringIndexOutOfBoundsException(beginIndex); } // 子字符串的長度 int subLen = value.length - beginIndex; // 子字符串的長度小于0拋出角標越界 if (subLen < 0) { throw new StringIndexOutOfBoundsException(subLen); } // 開始位置為0,返回當前字符串,不為0,創建一個新的子字符串對象并返回 return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);}解析:返回一個字符串,該字符串是該字符串的子字符串。子字符串以指定索引處的字符開頭【包含】,并且擴展到該字符串的末尾。
substring方法
// beginIndex:開始位置【包含】。 endIndex:結束位置【不包含】。public String substring(int beginIndex, int endIndex) { // 校驗指定的開始索引,小于0拋出角標越界 if (beginIndex < 0) { throw new StringIndexOutOfBoundsException(beginIndex); } // 校驗指定的結束索引,大于給定的字符串的char數組的長度拋出角標越界 if (endIndex > value.length) { throw new StringIndexOutOfBoundsException(endIndex); } // 要截取的長度 int subLen = endIndex - beginIndex; // 要截取的長度小于0,拋出角標越界 if (subLen < 0) { throw new StringIndexOutOfBoundsException(subLen); } // 截取字符串 return ((beginIndex == 0) && (endIndex == value.length)) ? this : new String(value, beginIndex, subLen);}解析:返回一個字符串,該字符串是該字符串的子字符串。子字符串從指定的beginIndex開始【包含】,并且擴展到索引endIndex-1處的字符【不包含】。
concat方法
public String concat(String str) { // 獲取給定的字符串的長度 int otherLen = str.length(); // 長度為0,直接返回當前的字符串 if (otherLen == 0) { return this; } // 獲取當前字符串的長度 int len = value.length; // 構建一個新的長度為len + otherLen的字符數組,并且將原始的數據放到這個數組 char buf[] = Arrays.copyOf(value, len + otherLen); // 這個底層調用是System.arraycopy這個方法的處理是使用c語言寫的 str.getChars(buf, len); return new String(buf, true);}將指定的字符串連接到該字符串的末尾。字符串拼接。
format方法
// 使用指定的格式字符串和參數返回格式化的字符串。public static String format(String format, Object... args) { return new Formatter().format(format, args).toString();}// 案例,這里是使用%s替換后面的如"-a-"@Testpublic void test_start(){ System.out.println(String.format("ha %s hh %s a %s h", "-a-", "-b-", "-c-"));}trim方法
public String trim() { // 指定字符串的長度 int len = value.length; // 定義一個開始位置的索引0 int st = 0; // 定義一個char[] val,用于避免使用getfiled操作碼,這個可以寫段代碼反編譯一下看看 char[] val = value; // 對于字符串的開頭進行去除空格,并記錄這個索引 while ((st < len) && (val[st] <= ' ')) { st++; } // 對于字符串的尾部進行去除空格,也記錄這個索引,這個索引就是去除尾部空格后的索引 while ((st < len) && (val[len - 1] <= ' ')) { len--; } // 根據上面記錄的長度判斷是否要截取字符串 return ((st > 0) || (len < value.length)) ? substring(st, len) : this;}返回一個字符串,其值就是這個字符串,并去掉任何首部和尾部的空白。
join方法
// 返回一個新的String,該字符串由給定的分隔符和要連接的元素組成。delimiter:分隔每個元素的分隔符。elements:連接在一起的元素。public static String join(CharSequence delimiter, CharSequence... elements) { // delimiter和elements為空拋出空指針異常,null會被攔截,""不會被攔截 Objects.requireNonNull(delimiter); Objects.requireNonNull(elements); // StringJoiner joiner = new StringJoiner(delimiter); // 遍歷給定的要拼接的元素,拼接的元素允許為null for (CharSequence cs: elements) { // 執行拼接方法 joiner.add(cs); } return joiner.toString();}// 拼接方法public StringJoiner add(CharSequence newElement) { // prepareBuilder()方法首次調用會創建StringBuilder對象,后面再調用會執行拼接分隔符 prepareBuilder().append(newElement); return this;}// 未進行拼接創建StringBuilder對象,已經拼接以后value != null執行拼接分隔符private StringBuilder prepareBuilder() { // 判斷拼接的value是否為空 if (value != null) { // 不為空執行拼接分隔符 value.append(delimiter); } else { // 最開始使用拼接的時候,調用這個方法創建一個空的StringBuilder對象,只調一次 value = new StringBuilder().append(prefix); } return value;}// 上面是調用的這個拼接元素方法@Overridepublic StringBuilder append(CharSequence s) { // 這里啥都沒處理,調用的是父類的append方法,設計模式為建造者模式 super.append(s); return this;}// 上面的prepareBuilder方法是拼接分隔符,這個方法是將分隔符和給定的元素拼接的方法@Overridepublic AbstractStringBuilder append(CharSequence s) { // 以下3個判斷根據類型和是否為空進行區別拼接 if (s == null) return appendNull(); if (s instanceof String) return this.append((String)s); if (s instanceof AbstractStringBuilder) return this.append((AbstractStringBuilder)s); // 拼接 return this.append(s, 0, s.length());}將給定的字符串以給定的分割符分割并返回分隔后的字符串。
replace方法
// target:要被替換的目標字符串。 replacement:替換的字符串public String replace(CharSequence target, CharSequence replacement) { return Pattern.compile(target.toString(), Pattern.LITERAL).matcher( this).replaceAll(Matcher.quoteReplacement(replacement.toString()));}解析:用指定的字符串替換這個字符串中與之匹配的每個子字符串。替換從字符串的開頭到結尾,例如,在字符串 "aaa "中用 "b "替換 "aa "將導致 "ba "而不是 "ab"。
replaceAll方法
// regex:這個支持正則表達式,也可以是要被替換的目標字符串。public String replaceAll(String regex, String replacement) { return Pattern.compile(regex).matcher(this).replaceAll(replacement);}問題:replace和replaceAll方法的區別是啥?
replaceAll支持正則表達式。
針對char的replace方法
// oldChar:要被替換的字符,newChar:替換的字符public String replace(char oldChar, char newChar) { // oldChar不等于newChar if (oldChar != newChar) { // 當前字符串的長度 int len = value.length; // 這個用于下面的while循環里的條件比較,val[i]中的i是從0開始的 int i = -1; // 定義一個char[] val,用于避免使用getfiled操作碼,這個可以寫段代碼反編譯一下看看 char[] val = value; /* avoid getfield opcode */ // 這個用于記錄這個i的值,并且判斷是否有要替換的,這個循環有利于性能的提升 while (++i < len) { // val[i]中的i是從0開始的 if (val[i] == oldChar) { // 有要替換的直接跳出循環 break; } } // 上面的while循環中如果有要替換的i肯定小于len,如果沒有下面這個判斷就不會執行 if (i < len) { // 能進到這個循環肯定是有要替換的,創建一個長度為len的char數組 char buf[] = new char[len]; // 上面的i是記錄第一個可以替換的char的索引,下面這個循環是將這個i索引前的不需要被替換的填充到buf[]數組中 for (int j = 0; j < i; j++) { // 填充buf[]數組 buf[j] = val[j]; } // 從可以替換的索引i開始將剩余的字符一個一個填充到 buf[]中 while (i < len) { // 獲取要被替換的字符 char c = val[i]; // 判斷這個字符是否真的需要替換,c == oldChar成立就替換,否則不替換 buf[i] = (c == oldChar) ? newChar : c; i++; } // 返回替換后的字符串 return new String(buf, true); } } // oldChar等于newChar直接返回當前字符串 return this;}案例
@Testpublic void test_matches(){ String a = "adddfdefe"; System.out.println(a.replace('d', 'b'));// abbbfbefe}仿寫replace方法參數針對char仿寫
// 和源碼給的唯一不同的是參數傳遞,其他的都和源碼一樣,自己寫一遍可以加深記憶和借鑒編程思public String replace(String source, char oldChar, char newChar) { char[] value = source.toCharArray(); 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); } } return new String(value);}intern方法
public native String intern();這是一個native方法。調用String#intern方法時,如果池中已經包含一個由equals方法確定的等于此String對象的字符串,則返回來自池的字符串。否則,將此String對象添加到池中,并返回這個String的引用。
分享
上面是本人在學習路上整理的一些比較干貨的java資料,如果有需要的兄弟可以先關注我,私信我回復【資料】即可。
總結
以上是生活随笔為你收集整理的java的map 使用string数组多了双引号_奥奥奥利给!!!再也不怕面试官问我String源码了!来吧...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 华中科技大学c语言期末考试题,华中科技大
- 下一篇: linux socat rpm,Rabb