我去,這麼簡單的條件表達式竟然也有這麼多坑

3{icon} {views}

最近,小黑哥在一個業務改造中,使用三目運算符重構了業務代碼,沒想到測試的時候竟然發生 NPE 的問題。

重構代碼非常簡單,代碼如下:

// 方法返回參數類型為 Integer
//  private Integer code;
SimpleObj simpleObj = new SimpleObj();
// 其他業務邏輯
if (simpleObj == null) {
    return -1;
} else {
    return simpleObj.getCode();
}

這段 if 判斷,小黑哥看到的時候,感覺很是繁瑣,於是使用條件表達式重構了一把,代碼如下:

// 方法返回參數類型為 Integer
SimpleObj simpleObj = new SimpleObj();
// 其他業務邏輯
return simpleObj == null ? -1 : simpleObj.getCode();

測試的時候,第四行代碼拋出了空指針,這裏代碼很簡單,顯然只有 simpleObj#getCode才有可能發生 NPE 問題。

但是我明明為 simpleObj做過判空判斷,simpleObj 對象肯定不是 null,那麼只有 simpleObj#getCode 返回為 null。但是我的代碼並沒有對這個方法返回值做任何操作,為何會觸發 NPE?

難道是又是自動拆箱導致的 NPE 問題?

在解答這個問題之前,我們首先複習一下條件表達式。

點贊再看,養成習慣。微信搜索『程序通事』,關注查看最新文章~

三目運算符

三目運算符,官方英文名稱:Conditional Operator ? :,又叫條件表達式,本文不糾結名稱,統一使用條件表達式。

條件表達式的基本用法非常簡單,它由三個操作數的運算符構成,形式為:

<表達式 1>?<表達式 2>:<表達式 3>

條件表達式的計算從左往右計算,首先需要計算計算表達式 1 ,其結果類型必須為 Booleanboolean,否則發生編譯錯誤。

當表達式 1 的結果為 true,將會執行表達式 2,否則將會執行表達式 3。

表達式 2 與表達式 3 最後的類型必須得有返回結果,即不能為是 void,若為 void ,編譯時將會報錯。

最後需要注意的是,表達式 2 與表達式 3 不會被同時執行,兩者只有一個會被執行。

踩坑案例

了解完三目運算符的基本原理,我們簡化一下開頭例子,復現一下三目運算符使用過程的一些坑。假設我們的例子簡化成如下:

boolean flag = true; //設置成true,保證表達式 2 被執行
int simpleInt = 66;
Integer nullInteger = null;

案例 1

第一個案例我們根據如下計算 result 的值。

int result = flag ? nullInteger : simpleInt;

這個案例為開頭的例子的簡化版本,運算上述代碼,將會發生 NPE 的。

為什麼會發發生 NPE 呢?

這裏可以給大家一個小技巧,當我們從代碼上沒辦法找到答案時,我們可以試試查看一下編譯之後字節碼,或許是 Java 編譯之後增加某些東西,從而導致問題。

使用 javap -s -c class 查看 class 文件字節碼,如下:

可以看到字節碼中加入一個拆箱操作,而這個拆箱只有可能發生在 nullInteger

那麼為什麼 Java 編譯器在編譯時會對表達式進行拆箱?難道所有数字類型的包裝類型都會進行拆箱嗎?

條件表達式表達式發生自動拆箱,其實官方在 「The Java Language Specification(簡稱:JLS)」15.25 節中做出一些規定,部分內容如下:

JDK7 規範

If the second and third operands have the same type (which may be the null type), then that is the type of the conditional expression.

If one of the second and third operands is of primitive type T, and the type of the other is the result of applying boxing conversion (§5.1.7) to T, then the type of the conditional expression is T.

用大白話講,如果表達式 2 與表達式 3 類型相同,那麼這個不用任何轉換,條件表達式表達式結果當然與表達式 2,3 類型一致。

當表達 2 或表達式 3 其中任一一個是基本數據類型,比如 int,而另一個表達式類型為包裝類型,比如 Integer,那麼條件表達式表達式結果類型將會為基本數據類型,即 int

ps:有沒有疑問?為什麼不規定最後結果類型都為包裝類那?

這是 Java 語言層面一種規範,但是這個規範如果強制讓程序員執行,想必平常使用三目運算符將會比較麻煩。所以面對這種情況, Java 在編譯器在編譯過程加入自動拆箱進制。

所以上述代碼可以等同於下述代碼:

int result = flag ? nullInteger.intValue() : simpleInt;

如果我們一開始的代碼如上所示,那麼這裏錯誤點其實就很明顯了。

案例 2

接下來我們在第一個案例基礎上修改一下:

boolean flag = true; //設置成true,保證表達式 2 被執行
int simpleInt = 66;
Integer nullInteger = null;
Integer objInteger = Integer.valueOf(88);

int result = flag ? nullInteger : objInteger;

運行上述代碼,依然會發生 NPE 的問題。當然這次問題發生點與上一個案例不一樣,但是錯誤原因卻是一樣,還是因為自動拆箱機制導致。

這一次表達式 2 與表達式 3 都為包裝類 Integer,所以條件表達式的最後結果類型也會是 Integer

但是由於 result是 int 基本數據類型,好傢伙,數據類型不一致,編譯器將會對條件表達式的結果進行自動拆箱。由於結果為 null,自動拆箱將報錯了。

上述代碼等同為:

int result = (flag ? nullInteger : objInteger).intValue();

案例 3

我們再稍微改造一下案例 1 的例子,如下所示:

boolean flag = true; //設置成true,保證表達式 2 被執行
int simpleInt = 66;
Integer nullInteger = null;
Integer result = flag ? nullInteger : simpleInt;

案例 3 與案例 1 右邊部分完全相同,只不過左邊部分的類型不一樣,一個為基本數據類型 int,一個為 Integer

按照案例 1 的分析,這個也會發生 NPE 問題,原因與案例 1 一樣。

這個之所以拿出來,其實想說下,上述條件表達式的結果為 int 類型,而左邊類型為 Integer,所以這裏將會發生自動裝箱操作,將 int類型轉化為 Integer

上述代碼等同為:

Integer result = Integer.valueOf(flag ? nullInteger.intValue() : simpleInt);

案例 4

最後一個案例,與上面案例都不一樣,代碼如下:

boolean flag = true; //設置成true,保證表達式 2 被執行
Integer nullInteger = null;
Long objLong = Long.valueOf(88l);

Object result = flag ? nullInteger : objLong;

運行上述代碼,依然將會發生 NPE 的問題。

這個案例表達式 2 與表達式 3 類型不一樣,一個為 Integer,一個為 Long,但是這兩個類型都是 Number的子類。

面對上述情況,JLS 規定:

Otherwise, binary numeric promotion (§5.6.2) is applied to the operand types, and the type of the conditional expression is the promoted type of the second and third operands.

Note that binary numeric promotion performs value set conversion (§5.1.13) and may perform unboxing conversion (§5.1.8).

大白話講,當表達式 2 與表達式 3 類型不一致,但是都為数字類型時,低範圍類型將會自動轉為高範圍數據類型,即向上轉型。這個過程將會發生自動拆箱。

Java 中向上轉型並不需要添加任何轉化,但是向下轉換必須強制添加類型轉換。

上述代碼轉化比較麻煩,我們先從字節碼上來看:

第一步,將 nullInteger拆箱。

第二步,將上一步的值轉為 long 類型,即 (long)nullInteger.intValue()

第三步,由於表達式 2 變成了基本數據類型,表達式 3 為包裝類型,根據案例 1 講到的規則,包裝類型需要轉為基本數據類型,所以表達式 3 發生了拆箱。

