????????在C語言中若要表示字符串只能使用字符數組或者字符指針,Java語言則專門提供了 String 類,在面向對象編程中具有重要地位。在開發和校招筆試中,字符串也是常客。
目錄
一、字符串的構造
二、常用方法
2.1 字符串的拼接
2.2 字符串之間的比較
2.3 計算長度
2.4 查找
2.5 轉化
2.6 替換
2.7 拆分
2.8?截取
2.9?刪除左右兩邊的空白字符
2.10 不可變性
2.11 修改
三、StringBuilder 和 StringBuffer
append 方法的比較
四、String 和 StringBuilder 之間的轉換原則
五、面試題
六、練習題
6.1 找出第一個只出現一次的字符
6.2 最后一個單詞的長度
6.3 檢測字符串是否是回文
?6.4 手搓 toLowerCase 方法
6.5 字符串中的單詞數
一、字符串的構造
String 類提供的構造方法非常多,常用的有以下3種。三種方法的區別只是語法上的精簡程度不一
String str1 = "hello";String str2 = new String("world"); // 相當于創建數組char[] chars = {'a','b','c'};
String str3 = new String(chars);
String 是引用類型,內部存儲的不上字符串本身,通過調試查看 String 類種的成員變量如下(jdk1.8環境下):
?字符串常量值是哈希表,涉及數據結構的知識。本文不涉及數據結構的知識。
(jdk1.8 中 String 類只有2個成員變量,而在 jdk9 之后有4個成員變量,加上 coder = 0 和 hashisZero = false。在 jdk1.8 中,字符串實際存放在 char 類型的數組中,而 value 存放的是數組的地址。)
二、常用方法
2.1 字符串的拼接
使用 “+” 可以對字符串進行拼接,注意字符串與整型進行“相加”的情況。
String str1 = "hello";
String str2 = "world";
System.out.println(str1 + str2); // 輸出 helloworld
int a = 10;
int b = 20;
System.out.println("a = " + a); // 輸出 a = 10
?這里的10是字符串,因為如果是字符串放在 + 號前面,后面的整型都將會隱式轉化成字符串,因此使用 “+” 也是將整型轉化成字符串的一種方法。如下,
int a = 10;
int b = 20;
System.out.println("a = " + a + b); // 輸出 a = 1020
????????如果想要得到 a = 30 的結果,就得將 a + b 用括號括起來,因為 () 的優先級比 + 高。但如果是System.out.println(a + b + "= a + b");
則輸出30 = a + b,此時
第一個 + 是求和,第二個 + 是拼接。
2.2 字符串之間的比較
????????對于引用類型,== 比較的是引用中的地址;String 中重寫了 Object 類的 .equals 方法按照字符大小的順序一個字符一個字符地進行比較,返回值是 Boolean 類型。
public class test {public static void main(String[] args) {String str1 = new String("hello");String str2 = new String("hello");String str3 = str1;System.out.println("====比較地址====");System.out.println(str1 == str2); // falseSystem.out.println(str1 == str3); // trueSystem.out.println(str2 == str3); // falseSystem.out.println("====比較內容====");System.out.println(str1.equals(str2)); // trueSystem.out.println(str1.equals(str3)); // trueSystem.out.println(str2.equals(str3)); // true}
}
比較大小用的是 compareTo 方法,返回 int 類型,比較規則如下:
- 先按照字典次序大小比較,如果出現不等的字符,直接返回這兩個字符的大小差值;
- 如果前k個字符相等(k為兩個字符長度最小值),返回值兩個字符串長度差值。
?若想忽略大小寫則調用?compareToIgnoreCase() 方法,該方法常用于驗證碼當中。
public class test {public static void main(String[] args) {// 比較字符串的大小String str1 = new String("abc");String str2 = new String("abc");String str3 = new String("acd");String str4 = new String("abcdef");String str5 = new String("AbC");System.out.println(str1.compareTo(str2)); // 相同輸出0System.out.println(str1.compareTo(str3)); // 不同輸出字符十進制差值-1System.out.println(str1.compareTo(str4)); // 前k=3個字符完全相同,輸出長度差值-3System.out.println(str1.compareToIgnoreCase(str5)); // 相同輸出0}
}
2.3 計算長度
使用 .length() 方法獲取字符串長度,注意是有 () 的。
String str = new String("ohana");
System.out.println(str.length()); // 輸出5System.out.println("ohana".length()); // 用""引起來的也是 String 類型對象
int[] array = {1,2,3,4,5};
System.out.println(array.length); // 而計算基本數據類型數組的長度沒有 ()
2.4 查找
方法 | 功能 |
---|---|
char chaAt(int index) | 返回 index 位置上的字符,如果 index 為負數或者越界,拋出 IndexOutOfBoundsException 異常 |
int indexOf(char ch) | 返回字符 ch 第一次出現的位置,沒有則返回 -1 |
int indexOf(char ch, int fromIndex) | 從 fromIndex 位置開始找 ch 第一次出現的位置,沒有則返回 -1 |
int indexOf(String str) | 返回 str 第一次出現的位置,沒有則返回 -1 |
int indexOf(String str, int fromIndex) | 從 fromIndex 位置開始找 str 第一次出現的位置,沒有則返回 -1 |
int lastIndexOf(char ch) | 從后往前找,返回 ch 第一次出現的位置,沒有則返回 -1 |
?int lastIndexOf(char?ch, int fromIndex) | 從 fromIndex 位置往前找 ch 第一次出現的位置,沒有則返回 -1 |
int lastIndexOf(String str) | 從后往前找,返回 str 第一次出現的位置,沒有則返回 -1 |
int lastIndexOf(String str, int fromIndex) | 從 fromIndex 位置往前找 str 第一次出現的位置,沒有則返回 -1? |
public class test {public static void main(String[] args) {// 1、charAt(int index) 根據下標查找相應的字符String str = "hello";char ch = str.charAt(2);System.out.println(ch);// charAt 通常用for循環打印字符串內容:for (int i = 0; i < str.length(); i++) {char c = str.charAt(i);System.out.print(c + " ");}// 2、indexOf() 返回要查找的字符或者字符串第一次出現的位置,沒有則返回-1// indexOf(int ch)int index = str.indexOf('l');System.out.println(index);// indexOf(String str)int index2 = str.indexOf("ll");System.out.println(index2);// 3、indexOf(char ch, int fromIndex) 從fromIndex位置開始找ch第一次出現的位置,沒有返回-1int index3 = str.indexOf('l',3);System.out.println(index3);// 4、從后往前找,返回ch/str第一次出現的位置,沒有返回-1// lastIndexOf(char ch)String str2 = "ababcabcd";int index4 = str2.lastIndexOf('a');System.out.println(index4);// lastIndexOf(String str)int index5 = str2.lastIndexOf("abc");System.out.println(index5);// 5、從fromIndex開始從右往左找int index6 = str2.lastIndexOf('a',4);System.out.println(index6);int index7 = str2.lastIndexOf("bc",7);System.out.println(index7);}
}
2.5 轉化
包括數值和字符串之間的轉化、大小寫字母轉化、字符串轉化成數組、格式化。
1、數值轉化成字符串,調用 String.valueOf() 方法
class Student{public String name;public int age;public Student(String name, int age) {this.name = name;this.age = age;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}
}
public static void main(String[] args) {int i = 4901;String s1 = String.valueOf(i);String s2 = String.valueOf(49.01);String s3 = String.valueOf(true);String s4 = String.valueOf(new Student("張三",16));System.out.println(s1);System.out.println(s2);System.out.println(s3);System.out.println(s4);
}
2、字符串轉化為數值,使用 包裝類型.parseXxx()方法(in 的包裝類型是 Integer、char 的包裝類型是 Character)
public static void main(String[] args) {int a = Integer.parseInt("409");double b = Double.parseDouble("40.901");System.out.println(a);System.out.println(b);
}
?3、大小寫轉化,只能把字母進行大小寫轉化,對漢字或者阿拉伯數字無效
public static void main(String[] args){String str1 = "hello";System.out.println(str1.toUpperCase()); // 輸出 HELLOString str2 = "IcE-creAM好吃";System.out.println(str2.toLowerCase()); // 輸出 ice-cream好吃
}
?4、字符串轉數組,使用 toCharArray() 并用字符數組進行接收
public static void main(String[] args){String str3 = "iceCream";char[] ch = str3.toCharArray();for (int j = 0; j < ch.length; j++) {System.out.println(ch[j]);}
}
5、數組轉字符串,前面講字符串的構造已有提及
char[] ch2 = {'i','c','e'};
String str4 = new String(ch2);
6、 格式化輸出
public static void main(String[] args){String str5 = String.format("%d-%d-%d",2025,11,1);System.out.println(str5); // 2025-11-1String str6 = String.format("%f@.com",4.09);System.out.println(str6); // 4.090000@.com
}
2.6 替換
使用一個特定的新的字符串替換掉已有的字符串數據。
方法 | 功能 |
---|---|
String replaceAll(String regex, String replacement) | 替換所有的指定內容 |
String replaceFirst(String regex, String replacement) | 替換首個內容 |
public class test {public static void main(String[] args) {String s = "ababcabcd";String tmp = s.replace('a','A'); // 因為不是修改原來的字符串,而是返回新字符串因此要接收System.out.println(tmp);tmp = s.replace("ab","uu");System.out.println(tmp);tmp = s.replaceFirst("ab","uu");System.out.println(tmp);tmp = s.replaceAll("ab","UU");System.out.println(tmp);}
}
????????【注意:涉及到字符串的修改的(如轉化和替換),并不是在原本的字符串上進行修改,而是返回一個新的字符串。】
2.7 拆分
將完整的字符串按照指定的分隔符劃分成若干個子字符串
方法 | 功能 |
---|---|
String[] split(String regex) | 將字符串全部拆分 |
String[] split(String regex, int limit) | 將字符串以指定的格式,拆分為 limit 個組 |
1、按照一個字符來拆分
public static void main(String[] args) {String s = "brand=Haagen-Dazs&price=699";String[] tmp = s.split("&");for (int i = 0; i < tmp.length; i++) {System.out.println(tmp[i]);}System.out.println();
}
2、按照兩個字符來拆分,有2種方法
public class test_7_27 {public static void main(String[] args) {String s = "brand=Haagen-Dazs&price=699";String[] tmp = s.split("&");// 多次拆分——嵌套for (int i = 0; i < tmp.length; i++) {String[] ss = tmp[i].split("=");for (int j = 0; j < ss.length; j++) {System.out.print(ss[j] + " ");}System.out.println();}System.out.println("-----------------------");tmp = s.split("=|&"); // 用字符“|”來間隔開兩個字符for (int i = 0; i < tmp.length; i++) {System.out.print(tmp[i]+" ");}}
}
3、按特殊字符來拆分
注意事項:
- 字符"|","*","+"都得加上轉義字符,前面加上"\";
- 而如果是"\",那么就得寫成"\\";
- 如果一個字符串中有多個分隔符,可以用"|"作為連字符。
public class test {public static void main(String[] args) {// 拆分IP地址String s = "192.168.1.0";String[] tmp = s.split("\\.");for (int i = 0; i < tmp.length; i++) {System.out.println(tmp[i]);}System.out.println("-------------");String s2 = "4+9+101";tmp = s2.split("\\+");for (int i = 0; i < tmp.length; i++) {System.out.println(tmp[i]);}System.out.println("-------------");String s3 = "ab\\cab\\cd"; // 實際上是字符串:ab\cab\cdtmp = s3.split("\\\\"); // 實際上是兩個\for (int i = 0; i < tmp.length; i++) {System.out.println(tmp[i]);}}
}
2.8?截取
public static void main(String[] args) {String s = "icecream";String ss = s.substring(3);System.out.println(ss);ss = s.substring(3,7); // 區間是左閉右開System.out.println(ss);
}
2.9?刪除左右兩邊的空白字符
trim() 方法刪除字符串左右兩邊空白字符(空格、換行、制表符等)
public static void main(String[] args) {String s = " ni hao saoa \t";System.out.println("[" + s + "]");String ss = s.trim(); // 該方法只能去除字符串左右兩邊的空白字符System.out.println("[" + ss + "]");
}
????????了解到那么多方法后,不知道你們有沒有發現有一些方法可以直接通過類名來調用,但有一些方法需要引用變量才能調用。那些直接通過類名調用的,如 String.valueOf() 方法,說明它們是靜態方法,又比如最常見的 System.out 中的 out 方法也是靜態方法。?
2.10 不可變性
????????在對 String 這個類創建的原對象進行操作的時候,我們發現原來的字符串并沒有被修改,而是接收到一個新的字符串,所以需要新建引用變量來接收“修改”之后的對象。這是為什么?
????????String 被 final 修飾,表示該類不能被繼承,而不是不能被修改的原因;驗證 value 數組被 final 修飾是不是不能被修改:
????????由上圖可見,被 final 修飾的數組可以修改原本的數組元素,但不能創建新對象,與 String 的實現相反。因此并不是這個原因令字符串不能被修改。
????????實際上是因為 value 被 private 修飾,只能在當前 String 類中使用,因此我們無法獲取 value 并對其進行操作。
為什么 String 要設計成不可變的?(不可變對象的好處是什么?)?
1. 方便實現字符串對象池。如果 String 可變,那么對象池就需要考慮寫時拷貝的問題了。
2. 不可變對象是線程安全的。
3. 不可變對象更方便緩存 hash code,作為 key 時可以更高效的保存到 HashMap 中。
2.11 修改
????????注意:盡量避免直接對String類型對象進行修改,因為String類是不能修改的,所有的修改都會創建新對象,效率非常低下。原因如下:
public static void main(String[] args) {String s = "hello";s += " world";System.out.println(s);
}
????????上面代碼將字符串 s 拼接成 helloworld,是某種意義上的“修改”。將這些代碼反匯編之后實際上的底層邏輯是下面的代碼:
?圖片對應的執行代碼如下:
public static void main(String[] args) {StringBuilder stringBuilder = new StringBuilder(); // 對應 <init>stringBuilder.append("hello");stringBuilder.append(" world");String ret = stringBuilder.toString();System.out.println(ret);
}
????????兩個代碼運行結果一致,但使用 “+” 進行拼接與調用 StringBuilder 的效率不同,因為 StringBuilder 不會創建一個對象之后銷毀再創建一個對象再銷毀……而是只創建了一個對象并對其進行操作之后最后返回同一個對象。以下是通過統計計算機當前以毫秒為單位的時間戳來分別計算 使用 “+” 進行拼接與調用 StringBuffer 和 StringBuilder 的效率:
public static void main(String[] args) {long start = System.currentTimeMillis();String s = "";for(int i = 0; i < 10000; ++i){s += i;}long end = System.currentTimeMillis();System.out.println(end - start);start = System.currentTimeMillis();StringBuffer sbf = new StringBuffer("");for(int i = 0; i < 10000; ++i){sbf.append(i);}end = System.currentTimeMillis();System.out.println(end - start);start = System.currentTimeMillis();StringBuilder sbd = new StringBuilder();for(int i = 0; i < 10000; ++i){sbd.append(i);}end = System.currentTimeMillis();System.out.println(end - start);
}
輸出結果如:
328
1
1
因此以后盡量不使用 “+” 進行拼接,而是借助 StringBuffer 或 StringBuilder 這兩個類。
三、StringBuilder 和 StringBuffer
????????由于 String 的不可更改特性,為了方便字符串的修改,Java 中又提供 StringBuilder 和 StringBuffer 類,String 類有的方法,它們同樣也有。這兩個類大部分功能是相同的,這里介紹 StringBuilder 常用的一些方法,其它需要用到的時候可參閱Java Platform SE 8(在網頁中按Ctrl+F,可輸入要查找的類)。
方法 | 說明 |
---|---|
append() | 拼接字符串 |
void setCharAt(int index, char ch) | 將index位置的字符設置為ch |
StringBuilder deleteCharAt(int index) | 刪除 index 位置字符 |
int capacity() | 獲取底層保存字符串空間總的大小 |
void ensureCapacity(int mininmumCapacity) | 擴容 |
StringBuilder insert(int offset, String str) | 在 offset 位置插入:八種基類類型 & String類型 & Object類型數據 |
StringBuilder replace(int start, int end, String str) | 將 [start, end) 位置的字符替換為 str |
StringBuilder delete(int start, int end) | 刪除 [start, end) 區間內的字符 |
String substring(int start) | 從start開始一直到末尾的字符以String的方式返回 |
String substring(int start,int end) | 將 [start, end) 范圍內的字符以String的方式返回 |
String toString() | 將所有字符按照String的方式返回 |
StringBuilder reverse() | 反轉字符串 |
public static void main(String[] args) {// 逆轉字符串StringBuilder stringBuilder = new StringBuilder("abcdef");stringBuilder.reverse(); // fedcbaSystem.out.println(stringBuilder);
}public static void main(String[] args) {// 插入字符串StringBuilder stringBuilder = new StringBuilder("hello,,nice");stringBuilder.insert(5,"world"); // helloworld,,niceSystem.out.println(stringBuilder);
}
append 方法的比較
@Override
public StringBuilder append(String str) {super.append(str);return this;
} // 主要用在單線程情況下@Override
public synchronized StringBuffer append(String str) {toStringCache = null;super.append(String.valueOf(obj));return this;
} // 主要用在多線程情況下,可以保證線程安全
????????synchronized “同步的”,相當于一把鎖,可以頻繁開鎖和關鎖,也就是說字符串如果被頻繁修改會占用資源,且一旦被占用就會鎖上,以保安全。因此如果要頻繁修改字符串 ,應該使用 StringBuilder 類處理。
四、String 和 StringBuilder 之間的轉換原則
注意:String 和 StringBuilder 類不能直接轉換。
如果要想互相轉換,可以采用如下原則:
· String 變為 StringBuilder:利用 StringBuilder 的構造方法或 append() 方法
· StringBuilder 變為 String:調用 toString() 方法。
五、面試題
1、String、StringBuffer、StringBuilder的區別
答:
① String 的內容不可修改,StringBuffer 與 StringBuilder 的內容可以修改;
② StringBuffer 與 StringBuilder 大部分功能是相似的;
③ StringBuffer 采用同步處理,屬于線程安全操作;而 StringBuilder 未采用同步處理,屬于線程不安全操作。
2、 字符串相加
????????給定兩個字符串形式的非負整數?num1
?和num2
?,計算它們的和并同樣以字符串形式返回。
????????不能使用任何內建的用于處理大整數的庫(比如?BigInteger
),?也不能直接將輸入的字符串轉換為整數形式。
六、練習題
6.1 找出第一個只出現一次的字符
????????給定一個字符串?s
?,找到?它的第一個不重復的字符,并返回它的索引?。如果不存在,則返回?-1
?。
????????解題思想:創建一個很長的整型數組 count[] ,其中 ‘a’ 字符對應的是97下標,‘b’字符對應的是98下標,因此字符也可以當作數組下標。如果遇到了這個字符 count 就加1,統計這個字符出現的次數得到存放每個字符在字符串中總出現次數的數組。例如 "baccdd" 得到數組是"1122"。
簡單來說,就是數組的下標是字符,內容是出現次數。
????????但我們發現前面和后面有大部分空間都被浪費了,所以我們可以重寫申請一個字符'a'開頭的數組,需要用 字符- 'a' ,若該字符正好是'a' ,則對應的就是下標0,以此類推。
????????但是不能直接遍歷數組,認為第1個出現次數是1的就是只出現一次的字母,因為 "baccdd" 中第一個只出現一次的字符是 ‘b’。
class Solution {public int firstUniqChar(String s) {int[] count = new int[26];// 統計每個字符出現的次數for(int i = 0; i < s.length(); i++){char ch = s.charAt(i);count[ch - 'a']++;}// 找到第一個只出現一次的字符for(int i = 0; i < s.length; i++){char ch = s.charAt(i);if(count[ch - 'a'] == 1){return i;}}return -1;}
}
6.2 最后一個單詞的長度
????????對于給定的若干個單詞組成的句子,每個單詞均由大小寫字母混合構成,單詞間使用單個空格分隔。輸出最后一個單詞的長度。
兩種方法:
1、從后往前查找第一個空格的下標,使用截取獲得該位置+1之后的字符串,然后計算其長度;
2、將字符串根據空格進行分割獲得字符串數組,拿下標為數組長度-1的元素出來計算其長度。
import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner in = new Scanner(System.in);String s = in.nextLine();// 第1種方法int index = s.lastIndexOf(" ");int len = s.substring(index + 1).length();System.out.println(len);// 第2種方法/*String[] ss = s.split(" ");int len = ss[ss.length-1].length();System.out.println(len);*/}
}
6.3 檢測字符串是否是回文
????????如果在將所有大寫字符轉換為小寫字符、并移除所有非字母數字字符(字母和數字都屬于字母數字字符)之后,短語正著讀和反著讀都一樣。則可以認為該短語是一個?回文串?。
給你一個字符串?s
,如果它是?回文串?,返回?true
?;否則,返回?false
?。
class Solution {public boolean isDigitOrCharacter(char ch) {if (Character.isDigit(ch) || Character.isLetter(ch)) {return true;}return false;}public boolean isPalindrome(String s) {s = s.toLowerCase();int left = 0;int right = s.length() - 1;while (left < right) {while (left < right && !isDigitOrCharacter(s.charAt(left))) {left++;}while (left < right && !isDigitOrCharacter(s.charAt(right))) {right--;}if (s.charAt(left) == s.charAt(right)) {left++;right--;}else {return false;}}return true;}
}public class Palindromic_String {public static void main(String[] args) {String s = "A man, a plan, a canal: Panama";Solution solution = new Solution();System.out.println(solution.isPalindrome(s));}
}
?6.4 手搓 toLowerCase 方法
????????因為大小寫字母之間的ASCII碼值相差32,因此大小寫的轉化可以是大寫字母+32,或者小寫字母-32,記得要強制類型轉換。
class Solution2{public String toLowerCase(String s) {StringBuilder stringBuilder = new StringBuilder();for (int i = 0; i < s.length(); i++) {char ch = s.charAt(i);// 判斷當前字符是否是字母if (Character.isLetter(ch)){// 判斷當前是不是大寫字母if (Character.isUpperCase(ch)){ch = (char)(ch + 32);}}stringBuilder.append(ch); // 將字符拼接起來}return stringBuilder.toString();}
}public class toLowerLetter {public static void main(String[] args) {Solution2 solution2 = new Solution2();String ret = solution2.toLowerCase("HEllo");System.out.println(ret);}
}
6.5 字符串中的單詞數
????????統計字符串中的單詞個數,這里的單詞指的是連續的不是空格的字符。請注意,你可以假定字符串里不包括任何不可打印的字符。
示例:
輸入:"Hello, my name is John"
輸出:5
解釋:這里的單詞是指連續的不是空格的字符,所以 "Hello," 算作 1 個單詞。
這道題目其實并沒有想象中的簡單,因為如果是"",應該返回0而不是1,這個比較好解決:
class Solution{public int countSegments(String s){int len = s.length;if(len == 0){return 0;}String[] ss = s.split(" ");return ss.length;
}
又或者是", , , ,? ? ? ? ?a, eaefa",應該返回6,但是上述的代碼返回的是13。調試得到數組存放具體的13個字符是什么:
?由圖可見,空格之間的間隙——空字符也被當作字符了。對此我們需要新增條件:
public class CountWords {public static int countSegments(String s) {int len = s.length();if(len == 0){return 0;}int count = 0;String[] ss = s.split(" ");for (String r: ss) {if (r.length() != 0){ // 長度如果是0的不算count++;}}return count;}public static void main(String[] args) {int count = countSegments(", , , , a, eaefa");System.out.println(count);}
}
?還有第2種調用 isEmpty() 的判斷方法:
public class CountWords {public static int countSegments(String s) {int len = s.length();if(len == 0){return 0;}int count = 0;String[] ss = s.split(" ");for (String r: ss) {if (!r.isEmpty()){ // 不為空則+1count++;}}return count;}public static void main(String[] args) {int count = countSegments(", , , , a, eaefa");System.out.println(count);}
}