淺析String不可變性

来源:http://www.cnblogs.com/think-in-java/archive/2016/12/03/6127804.html
-Advertisement-
Play Games

在所有編程語言領域,我想字元串應該是地球上最常用的表達手段了吧。 在java的世界里,String是作為類出現的,核心的一個域就是一個char數組,內部就是通過維護一個不可變的char數組,來向外部輸出的。 這是jdk一段String類定義,首先類是final,表明類不可被繼承;核心域是privat ...


 

在所有編程語言領域,我想字元串應該是地球上最常用的表達手段了吧。

 


 

在java的世界里,String是作為類出現的,核心的一個域就是一個char數組,內部就是通過維護一個不可變的char數組,來向外部輸出的。

這是jdk一段String類定義,首先類是final,表明類不可被繼承;核心域是private final的,final表明這個引用所指向的記憶體地址不會改變,但這還不足說明value[]是不可變的;因為引用所指向的記憶體的值有可能發生變化,但是jdk是不會讓這樣的事情發生的。private 保證這個域對外部來說是不可見的,這還不夠,對value還要進行 保護性拷貝 。

舉一個簡單的例子:

這是一個String的構造函數,參數是一個char數組引用,它並沒有把這個數組引用直接賦值給實例對象的value成員變數,而是通過一個Arrays.copyOf的方式拷貝一個數組再給到對象的成員變數。為什麼呢?假設它這裡是直接一個賦值,那String的不可變性就徹底被破壞了,因為如此一來,存在一個外部引用與實例對象value引用指向相同的記憶體地址,通過外部引用就可以改變這個char數組對象,最終導致的結果就是String不再不可變。幸好JDK中所有的對value的操作都是保護性拷貝操作,不管是被賦值,還是賦值給其它外部引用。

說了這麼多,為什麼JAVA要String保持一個不可變的狀態呢?原因其實很簡單,因為String太太太太常用了,地球上沒有能比這個更常用的對象了,設計成不可變的,是為了減少大量的同步鎖的開銷。但是要註意 並不是聲明成final的類一定是不可變的 。

根據effective java一書中提到,類不變需遵循五條規則:

1.不提供任何機會修改對象狀態的方法

2.保證類不被擴展

3.所有域都是final

4.所有域都是私有的

5.確保對於任何可變組件的互斥訪問

 

有興趣的同學可以參考 effective 第十五條,這裡就不展開講了。作了那麼久的鋪墊,接下來可以談談avoid getfield opcode了,按翻譯來說就是防止"調用訪問域的操作碼",這段tip來自一段註釋。

十分常用的replace方法,內部演算法大概是這樣一個過程:先找到第一個oldChar的下標i,拷貝小標i之前的舊數組的內容到新的數組,新數組[i]='newChar',遍歷i之後的內容,若舊數組出現為oldChar則在新數組中替換為newChar,若沒有出現,則拷貝舊值到新數組。

起初我很奇怪,到底為什麼,一定要找到第一個出現oldChar的下標,為什麼不直接遍曆數組中每一個char 若為舊值,替換為新值。我從時間複雜度,空間複雜度去考慮這個演算法,始終沒有得到結果。我還是太年輕啊,後來才發覺其實還是為了維護一個String的設計原則:"對於擁有相同的字元字面量的情況下,String的構造還是優先返回原字元串對象"。這麼做應該是為瞭解決堆記憶體吧。

 

那麼,這一句註釋到底是什麼意思呢?要理解這句話,需要對JVM有一定的瞭解。

 

JVM在運行中的數據區,分為五個部分:方法區,堆區,虛擬機棧,本地方法棧,程式計數器。

首先類相關的信息肯定是放在方法區的,堆中放一些實例對象,程式計數器始終指向下一條將要執行的指令,虛擬機棧和本地方法棧分別是用來於普通方法和本地方法的。