第四步,由於條件表達式最後的結果類型為基本數據類型:long,但是左邊類型為 Object,這裏就需要把 long 類型裝箱轉為包裝類型。

所以最後代碼等同於:

Object result = Long.valueOf(flag ? (long)nullInteger.intValue() : objLong.longValue());

總結

看完上述四個案例,想必大家應該會有種感受,沒想到這麼簡單的條件表達式,既然暗藏這麼多「殺機」。

不過大家也不用過度害怕,不使用條件表達式。只要我們在開發過程重點注意包裝類型的自動拆箱問題就好了,另外也要注意條件表達式的計算結果再賦值的時候自動拆箱引發的 NPE 的問題。

最好大家在開發過程中,都遵守一定的規範,即保持表達式 2 與表達式 3 的類型一致,不讓 Java 編譯器有自動拆箱的機會。

建議大家沒事經常看下阿里出品的『Java 開發手冊』,在最新的「泰山版」就增加條件表達式的這一節規範。

ps:公號消息回復:『開發手冊』,獲取最新版的 Java 開發手冊。

最後一定要做好的單元測試,不要慣性思維,覺得這麼簡單的一個東西,看起來根本不可能出錯的。

參考

  1. Java 開發手冊-泰山版
  2. 《Java 開發手冊》解讀:三目運算符為何會導致 NPE?

歡迎關注我的公眾號:程序通事,獲得日常乾貨推送。如果您對我的專題內容感興趣,也可以關注我的博客:studyidea.cn

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

※回頭車貨運收費標準

奇瑞全新小型SUV瑞虎3x智趣上市 售價5.89-8.09萬元

1{icon} {views}

9L,燃油經濟性十分突出。瑞虎3x大量選用環保材料技術,在零件、系統、整車各個階段對內飾空氣質量層層監控,營造綠色、環保、低氣味的內飾空間。同時,瑞虎3x遵循五星安全標準設計,具有高強度車身結構和嚴謹的製造工藝,不僅擁有輕量化前防撞鋼樑、環護式四位一體安全氣囊的全方位被動安全配置,還配備了全方位倒車雷達+動態輔助倒車影像系統、全新ESC車身穩定系統、TpMS直接式胎壓監測、EBA剎車輔助控制系統、上坡輔助HHC等越級高科技主動安全配置,全方位保障用戶行車安全。

11月15日,奇瑞全新小型SUV—瑞虎3x正式上市。新車共推出搭載1.5VVT發動機共10款車型,售價5.89萬——8.09萬元。

瑞虎3x是奇瑞戰略2.0體系下的第三款全新產品,將以“同級首款4G互聯SUV”的優勢進軍小型SUV市場,並與此前上市的新瑞虎3共同組成產品雙擎,進一步完善瑞虎家族產品線,實現對年輕時尚細分市場的全面覆蓋。

以“智趣”為標籤的瑞虎3x是奇瑞基於當下年輕消費群體的用車需求以及SUV年輕化、智能化、個性化的發展趨勢,為追求獨特個性、玩樂精神的年輕群體量身打造的一款“4G智趣SUV”。

“全新品質標杆” 引領小型SUV新潮流

作為奇瑞2.0產品的新一代小型SUV,無論是引領潮流的造型設計,還是智能互聯的科技屬性,都讓瑞虎3x領先一步樹立起小型SUV市場的全新品質標杆。

雖然是一款體量小的SUV,但瑞虎3x在尺寸空間和動力性能上都有獨特優勢。4200 *1760 *1570 mm的車身尺寸和2555 mm的超長軸距全面超越同級競爭對手。合理的設計使瑞虎3x在實際空間的表現上更為突出,提供了同級別最優的前排頭腿部空間、後排頭部空間,高達420L的後備箱載物空間也是同級容積最大。升級后的1.5VVT全國十佳發動機匹配5MT/4AT兩款變速箱,使瑞虎3x的最大輸出功率高達106馬力。百公里40m制動距離、186mm的最小離地間隙、24°接近角、32°離去角更是造就了瑞虎3x的同級最優通過性。

憑藉奇瑞2.0體系的強大支撐,瑞虎3x的品質和可靠性比肩國際水準。在耐久可靠性方面,瑞虎3x經歷了全球極限環境的苛刻路試,通過了近100項試驗驗證,開發過程中總計投入試驗車超過200台,累計行駛里程數約110萬公里;百公里綜合油耗僅5.9L,燃油經濟性十分突出。瑞虎3x大量選用環保材料技術,在零件、系統、整車各個階段對內飾空氣質量層層監控,營造綠色、環保、低氣味的內飾空間。同時,瑞虎3x遵循五星安全標準設計,具有高強度車身結構和嚴謹的製造工藝,不僅擁有輕量化前防撞鋼樑、環護式四位一體安全氣囊的全方位被動安全配置,還配備了全方位倒車雷達+動態輔助倒車影像系統、全新ESC車身穩定系統、TpMS直接式胎壓監測、EBA剎車輔助控制系統、上坡輔助HHC等越級高科技主動安全配置,全方位保障用戶行車安全。在售後服務方面也擁有尊貴禮遇,可享受“3年10萬公里整車保修”的服務品質保障。

“4G全時互聯” 定義全新用車生活

對以“智趣”為靈魂的瑞虎3x來說,“4G全時互聯”的引入,成為瑞虎3x最受關注之處。在智能科技配置方面,瑞虎3x深諳年輕用戶對互聯的需求,搭載了Cloudrive2.0智雲互聯行車系統,不僅配備了4G極速wifi、VOS智能語音控制、語音導航、Carplay手機互聯、8寸超大高清彩屏、動態輔助倒車影像、GSI換擋提醒等多項同級別獨有配置,還配有定速巡航、伴我回家功能、后視鏡電加熱 、外后視鏡集成轉向燈、遙控鑰匙等人性化科技配置,樹立起小型SUV配置標杆。

汽車“雲”服務是未來汽車發展的趨勢,瞄準年輕用戶的瑞虎3x,此次聯手業界“語音怪獸”科大訊飛推出Cloudrive 2.0智能語音交互系統,是同級唯一擁有4G互聯功能的SUV。這套系統此前在艾瑞澤5車型上已經體現了強大的功能和互動便利性,此次更是不惜成本在原先版本上升級優化,打造出“中國最強”車載語音系統。

“能說話絕不動手”,瑞虎3x還突破性地迎來了人機之間的無障礙溝通。系統內置4G無線網卡、硬件採用2G DDR3/8G ROM雙核,可實現最快的瀏覽分享速度;雙麥語音降噪模塊、雲+端語音識別技術的應用,普通話識別率近100%,16種方言也能精準識別交互;On Cloud全網雲端在線服務,私人定製在線服務、海量數據支持等互聯體驗使人與機器的交流像人與人之間的“對話”,一舉顛覆傳統用車方式。

2.0發力 品牌年輕化築優勢

從小型SUV瑞虎3到緊湊型SUV瑞虎5,再到中級SUV瑞虎7……瑞虎3x的上市,將進一步完成奇瑞在SUV市場的全方位布局。瑞虎3x將與新瑞虎3一起組合“雙子星“,實現奇瑞在SUV市場實現更細分的市場覆蓋,形成個性鮮明、高低搭配的瑞虎家族產品矩陣。

從今年上市的艾瑞澤5、瑞虎7、瑞虎3x這三款奇瑞2.0產品可以看到,奇瑞在設計中摒棄了中庸的設計理念,採用了大量時尚、個性的元素,充分迎合年輕消費者的審美和喜好。在科技配置上,新一代車型增強了智能互聯功能,搭載了語音交互、雲端服務、內置4G網卡等配置,滿足年輕消費群體高度依賴互聯網的生活方式和出行需求。奇瑞把未來品牌和產品的主力目標人群鎖定為年輕人,品牌形象正在變得更加年輕、時尚、國際范。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

