1.通過javap命令查看class文件的字節碼內容
最簡單的一個案例
public class Test { public static void main(String[] args) { int a = 2; int b = 5; int c = b-a; System.out.println(c); } }
先使用javac命令進行編譯,Test.class,再使用
javap ‐v Test1.class > Test.txt
Test.txt內容如下
內容大致分為4個部分:
- 第一部分:顯示了生成這個class的java源文件、版本信息、生成時間等。
- 第二部分:顯示了該類中所涉及到常量池,共26個常量。
- 第三部分:顯示該類的構造器,編譯器自動插入的。
- 第四部分:顯示了main方的信息。(這個是需要我們重點關注的)
Classfile /E:/Astudy/JVM/Test.class Last modified 2020-3-8; size 398 bytes MD5 checksum a638d93d1191aa144f8140f0fcfcd644 Compiled from "Test.java" public class Test minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: //常量池 #1 = Methodref //方法引用 #5.#14 // java/lang/Object."<init>":()V #2 = Fieldref //字段引用 #15.#16 // java/lang/System.out:Ljava/io/PrintStream; #3 = Methodref #17.#18 // java/io/PrintStream.println:(I)V #4 = Class //類 #19 // Test #5 = Class //UTF-8編碼的字符串 #20 // java/lang/Object #6 = Utf8 <init> #7 = Utf8 ()V #8 = Utf8 Code #9 = Utf8 LineNumberTable #10 = Utf8 main #11 = Utf8 ([Ljava/lang/String;)V #12 = Utf8 SourceFile #13 = Utf8 Test.java #14 = NameAndType //字段或方法的符號引用 #6:#7 // "<init>":()V #15 = Class #21 // java/lang/System #16 = NameAndType #22:#23 // out:Ljava/io/PrintStream; #17 = Class #24 // java/io/PrintStream #18 = NameAndType #25:#26 // println:(I)V #19 = Utf8 Test #20 = Utf8 java/lang/Object #21 = Utf8 java/lang/System #22 = Utf8 out #23 = Utf8 Ljava/io/PrintStream; #24 = Utf8 java/io/PrintStream #25 = Utf8 println #26 = Utf8 (I)V { public Test(); //無參構造 descriptor: ()V //返回值類型 flags: ACC_PUBLIC //訪問修飾符 Code: //依次為,棧的大小,局部變量表的大小,參數的個數 stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 1: 0 public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=4, args_size=1 0: iconst_2 //將int類型的2壓入棧 1: istore_1 //出棧一個變量放入局部變量表中下標為1的位置,下標為0的位置存放的是this指針,此時棧為空, 2: iconst_5 //將int類型的5壓入棧 3: istore_2 //出棧一個變量放入局部變量表中下標為2的位置,下標為0的位置存放的是this指針,此時棧為空, 4: iload_2 //從局部變量表中取出下標為2(實際值此處為5)的變量壓入操作數棧中 5: iload_1 //從局部變量表中取出下標為1(實際值此處為2)的變量壓入操作數棧中 6: isub //在操作數棧中做減操作,結果為3 7: istore_3 //出棧一個變量放入局部變量表中下標為3的位置,下標為0的位置存放的是this指針,此時棧為空, 8: getstatic #2 //去常量池中引用"#2"符號引用的類與方法 // Field java/lang/System.out:Ljava/io/PrintStream; 11: iload_3 //從局部變量表中取出下標為3(實際值此處為3)的變量壓入操作數棧中 12: invokevirtual #3 //調度對象的實現方法 // Method java/io/PrintStream.println:(I)V 15: return LineNumberTable: line 3: 0 line 4: 2 line 5: 4 line 6: 8 line 7: 15 } SourceFile: "Test.java"
常量池
Constant TypeValue 說明
- CONSTANT_Class 7 類或接口的符號引用
- CONSTANT_Fieldref 9 字段的符號引用
- CONSTANT_Methodref 10 類中方法的符號引用
- CONSTANT_InterfaceMethodref 11 接口中方法的符號引用
- CONSTANT_String 8 字符串類型常量
- CONSTANT_Integer 3 整形常量
- CONSTANT_Float 4 浮點型常量
- CONSTANT_Long 5 長整型常量
- CONSTANT_Double 6 雙精度浮點型常量
- CONSTANT_NameAndType 12 字段或方法的符號引用
- CONSTANT_Utf8 1 UTF-8編碼的字符串
- CONSTANT_MethodHandle 15 表示方法句柄
- CONSTANT_MethodType 16 標志方法類型
- CONSTANT_InvokeDynamic 18 表示一個動態方法調用點
圖示:你們一定沒見過畫的這么丑的圖

