車輕了反而安全 到底是高科技還是忽悠?

看過F1比賽都知道,就算賽車被撞個稀巴爛,賽車手最後還是能夠安然無恙的從一堆廢鐵里走出來。這都是得益於高強度的材料和科學合理的車身結構。邁銳寶XL的安全性如何。邁銳寶XL比上代車型減重了120kg,人們也可能會懷疑現在的輕量化汽車的碰安全性是不是就比舊時皮糙肉厚的美系車要差很多。

隨着人們環保意識的增強,汽車的輕量化成為了幾乎所有車企的重要課題。雪佛蘭的邁銳寶XL就是輕量化的一個典型例子,邁銳寶XL比上代車型邁銳寶重量足足減少了120kg。那麼我們最關心的問題來了,輕量化之後重量降低了的汽車在安全性方面真的比所謂“皮糙肉厚”的傳統汽車要差嗎?認為不一定。

為啥要減重?

大家都知道汽車的油耗跟車輛前進時受到的阻力有莫大的關係,包括空氣阻力和摩擦阻力等等,而車的重量又是這些阻力的重要影響因素。國外有研究表明,若汽車整車重量降低10%,燃油效率可提高6%—8%。而邁銳寶XL,其減重之後百公里油耗低至6L,這對於一輛中型車來說已經十分優秀了。

除了省油,汽車輕量化的最大優點之一就是提升了車輛的操控性。舊時的人們認為車輛夠大,夠重,高速穩定性就好。但實際上一輛車身輕但是車身結構整體性好的汽車,其高速穩定性也十分好。除此以外,輕量化的車身在加速、轉向、剎車等個方面都會有更加優秀的表現,某程度上甚至加強了主動安全性能。

減重后的汽車安全性怎樣?

要看一輛車的安全性,不是怎麼吹噓,或者怎麼羅列一些數據就可以讓別人相信的,最直觀的體現還是要看碰撞測試的表現。邁銳寶XL在IIHS的五大測試項目中均取得“優”的評價,要知道這其中包括了堪稱最慘烈碰撞的25%正面偏置碰撞。

下圖為進行25%正面偏置碰撞后的邁銳寶XL,可以看出A柱變形程度極低,車內成員空間也幾乎沒有縮減,難怪會得到IIHS官方“優”的評價。

要說到汽車輕量化,F1賽車就是最典型的例子。在F1賽車上,每一克重量都是十分寶貴的,因此F1賽車採用極高強度的單殼體車身,但單殼體車身的重量僅為35公斤。看過F1比賽都知道,就算賽車被撞個稀巴爛,賽車手最後還是能夠安然無恙的從一堆廢鐵里走出來。這都是得益於高強度的材料和科學合理的車身結構。

邁銳寶XL的安全性如何?

邁銳寶XL比上代車型減重了120kg,人們也可能會懷疑現在的輕量化汽車的碰安全性是不是就比舊時皮糙肉厚的美系車要差很多?事實上並不是的。

車身材料和結構決定了該車的安全性,邁銳寶XL的車身材料採用40%的超高強度鋼,最高抗壓強度達到1500Mpa。

這40% 的超高強度鋼不是隨便加入到車身的,是工程師通過高精度的仿真迭代,找出關鍵的位置來進行強化的。所以我們從邁銳寶XL的動力總成誘導轉向柱和座椅調節導軌響應示意圖可以看出其車身扭曲形變的幅度在優化後有明顯的減少。

而在車身結構方面,邁銳寶XL的工程師也沒少下苦心,邁銳寶XL採用通用全新中高級車平台,其低阻整車架構讓邁銳寶XL更低也更輕了。而減少的這些重量也是工程師通過結構拓撲分析實現的。除此之外,與安全息息相關的車頭也採用了新型的合成設計空間,能在碰撞時提供更好的吸能緩衝表現,這也是為什麼邁銳寶XL的IIHS碰撞成績那麼好。

邁銳寶XL的超高強度鋼和優異的車身結構在被動安全方面給乘客保駕護航。而在主動安全方面,邁銳寶XL全系標配博世第9代电子安全系統,共有20項子系統,能夠全系標配博世的电子安全系統,足見邁銳寶XL對安全的重視。

除了主動和被動安全,邁銳寶XL更配備十項电子智能安全系統,包括ACC自適應巡航系統、FCA前碰撞預警、AHBA遠近光燈自動控制、SBZA側盲區報警系統等等,讓駕駛者更容易發現潛在的危險,最大程度的避免事故發生。

總結:其實汽車安全是由多個因素共同影響的,主動安全方面的电子安全系統能夠減少碰撞發生的概率;而高強度的材料和科學的結構能夠減輕碰撞時的傷害,邁銳寶XL在這兩點都做得很好。而最後也是最重要的一點,就是人的操控,目前來說,強度再大的車身在高速碰撞中都可能造成人員傷亡。所以盲目的批判減重后的汽車安全性會下降,其實並不科學的。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※回頭車貨運收費標準

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※推薦評價好的iphone維修中心

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

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

國外多位用戶反應 M1 Mac 的 SSD 損耗太嚴重,擔心影響使用壽命

M1 Mac 自從推出以來就好評不斷,特別是在效能部分,不過隨著時間拉長,最近也陸續傳出一些問題,繼先前的粉紅小方塊、導致 iPhone 與 iPad Apps 閃退狀況,近日又有一個新問題出現,而且似乎是所有 Mac 都受影響。國外用戶反應,M1 Mac 的 SSD 耗損有點嚴重,健康度令人擔憂。

國外多位用戶反應 M1 Mac 的 SSD 損耗太嚴重

根據外媒 Macworld 報導,最近多位用戶陸續在 Twitter 上分享擔心 M1 Mac SSD 耗損速度的貼文,首先是 Longhorn,他提到他的 M1 Mac 只使用約 2 個月左右,SSD 卻已經損耗 1%,而且這塊 SSD 是焊死的不能更換,讓人覺得非常不 OK:

M1 Mac SSD update…

Apple, this is NOT okay. Especially as the internal SSD isn’t replaceable. The machine is just a bit over 2 months old. pic.twitter.com/xqlSMjxwTa

— Longhorn (@never_released) February 13, 2021

Macworld 編輯 Dan Moren 還跟他的舊 Mac 比較。左側為他舊的 2017 iMac,右側則是去年秋天買的 M1 Air,才三個多月過去,M1 Air 讀寫數據量已經是 3.5 年 iMac 的 15~20%:

This story about M1 Macs with excessive SSD read/write data does seem to be true. On the left, my 2017 iMac; on the right, my M1 Air from last fall. Somehow, in 3 months, it’s registered about 15-20% of what my iMac has in 3.5 years? Not sure what the implications are, but weird. pic.twitter.com/qNrnkzo62z

— Dan Moren (@dmoren) February 23, 2021

這位 David 的 M1 Macbook Pro 配備 2TB SSD 超大容量,才經過 2 個月,SSD 就已經損耗 3%。有人甚至推測,如果他的 M1 MBP 是 256GB 版本,現在搞不好 SSD 已經損耗 30%,並且在兩年後達到最高的 TBW 限制:

16GB M1 MBP, 2TB SSD, 2 months in. pic.twitter.com/SaSmieaT1s

— David (@david_rysk) February 15, 2021

即便 TBW 達標,SSD 依舊還是可以繼續使用,只是不確定可以維持多長時間。

目前還不確定是什麼導致這原因,但應該不是 M1 晶片,畢竟是檢測報告,實際是不是真的這樣,也只能等有人達到 TPW 限制才能得知,另外也可能是測試工具有問題。

無論如何,以 Apple 這麼大間且知名的公司來說,開發時應該有考量到這部分,M1 Mac 用戶也不用太過於擔心。

資料來源:Macworld

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

您也許會喜歡:

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

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

【其他文章推薦】

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

網頁設計最專業,超強功能平台可客製化

※別再煩惱如何寫文案,掌握八大原則!

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

不知不覺 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元/分

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

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

※回頭車貨運收費標準

新一代華為 MateBook 14 輕薄筆電在台推出,美感、效能一步到位