※回頭車貨運收費標準

【大廠面試04期】講講一條MySQL更新語句是怎麼執行的?

5{icon} {views}

在面試中,經常會問到在MySQL中一條更新語句是怎麼執行的?在本文中,我們就來詳細學習一下更新語句的執行流程,也有利於我們在工作中更好地使用MySQL。

流程圖

這是在網上找到的一張流程圖,寫的比較好,大家可以先看圖,然後看詳細閱讀下面的各個步驟。

執行流程:

1.連接驗證及解析

客戶端與MySQL Server建立連接,發送語句給MySQL Server,接收到後會針對這條語句創建一個解析樹,然後進行優化,(解析器知道語句是要執行什麼,會評估使用各種索引的代價,然後去使用索引,以及調節表的連接順序)然後調用innodb引擎的接口來執行語句。

2.寫undo log

innodb 引擎首先開啟事務,對舊數據生成一個UPDATE的語句(如果是INSERT會生成UPDATE語句),用於提交失敗后回滾,寫入undo log,得到回滾指針,並且更新這個數據行的回滾指針和版本號(會設置為更新的事務id)。

3.從索引中查找數據

根據查詢條件去B+樹中找到這一行數據(如果是唯一性索引,查到第一個數據就可以了(因為有唯一性約束),如果是普通索引,會把所有數據查找出來。)

4.更新數據

首先判斷數據頁是否在內存中?

4.1 如果數據頁在內存中

先判斷更新的索引是普通索引還是唯一性索引?

4.1.1 普通索引

如果更新的索引是普通索引,直接更新內存中的數據頁

4.1.2 唯一性索引

如果更新的索引是唯一性索引,判斷更新后是否會破壞數據的唯一性,不會的話就更新內存中的數據頁。

4.2 如果數據頁不在內存中

先判斷更新的索引是普通索引還是唯一性索引?

4.2.1 普通索引

如果是更新的索引是普通索引,將對數據頁的更新操作記錄到change buffer,change buffer會在空閑時異步更新到磁盤。

4.2.2 唯一性索引

如果是更新的索引是唯一性索引,因為需要保證更新后的唯一性,所以不能延遲更新,必須把數據頁從磁盤加載到內存,然後判斷更新后是否會數據衝突,不會的話就更新數據頁。

5.寫undo log(prepare狀態)

將對數據頁的更改寫入到redo log,將redo log設置為prepare狀態。

6.寫bin log(commit狀態),提交事務

通知MySQL server已經更新操作寫入到redo log 了,隨時可以提交,將執行的SQL寫入到bin log日誌,將redo log改成commit狀態,事務提交成功。(一個事務是否執行成功的判斷依據是是否在bin log中寫入成功。寫入成功后,即便MySQL Server崩潰,之後恢復時也會根據bin log, redo log進行恢復。具體可以看看下面的崩潰恢復原則)

補充資料:

二段提交制是什麼?

更新時,先改內存中的數據頁,將更新操作寫入redo log日誌,此時redo log進入prepare狀態,然後通知MySQL Server執行完了,隨時可以提交,MySQL Server將更新的SQL寫入bin log,然後調用innodb接口將redo log設置為提交狀態,更新完成。
如果只是寫了bin log就提交,那麼忽然發生故障,主節點可以根據redo log恢複數據到最新,但是主從同步時會丟掉這部分更新的數據。
如果只是寫binlog,然後寫redo log,如果忽然發生故障,主節點根據redo log恢複數據時就會丟掉這部分數據。
MySQL崩潰后,事務恢復時的判斷規則是怎麼樣的?(以redolog是否commit或者binlog是否完整來確定)
如果 redo log 裏面的事務是完整的,也就是已經有了 commit 標識,則直接提交;

如果 redo log 裏面的事務只有完整的 prepare,則判斷對應的事務 binlog 是否存在並完整:a. 如果是,則提交事務;b. 否則,回滾事務。

undo log是什麼?

undo log主要是保證事務的原子性,事務執行失敗就回滾,用於在事務執行失敗后,對數據回滾。undo log是邏輯日誌,記錄的是SQL。(可以認為當delete一條記錄時,undo log中會記錄一條對應的insert記錄,反之亦然,當update一條記錄時,它記錄一條對應相反的update記錄。)
在事務提交后,undo log日誌不會立即刪除,會放到一個待刪除的鏈表中,有purge線程判斷是否有其他事務在使用上一個事務之前的版本信息,然後決定是否可以清理,簡單的來說就是前面的事務都提交成功了,這些undo才能刪除。
change buffer是什麼(就是將更新數據頁的操作緩存下來)
在更新數據時,如果數據行所在的數據頁在內存中,直接更新內存中的數據頁。
如果不在內存中,為了減少磁盤IO的次數,innodb會將這些更新操作緩存在change buffer中,在下一次查詢時需要訪問這個數據頁時,在執行change buffer中的操作對數據頁進行更新。
適合寫多讀少的場景,因為這樣即便立即寫了,也不太可能會被訪問到,延遲更新可以減少磁盤I/O,只有普通索引會用到,因為唯一性索引,在更新時就需要判斷唯一性,所以沒有必要。

redo log 是什麼?

redo log就是為了保證事務的持久性。因為change buffer是存在內存中的,萬一機器重啟,change buffer中的更改沒有來得及更新到磁盤,就需要根據redo log來找回這些更新。
優點是減少磁盤I/O次數,即便發生故障也可以根據redo log來將數據恢復到最新狀態。
缺點是會造成內存臟頁,後台線程會自動對臟頁刷盤,或者是淘汰數據頁時刷盤,此時收到的查詢請求需要等待,影響查詢。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

※回頭車貨運收費標準

Google 雲端硬碟教育方案將不再享有免費無上限容量,2022 年 7 月生效

6{icon} {views}

G Suite 教育版在過去因為提供無上限的空間容量受到許多教育單位的愛用,許多相關資源都能放在雲端共享,近日 Google 旗下的 G Suite 教育版更名為 Google Workspace for Education,緊接著來的就是跟隨整個 Google 雲端硬碟政策的腳步改變儲存空間規則而即將進行的大異動。

Google 雲端硬碟教育方案將不再享有免費無上限容量,2022 年 7 月生效

在去年 11 月時,Google 宣布相簿、文件等無限免費空間將在 2021 年 6 月後取消,現在連 Google 雲端硬碟教育版的規則也跟著動起來,在明年將失去無限儲存空間的福利。Google 表示,在新的雲端硬碟政策中推出了全新的空間模式,基本上為各級學校和大學提供 100TB 的集區儲存空間(也就是單位中所有人的共同空間,可存放帳戶中包括但不限於 Google 雲端硬碟、Gmail 與 Google 相簿的所有檔案),讓所有關聯用戶可以共同使用,這個新模式所提供的空間足以存放 1 億份文件、800 萬份簡報甚至是總時數達 40 萬小時的影片,也就是說如果你的學校單位有巨量資料在雲端硬碟中,超過 100TB 的部分可能要移至他處,或者付費購買後續擴充方案繼續使用。