二:i++和++i的區別
java代碼
public class Test2 { public static void main(String[] args) { new Test2().method1(); new Test2().method2(); } public void method1() { int i = 1; int a = i++; System.out.println(a); //打印1 } public void method2() { int i = 1; int a = ++i; System.out.println(a); //打印2 } }
執行javac之后執行javap
字節碼節選
i++
stack=2, locals=3, args_size=1 0: iconst_1 //將int類型的1壓入棧 1: istore_1 //出棧一個變量放入局部變量表中下標為1的位置,下標為0的位置存放的是this指針,此時棧為空, 2: iload_1 //從局部變量表中取出下標為1(實際值此處為1)的變量壓入操作數棧中 3: iinc 1, 1 //將局部變量表中下標為1的變量進行加1操作 6: istore_2 //出棧一個變量放入局部變量表中下標為2的位置,這一步沒有對操作棧中的數進行操作,直接出棧到變量表中 7: getstatic #6 //去常量池中引用"#6"符號引用的類與方法 // Field java/lang/System.out:Ljava/io/PrintStream; 10: iload_2 //從局部變量表中取出下標為1(實際值此處為1)的變量壓入操作數棧中 11: invokevirtual #7 //執行println方法 // Method java/io/PrintStream.println:(I)V 14: return
++i
stack=2, locals=3, args_size=1 0: iconst_1 //將int類型的1壓入棧 1: istore_1 //出棧一個變量放入局部變量表中下標為1的位置,下標為0的位置存放的是this指針,此時棧為空, 2: iinc 1, 1 //將局部變量表中下標為1的變量進行加1操作 5: iload_1 //從局部變量表中取出下標為1(實際值此處為2)的變量壓入操作數棧中 6: istore_2 //出棧一個變量放入局部變量表中下標為2的位置,(實際值此處為2) 7: getstatic #6 //去常量池中引用"#6"符號引用的類與方法 // Field java/lang/System.out:Ljava/io/PrintStream; 10: iload_2 //從局部變量表中取出下標為1(實際值此處為2)的變量壓入操作數棧中 11: invokevirtual #7 //執行println方法 // Method java/io/PrintStream.println:(I)V 14: return
區別:
i++
- 只是在本地變量中對數字做了相加,並沒有將數據壓入到操作棧
- 將前面拿到的數字1,再次從操作棧中拿到,壓入到本地變量中
++i
- 將本地變量中的數字做了相加,並且將數據壓入到操作棧
- 將操作棧中的數據,再次壓入到本地變量中
三:字符串拼接
使用StringBuilder和"+"號拼接哪個效率高?
public class Test3 { public static void main(String[] args) { new Test3().m1(); new Test3().m2(); } public void m1() { String s1 = "123"; String s2 = "456"; String s3 = s1 + s2; System.out.println(s3); } public void m2() { String s1 = "123"; String s2 = "456"; StringBuilder sb = new StringBuilder(); sb.append(s1); sb.append(s2); String s3 = sb.toString(); System.out.println(s3); } }
直接上字節碼
字符串在類加載的時候會保存至常量池當中
Constant pool: #1 = Methodref #14.#25 // java/lang/Object."<init>":()V #2 = Class #26 // Test3 #3 = Methodref #2.#25 // Test3."<init>":()V #4 = Methodref #2.#27 // Test3.m1:()V #5 = Methodref #2.#28 // Test3.m2:()V #6 = String #29 // 123 #7 = String #30 // 456 #8 = Class #31 // java/lang/StringBuilder #9 = Methodref #8.#25 // java/lang/StringBuilder."<init>":()V #10 = Methodref #8.#32 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; #11 = Methodref #8.#33 // java/lang/StringBuilder.toString:()Ljava/lang/String; #12 = Fieldref #34.#35 // java/lang/System.out:Ljava/io/PrintStream; #13 = Methodref #36.#37 // java/io/PrintStream.println:(Ljava/lang/String;)V #14 = Class #38 // java/lang/Object #15 = Utf8 <init> #16 = Utf8 ()V #17 = Utf8 Code #18 = Utf8 LineNumberTable #19 = Utf8 main #20 = Utf8 ([Ljava/lang/String;)V #21 = Utf8 m1 #22 = Utf8 m2 #23 = Utf8 SourceFile #24 = Utf8 Test3.java #25 = NameAndType #15:#16 // "<init>":()V #26 = Utf8 Test3 #27 = NameAndType #21:#16 // m1:()V #28 = NameAndType #22:#16 // m2:()V #29 = Utf8 123 #30 = Utf8 456 #31 = Utf8 java/lang/StringBuilder #32 = NameAndType #39:#40 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder; #33 = NameAndType #41:#42 // toString:()Ljava/lang/String; #34 = Class #43 // java/lang/System #35 = NameAndType #44:#45 // out:Ljava/io/PrintStream; #36 = Class #46 // java/io/PrintStream #37 = NameAndType #47:#48 // println:(Ljava/lang/String;)V #38 = Utf8 java/lang/Object #39 = Utf8 append #40 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder; #41 = Utf8 toString #42 = Utf8 ()Ljava/lang/String; #43 = Utf8 java/lang/System #44 = Utf8 out #45 = Utf8 Ljava/io/PrintStream; #46 = Utf8 java/io/PrintStream #47 = Utf8 println #48 = Utf8 (Ljava/lang/String;)V
"+"號拼接
stack=2, locals=4, args_size=1 0: ldc #6 //將常量池中的"123"壓入棧 // String 123 2: astore_1 //將引用類型(String)的值存入局部變量表中的下標為1的位置,下標為0的位置為this 3: ldc #7 //將常量池中的"456"壓入棧 // String 456 5: astore_2 //將引用類型(String)的值存入局部變量表中的下標為2的位置 6: new #8 //創建一個新的StringBuilder對象 // class java/lang/StringBuilder 9: dup //復制棧頂部一個字長內容 10: invokespecial #9 //根據編譯時類型來調用實例方法 // Method java/lang/StringBuilder."<init>":()V 13: aload_1 //從局部變量表中取出下標為1的變量(此處為"123")放入到操作數棧中 14: invokevirtual #10 //調用StringBuilder的append()方法 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 17: aload_2 //從局部變量表中取出下標為1的變量(此處為"465")放入到操作數棧中 18: invokevirtual #10 //調用StringBuilder的append()方法 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 21: invokevirtual #11 //調用StringBuilder的toString()方法 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 24: astore_3 //將引用類型(String)的值存入局部變量表中的下標為3的位置 25: getstatic #12 //獲取靜態字段System.out // Field java/lang/System.out:Ljava/io/PrintStream; 28: aload_3 //從局部變量表中取出下標為3的變量(此處為"123456")放入到操作數棧中 29: invokevirtual #13 //調用System.out 的println()方法 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 32: return
StringBuilder
Code: stack=2, locals=5, args_size=1 0: ldc #6 //將常量池中的"123"壓入棧 // String 123 2: astore_1 //將引用類型(String)的值存入局部變量表中的下標為1的位置,下標為0的位置為this 3: ldc #7 //將常量池中的"456"壓入棧 // String 456 5: astore_2 //將引用類型(String)的值存入局部變量表中的下標為2的位置 6: new #8 //創建一個新的StringBuilder對象 // class java/lang/StringBuilder 9: dup //復制棧頂部一個字長內容 10: invokespecial #9 //根據編譯時類型來調用實例方法 // Method java/lang/StringBuilder."<init>":()V 13: astore_3 //將引用類型(String)的值存入局部變量表中的下標為3的位置 14: aload_3 //從局部變量表中取出下標為3的變量放入到操作數棧中 15: aload_1 //從局部變量表中取出下標為1的變量(此處為"123")放入到操作數棧中 16: invokevirtual #10 //調用StringBuilder的append()方法 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 19: pop //彈出棧頂端一個字長的內容 20: aload_3 //從局部變量表中取出下標為3的變量放入到操作數棧中 21: aload_2 //從局部變量表中取出下標為2的變量放入到操作數棧中 22: invokevirtual #10 //調用StringBuilder的append()方法 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 25: pop //彈出棧頂端一個字長的內容 26: aload_3 //從局部變量表中取出下標為3的變量放入到操作數棧中 27: invokevirtual #11 //調用StringBuilder的toString()方法 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 30: astore 4 //將引用類型或returnAddress類型值存入局部變量下標為4 32: getstatic #12 //獲取靜態字段System.out // Field java/lang/System.out:Ljava/io/PrintStream; 35: aload 4 //從局部變量表中取出下標為4的變量(此處為"123456")放入到操作數棧中 37: invokevirtual #13 //調用System.out 的println()方法 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 40: return
從通過字節碼的方式我們可以看出,+號其實還是使用了StringBuilder對象的append方法,所以他們的效率是一樣的。但是真的是這樣嗎?如果是循環拼接呢?
循環拼接
使用+拼接
Code: stack=2, locals=3, args_size=1 0: ldc #6 //去常量池中找字符串,結果為空 // String 2: astore_1 //將引用類型(String)的值存入局部變量表中的下標為1的位置,下標為0的位置為this 3: iconst_0 //將int類型的0壓入棧 4: istore_2 //將int類型的0彈出棧,存入局部變量表中下標為2的位置 5: iload_2 //將局部變量表中下標為2的值壓入棧中 6: iconst_5 //將int類型的5壓入棧 7: if_icmpge 35 //將操作數棧中的兩個數進行比較,如果一個int(第一次循環值0)類型值大於或者等於另外一個int(值為5)類型值,則跳轉35處執行 10: new #7 //創建一個StringBuilder對象 // class java/lang/StringBuilder 13: dup //復制棧頂部一個字長內容 14: invokespecial #8 // Method java/lang/StringBuilder."<init>":()V 17: aload_1 //從局部變量表中取出下標為1的String變量壓入操作數棧中 18: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 21: iload_2 //從局部變量表中取出下標為2的int類型變量壓入操作數棧中 22: invokevirtual #10 //調用append方法 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 25: invokevirtual #11 //調用toString方法 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 28: astore_1 //將引用類型(String)的值存入局部變量表中的下標為1的位置,下標為0的位置為this 29: iinc 2, 1 //將局部變量表中下標為2的值加1 32: goto 5 //跳轉到5 35: getstatic #12 //獲取System.out // Field java/lang/System.out:Ljava/io/PrintStream; 38: aload_1 //從局部變量表中取出下標為1的String變量壓入操作數棧中 39: invokevirtual #13 //調用println方法 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 42: return
使用StringBuilder
flags: ACC_PUBLIC Code: stack=2, locals=3, args_size=1 0: new #7 //創建一個StringBuilder對象 // class java/lang/StringBuilder 3: dup //復制棧頂部一個字長內容 4: invokespecial #8 // Method java/lang/StringBuilder."<init>":()V 7: astore_1 //將引用類型(String)的值存入局部變量表中的下標為1的位置,下標為0的位置為this 8: iconst_0 //將int類型的0壓入棧 9: istore_2 //將int類型的0彈出棧,存入局部變量表中下標為2的位置 10: iload_2 //將局部變量表中下標為2的值壓入棧中 11: iconst_5 //將int類型的5壓入棧 12: if_icmpge 27 //將操作數棧中的兩個數進行比較,如果一個int(第一次循環值0)類型值大於或者等於另外一個int(值為5)類型值,則跳轉27處執行 15: aload_1 //從局部變量表中取出下標為1的String變量壓入操作數棧中 16: iload_2 //從局部變量表中取出下標為2的int類型變量壓入操作數棧中 17: invokevirtual #10 //調用append方法 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 20: pop //彈出棧頂端一個字長的內容 21: iinc 2, 1 //將局部變量表中下標為2的值加1 24: goto 10 //跳轉到10 27: getstatic #12 //獲取System.ou // Field java/lang/System.out:Ljava/io/PrintStream; 30: aload_1 //從局部變量表中取出下標為1的String變量壓入操作數棧中 31: invokevirtual #11 //調用toString方法 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 34: invokevirtual #13 //調用println方法 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 37: return
結論,使用+號每一次循環都會創建一個StringBuilder對象每次拼接完之后都會調用toString()方法,這樣的話效率肯定沒有直接用StringBuildr效率高,因為StringBuildr只創建了一個對象,調用了一次toString()方法
代碼優化建議
1、盡可能使用局部變量
調用方法時傳遞的參數以及在調用中創建的臨時變量都保存在棧中速度較快,其他變
量,如靜態變量、實例變量等,都在堆中創建,速度較慢。另外,棧中創建的變量,隨
着方法的運行結束,這些內容就沒了,不需要額外的垃圾回收。
2、盡量減少對變量的重復計算
明確一個概念,對方法的調用,即使方法中只有一句語句,也是有消耗的。所以例如下
面的操作
for (int i = 0; i < list.size(); i++) {...}
建議替換為:
int length = list.size(); for (int i = 0, i < length; i++) {...}
3、盡量采用懶加載的策略,即在需要的時候才創建
String str = "aaa"; if (i == 1){ list.add(str); }//建議替換成 if (i == 1){ String str = "aaa"; list.add(str); }
4、異常不應該用來控制程序流程
異常對性能不利。拋出異常首先要創建一個新的對象,
Throwable
接口的構造函數調用
名為
fillInStackTrace()
的本地同步方 法,
fillInStackTrace()
方法檢查堆棧,收集調用跟蹤
信息。只要有異常被拋出,
Java
虛擬機就必須調整調用堆棧,因為在處理過程中創建 了
一個新的對象。異常只能用於錯誤處理,不應該用來控制程序流程。
5、不要將數組聲明為public static final
因為這毫無意義,這樣只是定義了引用為
static final
,數組的內容還是可以隨意改變的,
將數組聲明為
public
更是一個安全漏洞,這意味着這個數組可以被外部類所改變。
6、不要創建一些不使用的對象,不要導入一些不使用的類
如果代碼中出現
"The value of the local variable i is not used"
、
"The
import java.util is never used"
,那么請刪除這些無用的內容
7、程序運行過程中避免使用反射
反射是Java提供給用戶一個很強大的功能,功能強大往往意味着效率不高。不建議在程序運行過程中使用尤其是頻繁使用反射機制,特別是 Method的invoke方法。
如果確實有必要,一種建議性的做法是將那些需要通過反射加載的類在項目啟動的時候
通過反射實例化出一個對象並放入內存。
8、使用數據庫連接池和線程池
這兩個池都是用於重用對象的,前者可以避免頻繁地打開和關閉連接,后者可以避免頻
繁地創建和銷毀線程
9、容器初始化時盡可能指定長度
容器初始化時盡可能指定長度,如:new ArrayList<>(10); new HashMap<>(32); 避免容器長度不足時,擴容帶來的性能損耗。
10、ArrayList隨機遍歷快,LinkedList添加刪除快
11、使用Entry遍歷Map
Map<String,String> map = new HashMap<>(); for (Map.Entry<String,String> entry : map.entrySet()) { String key = entry.getKey(); String value = entry.getValue(); }
12、不要手動調用System.gc();
13、String盡量少用正則表達式
正則表達式雖然功能強大,但是其效率較低,除非是有需要,否則盡可能少用。
replace()
不支持正則
replaceAll()
支持正則
如果僅僅是字符的替換建議使用
replace()
14、日志的輸出要注意級別
// 當前的日志級別是error LOGGER.info("保存出錯!" + user);
15、對資源的close()建議分開操作
try{ XXX.close(); YYY.close(); }catch (Exception e){ ... } // 建議改為 try{ XXX.close(); }catch (Exception e){ ... } try{ YYY.close(); }catch (Exception e){ ... }
3.1
、盡可能使用局部變量
調用方法時傳遞的參數以及在調用中創建的臨時變量都保存在棧中速度較快,其他變
量,如靜態變量、實例變量等,都在堆中創建,速度較慢。另外,棧中創建的變量,隨
着方法的運行結束,這些內容就沒了,不需要額外的垃圾回收。
3.2
、盡量減少對變量的重復計算
明確一個概念,對方法的調用,即使方法中只有一句語句,也是有消耗的。所以例如下
面的操作:
建議替換為:
這樣,在
list.size()
很大的時候,就減少了很多的消耗。
3.3
、盡量采用懶加載的策略,即在需要的時候才創建
for
(
int
i =
0
;
i < list
.
size
();
i++
)
{...}
int
length = list
.
size
();
for
(
int
i =
0
,
i < length
;
i++
)
{...}
3.4
、異常不應該用來控制程序流程
異常對性能不利。拋出異常首先要創建一個新的對象,
Throwable
接口的構造函數調用
名為
fillInStackTrace()
的本地同步方 法,
fillInStackTrace()
方法檢查堆棧,收集調用跟蹤
信息。只要有異常被拋出,
Java
虛擬機就必須調整調用堆棧,因為在處理過程中創建 了
一個新的對象。異常只能用於錯誤處理,不應該用來控制程序流程。
3.5
、不要將數組聲明為
public static final
因為這毫無意義,這樣只是定義了引用為
static final
,數組的內容還是可以隨意改變的,
將數組聲明為
public
更是一個安全漏洞,這意味着這個數組可以被外部類所改變。
3.6
、不要創建一些不使用的對象,不要導入一些不使用的
類
這毫無意義,如果代碼中出現
"The value of the local variable i is not used"
、
"The
import java.util is never used"
,那么請刪除這些無用的內容
3.7
、程序運行過程中避免使用反射
反射是
Java
提供給用戶一個很強大的功能,功能強大往往意味着效率不高。不建議在程序
運行過程中使用尤其是頻繁使用反射機制,特別是
Method
的
invoke
方法。
如果確實有必要,一種建議性的做法是將那些需要通過反射加載的類在項目啟動的時候
通過反射實例化出一個對象並放入內存。
String
str =
"aaa"
;
if
(
i ==
1
){
list
.
add
(
str
);
}
//
建議替換成
if
(
i ==
1
){
String
str =
"aaa"
;
list
.
add
(
str
);
}
3.8
、使用數據庫連接池和線程池
這兩個池都是用於重用對象的,前者可以避免頻繁地打開和關閉連接,后者可以避免頻
繁地創建和銷毀線程。
3.9
、容器初始化時盡可能指定長度
容器初始化時盡可能指定長度,如:
new ArrayList<>(10); new HashMap<>(32);
避免容
器長度不足時,擴容帶來的性能損耗。
3.10
、
ArrayList
隨機遍歷快,
LinkedList
添加刪除快
3.11
、使用
Entry
遍歷
Map
避免使用這種方式:
3.12
、不要手動調用
System.gc();
3.13
、
String
盡量少用正則表達式
正則表達式雖然功能強大,但是其效率較低,除非是有需要,否則盡可能少用。
replace()
不支持正則
replaceAll()
支持正則
如果僅僅是字符的替換建議使用
replace()
。
3.14
、日志的輸出要注意級別
Map<
String
,
String
> map =
new
HashMap<>
();
for
(
Map
.
Entry<
String
,
String
> entry
:
map
.
entrySet
()) {
String
key = entry
.
getKey
();
String
value = entry
.
getValue
();
}
Map<
String
,
String
> map =
new
HashMap<>
();
for
(
String
key
:
map
.
keySet
()) {
String
value = map
.
get
(
key
);
}