華為旗下的筆電產品以及精緻、便利與高 CP 在台灣有一定的愛用者,今日(2/24)更宣布最新 MateBook 14 在台灣推出,延續過去兼顧精工雕琢的外表與效能平衡兩大方面,讓生產力與娛樂齊頭並進,為消費者開啟全新的 2K 輕薄螢幕新視野,多工處理體驗一次到位。

新一代華為 MateBook 14 輕薄筆電在台推出,美感、效能一步到位

MateBook 14 擁有鋁合金搭配 CNC 鑽切工藝,加上精密噴砂與鑽石倒腳切割,打造出手感細膩的高質感輕薄金屬機身,重量僅 1.49kg 恰到好處,低調簡約卻有耐看百搭。

搭配 3:2 顯示比例的 2K 超高解析度全螢幕,螢幕佔比達到 90%,並且覆蓋 100% sRGB 寬廣色域,通過德國萊茵低藍光護眼測試,視野清晰一覽無遺。

搭載 7nm 製程的 AMD Ryzen 4000H 系列行動處理器,與 AMD Radeon 系列顯示晶片,16GB 雙通道記憶體搭配 512GB PCIe NVMe SSD,小小的身軀裡面卻蘊含能涵蓋足以支援生產力、視訊短片與娛樂遊戲各方面的高效能。改良的全新鯊魚鰭熱系統,將葉片厚度減少 40%,葉片數量提升 49%,以增加 39% 風量、低噪音的雙風扇、雙導管來維持整體系統穩定性。

在鍵盤右上方則是電源搭配指紋辨識器:這款新機同樣地將視訊鏡頭隱藏於鍵盤之中,不用時就收起來,確保個人隱私無虞。華為筆電的另一個招牌功能「一碰傳」在這款同樣基因的產品上當然也沒有省略,加大觸控板碰傳區域,隨手傳超便利。內建 56Wh 大容量電池,配合華為自家的智慧省電技術,只需充電 15 分鐘就能使用 2.5 小時,隨附的 65W 口袋充電器重量輕巧僅 160g,隨時補充電量不給你負擔。

華為 MateBook 14 售價定為 30,990 元,上市期間購機就送 Office 365 個人版與羅技 M235 無線滑鼠,數量有限送完為止。另外,即日起至 2/28 止,只要在燦坤指定店點(請見下圖)、線上賣場與華為品牌店完成預購,就送市價 6,988 元的 ViewSonic 27 吋 2K 薄型顯示器一部。

整體算下來這台搭載 AMD 4600H處理器、16GB RAM、512GB SSD、全金屬機身、長效續航的筆電只賣30990已經相當高性價比了,如果扣掉價值近7000元的27吋螢幕、OFFICE365與滑鼠,等於這樣規格的筆電不到23000!相當划算,有興趣的朋友可以到華為品牌店庾燦坤門市購買(請點我):

您也許會喜歡:

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

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

【其他文章推薦】

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

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

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

網頁設計最專業,超強功能平台可客製化

※回頭車貨運收費標準

Anker 率先為 iPhone 12 系列推出「磁吸無線充電行動電源」,外出充電更方便

前段時間我們曾報導過 iOS 14.5 Beta 2 暗示將推出 MagSafe 充電配件的消息,近期 Anker 這間致力於研發充電配件品牌也針對 iPhone 12 系列推出磁吸式無線充電行動電源「Anker PowerCore Magnetic 5K Wireless Power Bank」。凡是適用支援 MagSafe 的 iPhone 12 系列手機,就能直接將行動電源吸附在機身背面直接進行無線充電。

Anker 率先為 iPhone 12 系列推出「磁吸無線充電行動電源」,外出充電更方便

在 CES 2021 首次亮相的 Anker PowerCore 系列磁吸無線充電行動電源,能通過磁力吸附固定在 iPhone 12 系列背蓋上進行無線充電。近日這款行動電源也搶先 Apple 原廠率先推出、悄悄在 Amazon 上架。
有別於 Apple 原廠的聰穎電池護殼或第三方配件品牌的充電保護殼,這類磁吸無線充電行動電源將能讓使用者在未充電狀態能將行動電源收在包包,只在需要充電期間直接吸附上去即可進行充電。

Anker 這款行動電源內建 5,000mAh 的電池容量,可為 iPhone 12 mini 從 0% 充電至 100% 、 iPhone 12 和 iPhone 12 Pro 從 0% 充電至 95% 、 iPhone 12 Pro Max 從 0% 充電至 75% 。雖然行動電源電池容量不大、僅能作為電力補給使用,但對於不想攜帶充電線、作為一整天下來最後緊急補給電力使用,這款行動電源還是能滿足需求。除了能透過無線充電進行充電,這款行動電源也支持 USB-C 雙向輸出充電:

行動電源支持最高 5W 無線充電、磁性也夠強,能牢牢吸附在 iPhone 12 系列機身背蓋邊充電邊使用:

除了裸機能充電,這款行動電源也可搭配 MagSafe 保護殼的 iPhone 12 系列進行磁吸無線充電:

充電安全方面,也支持溫度保護、過充保護和異物檢測等安全機制:

售價方面, Anker PowerCore 磁西無線充電行動電源 5000mAh 的售價為 39.99 美元(約合新台幣 1,117 元),目前已在 Amazon 上架。

圖片/消息來源:Amazon

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

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

您也許會喜歡:

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

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

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

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

※想知道最厲害的網頁設計公司"嚨底家"!

※別再煩惱如何寫文案,掌握八大原則!

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※回頭車貨運收費標準

台中搬家公司費用怎麼算?

Go文件操作

目錄

  • 示例1: 打開和關閉文件
  • 示例2: 打開文件並讀取內容
  • 示例3: 一次性讀取文件
  • 示例4: 帶緩衝的Reader讀文件
  • 示例5: 創建文件並寫入內容
  • 示例6: 寫文件的四種方式
  • 示例7: 把一個文件內容寫入到另一個文件
  • 示例8:使用bufio獲取用戶輸入
  • 示例9: 判斷文件或目錄是否存在
  • 示例10: 拷貝文件、圖片音視頻
  • 示例11: 遍歷目錄
    • 遍歷目錄
    • 僅遍歷目錄,忽略文件
  • 示例12: 修改文件名
  • 示例13:創建目錄
  • 示例14:刪除文件

對於文件,我們並不陌生,文件是數據源(保存數據的地方)的一種,比如大家經常使用的word文檔,txt文件,Excel文件…等等都是文件。文件最主要的作用就是保存數據,它既可以保存一張圖片,也可以保存視頻,聲音……
文件在程序中是以流的形式來操作的。

:數據在數據源(文件)和程序(內存)之間經歷的路徑
輸出流:數據從程序(內存)到數據源(文件)的路徑
輸入流:數據從數據源(文件)到程序(內存)的路徑
輸入與輸出都是相對於內存而言的,從內存向外流就是輸出,從外部向內存流就是輸入

在Go中,我們操作文件的方法在os包中,會經常使用到os.File結構體 Go語言標準庫文檔

示例1: 打開和關閉文件

package main

import (
    "fmt"
    "os"
)

func main() {

    //打開文件(/Users/xxx/Go/src/file.txt)
    //概念說明:file的叫法
    //1.file 叫 file對象
    //2.file 叫 file指針
    //3.file 叫 file文件句柄
    file, err := os.Open("/Users/itbsl/Go/src/file.txt")
    if err != nil {
        fmt.Println("文件打開失敗,原因是:", err)
        //os.Exit(0)
    }
    defer func() {
        //文件及時關閉
        err = file.Close()
        if err != nil {
            fmt.Println("文件關閉失敗,原因是", err)
        }
    }()
}

示例2: 打開文件並讀取內容

使用Read()函數按照字節讀

package main

import (
	"fmt"
	"io"
	"os"
)

