誠意大實力夠 試駕吉利帝豪GL

動態表現方面,特別是讓中國消費者一直不太放心的雙離合變速箱的表現。從起步的瞬間,還是能感覺到帝豪GL的起步很順暢,不會像市面常見的乾式雙離合變速器那樣遲鈍,在換擋邏輯還是會很清晰,先不說它換檔有多快,但是至少能夠預先了解到駕駛員的意思,然後完成換擋動作,在正常行駛時多踩一點油門,轉速的指針還是跳的比較慢,不會說瞬間給你一個大扭矩,在變速箱響應方面相比同等級當中還是令人滿意的,雖說沒有驚喜,但算的上中規中矩吧。

在眼球經濟盛行的當下,對一個品牌和產品來說,最重要的資源不再是信息本身,而是大眾的注意力,只有大眾關注到了產品,才有可能成為買單者,那對於今年吉利來說,最火的話題莫過於,七月十五號中國吉利汽車當著一眾媒體的面把自家博瑞拆了,同時還拆了一台日系合資車,如果為2016年自主汽車品牌的營銷排一個座次的話,吉利恐怕是當之無愧的第一。從洗腦神句“你好,博越”引發多家車企頭腦風暴,到帝豪GS領銜VR直播,再到將雅閣作為對手的24小時拆車大戰,在霸佔了營銷熱門榜的同時,吉利也展示了自己強大的底氣 。

但無論結果如何,其實在整個拆車過程也是一次汽車知識的普及課,能更直觀地增強受眾對汽車的設計、材料、底盤懸挂等汽車知識的了解,那對吉利而言,只要能做到讓消費者認識到,吉利的產品,和合資品牌同級別產品沒有什麼品質差異的話,“拆車營銷”這場仗,就已經贏了。

在帝豪GL上市之前,吉利又將它拉到了天津的中國汽車技術研究中心碰撞實驗室,兩輛帝豪GL真實模擬現實中的“十字路口魔鬼碰”,側面碰撞獲得18分(滿分為18分)、正面碰撞獲得17.92分(滿分為18分),而且此次帝豪GL的魔鬼碰撞還是直接由央視現場直播,看得出,吉利的營銷大手筆歸因於對自身實力有足夠自信,有些秀還真不是所有車企都能玩的。

所以車身尺寸上,帝豪GL比現款帝豪大了不止一圈,尤其是4725mm的車身長度,使它成為了同級中的佼佼者。這個尺寸已經超越了主流的緊湊級轎車,和標緻408一樣屬於A+級的尺寸。

確實在外觀設計上不僅告別了傳統中國品牌“東拼西湊”的借鑒之風,還打造出了屬於品牌自己的風格,因為從博瑞、博越再到帝豪GS,吉利已經將這個家族式的設計進行了普及,“套娃”已經不再是合資品牌的專利。

看的出來,內飾與配置上同樣延續了前面三款車型的高級質感,中控頂部採用吉利家族式的拱橋弧線,並運用了金屬拉絲飾板裝飾,儀錶盤看上去清晰易讀,還提供了8英寸觸控显示屏,此外,該車還配備了自適應巡航、城市預碰撞系統等。雖說這樣的配置與做工在吉利近來的車型中並不少見,但是在同級別中顯得非常豐富。

坐進車內,會更切實的感受到帝豪GL的在同級別中的“高質感” 可以跟任何同級合資車型相比都不落下風,一些旋鈕和按鍵的手感,阻尼和回饋也再詮釋着“高質感”。最贊的是在帝豪GL的車內,你並不會聞到市面上新車型普遍難以避免的刺鼻味道。

動態表現方面,特別是讓中國消費者一直不太放心的雙離合變速箱的表現。從起步的瞬間,還是能感覺到帝豪GL的起步很順暢,不會像市面常見的乾式雙離合變速器那樣遲鈍,在換擋邏輯還是會很清晰,先不說它換檔有多快,但是至少能夠預先了解到駕駛員的意思,然後完成換擋動作,在正常行駛時多踩一點油門,轉速的指針還是跳的比較慢,不會說瞬間給你一個大扭矩,在變速箱響應方面相比同等級當中還是令人滿意的,雖說沒有驚喜,但算的上中規中矩吧。