這項新規定將適用於 Google Workspace for Education 所有版本用戶,對現有用戶的生效時間從 2022 年 7 月起,至於在 2022 年申請服務的新用戶也同樣適用該政策。因應新規則,Google 說明了整體的計算方式,用戶可以在規則生效後使用多少儲存空間主要取決於學校機構所使用的版本,並且進行計算:

  • Google Workspace for Education Fundamentals ( 舊稱 G Suite 教育版) 
    符合資格的機構可獲得至少 100 TB 的集區雲端儲存空間,供機構共同使用。所有檔案都會計入這個額度,包括 Google 雲端硬碟和相簿檔案以及 Gmail。

  • Google Workspace for Education Plus (舊稱 G Suite Enterprise 教育版)
    每購買一份 Google Workspace for Education Plus 授權,共用集區就會另外獲得 20 GB 的儲存空間。如果是繼續使用 G Suite Enterprise 教育版 (舊版) 授權的客戶,只要每購買一份付費員工授權,共用集區就會另外獲得 100 GB 的儲存空間。

    舉例來說:如果你的單位購買了 5000 份教育版授權,那你們總共擁有 100TB(基本空間) + (5000 份授權 x 100GB),總數為 600TB 可用空間。

如果你的單位有大量的資料儲存需求,Google 另外列出了建議的執行步驟,幫助用戶瞭解、規劃並整理可用空間,藉以釋出或獲得更多容量來存放資料,大家可前往官方說明中查看詳情並及早應變。

◎資料來源:Google

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

※回頭車貨運收費標準

08_提升方法_AdaBoost算法

7{icon} {views}

  今天是2020年2月24日星期一。一個又一個意外因素串連起2020這不平凡的一年,多麼希望時間能夠倒退。曾經覺得電視上科比的畫面多麼熟悉,現在全成了陌生和追憶。

GitHub:https://github.com/wangzycloud/statistical-learning-method

提升方法

引入

  提升方法是一種常用的統計學習方法,還是比較容易理解的。在分類問題中,通過改變訓練樣本的權重,學習多個分類器,並將這些分類器進行線性組合,從而提高分類的性能。其實說白了,就是一個人干不好的活,我讓兩個人干;兩個人干不好,那就三個人四個人都來干。但是人多了不能像三個和尚那樣沒水喝,都不幹活。包工頭要採取一些措施,措施一:一個人幹活的時候,哪裡乾的不好?就讓第二個人補充在這個地方;兩個人幹活的時候,哪裡乾的不好?就讓第三個人補充在這個地方。措施二:這三四個人在同一個地方幹活,怎樣確定這個活的結果乾的好不好呢?有的人幹活細緻認真,自然對結果增益大;幹活粗糙的,對結果增益不多,因此需要有一種組合策略進行判斷。通過幾個人共同努力,就解決了一個人干不好的事情。

  接下來的內容,將按照書中順序,先介紹提升方法的思路和代表性的提升算法AdaBoost;再從前向分步加法模型角度解釋AdaBoost;最後看一個具體實例—提升樹。

提升方法AdaBoost算法

  提升方法基於這樣一種思想:對於一個複雜的任務來說,將多個專家的判斷進行適當的綜合所得出的判斷,要比其中任何一個專家單獨的判斷好。實際上,就是“三個臭皮匠頂個諸葛亮“的道理。書中上來提到了兩個稍晦澀的概念“強可學習”、“弱可學習”,下文沒有繼續闡述這兩個概念,這裏簡單的複述一下。在概率近似正確學習的框架中(不懂),一個概念,如果存在一個多項式的學習算法能夠學習它,並且正確率很高,那麼就稱這個概念是強可學習的;一個概念,如果存在一個多項式的學習算法能夠學習它,學習的正確率僅比隨機猜測略好,那麼就稱這個概念是弱可學習的。這兩個概念之間的相互關係,被證明是等價的,也就是說在一定的學習框架下,一個概念是強可學習的充分必要條件是這個概念是弱可學習的。(模型效果好==》強可學習;模型效果差==》弱可學習;兩者有聯繫)

  這樣一來,在學習的過程中如果已經發現了“弱學習算法”,那麼能否將它提升為“強學習算法”。我們知道,發現弱學習算法通常要比發現強學習算法容易得多,如何具體實施提升,是我們開發提升方法時所要解決的問題。

  對於分類問題而言,給定一個訓練數據樣本集,求比較粗糙的分類規則(弱分類器)要比求精確的分類規則容易得多。提升方法就是從弱學習算法出發,反覆學習,得到一系列弱分類器(又稱為基本分類器),然後組合這些弱分類器,構成一個強分類器。並且大多數的提升方法都是改變訓練數據的概率分佈(訓練數據的權值分佈),針對不同的訓練數據分佈調用弱學習算法學習一系列弱分類器。可以理解為難分的數據加大權重,讓下一個弱分類器重點學習該數據。

  這樣,對於提升方法來說,有兩個問題需要解決:一是在每一輪如何改變訓練數據的權值或概率分佈;二是如何將弱分類器組合成一個強分類器。我們接下來要學習的AdaBoost算法,針對第一個問題,提高那些被前一輪分類器錯誤分類樣本的權值,而降低那些被正確分類樣本的權值。這樣一來,那些沒有得到正確分類的數據,由於其權重的加大而受到后一輪弱分類器的更多關注。於是,分類問題被一系列的弱分類器“分而治之”。針對第二個問題,AdaBoost採取加權多數表決的方法。具體的,加大分類誤差率小的弱分類器的權值,使其在表決中起較大的作用;減小分類誤差率大的弱分類器的權值,使其在表決中起到較小的作用。

AdaBoost算法

  這裏先簡單的羅列AdaBoost算法過程,先趕完整體進度,後期編寫代碼時再整理細節。

  假設給定一個二類分類的訓練數據集

AdaBoost算法各個步驟的說明:

  步驟(1)假設訓練數據集具有均勻的權值分佈,即每個訓練樣本在基本分類器的學習中作用相同,這一假設保證第一步能夠在原始數據上學習基本分類器G1(x)。

  步驟(2)AdaBoost反覆學習基本分類器,在每一輪m=1,2,…,M順次執行四步操作:

  第a步,使用當前分佈Dm加權的訓練數據集,學習基本分類器Gm(x);

  第b步,計算基本分類器Gm(x)在加權訓練數據集上的分類誤差率。實際上,第m個分類器的分類誤差率,就是被分類器Gm(x)誤分類樣本的權值之和,這表明權重大的樣本起的作用更大一些。如果將其誤分類,則誤差率大大增加,從而進一步影響Gm(x)的係數;

  第c步,從公式8.8中,可以看出em≤1/2時,αm≥0,並且αm隨着em的減小而增大,所以分類誤差率越小的基本分類器在最終分類器中的作用越大;

  第d步,更新訓練數據的權值分佈為下一輪做準備。可以看到,誤分類樣本在下一輪學習中起更大的作用,不改變所給的訓練數據,而不斷改變訓練數據權值的分佈,使得訓練數據在基本分類器的學習中起不同的作用。

  步驟(3)線性組合f(x)實現M個基本分類器的加權表決。係數αm表示了基本分類器Gm(x)的重要性,這裏所有αm之和並不為1。f(x)的符號決定實例x的類,f(x)的絕對值表示分類的確信度。

AdaBoost的例子

AdaBoost算法的訓練誤差分析

  該訓練誤差分析部分,用證明出來的定理形式,表明AdaBoost算法的最基本性質就是能在學習過程中不斷減少訓練誤差。也就是“該算法能夠降低在訓練數據集的分類誤差率”這件事用數學方式證明了。分別是AdaBoost訓練誤差上界定理8.1、二分類問題的AdaBoost訓練誤差上界定理8.2和推論8.1。

  該定理說明,可以在每一輪選取適當的Gm使得Zm最小,從而使訓練誤差下降的最快。對於二分類問題,有定理8.2。

  該推論表明,在此條件下,AdaBoost的訓練誤差是以指數速率下降的(看着像,不懂)。