func main() {

	file, err := os.Open("./test.txt")
	if err != nil {
		fmt.Printf("open file failed, err:%v\n", err)
		return
	}
	defer func() {
		err = file.Close()
		if err != nil {
			fmt.Printf("close file failed, err:%v\n", err)
		}
	}()

	var content []byte
	var tmp = make([]byte, 128)
	for {
		n, err := file.Read(tmp)
		//為什麼是tmp[:n]而不是tmp[:]?
		//因為當讀取到最後一行的內容長度不足tmp的長度的時候
		//新讀取的內容只會覆蓋前半部分上次讀取到的tmp的內容,
		//後半部分還是上一次讀取的內容,如果用tmp[:]就會導致
		//後半部分久內容又會被重新賦值一次,這其實是錯誤的
		content = append(content, tmp[:n]...)
		if err == io.EOF {//讀到文件末尾
			break
		}
	}
	fmt.Printf("讀取出來的內容為:\n")
	fmt.Printf("%q\n", string(content))
}

示例3: 一次性讀取文件

讀取文件內容並显示在終端,將文件內容一次性讀取到終端,適用於文件不大的情況。

package main

import (
    "fmt"
    "io/ioutil"
)

func main() {

    //打開文件,文件路徑相對於GOPATH開始,或者寫全路徑(/Users/xxx/Go/src/file.txt)
    file, err := ioutil.ReadFile("src/file.txt")
    if err != nil {
        fmt.Println("文件打開失敗,原因是:", err)
    }

    fmt.Printf("%s", string(file))
}

示例4: 帶緩衝的Reader讀文件

讀取文件的內容並显示在終端(帶緩衝區的方式),使用os.Open, file.Close,bufio.NewReader,reader.ReadString函數和方法。適合讀取大文件
1.使用ReadBytes方法
代碼1:

package main

import (
	"bufio"
	"fmt"
	"io"
	"log"
	"os"
)

func main() {

	file, err := os.Open("./test.txt")
	if err != nil {
		log.Fatalf("open file failed, err: %v\n", err)
	}
	defer func() {
		err = file.Close()
		if err != nil {
			log.Fatalf("close file failed, err: %v\n", err)
		}
	}()

	//定義變量result用來存儲讀取結果
	var result string
	//創建一個帶有緩衝區的reader
	reader := bufio.NewReader(file)
	for {
		buf, err := reader.ReadBytes('\n')
		if err != nil && err == io.EOF { //EOF代表文件的末尾
			//注意:為什麼要判斷err是否等於io.EOF?
			//因為存在這種情況,文件有內容的最後那一行尾部沒有換行
			//當使用ReadBytes或者ReadString方法按照'\n'換行讀取時,讀到尾部沒有換行的這種情況時就會報io.EOF錯誤
			//此時buf是讀取到了內容的,如果忽略掉了,那麼最終的讀取結果會少了最後一行的內容
			result += string(buf)
			break
		}
		result += string(buf)
	}
	fmt.Println(result)
}

代碼2:

package main

import (
	"bufio"
	"fmt"
	"io"
	"log"
	"os"
)

func main() {

	file, err := os.Open("./test.txt")
	if err != nil {
		log.Fatalf("open file failed, err: %v\n", err)
	}
	defer func() {
		err = file.Close()
		if err != nil {
			log.Fatalf("close file failed, err: %v\n", err)
		}
	}()

	//定義變量result用來存儲讀取結果
	var result string
	//創建一個帶有緩衝區的reader
	reader := bufio.NewReader(file)
	for {
		buf, err := reader.ReadBytes('\n')
		if err != nil {
			if err == io.EOF { //EOF代表文件的末尾
			//注意:為什麼要判斷err是否等於io.EOF?
			//因為存在這種情況,文件有內容的最後那一行尾部沒有換行
			//當使用ReadBytes或者ReadString方法按照'\n'換行讀取時,讀到尾部沒有換行的這種情況時就會報io.EOF錯誤
			//此時buf是讀取到了內容的,如果忽略掉了,那麼最終的讀取結果會少了最後一行的內容
				result += string(buf)
				break
			} else {
				log.Fatalf("ReadBytes failed, err: %v\n", err)
			}
		}
		result += string(buf)
	}
	fmt.Println(result)
}

2.ReadString方法

package main

import (
    "bufio"
    "fmt"
    "io"
    "os"
)

func main() {

    //打開文件
    file, err := os.Open("./files/test.txt")
    if err != nil {
        fmt.Println("文件打開失敗,原因是:", err)
        return
    }

    //當函數退出時,要及時的關閉file
    defer func() {
        //文件及時關閉
        err = file.Close()
        if err != nil {
            fmt.Println("文件關閉失敗,原因是", err)
        }
    }()

    //創建一個 *Reader,是帶緩衝的
    reader := bufio.NewReader(file)
    var result string
    //循環讀取文件內容
    for {
        str, err := reader.ReadString('\n') //讀到一個換行就結束
        result += str
        if err == io.EOF {//io.EOF代表文件的末尾
            //注意:如果文件最後一行文字沒有換行,則會一直讀取到文件末尾,
            //所以即使是判斷讀到了文件末尾,也要把讀取的內容輸出一下
            break
        }
    }
    fmt.Println(result)
}

示例5: 創建文件並寫入內容

第二個參數:文件代開模式(可以組合);第三個參數:權限控制(如0755)

package main

import (
	"fmt"
	"os"
)

func main() {

	//1.創建文件file.txt
	file, err := os.OpenFile("src/file.txt", os.O_WRONLY | os.O_CREATE, 0755)
	if err != nil {
		fmt.Println("文件打開/創建失敗,原因是:", err)
		return
	}

	defer func() {
		err  = file.Close()
		if err != nil {
			fmt.Println("文件關閉失敗,原因是:", err)
		}
	}()

	//寫入數據
	var str = "暗黑西遊獅駝嶺,斗戰勝佛孫悟空。\n"

	for i := 0; i < 5; i++ {
		file.WriteString(str)
	}
}

示例6: 寫文件的四種方式

1.使用WriteAt()搭配Seek()方法實現寫文件功能

package main

import (
	"io"
	"log"
	"os"
)

func main() {

	file, err := os.OpenFile("./test.txt", os.O_RDWR|os.O_CREATE, 0755)
	if err != nil {
		log.Fatalf("open file failed, err: %v\n", err)
	}
	defer func() {
		err = file.Close()
		if err != nil {
			log.Fatalf("close file failed, err: %v\n", err)
		}
	}()
    //Seek(): 修改文件的讀寫指針位置.
    //參數1: 偏移量. 正:向文件尾部偏移, 負:向文件頭部偏移
    //參數2: 偏移起始位置
    //       io.SeekStart: 文件起始位置
    //       io.SeekCurrent: 文件當前位置
    //       io.SeekEnd: 文件結尾位置
    //返回值:表示從文件起始位置,到當前文件讀寫指針位置的偏移量。
    //WriteAt(): 在文件指定偏移位置,寫入[]byte,通常搭配Seek()
    //參數1: 待寫入的數據
    //參數2: 偏移量
    //返回: 實際寫出的字節數
	for i := 0; i < 5; i++ {
		offset, _ := file.Seek(-3, io.SeekEnd)
		_, _ = file.WriteAt([]byte("你好"), offset)
	}
}

注意: 由於使用的OpenFile函數打開的文件,所以在選擇打開模式的時候不能選擇os.O_APPEND模式,因為該模式表示的是在文件末尾追加,這與WriteAt在指定的位置寫是想衝突的,雖然我在測試的時候加上os.O_APPEND模式並沒有報錯,但是代碼執行完之後發現,想要寫入的內容並沒有真正的寫入到文件中。
寫入前

寫入后

2.一次性寫文件

package main

import (
	"io/ioutil"
	"log"
)

func main() {
	str := "hello樹先生"
	//如果文件已存在,則會清空原來的內容,寫入新內容,如果文件不存在,則會創建文件並寫入內容
	err := ioutil.WriteFile("./test.txt", []byte(str), 0755)
	if err != nil {
		log.Fatalf("寫入文件錯誤,錯誤為:%v\n", err)
	}
}

3.使用帶緩衝的方式寫文件