1.3T的發動機,129馬力的最大功率並不驚艷,調至1400轉就開始的最大扭矩輸出平台,初段的提速能力雖然有些遲滯但不會給你突兀感,但是記得,不要暴力駕駛,不要希望有推背感,溫柔合理舒適的開法,才是正確對待它的方式,因為深踩油門,在後段的加速力會有很明顯的乏力,轉速拉到三千轉時,當發動機的“呼呼呼”聲音傳進駕駛室時,就會觸動你的“憐憫”之心,右腳鬆開油門,讓它轉速掉到一個它舒服的位置上,此時你就會感覺在低轉速下它可以給你非常不錯的行駛質感,而且整個的動力輸出還是比較平順的。

動力方面,帝豪GL搭載了與帝豪GS相同的1.3T和1.8L兩款發動機,均匹配6速手動和6速雙離合變速箱,此外,帝豪GL自動擋車型配備6擋雙離合變速箱是由德國格特拉克與LUK聯合打造的產品,這是一款乾式雙離合變速箱。眾所周知,就連大眾和福特這一級別的汽車“大腕”都無法把控乾式雙離合變速箱的質量,吉利能不能做得到的確是一個問號;再有,一直號稱標杆的帝豪GL后懸給了個扭力梁,雖然不是說扭力梁就一定不如多連桿,但至少給人的感覺上,這車就降了一個檔次,希望吉利廠家在這一問題上引起注意。

帝豪GL的目標客戶群,可以說是最“貪婪”的一群人:“我要漂亮的外觀、要精緻的內飾、要超高的配置、要動感的駕駛感受、更要舒服的乘坐體驗。而我卻只有十萬的預算,吉利就是恰恰抓住了這個需求點,對於帝豪GL的外觀造型,雖然少了當初博瑞亮相后的那種驚艷,但是成熟的家族化設計,並不比同級別的合資產品遜色;車內簡約且富有質感的內飾氛圍,徹底的擺脫了自主品牌浮夸稚嫩的設計風格;博瑞上的高科技配置也被下放到了這款緊湊級家轎上,重新樹立了同級新標杆,雖然帝豪GL的產品力做的足夠優秀,但想要打破在緊湊型車裡合資車型的垄斷,還是要繼續努力做出好產品,才能讓消費者敞開心扉的去接納它。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

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

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

看過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維修中心

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

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

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

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

明(25)日 Redmi 即將發表 Redmi K40 系列雙旗艦新機,不過在這之前於小米中國官網悄悄推出了新款「小米GaN充電器 Type-C 33W」,這款 GaN 氮化鎵充電器擁有超小的體積、採用單 Type-C 接口、還佛心附贈一條 Type-C 充電線,售價僅約新台幣 340 元。充電方面除了最高充電功率可達 33W ,也適用於 iPhone 快速充電,可說是目前 iPhone 快充最小巧且便宜的快速充電器選擇。

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

根據過去傳聞指出,即將發表的 Redmi K40 系列旗艦新機可能最高支持 33W 快速充電,不過這次 Redmi K40 可能會跟進小米11 將盒裝「輕裝上陣」的設計,因此當初外界推測 Redmi 可能也會取消附贈充電器。不過在 Redmi K40 系列新機發表會之前,在中國小米官網也率先推出了新款 33W GaN 氮化鎵充電器,極有可能就是為了接下來將發表的 Redmi K40 系列作準備。

小米 GaN 充電器 Type-C 33W 之所以體積比起以往 33W 功率的充電器相比大幅縮小,其主因在於採用 GaN(氮化镓)是一種新型半導體材料,擁有優異的導熱效率、耐高溫和耐酸鹼等特性,過去也廣泛應用在航太和軍事領域,近年也陸續在充電器採用。GaN(氮化镓)充電器不僅可以做到更小的體積和更輕盈的重量,在充電功率轉換效率也比起非 GaN 充電器更高。

對比原本 33W 的小米USB-C 充電器,新款的「小米GaN充電器 Type-C 33W」體積只有 33 立方公分,體積整整縮小 56% 更便於攜帶:

充電功率方面,小米GaN充電器 Type-C 33W 最高支持 33W 快速充電,輸出支持 5V3A、9V3A、11V3A、12V2.5A :

另外,小米GaN充電器 Type-C 33W 也可搭配 Type-C 轉 Lightning 快充線為支持快充的 iPhone(例如: iPhone 12)進行快速充電,在快速充電下可讓 iPhone 12 在 30 分鐘充電達到 60% 。考量其價格、體積和充電功率,對於想輕鬆讓 iPhone 12 升級快充的 iPhone 用戶來說,它也是相當具吸引力的快速充電器。