AdaBoost算法的解釋

  本節內容從另外一個已經驗證的模型來分析AdaBoost算法,並得到該算法的另外解釋(我的理解是:AdaBoost算法是一般化的加法模型的特例,現在要從加法模型的角度分析)。可以認為AdaBoost算法是模型為加法模型、損失函數為指數函數、學習算法為前向分佈算法時的二類分類學習方法。接下來的內容,先對加法模型及前向分佈算法做簡單介紹,通過對損失函數的分析,得到與AdaBoost算法等價的參數表示。

  考慮加法模型,b(x)函數相當於基分類器:

  在給定訓練數據及損失函數L(y,f(x))的條件下,學習加法模型f(x)成為經驗風險極小化及損失函數極小化問題:

  一般來說,加法模型f(x)中的M次基函數相加模型是一個複雜的優化問題,利用前向分佈算法可以求解該優化問題。前向分佈算法的思想是:因為學習的是加法模型,如果能夠從前向後,每一步只學習一個基函數及其係數,逐步逼近優化目標8.14,那麼就可以簡化優化的複雜度。具體的,每一步只需優化以下損失函數(相當於每一步優化一個基函數,優化一個前進一個):

  那麼,前向分佈算法如下:

  本來公式8.14中的同時求解從m=1到M所有參數β、γ的優化問題,通過前向分佈算法簡化成了逐次求解各個β、γ的優化問題。

  那麼,前向分佈算法與AdaBoost算法有什麼聯繫呢?我們可以用定理的形式敘述之間的聯繫,也就是我們可以由前向分佈算法推導出AdaBoost。定理如下:

  以上內容直接截圖了。本是作為筆記進行整理,數學推導插不上解釋的嘴。要是以後文章被看到的多了,手推一下,再把內容整理上來。

提升樹

  我們知道,提升方法實際上是採用加法模型(即基函數的線性組合)與前向分步算法。本節提升樹是以決策樹作為基函數的提升方法,對分類問題決策樹是二叉分類樹,對回歸問題決策樹是二叉回歸樹。提升樹被認為是統計學習中性能最好的方法之一。

  在例8.1中看到的基本分類器x<v或x>v,可以看作是一個根節點直接連接兩個恭弘=叶 恭弘節點的簡單決策樹,即所謂的決策樹樁。提升樹模型可以表示為決策樹的加法模型:

  即使數據中的輸入與輸出之間的關係很複雜,樹的線性組合都可以很好地擬合訓練數據。分類問題與回歸問題的區別主要在於使用的損失函數不同,回歸問題中一般使用平方誤差損失函數,分類問題一般使用指數損失函數。對於二分類問題,提升樹算法只需要將AdaBoost算法8.1中的基本分類器限製為二類分類即可。下面看一下用於回歸問題的提升樹:

  R是當前模型擬合數據的殘差,所以對回歸問題的提升樹算法來說,只需要簡單的擬合當前模型的殘差即可。具體算法如下:

  具體例子8.2:

  算法8.1代碼已上傳至github,先理解決策樹章節的算法5.5,該提升樹會非常好理解~

代碼效果

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

※回頭車貨運收費標準

Google Pixel 6 傳臉部辨識將回歸,且還會具備螢幕下指紋解鎖

離今年 Google Pixel 6 可能發表的時間雖然還有超過半年,但已經開始有一些傳言現身,而且這一個可說是很多 Pixel 用戶都會高興的消息。最近國外開發者從 App 程式碼中發現,Pixel 5 被取消的臉部辨識技術,Pixel 6 很有機會回歸,而且還會再加入螢幕下指紋解鎖功能,等於說生物辨識技術部分跟 iPhone 13 目前傳聞一樣。

Google Pixel 6 臉部辨識將回歸?

近日一位國外知名 XDA-Developers 開發者 Mishaal Raman 在 Android 12 開發版的設定 App 中,找到關於 “用指紋和臉部安全解鎖手機”、”於口袋中節省時間解鎖手機”、以及 ”輕鬆快速解鎖手機” 的程式碼,這也暗示著,今年 Google Pixel 6 可能臉部、指紋解鎖都有,一次滿足各種使用需求:

So the Settings app is preparing to support devices with both facial auth and fingerprint scanners. Not much else on this, so I wouldn’t read too much into it yet. pic.twitter.com/xzPWd5cV2m

— Mishaal Rahman (@MishaalRahman) February 18, 2021

臉部辨識技術早在 Pixel 4 時代就已經有,不過 Pixel 5 不知為何取消,改回指紋辨識,有人猜測是成本考量,這也是為什麼 Pixel 5 能賣這麼便宜的其中一個原因(處理器為主因)。

另外程式碼中並沒有提到是採用螢幕下指紋辨識技術,不過現在國外媒體都認為是這一個。

Pixel 5,開箱文章請點我閱讀:

話說回來,原本以為 Pixel 5 降價,未來 Pixel 系列就會走這路線,不跟其他大品牌的旗艦手機拼硬體規格,但隨著這傳言出現,搞不好 Pixel 6 也會再度變回真正的旗艦手機,對於硬體至上的 Pixel 用戶來說,可以期待一下。

至於其他,目前就沒有相關消息,根據外媒 Tom’s Guide 的整理,處理器有可能是 Snapdragon 888 或 Snapdragon 870 5G,就看 Pixel 6 的售價而定。電池可能會變更大,顯示器可以期待提升到 120Hz,沒意外應該會有 Pixel 6 與 Pixel 6 XL 兩種選擇,預計今年 10 月初推出。

資料來源:Notebookcheck

Google 公布 12 月更新內容,多個 Pixel 5 特色功能都下放到 Pixel 3 以上的舊機種

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

※回頭車貨運收費標準

Apple 承認 M1 Mac Mini 有顯示畫面的問題,出現粉紅色小方塊(附暫時解決辦法)

1{icon} {views}

繼先前的 iPhone、iPad 連接 M1 Mac 後,Apps 會立刻閃退災情後,最近 M1 Mac Mini 又有新的問題出現,而且還是 Apple 官方已經承認。如果你的 Mac Mini 顯示畫面有時候會出現粉紅色小方塊,那無需擔心,還不用送修機器,這是軟體部分的故障,目前也有暫時解決方案了。

Apple 承認 M1 Mac Mini 有顯示畫面的問題

根據外媒 MacRumors 的報導,自 11 月 Apple 發表 M1 Mac Mini 以來,在 Apple 官方論壇、MacRumors 論壇與 Reddit 論壇,都有用戶回報這個問題,有時候顯示畫面會出現一排粉紅色小方塊,如首圖所示:

而本週的 Apple 內部備忘錄中,官方已經通知服務商調查 M1 Mac Mini 的這個問題,並提出暫時解決方案。

如果你有碰到這狀況,請執行以下故障排除步驟:

  1. 讓你的 Mac Mini 進入睡眠模式。
  2. 等待 2 分鐘後,再次喚醒 Mac Mini。
  3. 接著請拔下 Mac Mini 的顯示器連接線,然後重新插入。
  4. 於系統偏好設定中,調整顯示器的解析度。
  5. 最後重新啟動 Mac Mini。

Apple 也補充,如果狀況沒改善,那請重複以上步驟。

Did Big Sur 11.2 fix the pink squares error on M1MacMinis ? @AppleSupport @appleinsider @AppleSupport #mac #apple #m1 #m1mac #siliconmacs #macmini #m1macmini pic.twitter.com/UHmIuXhTto

— Fatih @Vidyograf (@FatihVidyograf) January 22, 2021

目前還不知道是什麼原因導致,有用戶提到使用 HDMI 時,出現這狀況的情形比 Thunderbolt 還多。

根據 Apple 的故障排除操作,看起來像是軟體錯誤導致,沒意外下一個版本(macOS Big Sur 11.3)就有可能修復,不過最後還是要看調查報告才知道,如果是硬體,Apple 也會提供免費維修方案,所以也無需太擔心。