package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {

	//1.創建文件file.txt
	file, err := os.OpenFile("src/file.txt", os.O_WRONLY | os.O_CREATE | os.O_TRUNC, 0755)
	defer func() {
		err  = file.Close()
		if err != nil {
			fmt.Println("文件關閉失敗,原因是:", err)
		}
	}()

	if err != nil {
		fmt.Println("文件創建失敗,原因是:", err)
		return
	}

	//寫入數據
	var str = "你好,世界\n"

	//寫入時,使用帶緩存的*Writer
	writer := bufio.NewWriter(file)

	for i := 0; i < 5; i++ {
		writer.WriteString(str)
	}

	//因為writer是帶緩存的,因此在調用writeString方法時,其實內容是先寫入到緩存
	//因此需要調用Flush方法,將緩存數據寫入到文件中,否則文件中會丟失數據
	writer.Flush()
}

示例7: 把一個文件內容寫入到另一個文件

package main

import (
    "fmt"
    "io/ioutil"
)

func main() {

    //打開文件,文件路徑相對於GOPATH開始,或者寫全路徑(/Users/xxx/Go/src/file.txt)
    data, err := ioutil.ReadFile("src/1.txt")
    if err != nil {
        fmt.Println("文件打開失敗,原因是:", err)
    }

    err = ioutil.WriteFile("src/2.txt", data, 0755)

    if err != nil {
        fmt.Println("文件寫入失敗,原因是:", err)
    }
}

示例8:使用bufio獲取用戶輸入

package main

import (
    "bufio"
    "fmt"
    "os"
)

func main() {
    var s string
    var reader = bufio.NewReader(os.Stdin)
    s, _ = reader.ReadString('\n')
    fmt.Printf("讀取到的內容為:%s\n", s)
}

示例9: 判斷文件或目錄是否存在

Go判斷文件或文件夾是否存在的方法為使用os.Stat()函數返回的錯誤值進行判斷:
(1)如果返回的錯誤為nil,說明文件或文件夾存在
(2)如果返回的類型使用os.IsNotExist()判斷為true,說明文件或文件夾不存在
(3)如果返回的錯誤為其它類型,則不確定是否存在

package main

import (
	"fmt"
	"os"
)

func main() {

	isExist, err := isFileExists("src/sfile.txt")
	if err != nil {
		fmt.Println("發生錯誤:", err)
	}

	if isExist {
		fmt.Println("存在")
	} else {
		fmt.Println("不存在")
	}
}

//判斷文件或者目錄是否存在
func isFileExists(path string) (bool, error) {

	_, err := os.Stat(path)
	if err == nil {
		return true, nil
	}
	if os.IsNotExist(err) {
		return false, nil
	}
	return false, err
}

示例10: 拷貝文件、圖片音視頻

io.Copy方法

package main

import (
	"fmt"
	"io"
	"os"
)
func CopyFile(srcFileName string, dstFileName string) (int64, error) {

	//源文件處理
	srcFile, err := os.Open(srcFileName)
	defer func() {
		err = srcFile.Close()
		if err != nil {
			fmt.Println("源文件關閉失敗,原因是:", err)
		}
	}()

	if err != nil {
		fmt.Println("源文件打開失敗,原因是:", err)
		return 0, err
	}

	//目標文件處理
	dstFile, err := os.OpenFile(dstFileName, os.O_CREATE | os.O_WRONLY, 0755)
	defer func() {
		err = dstFile.Close()
		if err != nil {
			fmt.Println("目標文件關閉失敗,原因是:", err)
		}
	}()
	if err != nil {
		fmt.Println("目標文件打開失敗,原因是:", err)
		return 0, err
	}

	return io.Copy(dstFile, srcFile)
}

func main() {

	result, err := CopyFile("src/dst.jpeg", "src/哈哈.jpeg")

	if err == nil {
		fmt.Println("拷貝成功!拷貝的字節數為: ", result)
	}
}

對於大文件,我們還可以採用下面的方式

package main

import (
	"io"
	"log"
	"os"
)

func CopyFile(srcFileName string, dstFileName string) {
	//打開源文件
	srcFile, err := os.Open(srcFileName)
	if err != nil {
		log.Fatalf("源文件讀取失敗,原因是:%v\n", err)
	}
	defer func() {
		err = srcFile.Close()
		if err != nil {
			log.Fatalf("源文件關閉失敗,原因是:%v\n", err)
		}
	}()

	//創建目標文件,稍後會向這個目標文件寫入拷貝內容
	distFile, err := os.Create(dstFileName)
	if err != nil {
		log.Fatalf("目標文件創建失敗,原因是:%v\n", err)
	}
	defer func() {
		err = distFile.Close()
		if err != nil {
			log.Fatalf("目標文件關閉失敗,原因是:%v\n", err)
		}
	}()
	//定義指定長度的字節切片,每次最多讀取指定長度
	var tmp = make([]byte, 1024*4)
	//循環讀取並寫入
	for {
		n, err := srcFile.Read(tmp)
		n, _ = distFile.Write(tmp[:n])
		if err != nil {
			if err == io.EOF {//讀到了文件末尾,並且寫入完畢,任務完成返回(關閉文件的操作由defer來完成)
				return
			} else {
				log.Fatalf("拷貝過程中發生錯誤,錯誤原因為:%v\n", err)
			}
		}
	}
}

func main() {
	CopyFile("./worm.mp4", "./dist.mp4")
}

示例11: 遍歷目錄

遍歷目錄

package main

//我們讀寫的文件一般存放於目錄中.因此,有時需要指定到某一個目錄下,根據目錄存儲的狀況
//再進行文件的特定操作.接下來我們看看目錄的基本操作方法.
import (
	"fmt"
	"log"
	"os"
)
//打開目錄
//打開目錄我們也使用OpenFile函數,但要指定不同的參數來通知系統,要打開的是一個目錄文件.
//func OpenFile(name string, flag int, perm FileMode) (file *File, err error)
//參數1: name,表示要打開的目錄名稱.使用絕對路徑較多
//參數2: flag,表示打開文件的讀寫模式
//參數3: perm,表示打開權限.但對於目錄來說有所不同,通常傳os.ModeDir.
//返回值:由於是操作目錄,所以file是指向目錄的文件指針.err中保存錯誤信息

//讀目錄內容
//這與讀文件有所不同.目錄中存放的是文件名和子目錄名.所以使用Readdir函數
//func (f *File) Readdir(n int) (fi []FileInfo, err error)
//如果n>0,Readdir函數會返回一個最多n個成員的切片。這時,如果Readdir返回一個空切片,
//它會返回一個非nil的錯誤說明原因。如果到達了目錄f的結尾,返回值err會是io.EOF。
//
//如果n<=0,Readdir函數返回目錄中剩餘所有文件對象的FileInfo構成的切片。
//此時,如果Readdir調用成功(讀取所有內容直到結尾),它會返回該切片和nil的錯誤值。
//如果在到達結尾前遇到錯誤,會返回之前成功讀取的FileInfo構成的切片和該錯誤。

func main() {
	//不推薦,因為通過查看ioutil.ReadDir()函數可知,官方使用的是os.Open()函數打開的目錄
	//file, err := os.OpenFile("./dir", os.O_RDWR, os.ModeDir)
	file, err := os.Open("./dir")
	if err != nil {
		log.Fatalf("文件打開失敗,原因是:%v\n", err)
	}
	defer func() {
		err = file.Close()
		if err != nil {
			log.Fatalf("文件關閉失敗,原因是:%v\n", err)
		}
	}()
	//Readdir方法返回一個FileInfo接口類型的切片和一個error類型的錯誤
	infos, err := file.Readdir(-1)
	for _, info := range infos {
		fmt.Printf("%v, %v\n", info.Name(), info.IsDir())
	}
}

僅遍歷目錄,忽略文件

方法1:使用os包

package main

import (
    "fmt"
    "os"
)

var dirNames = make([]string, 0, 50)
var pathSeparator = string(os.PathSeparator)
func traverseDir(filePath string) error {
    file, err := os.Open(filePath)
    if err != nil {
        return err
    }
    fileInfo, err := file.Readdir(0)
    if err != nil {
        return err
    }

    for _, value := range fileInfo {
        if value.IsDir() {
            dirNames = append(dirNames, value.Name())
            err = traverseDir(filePath+pathSeparator+value.Name())
            if err != nil {
                return err
            }
        }
    }
    return err
}