另外,這款充電器也支持智慧辨識輸出電流,能為 iPhone、 iPad、Android 手機、Android 平板、 Switch 遊戲機進行充電,不過因輸出電壓限制,小米 GaN 充電器 Type-C 33W 不支持為筆記型電腦、20W(含)以上的小米無線充電器充電。

充電安全部分,小米 GaN 充電器 Type-C 33W 支援多重安全保護,同時提升充電效率:

小米 GaN 充電器 Type-C 33W 體積就像顆小豆腐頭一般,不過如此小的充電器的插頭也就無法收折算是稍顯可惜的一點:

最後是大家關心的售價,小米 GaN 充電器 Type-C 33W 雖然售價只要人民幣 79 元(約合新台幣 340 元),不過仍標配 3A 大電流的 C to C 充電線。

圖片/消息來源:小米商城(中國)

延伸閱讀:
小米米家筋膜槍眾籌推出:強勁動力有效放鬆筋膜,眾籌價約 1,942 元

星巴克 STAY COFFEE TOGETHER 數位體驗活動,連續 3 週飲料買一送一!(2/22-3/12)

您也許會喜歡:

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

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

【其他文章推薦】

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

※台北網頁設計公司全省服務真心推薦

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

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

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

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

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維修中心

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

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

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

【JVM故事】了解JVM的結構,好在面試時吹牛


class文件格式

參考上一篇文章《【JVM故事】一個Java字節碼文件的誕生記》,後續還會專門講解class文件的內部結構。

數據類型

jvm包括兩種數據類型,基本類型和引用類型。

基本類型包括,數值類型,boolean類型,和returnAddress類型。

數值類型包括,整型,浮點型,和char類型。

boolean類型同樣只有true和false。

returnAddress類型是一個指針,指向jvm指令的操作碼,在Java中沒有與之對應的類型。

boolean類型的操作會被轉化為int類型的操作進行,boolean數組會當成byte數組去操作。1表示true,0表示false。

引用類型包括三種,類類型,數組類型,和接口類型。

它們的值是動態創建的類實例,數組,或實現接口的類實例。

數組有component類型和element類型,component類型就是數組去掉最外層維度后剩下的類型,可能還是一個數組類型(對於多維數組)。

element類型就是數組裡面存儲的最小數據的類型,它必須是一個基本類型,類類型,或接口類型。

對於一維數組的話,component類型和element類型是相同的。

引用類型還有一個特殊值,就是null,表示沒有引用任何對象。

運行時公有數據區

jvm有一個堆,在所有jvm線程間共享,堆是一個運行時數據區域,所有為類實例和數組分配的內存都來自於它。

堆在jvm啟動時創建,堆中對象不用顯式釋放,gc會幫我們釋放並回收內存。

方法區

jvm有一個方法區,在所有jvm線程間共享,它存儲每一個類的結構。

像運行時常量池,字段和方法數據,方法和構造函數的代碼,還有特殊的方法用於類和實例的初始化,以及接口的初始化。

方法區在jvm啟動時創建,雖然方法區在邏輯上是堆的一部分。

但簡單實現時可以選擇不進行gc和壓縮,本規範沒有強制要求方法區的位置,也沒有要求管理已編譯代碼的策略。

運行時常量池

運行時常量池就是類或接口的字節碼文件里的常量池的運行時表示形式,它包含幾種常量。

如在編譯時就已經知道的数字字面量值,和必須在運行時解析的方法和字段的引用,運行時常量池的功能類似於傳統語言的符號表,不過它包含的數據會更加寬泛。

運行時常量池分配在jvm的方法區,類或接口的運行時常量池在類或接口被jvm創建時才會構建。

運行時私有數據區

pc寄存器

jvm支持一次運行多個線程,每個線程都有自己的pc寄存器,任何時候一個線程只能運行一個方法的代碼。

如果方法不是native的,pc寄存器包含當前正在被執行的jvm指令地址,如果方法是native的,pc寄存器的值是未定義的。

jvm棧

每一個jvm線程都有一個私有的jvm棧,隨着線程的創建而創建,棧中存儲的是幀。

jvm棧和傳統語言如C的棧相似,保存局部變量和部分計算結果,參与方法的調用和返回。jvm棧主要用於幀的出棧和入棧,除此之外沒有其它操作,

