摘要:性能,大量運用在哈希的處理中,由于的不可變性,可以只計算一次哈希值,然后緩存在內部,后續直接取就好了。這是目前的一個底層字節碼的實現,那么是不是沒有使用或者的必要了呢。
1. 前言凱倫說,公眾號ID: KailunTalk,努力寫出最優質的技術文章,歡迎關注探討。
最近看到幾個有趣的關于Java核心類String的問題。
String類是如何實現其不可變的特性的,設計成不可變的好處在哪里。
為什么不推薦使用+號的方式去形成新的字符串,推薦使用StringBuilder或者StringBuffer呢。
翻閱了網上的一些博客和stackoverflow,結合自己的理解做一個匯總。
2. String類是如何實現不可變的String類的一大特點,就是使用Final類修飾符。
A class can be declared final if its definition is complete and no subclasses are desired or required.
Because a final class never has any subclasses, the methods of a final class are never overridden .
Java SE 7 官方手冊中的定義如上,如果你認為這個類已經定義完全并且不需要任何子類的話,可以將這個類聲明為Final,Final類中的方法將永遠不會被重寫。
在Java中,String是被設計成一個不可變(immutable)類,一旦創建完后,字符串本身是無法通過正常手段被修改的。
private final char value[]; // 一旦初始化后,引用不能被修改 public String substring(int beginIndex, int endIndex) { if (beginIndex < 0) { throw new StringIndexOutOfBoundsException(beginIndex); } if (endIndex > value.length) { throw new StringIndexOutOfBoundsException(endIndex); } int subLen = endIndex - beginIndex; if (subLen < 0) { throw new StringIndexOutOfBoundsException(subLen); } return ((beginIndex == 0) && (endIndex == value.length)) ? this : new String(value, beginIndex, subLen); }
選了substring方法來做一個代表,其他常見的涉及String操作的方法都是類似,如果你操作后的內容會和目前String中的內容不一致的話,那么都是重新創建一個新的String類返還,不會讓你去修改內部的內容。
將String類設計成Final類,能夠避免其方法被子類重寫,從而破壞了它本身方法的實現,進而破壞了不可變的特性。
2.1 String類設計成不可變的好處我們都不是Java語言的設計者,不知道其為何一定要設計成不可變,試著做一些猜想。
可以實現多個變量引用JVM內存中的同一個字符串實例。見后文String Pool的介紹。
安全性,String類的用途實在太廣了,如果可以隨意修改的,是不是很恐怖。
性能,String大量運用在哈希的處理中,由于String的不可變性,可以只計算一次哈希值,然后緩存在內部,后續直接取就好了。如果String類是可變的話,在進行哈希處理的時候,需要進行大量的哈希值的重新計算。
這是結合個人理解和stackoverflow上看的匯總,我們來看看Java語言的爸爸James Gosling是怎么說的。
From a strategic point of view, they tend to more often be trouble free. And there are usually things you can do with immutables that you can"t do with mutable things, such as cache the result. If you pass a string to a file open method, or if you pass a string to a constructor for a label in a user interface, in some APIs (like in lots of the Windows APIs) you pass in an array of characters. The receiver of that object really has to copy it, because they don"t know anything about the storage lifetime of it. And they don"t know what"s happening to the object, whether it is being changed under their feet.
You end up getting almost forced to replicate the object because you don"t know whether or not you get to own it. And one of the nice things about immutable objects is that the answer is, "Yeah, of course you do." Because the question of ownership, who has the right to change it, doesn"t exist.
One of the things that forced Strings to be immutable was security. You have a file open method. You pass a String to it. And then it"s doing all kind of authentication checks before it gets around to doing the OS call. If you manage to do something that effectively mutated the String, after the security check and before the OS call, then boom, you"re in. But Strings are immutable, so that kind of attack doesn"t work. That precise example is what really demanded that Strings be immutable.
這是James Gosling在2001年5月的一次訪談中,談到了不可變類和String,大意就是 他會更傾向于使用不可變類,它能夠緩存結果,當你在傳參的時候,使用不可變類不需要去考慮誰可能會修改其內部的值,這個問題不存在的。如果使用可變類的話,可能需要每次記得重新拷貝出里面的值,性能會有一定的損失。
老爺子還說了,迫使String類設計成不可變的另一個原因是安全,當你在調用其他方法,比如調用一些系統級操作之前,可能會有一系列校驗,如果是可變類的話,可能在你校驗過后,其內部的值被改變了,可能引起嚴重的系統崩潰問題,這是迫使String類設計成不可變類的重要原因。
2.2 String Pool上文說了,設計成不可變后,可以多個變量引用JVM上同一塊地址,可以節省內存空間,相同的字符串不用重復占用Heap區域空間。
String test1 = "abc"; String test2 = "abc";
通常我們平時在使用字符串是,都是通過這種方式使用,那么JVM中的大致存儲就是如下圖所示。
?
兩個變量同時引用了String Pool中的abc,如果String類是可變的話,也就不能存在String Pool這樣的設計了。
在平時我們還會通過new關鍵字來生成String,那么新創建的String是否也會和上文中的示例一樣共享同一個字符串地址呢。
String test1 = "abc"; String test2 = "abc"; String test3 = new String("abc");
答案是不會,使用new關鍵字會在堆區在創建出一個字符串,所以使用new來創建字符串還是很浪費內存的,內存結構如下圖所示。
?
2.3 不推薦使用+來拼裝字符串的原因。首先我們來看這一段代碼,應該是之前寫代碼比較常見的。
String test1 = "abc"; String test2 = "abc"; String test3 = test1 + test2;
test3通過test1和test2拼接而成,我們看一下這個過程中的字節碼。
?
從以上圖我們可以看到,目前的JDK7的做法是,會通過新建StringBuilder的方式來完成這個+號的操作。這是目前的一個底層字節碼的實現,那么是不是沒有使用StringBuilder或者StringBuffer的必要了呢。還是有的,看下一個例子。
String test2 = "abc"; String test3 = "abc"; for (int i = 0; i < 5; i++) { test3 += test2; }
在上述代碼中,我們還是使用+號進行拼接,但這次我們加了一個循環,看一下字節碼有什么變化。
?
每次循環都會創建一個StringBuilder,在末尾再調用toString返還回去,效率很低。繼續看下一個例子,我們直接使用StringBuilder,來做拼接。
String test2 = "abc"; // 使用StringBuilder進行拼接 StringBuilder test4 = new StringBuilder("abc"); for (int i = 0; i < 5; i++) { test4.append(test2); }
?
每次循環體中只會調用之前創建的StringBuilder的append方法進行拼接,效率大大提高。
至于StringBuilder 的內部實現,諸位有興趣可以自己再去看一下,本質上也是一個char數組上的操作,和StringBuffer的區別在于,StringBuffer是有做同步處理的,而StringBuilder沒有。
3. 總結本文主要探討了String類設計為Final修飾和不可變類的原因,以及為何在日常工作中不推薦使用+號進行字符串拼接。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/67468.html
摘要:下文將從字節碼的角度,分析中基本類型傳參和對象傳參。主函數執行時,操作棧會推入主函數棧幀,其中包含了主函數的局部變量表,字節碼,返回值等信息。主函數的棧幀會被推入棧,成為當前操作棧。 個人網站地址: http://kailuncen.me/2017/06/0... 一個小問題 在開源中國看到這樣一則問題 https://www.oschina.net/quest...,其中的變量a前...
摘要:減少垃圾收集壓力因為所有長生命周期的數據都是在的管理內存中以二進制表示的,所以所有數據對象都是短暫的,甚至是可變的,并且可以重用。當然,并不是唯一一個基于且對二進制數據進行操作的數據處理系統。 showImg(https://segmentfault.com/img/remote/1460000020044119?w=1280&h=853); 前言 如今,許多用于分析大型數據集的開源系...
摘要:基礎問題的的性能及原理之區別詳解備忘筆記深入理解流水線抽象關鍵字修飾符知識點總結必看篇中的關鍵字解析回調機制解讀抽象類與三大特征時間和時間戳的相互轉換為什么要使用內部類對象鎖和類鎖的區別,,優缺點及比較提高篇八詳解內部類單例模式和 Java基礎問題 String的+的性能及原理 java之yield(),sleep(),wait()區別詳解-備忘筆記 深入理解Java Stream流水...
閱讀 2853·2023-04-25 17:59
閱讀 685·2023-04-25 15:05
閱讀 674·2021-11-25 09:43
閱讀 3036·2021-10-12 10:13
閱讀 3539·2021-09-27 13:59
閱讀 3586·2021-09-23 11:21
閱讀 3884·2021-09-08 09:35
閱讀 569·2019-08-29 17:12