資料來源:MacRumors

國外 YouTuber 實測 M1 MacBook Air 貼上導熱矽膠片後,效能有感提升(更接近 MacBook Pro)

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

※回頭車貨運收費標準

傳聞 2021 年款全新 MacBook Pro 或將把 HDMI 接口、 SD 讀卡機回歸

6{icon} {views}

不知不覺 Apple 在 MacBook Pro 加入 Touch Bar 觸控列已經也有近五年時間,不過上個月才傳出 2021 年 MacBook Pro 可能會取消 Touch Bar 並回歸實體鍵盤,當時就有傳聞指出有可能 MagSafe 磁吸充電和更多 I/O 介面也可能回歸。近日,根據天風證券分析師郭明錤(Ming-Chi Kuo)的報告預測,今年下半年登場的 2021 年款全新 MacBook Pro  幾有多個設計和規格的變化,將配備 SD 卡插槽與 HDMI 接口。

▲圖片來源:MacRumors

傳聞 2021 年款全新 MacBook Pro 或將把 HDMI 接口、 SD 讀卡機回歸

根據天風證券分析師郭明錤(Ming-Chi Kuo)的預測, Apple 預計在今年下半年推出全新設計的 14 吋與 16 吋兩款 MacBook Pro ,在設計上將有許多對使用者實用的改變,包括之前曾提到將採用 mini-LED 顯示器、回歸經典的 MagSafe 磁吸充電、取消 Touch Bar 觸控列。另外,近日也提到新款的 MacBook Pro 將恢復在機身上配置 SD 記憶卡插槽和 HDMI 接口,這對於經常有外接螢幕、記憶卡傳輸需求的用戶來說,未來也將不必再另外攜帶 USB Hub 轉接器。
郭明錤也預測,新款 MacBook Pro 將配備 SD 卡插槽與 HDMI 接口,而此實用改變將有助於推升換機需求。

▲圖片來源:Apple Hub (Twitter/ @theapplehub)

然而,對於目前已經逐漸習慣以 USB-C 轉接其他像是 SD 記憶卡、 HDMI、 USB-A 、 RJ45 等介面的 MacBook Pro 使用者來說,只在有需要時才將 USB-C Hub 轉接器接上使用,平時也能減少接口未使用入塵的機會,也不完全是件壞事。另外,考量到機身設計最實際要面對的就是機身厚度限制,若回歸這些介面勢必會讓機身厚度再次增加。那麼,各位是否真的會希望 Apple 在新版 MacBook Pro 加回 HDMI 接口與 SD 記憶卡插槽呢?

消息來源:MacRumors

延伸閱讀:
小米推出 33W GaN 氮化鎵充電器:超小體積、 iPhone 也能快速充電,售價僅約 340 元

LINE 24 款免費貼圖整理:多款 LINE 貼圖免費下載!

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

※回頭車貨運收費標準

終於我用JOL打破了你對java對象的所有想象

8{icon} {views}

目錄

  • 簡介
  • JOL簡介
  • 使用JOL分析VM信息
  • 使用JOL分析String
  • 使用JOL分析數組
  • 使用JOL分析自動裝箱
  • 使用JOL分析引用關係
  • 總結

簡介

使用面向對象的編程語言的好處就是,雖然沒有女朋友,但是仍然可以new對象出來。Java是面向對象的編程語言,我們天天都在使用java來new對象,但估計很少有人知道new出來的對象到底長的什麼樣子,是美是丑到底符不符合我們的要去?

對於普通的java程序員來說,可能從來沒有考慮過java中對象的問題,不懂這些也可以寫好代碼。

但是對於一個有鑽研精神的極客來說,肯定會想多一些,再多一些,java中的對象到底是什麼樣的。

今天,小F給大家介紹一款工具JOL,可以滿足大家對java對象的所有想象。

更多精彩內容且看:

  • 區塊鏈從入門到放棄系列教程-涵蓋密碼學,超級賬本,以太坊,Libra,比特幣等持續更新
  • Spring Boot 2.X系列教程:七天從無到有掌握Spring Boot-持續更新
  • Spring 5.X系列教程:滿足你對Spring5的一切想象-持續更新
  • java程序員從小工到專家成神之路(2020版)-持續更新中,附詳細文章教程

更多內容請訪問www.flydean.com

JOL簡介

JOL的全稱是Java Object Layout。是一個用來分析JVM中Object布局的小工具。包括Object在內存中的佔用情況,實例對象的引用情況等等。

JOL可以在代碼中使用,也可以獨立的以命令行中運行。命令行的我這裏就不具體介紹了,今天主要講解怎麼在代碼中使用JOL。

使用JOL需要添加maven依賴:

<dependency>
            <groupId>org.openjdk.jol</groupId>
            <artifactId>jol-core</artifactId>
            <version>0.10</version>
</dependency>

添加完依賴,我們就可以使用了。

使用JOL分析VM信息

首先我們看下怎麼使用JOL來分析JVM的信息,代碼非常非常簡單:

log.info("{}", VM.current().details());

輸出結果:

# Running 64-bit HotSpot VM.
# Using compressed oop with 3-bit shift.
# Using compressed klass with 3-bit shift.
# WARNING | Compressed references base/shifts are guessed by the experiment!
# WARNING | Therefore, computed addresses are just guesses, and ARE NOT RELIABLE.
# WARNING | Make sure to attach Serviceability Agent to get the reliable addresses.
# Objects are 8 bytes aligned.
# Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
# Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]

上面的輸出中,我們可以看到:Objects are 8 bytes aligned,這意味着所有的對象分配的字節都是8的整數倍。

使用JOL分析String

上面的都不是重點,重點是怎麼使用JOL來分成class和Instance信息。

其實java中的對象,除了數組,其他對象的大小應該都是固定的。我們先舉一個最最常用的字符串來看一下:

log.info("{}",ClassLayout.parseClass(String.class).toPrintable());

上面的例子中,我們使用ClassLayout來解析一個String類,先看下輸出:

[main] INFO com.flydean.JolUsage - java.lang.String object internals:
 OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0    12           (object header)                           N/A
     12     4    byte[] String.value                              N/A
     16     4       int String.hash                               N/A
     20     1      byte String.coder                              N/A
     21     1   boolean String.hashIsZero                         N/A
     22     2           (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 2 bytes external = 2 bytes total

先解釋下各個字段的含義,OFFSET是偏移量,也就是到這個字段位置所佔用的byte數,SIZE是後面類型的大小,TYPE是Class中定義的類型,DESCRIPTION是類型的描述,VALUE是TYPE在內存中的值。

分析下上面的輸出,我們可以得出,String類中佔用空間的有5部分,第一部分是對象頭,佔12個字節,第二部分是byte數組,佔用4個字節,第三部分是int表示的hash值,佔4個字節,第四部分是byte表示的coder,佔1個字節,最後一個是boolean表示的hashIsZero,佔1個字節,總共22個字節。但是JVM中對象內存的分配必須是8字節的整數倍,所以要補全2字節,最後String類的總大小是24字節。

有人可能要問小F了,如果字符串裏面存了很多很多數據,那麼對象的大小還是24字節嗎?

這個問題問得非常有水平,下面我們就來看看怎麼使用JOL來解析String對象的信息:

log.info("{}",ClassLayout.parseInstance("www.flydean.com").toPrintable());

上面的例子,我們使用了parseInstance而不是parseClass來解析String實例的信息。

輸出結果:

[main] INFO com.flydean.JolUsage - java.lang.String object internals:
 OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           01 c2 63 a2 (00000001 11000010 01100011 10100010) (-1570520575)
      4     4           (object header)                           0c 00 00 00 (00001100 00000000 00000000 00000000) (12)
      8     4           (object header)                           77 1a 06 00 (01110111 00011010 00000110 00000000) (399991)
     12     4    byte[] String.value                              [119, 119, 119, 46, 102, 108, 121, 100, 101, 97, 110, 46, 99, 111, 109]
     16     4       int String.hash                               0
     20     1      byte String.coder                              0
     21     1   boolean String.hashIsZero                         false
     22     2           (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 2 bytes external = 2 bytes total

先看結論,和String Class一樣,這個String對象確實只佔24字節。

實例的解析和Class解析的結果差不多,因為是實例對象,所以多了VALUE的值。

我們知道在JDK9之後,String的底層存儲從Char[] 變成了Byte[]用於節約String的存儲空間。上面的輸出中,我們可以看到String.value值確實很長,但是保存在String中的只是Byte數組的引用地址,所以4字節就夠了。

使用JOL分析數組

雖然String的大小是不變的,但是其底層數組的大小是可變的。我們再舉個例子:

log.info("{}",ClassLayout.parseClass(byte[].class).toPrintable());

輸出結果:

[main] INFO com.flydean.JolUsage - [B object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0    16        (object header)                           N/A
     16     0   byte [B.<elements>                             N/A
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

類的解析結果,可以看到Byte數組佔16個字節。

再看實例的情況:

log.info("{}",ClassLayout.parseInstance("www.flydean.com".getBytes()).toPrintable());

輸出結果:

[main] INFO com.flydean.JolUsage - [B object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           22 13 07 00 (00100010 00010011 00000111 00000000) (463650)
     12     4        (object header)                           0f 00 00 00 (00001111 00000000 00000000 00000000) (15)
     16    15   byte [B.<elements>                             N/A
     31     1        (loss due to the next object alignment)
Instance size: 32 bytes
Space losses: 0 bytes internal + 1 bytes external = 1 bytes total

可以看到數組的大小真的變化了,這次變成了32字節。

使用JOL分析自動裝箱

我們知道,java中的基本類型都有一個和它對於的Object類型,比如long和Long,下面我們來分析下他們兩個在JVM中的內存區別:

log.info("{}",ClassLayout.parseClass(Long.class).toPrintable());

輸出結果:

[main] INFO com.flydean.JolUsage - java.lang.Long object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0    12        (object header)                           N/A
     12     4        (alignment/padding gap)                  
     16     8   long Long.value                                N/A
Instance size: 24 bytes
Space losses: 4 bytes internal + 0 bytes external = 4 bytes total

可以看到1個Long對象是佔24個字節的,但是其中真正存儲long的value只佔8個字節。

看一個實例:

log.info("{}",ClassLayout.parseInstance(1234567890111112L).toPrintable());

輸出結果:

[main] INFO com.flydean.JolUsage - java.lang.Long object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           9a 15 00 00 (10011010 00010101 00000000 00000000) (5530)
     12     4        (alignment/padding gap)                  
     16     8   long Long.value                                1234567890111112
Instance size: 24 bytes
Space losses: 4 bytes internal + 0 bytes external = 4 bytes total

使用JOL分析引用關係

上面我們使用JOL分析的是class內部的空間使用情況,那麼如果有外部引用可不可以分析呢?

HashMap hashMap= new HashMap();
hashMap.put("flydean","www.flydean.com");
log.info("{}", GraphLayout.parseInstance(hashMap).toPrintable());

上面我們使用一個不同的layout:GraphLayout,它可以用來分析外部引用情況。

輸出結果:

[main] INFO com.flydean.JolUsage - java.util.HashMap@57d5872cd object externals:
          ADDRESS       SIZE TYPE                      PATH                           VALUE
        7875f9028         48 java.util.HashMap                                        (object)
        7875f9058         24 java.lang.String          .table[14].key                 (object)
        7875f9070         24 [B                        .table[14].key.value           [102, 108, 121, 100, 101, 97, 110]
        7875f9088         24 java.lang.String          .table[14].value               (object)
        7875f90a0         32 [B                        .table[14].value.value         [119, 119, 119, 46, 102, 108, 121, 100, 101, 97, 110, 46, 99, 111, 109]
        7875f90c0         80 [Ljava.util.HashMap$Node; .table                         [null, null, null, null, null, null, null, null, null, null, null, null, null, null, (object), null]
        7875f9110         32 java.util.HashMap$Node    .table[14]                     (object)

從結果我們可以看到HashMap本身是佔用48字節的,它裏面又引用了佔用24字節的key和value。

總結

使用JOL可以分析java類和對象,這個對於我們對JVM和java源代碼的理解和實現都是非常有幫助的。

本文的例子https://github.com/ddean2009/
learn-java-base-9-to-20

本文作者:flydean程序那些事

本文鏈接:http://www.flydean.com/java-object-layout-jol/

本文來源:flydean的博客

歡迎關注我的公眾號:程序那些事,更多精彩等着您!

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

※回頭車貨運收費標準

搞清楚C語言指針

4{icon} {views}

Part 0:為什麼要寫這篇文章

C語言中的指針是C語言的精髓,也是C語言的重難點之一。
然而,很少有教程能把指針講的初學者能聽懂,還不會引起歧義。
本文章會嘗試做到這一點,如有錯誤,請指出。

Part 1:地址和&

我們先拋開指針不談,來講一個小故事:

一天,小L準備去找小S玩。但是小L不知道小S的家住在哪裡,正當他着急的時候,他看到了一個路牌,上面寫着:小S的家在神仙小區403

哦,真的是要素過多。為什麼這麼說?

  1. 小L和小S:我們可以看做是兩個變量/常量。
  2. 小S的家:這裏可以看做是變量/常量小S的地址。
    我們要搞清楚,每個變量/常量都和我們一樣:我們每個人都有自己的家,正如變量也有自己的地址。通俗的理解,地址是給變量/常量來存放值的地點
  3. 路牌:注意注意注意!這裏就指出了變量/常量小S的地址:神仙小區403
    事實上,我們等會會講,輸出一個變量的地址其實是個16進制的数字。

搞懂了上面,我們再來聊聊&
&這個符號我們一個不陌生,你最初用到應該是在:scanf("%d",&a)裡邊。
&叫做取址符,用來獲取一個變量/常量的地址。
那麼我們為什麼要在scanf裡邊用&,不在printf裡邊用呢?
一開始我也很疑惑,後來我看到了這個例子:
你是一個新生,你要進教室。
但是你並不知道教室在哪裡,這個時候你需要教室的地址。
下課了,你要出教室。
由於你已經在教室里了,你就不需要獲取教室的地址就可以出去了。

Part 2:一定要記住的東西

一定要記住:指針就是個變量!
重要的事情說三次:
指針就是個變量!他儲存的是地址!他自己也有地址!
指針就是個變量!他儲存的是地址!他自己也有地址!
指針就是個變量!他儲存的是地址!他自己也有地址!

為什麼這麼說?我們從指針的定義開始:

指針的定義方法:<類型名+*> [名稱]
也就是說,指針的定義大概是這樣的:

int* ip;            //類型是int*,名稱是ip
float* fp;          //類型是float*,名稱是fp
double* dp;         //類型是double*,名稱是dp

有的書上會這麼寫:

int *ip;
float *fp;
double *dp;

這麼寫當然沒問題,但是對於初學者來說,有兩個問題:

  1. 有的初學者會把*p當做是指針名
  2. 有的初學者會把定義時出現的*p取值時出現的*p弄混

指針他有沒有值?有!我們會在下一節給他賦值。
既然他的定義方式和變量一樣,他也有值,他為什麼不是變量呢?

Part 3:與指針相關的幾個符號

與指針相關的符號有兩個,一個是&,一個是*
先來聊聊&
&我們上面講過,他是來取地址的。舉個例子:

#include <stdio.h>
int main(){
    int a = 10;
    float b = 10.3;
    printf("%p,%p",&a,&b);
}

%p用來輸出地址,當然,你也可以寫成%d或者%x。先不管這個,我們來看看他會輸出什麼:

那麼也就是說,變量ab的地址是000000000062FE1C000000000062FE18
那麼我們怎麼把這個地址給指針呢?很簡單:p = &a;,舉個例子:

#include <stdio.h>
int main(){
    int a = 10;
    int* p;
    p = &a;
    printf("a的地址:%p\n",&a);
    printf("指針p自身的地址:%p\n",&p);
    printf("指針p指向的地址:%p",p);
}

得到輸出:

a的地址:000000000062FE1C
指針p自身的地址:000000000062FE10
指針p指向的地址:000000000062FE1C

你發現了嗎?如果我們有p = &a;,我們發現:直接輸出p會輸出a的地址,輸出&p會輸出p的地址(這就是為什麼我一再強調p是個變量,他有自己的地址,正如路牌上有地址,路牌自身也有個地址一樣)。

請注意!如果你的指針為int*,那麼你只能指向int類型;如果是double*類型,只能指向double類型,以此類推

當然,void*類型的指針可以轉化為任何一種不同的指針類型(如int*,double*等等)

那麼,我們來聊聊第二個符號*
*有兩個用法。第一個在定義指針時用到,第二個則是取值,什麼意思?看下面這個例子:

#include <stdio.h>
int main(){
    int a = 10;
    int* p;
    p = &a;
    printf("a的地址:%p\n",&a);
    printf("指針p自身的地址:%p\n",&p);
    printf("指針p指向的地址:%p\n",p);
    printf("指針p指向的地址的值:%d",*p);
}

得到輸出:

a的地址:000000000062FE1C
指針p自身的地址:000000000062FE10
指針p指向的地址:000000000062FE1C
指針p指向的地址的值:10

哈,我們得到了a的值!
也就是說,當我們有p = &a,我們可以用*p得到a的值。
那能不能操作呢?當然可以。
我們可以把*p當做a的值,那麼,我們嘗試如下代碼:

#include <stdio.h>
int main(){
    int a = 10;
    int* p;
    p = &a;
    printf("指針p指向的地址的值:%d\n",*p);
    *p = 13;
    printf("指針p指向的地址的值:%d\n",*p);
    *p += 3;
    printf("指針p指向的地址的值:%d\n",*p);
    *p -= 3;
    printf("指針p指向的地址的值:%d\n",*p);
    *p *= 9;
    printf("指針p指向的地址的值:%d\n",*p);
    *p /= 3;
    printf("指針p指向的地址的值:%d\n",*p);
    *p %= 3;
    printf("指針p指向的地址的值:%d\n",*p);
}

得到輸出:

指針p指向的地址的值:10
指針p指向的地址的值:13
指針p指向的地址的值:16
指針p指向的地址的值:13
指針p指向的地址的值:117
指針p指向的地址的值:39
指針p指向的地址的值:0

棒極了!我們可以用指針來操作變量了。
那麼,我們要這個干什麼用呢?請看下一節:實現交換函數

Part 4:交換函數

交換函數是指針必學的一個東西。一般的交換我們會這麼寫:

t = a;
a = b;
b = t;

那麼我們把它塞到函數裡邊:

void swap(int a,int b){
      int t;
      t = a;
      a = b;
      b = t;
}

好,我們滿懷信心的調用他:

#include <stdio.h>
void swap(int a,int b){
      int t;
      t = a;
      a = b;
      b = t;
}
int main(){
      int x = 5,y = 10;
      printf("x=%d,y=%d\n",x,y);
      swap(x,y);
      printf("x=%d,y=%d",x,y);
}

於是乎,你得到了這個輸出:

x=5,y=10
x=5,y=10

啊啊啊啊啊啊啊啊,為什麼不行!!!
問題就在你的swap函數,我們來看看他們做了些啥:

swap(x,y);             --->把x賦值給a,把y賦值給b
///進入函數體
int t;                 --->定義t
t = a;                 --->t賦值為a
a = b;                 --->a賦值為b
b = t;                 --->b賦值為t

各位同學,函數體內有任何一點談到了x和y嗎?
所謂的交換,交換的到底是a和b,還是x和y?
我相信你這時候你恍然大悟了,我們一直在交換a和b,並沒有操作x和y

那麼我們怎麼操作?指針!
因為x和y在整個程序中的地址一定是不變的,那麼我們通過上一節的指針運算可以得到,我們能夠經過指針操作變量的值。
那麼,我們改進一下這個函數

void swap(int* a,int* b){
      int t;
      t = *a;
      *a = *b;
      *b = t;
}

我們再來試試,然後你就會得到報錯信息。

我想,你是這麼用的:swap(x,y)
問題就在這裏,我們看看swap需要怎樣的兩個變量?int*int*類型。
怎麼辦?我告訴你一個小秘密:
任何一個變量加上&,此時就相當於在原本的類型加上了*
什麼意思?也就是說:

int a;
&a ---> int*;
double d;
&d ---> double*;
int* p;
&p ---> int**;//這是個二級指針,也就是說指向指針的指針

那麼,我們要這麼做:swap(&a,&b),把傳入的參數int換為int*

再次嘗試,得到輸出:

x=5,y=10
x=10,y=5

累死了,總算是搞好了

Part 5:char*表示字符串

char*這個神奇的類型可以表示個字符串,舉個例子:


#include <stdio.h>

int main()
{
    char* str;
    str = "YOU AK IOI!";
    printf("%s",str);
}

請注意:輸入和輸出字符串的時候,都不能帶上*&

你可以用string.h中的函數來進行操作

Part 6:野指針

有些同學他會這麼寫:

int* p;
printf("%p",p);

哦千萬不要這麼做!
當你沒有讓p指向某個地方的時候,你還把他用了!這個時候就會產生野指針。
野指針的危害是什麼?
第一種是指向不可訪問(操作系統不允許訪問的敏感地址,譬如內核空間)的地址,結果是觸發段錯誤,這種算是最好的情況了;

第二種是指向一個可用的、而且沒什麼特別意義的空間(譬如我們曾經使用過但是已經不用的棧空間或堆空間),這時候程序運行不會出錯,也不會對當前程序造成損害,這種情況下會掩蓋你的程序錯誤,讓你以為程序沒問題,其實是有問題的;

第三種情況就是指向了一個可用的空間,而且這個空間其實在程序中正在被使用(譬如說是程序的一個變量x),那麼野指針的解引用就會剛好修改這個變量x的值,導致這個變量莫名其妙的被改變,程序出現離奇的錯誤。一般最終都會導致程序崩潰,或者數據被損害。這種危害是最大的。

不論如何,我們都不希望看到這些發生。
於是,養成好習慣:變量先賦值。

指針你可以這麼做:int *p =NULL;讓指針指向空

不論如何,他總算有個值了。

Part 7:總結

本文乾貨全部在這裏了:

  1. 指針是個變量,他的類型是數據類型+*,他的值是一個地址,他自身也有地址
  2. 指針有兩個專屬運算符:&*
  3. 指針可以操作變量,不能操作常量
  4. 指針可以表示字符串
  5. 請注意野指針的問題

本文沒有講到的:

  1. char[],char,const char的區別與聯繫
  2. const修飾指針會怎麼樣?
  3. void*指針的運用
  4. 多級指針的運用
  5. NULL到底是什麼
  6. malloc函數的運用

感謝觀看!

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

※回頭車貨運收費標準