func main() {

    var filePath = "./dir"
    err := traverseDir(filePath)
    if err != nil {
        fmt.Println(err)
    }
    fmt.Println(dirNames)
}

方法2:使用ioutil包

package main

import (
    "fmt"
    "io/ioutil"
    "os"
)

var dirNames = make([]string, 0, 50)
var pathSeparator = string(os.PathSeparator)
func traverseDir(filePath string) error {
    fileInfos, err := ioutil.ReadDir(filePath)
    if err != nil {
        return err
    }
    for _, fileInfo :=range fileInfos {
        if fileInfo.IsDir() {
            dirNames = append(dirNames, fileInfo.Name())
            err =  traverseDir(filePath+pathSeparator+fileInfo.Name())
            if err != nil {
                return err
            }
        }
    }
    return err
}

func main() {

    var filePath = "./dir"
    err := traverseDir(filePath)
    if err != nil {
        fmt.Println(err)
    }
    fmt.Println(dirNames)
}

示例12: 修改文件名

package main

import (
    "fmt"
    "io/ioutil"
    "os"
    "strings"
)

var pathSeparator = string(os.PathSeparator)
//重命名文件
func renameFileName(filePath string, old string, new string) error {
    files, err := ioutil.ReadDir(filePath)
    if err != nil {
        return err
    }
    for _, fileInfo := range files {
        if !fileInfo.IsDir() {
            err = os.Rename(filePath + pathSeparator + fileInfo.Name(),
                filePath + pathSeparator + strings.Replace(fileInfo.Name(), old, new, -1),
            )
            if err != nil {
                return err
            }
        }
    }
    return err
}

func main() {
    var filePath = "./dir"
    err := renameFileName(filePath, "f", "kkk")
    if err != nil {
        fmt.Printf("錯誤: %v\n", err)
    }
}

示例13:創建目錄

package main

import (
	"fmt"
	"os"
)

func main() {
	//Mkdir使用指定的權限和名稱創建一個目錄。如果出錯,會返回*PathError底層類型的錯誤。
	err := os.Mkdir("./foo", 0755)
	if os.IsExist(err) {
		fmt.Println("目錄已存在")
		return
	}

	//MkdirAll使用指定的權限和名稱創建一個目錄,包括任何必要的上級目錄,並返回nil,否則返回錯誤。
	//權限位perm會應用在每一個被本函數創建的目錄上。如果path指定了一個已經存在的目錄,MkdirAll不做任何操作並返回nil。
	err = os.MkdirAll("./foo/bar", 0755)
	if err != nil {
		fmt.Printf("%v\n", err)
		return
	}
}

示例14:刪除文件

package main

import (
	"fmt"
	"os"
)

func main() {
	//Remove刪除name指定的文件或目錄。如果出錯,會返回*PathError底層類型的錯誤。
	//該方法不能刪除非空目錄,如果想刪除目錄以及目錄下的所有文件,可以使用RemoveAll
	err := os.Remove("./def")
	if os.IsNotExist(err) {
		fmt.Println("您要刪除的文件或目錄不存在")
		return
	}
	if err != nil {
		fmt.Println(err)
	}

	//RemoveAll刪除path指定的文件,或目錄及它包含的任何下級對象。
	//它會嘗試刪除所有東西,除非遇到錯誤並返回。
	//如果path指定的對象不存在,RemoveAll會返回nil而不返回錯誤。
	err = os.RemoveAll("./def")
	if err != nil {
		fmt.Println(err)
	}
}

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

【其他文章推薦】

網頁設計最專業,超強功能平台可客製化

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※回頭車貨運收費標準

※推薦評價好的iphone維修中心

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

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

台中搬家公司費用怎麼算?

【原創】Linux中斷子系統(二)-通用框架處理

背景

  • Read the fucking source code! –By 魯迅
  • A picture is worth a thousand words. –By 高爾基

說明:

  1. Kernel版本:4.14
  2. ARM64處理器,Contex-A53,雙核
  3. 使用工具:Source Insight 3.5, Visio

1. 概述

【原創】Linux中斷子系統(一)-中斷控制器及驅動分析講到了底層硬件GIC驅動,以及Arch-Specific的中斷代碼,本文將研究下通用的中斷處理的過程,屬於硬件無關層。當然,我還是建議你看一下上篇文章。

這篇文章會解答兩個問題:

  1. 用戶是怎麼使用中斷的(中斷註冊)?
  2. 外設觸發中斷信號時,最終是怎麼調用到中斷handler的(中斷處理)?

2. 數據結構分析

先來看一下總的數據結構,核心是圍繞着struct irq_desc來展開:

  • Linux內核的中斷處理,圍繞着中斷描述符結構struct irq_desc展開,內核提供了兩種中斷描述符組織形式:

    1. 打開CONFIG_SPARSE_IRQ宏(中斷編號不連續),中斷描述符以radix-tree來組織,用戶在初始化時進行動態分配,然後再插入radix-tree中;
    2. 關閉CONFIG_SPARSE_IRQ宏(中斷編號連續),中斷描述符以數組的形式組織,並且已經分配好;
    3. 不管哪種形式,最終都可以通過linux irq號來找到對應的中斷描述符;
  • 圖的左側灰色部分,主要在中斷控制器驅動中進行初始化設置,包括各個結構中函數指針的指向等,其中struct irq_chip用於對中斷控制器的硬件操作,struct irq_domain與中斷控制器對應,完成的工作是硬件中斷號到Linux irq的映射;

  • 圖的上側灰色部分,中斷描述符的創建(這裏指CONFIG_SPARSE_IRQ),主要在獲取設備中斷信息的過程中完成的,從而讓設備樹中的中斷能與具體的中斷描述符irq_desc匹配;

  • 圖中剩餘部分,在設備申請註冊中斷的過程中進行設置,比如struct irqactionhandler的設置,這個用於指向我們設備驅動程序中的中斷處理函數了;

中斷的處理主要有以下幾個功能模塊:

  1. 硬件中斷號到Linux irq中斷號的映射,並創建好irq_desc中斷描述符;
  2. 中斷註冊時,先獲取設備的中斷號,根據中斷號找到對應的irq_desc,並將設備的中斷處理函數添加到irq_desc中;
  3. 設備觸發中斷信號時,根據硬件中斷號得到Linux irq中斷號,找到對應的irq_desc,最終調用到設備的中斷處理函數;

上述的描述比較簡單,更詳細的過程,往下看吧。

3. 流程分析

3.1 中斷註冊

這一次,讓我們以問題的方式來展開:
先來讓我們回答第一個問題:用戶是怎麼使用中斷的?

  1. 熟悉設備驅動的同學應該都清楚,經常會在驅動程序中調用request_irq()接口或者request_threaded_irq()接口來註冊設備的中斷處理函數;
  2. request_irq()/request_threaded_irq接口中,都需要用到irq,也就是中斷號,那麼這个中斷號是從哪裡來的呢?它是Linux irq,它又是如何映射到具體的硬件設備的中斷號的呢?

先來看第二個問題:設備硬件中斷號到Linux irq中斷號的映射

  • 硬件設備的中斷信息都在設備樹device tree中進行了描述,在系統啟動過程中,這些信息都已經加載到內存中並得到了解析;
  • 驅動中通常會使用platform_get_irqirq_of_parse_and_map接口,去根據設備樹的信息去創建映射關係(硬件中斷號到linux irq中斷號映射);
  • 【原創】Linux中斷子系統(一)-中斷控制器及驅動分析提到過struct irq_domain用於完成映射工作,因此在irq_create_fwspec_mapping接口中,會先去找到匹配的irq domain,再去回調該irq domain中的函數集,通常irq domain都是在中斷控制器驅動中初始化的,以ARM GICv2為例,最終回調到gic_irq_domain_hierarchy_ops中的函數;
  • 如果已經創建好了映射,那麼可以直接進行返回linux irq中斷號了,否則的話需要irq_domain_alloc_irqs來創建映射關係;
  • irq_domain_alloc_irqs完成兩個工作:
    1. 針對linux irq中斷號創建一個irq_desc中斷描述符;
    2. 調用domain->ops->alloc函數來完成映射,在ARM GICv2驅動中對應gic_irq_domain_alloc函數,這個函數很關鍵,所以下文介紹一下;