著重說一下虛擬機棧,它是線程私有的,描述的是java方法執行的記憶體模型:每個方法在執行的同時創建一個棧幀,用於存放局部變數表,操作數棧,方法入口,動態鏈接等。

 

 局部變數表用來存放一些基本數據類,和引用。操作數棧的話,是用來作運算用的,打個比方

int a=1;
int b =2;
int c =a+b;

JVM會先把a,b的值壓入到操作數棧保存起來,等到程式計數器執行加法指令的時候,再把a,b從棧中pop出來。重點就在於a,b值是從哪裡壓入到棧中的,如果沒有那麼接下來要遍歷的就是value數組了,value毫無疑問是堆中的數據,也就是每一次遍歷會經歷,數據由堆中取出,進入棧幀,再由棧幀壓入到操作數棧,最後pop出來進行運算。

如果執行了上述代碼,情況就大不相同了,val就是一個局部變數,它會被存放在局部變數表中,接來下的運算,就是局部變數表到操作數棧了,屬於一個棧幀內的數據轉移。JVM為了避免頻繁進行堆棧數據轉移,將值複製到本地變數一次,以避免在接下來的幾行中迴圈的每一次迭代,從堆中多次取下的欄位值。

 

除了我提到的這些,String源碼中還有許多值得學習的演算法,設計方式,代碼優化,比如,還有常量池的實現。

 

最後我想求教一個問題啊,在JDK7點版本中,String引入了一段靜態代碼塊,

我甚是不解啊,不知道有沒有大神幫我解讀一下這段代碼的含義?


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前邊我們說過了 Http協議 有兩個缺陷 一個是無狀態 、 一個是純文本 。 純文本也就是說Http請求中的內容都是以字元串的形式發送的。 但是Java又是一個強類型語言,所以將一個字元串轉換成Java中的數據類型這一重任就落在了Struts2的肩膀上。 類型轉換最常見的可能就是將將10/29/20 ...
  • 系統環境 ubuntu 14.04 LTS vsftpd安裝 配置文件 預設配置文件的位置為 /etc/vsftpd.conf 常見問題 500 OOPS: vsftpd: refusing to run with writable root inside chroot 解:所設置的目錄不能有寫許可權 ...
  • 2016-12-03 數組定義字元串: 每次定義數組的時候,系統都會在記憶體開闢你指定數組大小的空間,並且數組中的內容對於我們是可讀可寫的,看如下代碼: 1 #include<stdio.h> 2 int main() 3 { 4 char str[100] = "hello world"; 5 ch ...
  • 原文:http://www.cnblogs.com/imaker/p/6128049.html 所屬年份:2010.9;2012.3編寫函數fun,其功能是:根據以下公式求π的值(要求精度0.0005,即某項小於0.0005時停止迭代)。 程式運行後,若輸入精度0.0005,則程式應輸出為3.14… ...
  • 如下是作業,用python做一個ftp,主要利用socket。 server端在linux下運行,在client端可以執行shell命令(靜態的) 在client端輸入get xxx,即可下載。 在client端輸入put xxx,即可上傳。 server端: client端: ...
  • 1.簡化Java開發 Spring是一個開源框架,它的根本使命在於簡化java開發。為了降低java開發的複雜性,Spring採取了以下4種關鍵策略: 1.基於POJO的輕量級和最小侵入性編程; 有很多框架強迫應用繼承它們的類或實現它們的介面從而導致應用與框架綁死,而基於Spring構建的應用通常沒 ...
  • STL的pair,有兩個值,可以是不同的類型。 template struct pair; 註意,pair在頭文件utility中,不要include。(一個錯誤是 include ) 成員類型 first_type first的類型 second_type second的類型 成員變數 first... ...
  • 解釋如下: content 中需要被替換的就是{}中的參數,array數組中存放的是對應的要替換的參數;使用MessageFormat方法的時候,需要要將這些參數的個數匹配正確,並且數序要指定,否則匹配出錯。這樣就實現了參數的替換。很簡單,也很死板。 MessageFormat:出自java.tex ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...