幀可能是在堆上分配的,所以jvm棧使用的內存不必是連續的。

native方法棧

native方法不是用Java語言寫的,為了支持它需要使用傳統棧,如C語言棧。不過jvm不能加載native方法,所以也不需要提供native方法需要的棧。

每次當一個方法被調用時一個新的幀會被創建。當方法調用完成時,與之對應的幀會被銷毀,無論是正常完成還是拋異常結束。

所以幀是方法調用的具體體現形式,或稱方法調用是以幀的形式進行的。幀用來存儲數據和部分計算結果,和執行動態鏈接,方法返回值,分發異常。

幀分配在創建幀的線程的jvm棧上,每一個幀都有自己的本地變量數組,自己的操作數據棧,和一個對當前方法所在類的運行時常量池的引用。

本地變量數組和操作數棧的大小在編譯時就確定了,它們隨着和幀關聯的方法編譯后的代碼一起被提供,因此幀這種數據結構的大小隻依賴於jvm的實現,這些結構所需的內存可以在方法調用時同時被分配。

在一個線程執行的任何時刻,都只會有一個幀是處於激活的。這個幀被稱為當前幀,與之對應的方法被稱為當前方法,方法所在的類被稱為當前類,此時用到的本地變量數組和操作數棧也都是當前幀的。

一個幀將不在繼續是當前幀,如果它的方法調用了另一個方法,或者它的方法結束了。

當一個方法被調用,一個新的幀被創建,當執行控制由原來的方法傳遞到新的方法時,這個新的幀變為當前幀。

當方法返回時,當前幀把方法執行的結果傳回到上一幀,當上一幀被激活的同時當前幀會被丟棄。

本地變量數組

每一幀都包含一個變量數組,就是都熟知的本地變量存儲的地方。這個本地變量數組的長度在編譯時確定,隨着編譯后的方法代碼一起提供。

通常一個本地變量(的位置)能夠存儲一個類型的值,但是long和double類型卻需要兩個本地變量(的位置)才能存一個值。

本地變量按索引尋址,第一個本地變量的索引是0。long和double需要消耗兩個連續的索引,但卻是按照較小的這個索引尋址的。不能按照較大的那個索引去讀數據,但是可以寫入,當然這樣將使本地變量內容錯亂。

在方法被調用時,jvm使用本地變量來接收傳遞進來的參數值。在類(靜態)方法調用時,所有參數被傳入從索引0開始的連貫的本地變量數組裡。

在實例(非靜態)方法調用時,索引0處總是傳入正在其上執行方法調用的那個對象的引用,(就是Java中的this了),所有參數被傳入從1開始的連貫的本地變量數組裡。

操作數棧

每個幀包含一個後進先出的棧,用於存儲正在執行的jvm指令的操作數,就是都熟知的操作數棧,這個棧的最大深度在編譯時就已確定,隨着編譯后的方法代碼一起提供。

當幀被創建時,操作數棧是空的,jvm提供一些指令用於加載常量值,本地變量值,字段值到操作數棧上,另一些jvm指令採用操作數棧上的操作數進行操作,並把結果放回到操作數棧上。

操作數棧也用於準備將要傳遞給方法調用的參數和接收方法調用返回的結果。

long和double類型的值佔用兩個單位的棧深度,其它類型的值佔用一個單位的棧深度。

動態鏈接

每一個幀都包含了對當前方法所屬類型的運行時常量池的引用。目的是為了支持方法代碼的動態鏈接。class文件中描述一個方法引用被調用的方法和被訪問的變量的代碼,是採用符號引用的形式實現的。

符號引用的形式可以粗略的認為是字符串的形式,就是用字符串標明需要調用哪個類的哪個方法或訪問哪個字段或變量。就像符號引用這個名字一樣,這些僅僅是符號,是拿不到具體值的,所以必須要進行轉換。

動態鏈接就是把這些符號方法引用轉換為具體的方法引用,在必要時加載類來解析尚未明確的符號,把符號變量的訪問轉換為這些變量運行時所在存儲結構的適合的偏移量(索引)。這樣的方式又稱為後期綁定。

方法調用

一個方法調用正常完成(即沒有拋異常)時,會根據所返回的值的類型執行一個適合的return指令,當前幀會去恢復調用者的狀態,包括它的本地變量和操作數棧,使調用者的程序計數器適合的遞增來跳過剛剛的那個方法調用指令。