gic_irq_domain_alloc函數如下:

  • gic_irq_domain_translate:負責解析出設備樹中描述的中斷號和中斷觸發類型(邊緣觸發、電平觸發等);
  • gic_irq_domain_map:將硬件中斷號和linux中斷號綁定到一個結構中,也就完成了映射,此外還綁定了irq_desc結構中的其他字段,最重要的是設置了irq_desc->handle_irq的函數指針,這個最終是中斷響應時往上執行的入口,這個是關鍵,下文講述中斷處理過程時還會提到;
  • 根據硬件中斷號的範圍設置irq_desc->handle_irq的指針,共享中斷入口為handle_fasteoi_irq,私有中斷入口為handle_percpu_devid_irq

上述函數執行完成后,完成了兩大工作:

  1. 硬件中斷號與Linux中斷號完成映射,併為Linux中斷號創建了irq_desc中斷描述符;
  2. 數據結構的綁定及初始化,關鍵的地方是設置了中斷處理往上執行的入口;

再看第一個問題:中斷是怎麼來註冊的?

設備驅動中,獲取到了irq中斷號后,通常就會採用request_irq/request_threaded_irq來註冊中斷,其中request_irq用於註冊普通處理的中斷,request_threaded_irq用於註冊線程化處理的中斷;

在講具體的註冊流程前,先看一下主要的中斷標誌位:

#define IRQF_SHARED		0x00000080              //多個設備共享一个中斷號,需要外設硬件支持
#define IRQF_PROBE_SHARED	0x00000100              //中斷處理程序允許sharing mismatch發生
#define __IRQF_TIMER		0x00000200              //時鐘中斷
#define IRQF_PERCPU		0x00000400              //屬於特定CPU的中斷
#define IRQF_NOBALANCING	0x00000800              //禁止在CPU之間進行中斷均衡處理
#define IRQF_IRQPOLL		0x00001000              //中斷被用作輪訓
#define IRQF_ONESHOT		0x00002000              //一次性觸發的中斷,不能嵌套,1)在硬件中斷處理完成后才能打開中斷;2)在中斷線程化中保持關閉狀態,直到該中斷源上的所有thread_fn函數都執行完
#define IRQF_NO_SUSPEND		0x00004000              //系統休眠喚醒操作中,不關閉該中斷
#define IRQF_FORCE_RESUME	0x00008000              //系統喚醒過程中必須強制打開該中斷
#define IRQF_NO_THREAD		0x00010000              //禁止中斷線程化
#define IRQF_EARLY_RESUME	0x00020000              //系統喚醒過程中在syscore階段resume,而不用等到設備resume階段
#define IRQF_COND_SUSPEND	0x00040000              //與NO_SUSPEND的用戶共享中斷時,執行本設備的中斷處理函數

  • request_irq也是調用request_threaded_irq,只是在傳參的時候,線程處理函數thread_fn函數設置成NULL;
  • 由於在硬件中斷號和Linux中斷號完成映射后,irq_desc已經創建好,可以通過irq_to_desc接口去獲取對應的irq_desc
  • 創建irqaction,並初始化該結構體中的各個字段,其中包括傳入的中斷處理函數賦值給對應的字段;
  • __setup_irq用於完成中斷的相關設置,包括中斷線程化的處理:
    1. 中斷線程化用於減少系統關中斷的時間,增強系統的實時性;
    2. ARM64默認開啟了CONFIG_IRQ_FORCED_THREADING,引導參數傳入threadirqs時,則除了IRQF_NO_THREAD外的中斷,其他的都將強制線程化處理;
    3. 中斷線程化會為每个中斷都創建一個內核線程,如果中斷進行共享,對應irqaction將連接成鏈表,每個irqaction都有thread_mask位圖字段,當所有共享中斷都處理完成后才能unmask中斷,解除中斷屏蔽;

3.2 中斷處理

當完成中斷的註冊后,所有結構的組織關係都已經建立好,剩下的工作就是當信號來臨時,進行中斷的處理工作。

來回顧一下【原創】Linux中斷子系統(一)-中斷控制器及驅動分析中的Arch-specific處理流程:

  • 中斷收到之後,首先會跳轉到異常向量表的入口處,進而逐級進行回調處理,最終調用到generic_handle_irq來進行中斷處理。

generic_handle_irq處理如下圖:

  • generic_handle_irq函數最終會調用到desc->handle_irq(),這個也就是對應到上文中在建立映射關係的過程中,調用irq_domain_set_info函數,設置好了函數指針,也就是handle_fasteoi_irqhandle_percpu_devid_irq
  • handle_fasteoi_irq:處理共享中斷,並且遍歷irqaction鏈表,逐個調用action->handler()函數,這個函數正是設備驅動程序調用request_irq/request_threaded_irq接口註冊的中斷處理函數,此外如果中斷線程化處理的話,還會調用__irq_wake_thread()喚醒內核線程;
  • handle_percpu_devid_irq:處理per-CPU中斷處理,在這個過程中會分別調用中斷控制器的處理函數進行硬件操作,該函數調用action->handler()來進行中斷處理;

來看看中斷線程化處理后的喚醒流程吧__handle_irq_event_percpu->__irq_wake_thread

  • __handle_irq_event_percpu->__irq_wake_thread將喚醒irq_thread中斷內核線程;
  • irq_thread內核線程,將根據是否為強制中斷線程化對函數指針handler_fn進行初始化,以便後續進行調用;
  • irq_thread內核線程將while(!irq_wait_for_interrupt)循環進行中斷的處理,當滿足條件時,執行handler_fn,在該函數中最終調用action->thread_fn,也就是完成了中斷的處理;
  • irq_wait_for_interrupt函數,將會判斷中斷線程的喚醒條件,如果滿足了,則將當前任務設置成TASK_RUNNING狀態,並返回0,這樣就能執行中斷的處理,否則就調用schedule()進行調度,讓出CPU,並將任務設置成TASK_INTERRUPTIBLE可中斷睡眠狀態;

3.3 總結

中斷的處理,總體來說可以分為兩部分來看:

  1. 從上到下:圍繞irq_desc中斷描述符建立好連接關係,這個過程就包括:中斷源信息的解析(設備樹),硬件中斷號到Linux中斷號的映射關係、irq_desc結構的分配及初始化(內部各個結構的組織關係)、中斷的註冊(填充irq_desc結構,包括handler處理函數)等,總而言之,就是完成靜態關係創建,為中斷處理做好準備;
  2. 從下到上,當外設觸發中斷信號時,中斷控制器接收到信號併發送到處理器,此時處理器進行異常模式切換,並逐步從處理器架構相關代碼逐級回調。如果涉及到中斷線程化,則還需要進行中斷內核線程的喚醒操作,最終完成中斷處理函數的執行。

歡迎關注個人公眾號,不定期分享Linux內核機制文章

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

【其他文章推薦】

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※回頭車貨運收費標準

※推薦評價好的iphone維修中心

※超省錢租車方案

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

※推薦台中搬家公司優質服務,可到府估價

為.netcore助力–WebApiClient正式發布core版本

1 前言

WebApiClient已成熟穩定,發布了WebApiClient.JIT和WebApiClient.AOT兩個nuget包,累計近10w次下載。我對它的高可擴展性設計相當滿意和自豪,但WebApiClient並不因此而停下腳步,在一年前,我產生了編寫其core版本的想法,將asp.netcore服務端先進的思想融入到core版本,在性能與擴展性上得到進一步升華。
對應的,給它叫了WebApiClientCore的名字,為了對得起名字裏面的Core字,我在框架設計、性能優化上佔用整體開發時間一半以上。

2 框架設計

IActionInvoker

WebApiClient時還沒有IActionInvoker概念,對應的執行邏輯直接在ApiActionContext上實現。現在我覺得,Context應該是一個狀態數據類,而不能也成為一個執行者,因為一個執行者的實例可以無限次地執行多個Context實例。

Refit則更簡單粗暴,將所有實現都在一個RequestBuilderImplementation的類上:你們只要也只能使用我內置的Attribute聲明,一切執行在我這個類裡面包辦,因為我是一個萬能類。