返回值會被放到調用者幀的操作數棧上,然後繼續執行調用者方法的幀。

一個方法在調用時拋出了異常,且這個異常沒有在這個方法內被捕獲處理,將會導致這個方法調用的突然結束,這種情況下永遠不會向方法的調用者返回一個值。

特殊方法

站在jvm的級別,每一個用Java寫的構造函數都以一個實例初始化方法出現,且都是特殊的名字,就是<init>,這個名字是編譯器提供的。

實例初始化方法只能在jvm內部使用invokespecial這個指令調用,且只能在尚未初始化的類實例上調用。

一個類或接口最多可以有一個類或接口初始化方法,通過調用這個方法被初始化。類或接口的初始化方法也有特殊的名字,就是<clinit>,該方法沒有參數,且返回值是void。

方法名稱也是由編譯器提供的,從Java7開始,在字節碼中這個方法必須被標記為靜態的才行。

這個初始化方法是被jvm隱式調用的,它們絕對不會直接被用任何jvm指令調用,僅作為類初始化進程的一部分被間接的調用。

Java類庫

jvm必須為Java類庫的實現提供足夠的支持。一些類庫中的類如果沒有jvm協助是無法實現的。

反射,就是在運行時獲取某個類的類型相關信息,如它的字段信息,方法信息,構造函數信息,父類信息,實現的接口信息。

這些信息都必須是把一個類加載完之後才可以知道的,只有jvm才可以加載類。如java.lang.reflect這個包下的類和Class這個類。

在Java中加載一個類或接口用類加載器,即ClassLoader,背後還是委託給jvm來實現的。

鏈接和初始化一個類或接口。

安全,如java.security包下的類,還有其它類像SecurityManager。

多線程,如線程這個類Thread。

弱引用,像java.lang.ref包下的類。

公有設計,私有實現

以上內容只是jvm的一個“相對寬泛”的規範,它並不是實現方案,也不是實現細節。

實現者可以根據自身的需要來實現jvm,如運行在後端服務器上的jvm和運行在移動設備上的jvm肯定側重點有所不同。

從事Java的人都知道,事實上jvm是有較多的實現版本。

由於jvm是處在Java語言和操作系統之間的,所以它要向上提供對Java的支持,向下與操作系統良好交互。

寫在最後

高級語言(Java,C#)中的很多操作如文件操作,網絡操作,內存操作,線程操作,I/O操作等,都不是高級語言自身能夠實現的。

也不是它們的虛擬機(JVM,CLR)能夠實現的,實際最終是由操作系統實現的,因為這些都是系統資源,只有操作系統才有權限訪問。

如果你用Java或C#代碼創建了一個文件,千萬不要以為是Java或C#創建了這個文件,它們只是層層向下調用了操作系統的API,然後到文件系統API,最後可能到磁盤驅動程序。

由此可以看出,要想設計一門語言,不單單是關鍵字、語法、編譯器,類庫,虛擬機這些,還要深度了解操作系統,甚至是硬件,如CPU架構和CPU指令集等。

所以,和語言相關的事情,每一項都是異常的繁瑣複雜,都需要投入大量的人力、財力、時間去研究,最後即使研究成功了,可能沒有生態,沒人使用,自然也無法賺錢。

因此,國人現在還沒有一門屬於自己的真正語言。

>>> 熱門文章集錦 <<<

 

畢業10年,我有話說

【面試】我是如何面試別人List相關知識的,深度有點長文

我是如何在畢業不久只用1年就升為開發組長的

爸爸又給Spring MVC生了個弟弟叫Spring WebFlux

【面試】我是如何在面試別人Spring事務時“套路”對方的

【面試】Spring事務面試考點吐血整理(建議珍藏)

【面試】我是如何在面試別人Redis相關知識時“軟懟”他的

【面試】吃透了這些Redis知識點,面試官一定覺得你很NB(乾貨 | 建議珍藏)

【面試】如果你這樣回答“什麼是線程安全”,面試官都會對你刮目相看(建議珍藏)

【面試】迄今為止把同步/異步/阻塞/非阻塞/BIO/NIO/AIO講的這麼清楚的好文章(快快珍藏)

【面試】一篇文章幫你徹底搞清楚“I/O多路復用”和“異步I/O”的前世今生(深度好文,建議珍藏)

【面試】如果把線程當作一個人來對待,所有問題都瞬間明白了

Java多線程通關———基礎知識挑戰

品Spring:帝國的基石

 

作者是工作超過10年的碼農,現在任架構師。喜歡研究技術,崇尚簡單快樂。追求以通俗易懂的語言解說技術,希望所有的讀者都能看懂並記住。下面是公眾號的二維碼,歡迎關注!

 

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

【其他文章推薦】

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

※台北網頁設計公司全省服務真心推薦

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

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

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

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

去擺攤吧,落魄的Java程序員

真的,我也打算去擺攤,宣傳語我都想好了。沉默王二,一枚有顏值卻靠才華苟且的程序員,《Web 全棧開發進階之路》作者,CSDN 明星博主,周排名第 4,總排名 40,這數據在眾多互聯網大咖面前不值一提,但在洛陽,我想還是有一席之地的。

況且我家裡有很多書,每天晚上帶上二三十本書,到河科大學校門口擺個攤,前十名免費送,後面的書,價格隨意,只要同學們能夠負擔得起,隨意,一塊,一毛都可以,能拉動點經濟是點,也算是做出貢獻了。

另外,我還附送上我的個人微信,這個價值比書還要值錢得多,對吧?加了我的微信,同學們可以隨時隨地找我提問,還可以第一時間從我的朋友圈收到各種有趣有益的消息,真的超值啊。

我這算是积極響應國家號召了,對吧?總理都點贊好幾次地攤經濟了,朋友圈和微信社群都刷爆了。有一段白岩松老師的話,我覺得挺經典的,分享出來,大家感受一下。

我不喜歡地攤這個詞,應該叫室外經營,或者有序佔道經營,只要地方政府能夠放得開,就一定能夠拉動商戶的經營狀況。

連我老婆都坐不住了,強烈建議我去擺地攤,並且願意下班后和我一起,不嫌丟人——真患難與共啊。宣傳文案她已經幫我打印好了,今天晚上我們一家三口(帶上女兒)就去大學門口體驗一下,之前從未有過這方面的經驗,一想到這,內心竟然有些小激動。

本來,擺攤在我心目中是一種挺 low 的行為,要拋頭露面,要使勁的吆喝,還要被城管追着屁股跑,實在是狼狽。但現在我改變看法了,覺得擺攤不僅接地氣,說不定真能體驗出不一樣的生活樂趣。掙不掙錢是小事,重在參与,重在振興城市經濟。

況且,生活實在是太難了,必須得做出點改變了。就拿我來說吧,公眾號的亂序讓文章的打開率下降到了 4% 左右,之前是 8% 左右,四月底那會真的是信心滿滿,現在基本上一半的打開率沒有了,搞得挺焦慮的。

讀者訂閱數在增加,但閱讀量在下降,微信這波操作挺讓我心碎的。雖然說,面對短視頻的衝擊,圖文的整體閱讀在下降,微信不得不做出改變。但這次改變,我顯然不是受益者。

想想也挺悲哀的。所有的作者都拼了命的,從外部引流到公眾號,結果公眾號學起了今日頭條,強行加了推薦算法。這意味着什麼?

作者不再是公號的主人,讀者不再是公號的客人,中間多了一層皮條客,他願意撮合你倆,你倆就能見面,不願意的話,哼,門都沒有。

這對於讀者訂閱數龐大的號來說,閱讀量根本就不會受到影響,對吧,反正這個讀者看不到,另外一個讀者能看得到。

有一小部分讀者應該知道,我還有一個小號,“沉默王三”,已經有一段時間沒有更新了,原因很簡單,讀者訂閱數出現了負增長,所以我就喪失了更新的動力。

你看,連我這種有一些讀者基數的作者都養不動一個小號,更何況那些真正零起步的作者——太難了,還不如想想辦法去擺攤吧,不,還是好好乾自己的本職工作吧。

幸好“沉默王二”這個號的讀者增長還算是不那麼令人失望,否則真的有點坐不住了。面對這種困難的局面,我所能做的就是堅持初心,擁抱變化。

我寫作的初心是什麼?就是為了分享自己的心聲,自己的故事,自己對技術的一些理解,對人生的一些思考,給需要的讀者一丟丟幫助。

也許之前一個讀者的留言是對的,我不應該過多的關注閱讀量,更要注重文章的質量。總之,先佛。

我應該做出哪些改變呢?擺攤算是一種吧。更概括性的說法,就是,把自己寫作的主題與社會的熱點貼近一些,同時,親身去體會一些從前從未嘗試過的事情。這不僅能夠讓我的臉皮更厚一些,也能讓我多接觸一些新鮮的事情,從生活中尋找寫作的靈感。

堅持和變化,兩者相輔相成,我想一定能夠幫助我渡過難關,我是有這種信心的。

下面這段話是我在網上看到的,我覺得挺符合我現在的心境的:

生活總要嘗試不同的風景,人生總要嘗試不同的體驗。就好比旅遊,不就是從一個自己待煩的地方去另一個別人待煩的地方嗎?

時隔多年後,究竟會怎樣,不重要,重要的是經歷。起起伏伏才是人生,平淡無奇才最無聊。我去擺攤,追求的不是利潤,而是生活的體驗。

如果這次賣書能夠大獲成功的話,我還有很多才藝可以就地販賣,比如說裝機、賣假髮、賣格子衫,對了,我精通 Java,沒有對象的同學,我可以幫你 new 一個。

如果覺得文章對你有點幫助,請微信搜索「 沉默王二 」第一時間閱讀。回復關鍵字「簡歷」更有一份技術大佬整理的優質簡歷模板,助你一臂之力。

本文已收錄 GitHub,傳送門~ ,裏面更有大廠面試完整考點,歡迎 Star。

我是沉默王二,一枚有顏值卻靠才華苟且的程序員。關注即可提升學習效率,別忘了三連啊,點贊、收藏、留言,我不挑,嘻嘻

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

【其他文章推薦】

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

※台北網頁設計公司全省服務真心推薦

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

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

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

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

學者:野火致觀光客減少 有助控制澳洲疫情

摘錄自2020年4月13日中央社報導

一位澳洲學者說,幾個月前的森林大火雖然造成重大傷亡和損失,但減少可能染疫的觀光客到訪,意外地降低2019新型冠狀病毒(COVID-19,武漢肺炎)疫情對澳洲的衝擊。

澳洲國立大學傳染病教授柯里諾(Peter Collignon)說,要是沒有這場災難,澳洲的新型冠狀病毒疫情恐怕一發不可收拾。澳洲觀光及交通業界論壇組織(Tourism and Transport Forum)執行長奧斯蒙(Margy Osmond)表示,早在武漢肺炎疫情爆發之前,澳洲觀光業本就因為森林大火而受到重創。

澳洲衛生部最新的數據顯示,截至澳洲東部標準時間(AEST)13日下午3時止,澳洲2019冠狀病毒疾病確診病例24小時內增加46例,累計達6359例,其中61人死亡。確診人數最多的新南威爾斯州有2863個病例,約占45%。全國有36萬2000多人已接受病毒檢測。

生活環境
國際新聞
澳洲
澳洲野火
地方觀光
控制疫情
武漢肺炎
動物與大環境變遷

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

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

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

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

中國傳統市場照賣 澳:全球健康威脅

摘錄自2020年4月15日自由時報報導

武漢肺炎疫情被認為起源自中國傳統市場販售的野味,最早傳出疫情的武漢市華南海鮮批發市場目前仍被關閉。但另一個武漢市最大的傳統市場「白沙洲大市場」,不僅在封城期間維持基本的運作,解封後也很快就恢復疫情爆發前的熱絡景況。

為防堵疫情因市場活動二度爆發,民眾必須持有健康認證「綠碼」、量測體溫後才能進入白沙洲大市場,載貨的車輛也需經過消毒。白沙洲大市場販賣蔬菜、魚貨、冷藏雞肉與當季食品,當地攤販指出,該市場禁止宰殺雞、豬、羊等家禽畜,必須在專門的屠宰場完成宰殺後,再送至市場販賣。

澳洲總理莫里森14日指出,他難以理解世衛組織竟坐視中國的傳統市場繼續營運,並表示這不是第一次出現源自市場的病毒,稱傳統市場為「全球健康威脅」。

但世衛組織上週表示,傳統市場為中國人民重要的收入與食物來源,不支持中國關閉所有傳統市場,且中國目前只允許傳統市場銷售安全的食物,衛生水準已經提升。世衛組織特使納巴羅也於14日受訪時說,為防止下一次的全球大流行,世衛組織建議各國應該關閉「危險的」傳統市場,但也強調,世衛組織無法強制任何國家關閉市場。

生活環境
國際新聞
中國新聞
中國
武漢
傳統市場
食品安全

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

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