Core版本增加了IActionInvoker概念,從中Context分開,用於執行Context,職責分明。在實現上又分為多種Invoker:Task聲明返回執行者ActionInvoker、ITask聲明返回處理處理者ActionTask,以及聚合的MultiplexedActionInvoker。

Middleware思想

WebApiClient時在處理各個特性、參數驗證、返回值驗證時沒有使用Middleware思想,特別是在處理響應結果和異常短路邏輯難以維護。

Refit還是簡單粗暴,將所有特性的解釋實現都在這個RequestBuilderImplementation的類上,因為我還是一個萬能類。

Core版本增加中間件Builder,將請求前的相關Attribute的執行編排Build為一個請求處理委託,將請求后相關Attribute的執行編排Build為一個響應處理委託,然後把兩個委託與真實http請求串在一起,Build出一個完整的請求響應委託。

得益於Middleware,流程中的請求前參數值驗證、結果處理特性短路、異常短路、請求后結果值驗和無條件執行IApiFilterAtrribue等這些複雜的流程變成簡單的管道處理;另外接口也變成支持服務端響應多種格式內容,每種格式內容在一個IApiReturnAttribute上實現和處理,比如請求為Accept: application/json, application/xml,不管服務器返回xml或json都能處理。

/// <summary>
/// 創建執行委託
/// </summary>
/// <param name="apiAction">action描述器</param>
/// <returns></returns>
public static Func<ApiRequestContext, Task<ApiResponseContext>> Build(ApiActionDescriptor apiAction)
{
    var requestHandler = BuildRequestHandler(apiAction);
    var responseHandler = BuildResponseHandler(apiAction);

    return async request =>
    {
        await requestHandler(request).ConfigureAwait(false);
        var response = await HttpRequest.SendAsync(request).ConfigureAwait(false);
        await responseHandler(response).ConfigureAwait(false);
        return response;
    };
}

Context思想

WebApiClient只有一個ApiActionContext,其Result和Exception屬性在請求前就可以訪問或設置,但實際上就算設置了值,流程也不會短路和中斷,屬於設計失誤。

Refit沒有相關Context概念,因為它不提供給用戶自定義擴展Attribute的能力,它內置的Attribute也沒有執行能力,一個RequestBuilderImplementation類夠了。

Core版本將設計了多個Context概念,不同階段有不同的Context,如同asp.netcore不同Filter的Context也不同一樣。對於一個Action,請求階段對應是ApiRequestContext,響應階段是ApiResponseContext;對於Action的參數,對應是ApiParameterContext。每種Context裏面都包含核心的HttpContext屬性,HttpContext包含請求消息、響應消息和接口配置選項等。

Interface思想

輸入WebApiClientCore命名空間,會發現定義了很多Interface,這些Interface都是為了用戶實現自定義特性用的,當然內置的所有特性,都是實現了這些接口而已。如果一個特性實現了多個接口,它就有多種能力,比如內置的HeaderAttribute,它既可以修飾於Interface和Method,也可以修飾參數。

WebApiClientCore的Attribute描述的邏輯,是由Attribute自我實現,所以整個請求的數據裝配邏輯是分散為各個Attribute上,用什麼Attribute就有什麼邏輯,包含框架之外的非內置的自定義Attribute。

Refit的內置Attribute只有欲描述邏輯,沒有實現邏輯,實現邏輯由RequestBuilderImplementation包辦,所以它不需要接口也沒有接口。

3 性能優化

更快的字符串替換

像[HttpGet(“objects/{id}”)]這樣的path參數,在restful中常常遇到,通過Span優化,Core版本在替換path參數值cpu佔用時間降低為原版本的十分之一。

更快的json序列化

得益於Sytem.Text.Json,json序列化和反序列化上性能顯明提升。

更少的緩衝區分配

WebApiClientCore使用了可回收復用的IBufferWriter,在json序列化得到json、json裝配為HttpContent只申請一次Buffer,而且HttpContent在發送之後,這個Buffer被回收復用。IBufferWriter還於用表單的uri編碼,編碼產生的Buffer不用申請新的內存內容,直接寫入表單的HttpContent。

更少的編碼操作

WebApiClientCore的json不再使用utf16的string中間類型,直接將對象序列化為網絡請求需要的utf8編碼二進制json;表單的key和Value編碼時,也不產生string中間類型,而是編碼后的二進制數據內容,然後寫入表單的IBufferWriter。

更快的緩存查找

WebApiClient創建代理類實例來執行一個請求時,要查找兩次緩存:通過接口類型查找字典緩存的接口代理類,然後實例化代理類;在ApiInterceptor裏面通過MethodInfo查找字典緩存的ApiActionDescriptor。

Refit執行同樣邏輯也使用了兩次字典緩存,接口和接口代理類安全字典緩存TypeMapping,接口和接口方法描述的字典緩存interfaceHttpMethods。

WebApiClientCore取消了字典緩存,使用靜態泛型類的字段作緩存,因為字段訪問遠比字典查找高效。同時通過巧妙的設計,在代理類攔截方法執行時,直接回傳IActionInvoker替換原來的MethodInfo,IActionInvoker包含了ApiActionDescriptor,而IActionInvoker與代理類型都一起緩存在靜態泛型類的字段,減少了一次必須的字典緩存查找過程。

性能對比

排除掉真實的網絡請求IO等待時間,WebApiClientCore使用的cpu時間僅僅為WebApiClient.JIT和Refit的三分之一。

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.18362.836 (1903/May2019Update/19H1)
Intel Core i3-4150 CPU 3.50GHz (Haswell), 1 CPU, 4 logical and 2 physical cores
.NET Core SDK=3.1.202
  [Host]     : .NET Core 3.1.4 (CoreCLR 4.700.20.20201, CoreFX 4.700.20.22101), X64 RyuJIT
  DefaultJob : .NET Core 3.1.4 (CoreCLR 4.700.20.20201, CoreFX 4.700.20.22101), X64 RyuJIT
Method Mean Error StdDev
HttpClient_GetAsync 3.146 μs 0.0396 μs 0.0370 μs
WebApiClientCore_GetAsync 12.421 μs 0.2324 μs 0.2174 μs
Refit_GetAsync 43.241 μs 0.6713 μs 0.6279 μs
Method Mean Error StdDev
HttpClient_PostJsonAsync 5.263 μs 0.0784 μs 0.0733 μs
WebApiClientCore_PostJsonAsync 13.823 μs 0.1874 μs 0.1753 μs
Refit_PostJsonAsync 45.218 μs 0.8166 μs 0.7639 μs

4 Nuget包與文檔

Nuget包

<PackageReference Include="WebApiClientCore" Version="1.0.*" />

項目地址與文檔

點擊項目鏈接,帶你GET到N種使用技能,不求star,只求提供良好建議。

https://github.com/dotnetcore/WebApiClient

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

【其他文章推薦】

※回頭車貨運收費標準

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※推薦評價好的iphone維修中心

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

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

循序漸進VUE+Element 前端應用開發(8)— 樹列表組件的使用,循序漸進VUE+Element 前端應用開發(5)— 表格列表頁面的查詢,列表展示和字段轉義處理,

在我前面隨筆《循序漸進VUE+Element 前端應用開發(6)— 常規Element 界面組件的使用》裏面曾經介紹過一些常規的界面組件的處理,主要介紹到單文本輸入框、多文本框、下拉列表,以及按鈕、圖片展示、彈出對話框、表單處理,本篇隨筆補充這一個主題,介紹樹列表組件和下拉列表樹組件在項目中的使用,以及一個SplitPanel的組件。

1、常規樹列表控件的使用

眾所周知,一般界面很多情況涉及到樹列表的處理,如類型展示,如果是一層的,可以用下拉列表代替,如果是多個層級的,採用樹控件展示會更加直觀。

在Element裏面也有一個el-tree的控件,如下所示,這裏主要對它的各種屬性和方法進行介紹。

 簡單的代碼如下所示

<el-tree :data="data"  @node-click="handleNodeClick"></el-tree>

主要在script部分裏面指定它的data數據,以及單擊節點的事件處理,結合卡片控件的展示,我們可以把樹放在其中進行展示

 界面代碼如下所示,通過 default-expand-all 可以設置全部展開,icon-class 指定節點圖標(也可以默認不指定)

        <el-card class="box-card">
          <div slot="header" class="clearfix">
            <span>樹列表</span>
            <el-button style="float: right; padding: 3px 0" type="text">操作按鈕</el-button>
          </div>
          <div>
            <el-tree
              style="padding-top:10px"
              :data="treedata"
              node-key="id"
              default-expand-all
              icon-class="el-icon-price-tag"
              highlight-current
              @node-click="handleNodeClick"
            >
              <span slot-scope="{ node, data }" class="custom-tree-node">
                <span>
                  <i :class="node.icon ? node.icon : 'el-icon-price-tag'" />
                  {{ node.label }}
               &nbsp;&nbsp;
                </span>
              </span>
            </el-tree>
          </div>
        </el-card>

其中界面裏面,我們通過 class=”custom-tree-node”,來指定樹列表的展現內容,可以加入圖標等信息

而在script裏面,定義了一個treedata的屬性

      // 初始化樹列表
      treedata: [
        {
          label: '一級 1',
          id: '1',
          children: [{
            id: '1-1',
            label: '二級 1-1',
            children: [{
              label: '三級 1-1-1',
              id: '1-1-1'
            }, {
              label: '三級 1-1-2',
              id: '1-1-2'
            }, {
              label: '三級 1-1-3',
              id: '1-1-3'
            }]
          }]
        }
      ]

如果設置有選擇框,得到界面如下所示。

 主要設置  show-checkbox 和 @check-change=”handleCheckChange” 即可。

界面代碼如下所示

<el-tree
  style="padding-top:10px"
  :data="treedata"
  node-key="id"
  default-expand-all
  highlight-current
  show-checkbox
  :default-checked-keys="['1-1-1']"
  @node-click="handleNodeClick" @check-change="handleCheckChange"
>
  <span slot-scope="{ node, data }" class="custom-tree-node">
    <span>
      <i :class="node.icon ? node.icon : 'el-icon-price-tag'" />
      {{ node.label }}
   &nbsp;&nbsp;
    </span>
  </span>
</el-tree>

而對於樹列表,可以進行一個過濾處理操作,如下界面所示。

 在內容區增加一個input的文本框進行過濾處理,並綁定對應的屬性變量

<el-input
  v-model="filterText"
  placeholder="輸入關鍵字進行過濾"
  clearable
  prefix-icon="el-icon-search"
/>

樹列表控件需要增加過濾函數綁定 :filter-node-method=”filterNode”,如下代碼所示。

<el-tree
  ref="tree"
  class="filter-tree"
  style="padding-top:10px"
  :data="treedata"
  node-key="id"
  default-expand-all
  highlight-current
  show-checkbox
  :filter-node-method="filterNode"
  @check-change="handleCheckChange"
  @node-click="handleNodeClick"
>
  <span slot-scope="{ node, data }" class="custom-tree-node">
    <span>
      <i :class="node.icon ? node.icon : 'el-icon-price-tag'" />
      {{ node.label }}
   &nbsp;&nbsp;
    </span>
  </span>
</el-tree>

script的處理代碼如下所示,需要watch過濾的綁定值,變化就進行過濾處理。

 為了在列表結合中進行快速的過濾,我們可以在上次介紹的列表界面裏面增加一個樹列表的快速查詢處理。如下界面所示。

 這裏列表裡面增加了一個第三方組件 splitpanes,用來劃分區塊展示,而且可以拖動,非常不錯,地址是:https://github.com/antoniandre/splitpanes

這個組件的Demo展示地址如下所示:https://antoniandre.github.io/splitpanes

效果大概如下所示

  npm 安裝如下所示

npm i --S splitpanes

安裝成功后,然後在vue文件的script部分裏面引入即可

import { Splitpanes, Pane } from 'splitpanes'
import 'splitpanes/dist/splitpanes.css'

它的使用代碼也很簡單

<splitpanes style="height: 400px">
  <pane min-size="20">1</pane>
  <pane>
    <splitpanes horizontal>
      <pane>2</pane>
      <pane>3</pane>
      <pane>4<pane>
    </splitpanes>
  </pane>
  <pane>5</pane>
</splitpanes>

我的列表界面使用了兩個Panel即可實現左側樹的展示,和右側常規列表查詢的處理。

  

2、下拉框樹列表的處理

除了常規的樹列表展示內容外,我們也需要一個在下拉列表中展示樹內容的界面組件。

這裏又得引入一個第三方的界面組件,因此Element的Select組件不支持樹列表。

GitHub地址:https://github.com/riophae/vue-treeselect

官網地址:https://vue-treeselect.js.org/

NPM安裝:

npm install --save @riophae/vue-treeselect

界面代碼如下所示。

<template>
  <div id="app">
    <treeselect v-model="value" :multiple="true" :options="options" />
  </div>
</template>

這裏的value就是選中的集合,options則是樹列表的節點數據。

<script>
  // import the component
  import Treeselect from '@riophae/vue-treeselect'
  // import the styles
  import '@riophae/vue-treeselect/dist/vue-treeselect.css'

  export default {
    // register the component
    components: { Treeselect },
    data() {
      return {
        // define the default value
        value: null,
        // define options
        options: [ {
          id: 'a',
          label: 'a',
          children: [ {
            id: 'aa',
            label: 'aa',
          }, {
            id: 'ab',
            label: 'ab',
          } ],
        }, {
          id: 'b',
          label: 'b',
        }, {
          id: 'c',
          label: 'c',
        } ],
      }
    },
  }
</script>

我的測試界面代碼如下所示

          <div style="height:180px">
            <!--
                v-model 綁定選中的集合
                options 樹節點數據
                 defaultExpandLevel 展開層次,Infinity為所有
                 flat 為子節點不影響父節點,不關聯
             -->
            <treeselect
              v-model="value"
              :options="treedata"
              :multiple="true"
              :flat="true"
              :default-expand-level="Infinity"
              :open-on-click="true"
              :open-on-focus="true"
              clearable
              :max-height="200"
            />
          </div>
<script>
// import vue-treeselect component
import Treeselect from '@riophae/vue-treeselect'
// import the styles
import '@riophae/vue-treeselect/dist/vue-treeselect.css'

export default {
  name: 'Tree',
  components: { Treeselect },
  data() {
    return {
      // 過濾條件
      filterText: '',
      // 初始化樹列表
      treedata: [
        {
          label: '一級 1',
          id: '1',
          children: [{
            id: '1-1',
            label: '二級 1-1',
            children: [{
              label: '三級 1-1-1',
              id: '1-1-1'
            }, {
              label: '三級 1-1-2',
              id: '1-1-2'
            }, {
              label: '三級 1-1-3',
              id: '1-1-3'
            }]
          }]
        }
      ],
      value: ['1-1-2']
    }
  },
................
}
</script>

來一張幾個樹列表一起的對比展示界面。

 以上就是普通樹列表和下拉列表樹展示的界面效果,往往我們一些特殊的界面處理,就需要多利用一些封裝良好的第三方界面組件實現,可以豐富我們的界面展示效果。

 

列出以下前面幾篇隨筆的連接,供參考:

循序漸進VUE+Element 前端應用開發(1)— 開發環境的準備工作

循序漸進VUE+Element 前端應用開發(2)— Vuex中的API、Store和View的使用

循序漸進VUE+Element 前端應用開發(3)— 動態菜單和路由的關聯處理

循序漸進VUE+Element 前端應用開發(4)— 獲取後端數據及產品信息頁面的處理

循序漸進VUE+Element 前端應用開發(5)— 表格列表頁面的查詢,列表展示和字段轉義處理

循序漸進VUE+Element 前端應用開發(6)— 常規Element 界面組件的使用

循序漸進VUE+Element 前端應用開發(7)— 介紹一些常規的JS處理函數

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

【其他文章推薦】

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

網頁設計最專業,超強功能平台可客製化

※別再煩惱如何寫文案,掌握八大原則!

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

目錄

  • 簡介
  • 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的博客

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

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

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

※回頭車貨運收費標準