標籤: USB CONNECTOR

  • 深入理解計算機系統 — 信息的表示和處理

    深入理解計算機系統 — 信息的表示和處理

    1. 信息的存儲

        大多數計算機使用 8 位的塊,或者字節,作為最小的尋址內存單位,而非訪問內存中單獨的位,機器級程序將內存視為一個非常大的字節數組,稱為 虛擬內存 ,內存的每個字節都用一個唯一的数字標識,稱為它的 地址 。以 C 語言的指針為例,指針使用時指向某一個存儲塊的首字節的 虛擬地址 ,C 編譯器將指針和其類型信息結合起來,這樣即可以根據指針的類型,生成不同的機器級代碼來訪問存儲在指針所指向位置處的值。每個程序對象可以簡單視為一個字節塊,而程序本身就是一個字節序列。

    1.1 十六進製表示法

        一個字節由 8 位組成。用二進製表示即 00000000 ~ 11111111 。十進製表示為 0 ~ 255 。由於兩者表示要麼過於冗餘,要麼轉換不遍,因此通常使用十六進制來表示一個字節。這幾種進制的轉換在此就不多說了。

    1.2 字數據大小

        每台計算機都會有一個字長(此處字長非字節長度),指明 指針數據的標稱大小(nominal size),因為虛擬地址是以這樣的一個字來進行編碼的,所以字長決定的最重要的一個系統參數即是虛擬地址空間的最大大小。 對於一個字長為 w 位的機器而言,虛擬地址的範圍為 0 ~ (2 ^w )- 1 ,程序最多訪問 2 ^ w 個字節。以 32 位機器為例,32位字長限制虛擬地址空間為 (2 ^32) -1 ,程序最多訪問 2 ^ 32 個字節,大約為 4 x 10^9 字節,即4 GB ( 根據 2 ^ 10 (1024) 約等於 10 ^ 3 (1000) ,可以得到 2 ^ 32 =  4 * 2^30 = 4 * 10 ^ 9 ) 。64位機器的限制虛擬地址空間為 16 EB。大約為 1.84 x 10 ^9 。

    1.3 尋址和字節順序

        對於跨越多個字節的對象,我們必須建立兩個規則:這個對象的地址是什麼以及在內存中如何排列這些字節。在幾乎所有的機器上,多字節對象都被存儲為連續的字節序列,對象的地址為這個字節序列中最小的字節地址。以 int 類型為例,假定int 大小為32 位,有變量 int x = 0x01234567 。若 x 的地址為 0x100 ,則 x 的 4 個字節將被存儲在 0x100 , 0x101 , 0x102, 0x103 的位置,此時 4個字節的值分別為 0x01, 0x23, 0x45, 0x67,那麼在內存中的排列順序有如下兩種情況,

    • 大端法:最高有效字節放在最前面的方式稱為大端法,即將一個数字的最高位字節放在最小的字節地址。
    • 小端法:最低有效字節放在最前面的方式稱為小端法,即將一個数字的最低位字節放在最小的字節地址。

      以上面的 x 為例,x 的最高位字節是 0x01 ,將其放在最小的字節地址即 0x100。x 的最低位字節為 0x67 ,將其放在最小的字節地址 0x100 。即大小端對應高低位字節。對於我們來說,機器的字節順序是完全不可見的,我們大部分情況下也無需關心其字節順序,但是在不同類型的機器之間通過網絡傳遞二進制數據的時候,如小端法機器傳送數據給大端法機器時,接受方接收到的字節序會變成反序,為了避免這種問題的產生,發送方和接收方都需要遵循一個網絡規則,發送方將二進制數據轉換成網絡標準,接收方再將這個網絡標準的字節序轉換成自己的字節序。此外,我們在閱讀機器級代碼的時候,可能會出現如下的情況:

      暫時忽略這條指令的意義,可以看到左邊6個字節分別為 01 05 43 0b 20 00 ,而右邊的指令中的地址為 0x200b43,可以看到從左邊的第三個字節開始,43 0b 20 是右邊指令地址的倒序,因此在閱讀這種機器級代碼的時候,也需要注意字節序的問題。此外還存在一種情況。如下圖所示。

        我們可以看到, show_bytes 這個函數可以打印出 start 指針指向的地址開始的 len 個字節內容,且不受字節序的影響,那麼它是如何做到的呢?在 show_int 函數中,可以看到它將 參數 x 的地址強制類型轉換為了 byte_pointer , 即 unsigned char * 。通過強制類型轉換的 start 指針指向的仍是 x 的最低字節地址,但是其類型改變了,通過其類型編譯器會認為該指針指向的對象大小為 1 個字節,此時將該指針進行 ++ 操作可以得到順延下一個字節的內容,從而得到對應的整個對象的字節序列中每個字節的內容而不受字節序影響。

    1.4 字符串

        在C語言中,字符串被編碼為一個以 null (其值為0 )字符結尾的字符數組。每個字符都有某個標準編碼來表示,最常見的則是 ASCII 字符碼。假如我們調用 show_bytes(“12345”, 6),那麼會輸出 31 32 33 34 35 00 。可以看到最後打印出了一個終止符,所以通常 C 字符串的長度為實際字符串長度 + 1。 在C 標準庫中的 strlen 函數可以傳入一個字符串得出其長度,這裏的長度即是實際長度,不包含終止符。

    2. 整數表示

        在本章節中,介紹了編碼整數的兩種不同的方式,一種只能表示非負數,另一種則能夠表示負數,正數和零。接下來逐一進行介紹。

    2.1 整型數據類型

        C語言中,整數有多種數據類型,如下圖所示,此外可以通過加上 unsigned 符號來限定該數據類型為非負數。這些數據類型有的是根據機器的字長(32位和64位)決定其實際最大值和最小值的範圍。我們可以看到,圖中最小值和最大值的取值範圍是不對稱的,負數的取值範圍比正數大一,當我們考慮如何表現負數時,會看到為什麼會這樣。

        關於無符號整數的編碼,其實與普通的十進制正數轉換成二進制沒有什麼區別,假設字長 w = 32 位,轉換后大於 32 位的数字將被捨去。這裏主要介紹一下關於有符號数字的編碼,通常計算機使用的編碼錶示方式為 補碼 ,在這個表示方式中,將字的最高有效位(即符號位)表示為負權,權重為 – 2^(w-1) ,當 w 位的值為 1 時表示為負數,反之為正數。以 -1 為例,-1 的補碼為1111 1111  …. …. 1111 ,即 -2^31 + 2^30 + … + 2^0 = -1 ,通常我們看到一個負數想要直接將其使用補碼錶示還是有些不方便的,因此我們可以先使用原碼錶示,所謂原碼和普通的十進制數轉二進制數沒有區別,只不過最高位用來表示符號位,然後再求其反碼,即符號位不變,其餘位取反加 1,就可以得到這個負數的補碼了,還是以 -1 舉例, -1 的原碼為 1000 0000 …. 0001 ,其反碼的值為 1111 1111 …. 1111 ,與 -1 的補碼值是相同的。而正數的補碼為其本身,不需要做這種轉換。

        那麼為什麼要使用補碼這種表示方式呢,首先,二進制補碼可以使正負數相加時仍然採用正常加法的邏輯,不需要做特殊的處理,此外,如果不採用補碼錶示,採用原碼的表示方法,那麼會出現幾個問題,正負零的存在,以及提高了減法的計算複雜度,而補碼可以十分簡單的計算正負數相加,只需求出兩者的補碼對其進行加法,更多關於補碼的解釋可以參考  。

    PS: 為什麼正負數補碼相加會得到正確的結果,這裏個人的見解是:由於補碼最高位為負權,而正數與負數補碼相加相當於正數去抵消這個負權。比如 -16 的補碼為 1111 …. 1111 0000,加上正數 1,由於正數的補碼為本身,所以等價於 -16 + 1  == (-2^31 + 2^30 + … + 2^4 ) +  2^ 0 ,相當於多了一個 2^0 的正權去抵消其最高位的負權。

    2.2 有符號數和無符號數之間的轉換

        C語言允許各種不同的数字類型之間進行強制類型轉換, 如 int x= -1 ; unsigned y = (unsigned) x ; 此時會將 x 的值強制類型轉換成 unsigned 類型然後賦值給 y ,那麼此時 y 的值是多少呢?可以通過打印兩者的十六進制值來看有什麼區別。下面為 test.c 的代碼:

    int main()

    {

    int x = -1;

    unsigned y = (unsigned) x;

    printf(“%x \n”, x);

    printf(“%x \n”, x);

    return 0;

    }

    此處為編譯后可執行文件的輸出結果:

    ffffffff
    ffffffff

    可以看到, x 和 y 的十六進制值是相同的,這也說明了,強制類型轉換並不會改變數據底層的位表示,只是改變了解釋位模式的方式。我們可以利用 printf 的指示符進一步驗證這個結果,使用 %d (有符號十進制), %u (無符號十進制), 來打印 x 和 y 的值。以下是代碼:

    int main()

    {

    int x = 1;

    unsigned y = (unsigned) x;

    printf(“x format d = %d , format u = %u \n”, x, x);

    printf(“y format d = %d , format u = %u \n”, y, y);

    return 0;

    }

    這是編譯后可執行文件的對應輸出:

    x format d = -1 , format u = 4294967295
    y format d = -1 , format u = 4294967295

     我們可以看到,我們使用指示符控制了解釋這些位的方式,得到的結果是一致的。

    2.3 整數運算

     關於整數的運算,主要就是加減乘除四種運算,補碼的加減乘除都比較簡單明了,這裏主要說一下除法的舍入問題,首先,我們先確認下 C 語言中的舍入方式,在 C 語言中,浮點數被賦值給整數時,小數位總是被捨去,如

    float f = 1.5;

    int x = f ;

    printf(“%d \n “, x);

    輸出的結果為:

    1

    當 f 為負數時結果又是如何呢 ?

    float f = -1.5 ;

    int x = f;

    printf(“%d \n”, x);

    輸出的結果為:

    -1

    因此我們可以認為,C語言的舍入方式為向零舍入。接下來看一下除法的舍入問題。此處先以除以 2 的冪的無符號除法為例,

    上圖表示 12340 / 2^k 的時候二進制與對應的十進制的表示,此時的舍入是完全沒有問題的。接下來看下除以 2 的冪的有符號除法。

        當k = 4 的時候,-12340 / 2^ 4 == -771.25,此時的正確舍入值應該為 -771,但是其卻舍入成了 -772。這是因為,如果我們單純使用右移來進行除法的時候,其舍入方式為向下舍入,即總是往更小值的方向舍入,在沒有小數位的情況下是正確的,但是如果有小數位的時候,如 -771.25 舍入為 -772, 771.25 舍入為 771。而C語言的舍入方式為向零舍入,即總是往靠近零的值舍入,如 771.25 舍入為 771, -771.25 舍入為 -771。那麼如何實現這種舍入方式呢。當被除數為負數時,我們可以通過加上一個偏置值來糾正這種不正確的舍入方式。

        我們可以觀察一下上圖的有符號除法例子,可以發現,當右移的 k 位單獨拿出來,不為 0 的時候,會導致舍入結果不正確,這是因為,k 位的值不為 0 的時候,表示該結果有小數,所以可以通過 (x + (1 << k) – 1) >> k 得到正確的結果, (1 << k) – 1 可以獲得 k 個 1,x 加上 k 個 1 可以使捨去的 k 位不為 0 時產生進位,x >> k 的結果加一,從而使舍入正確。

    關於整數的表示和運算,個人覺得有幾個需要關注的點,一是溢出問題,由於使用有限的位來表示整數,所以當数字過大的時候可能會產生溢出,溢出的位會被捨去,但是有符號數的溢出可能會使符號位被置反,如 0111 1111 …. 1111 + 1 = 1000 0000 …. 0000,0111 1111 …. 1111 為 INT_MAX , INT_MAX + 1 會得到 INT_MIN。此外,無符號數與有符號數進行比較的時候,會使有符號數強制轉換為無符號數,如果有以下循環代碼:

    for(size_t i = 10; i >= 0 ; i–);

    由於 i 為無符號數,當 i == 0 的時候,判斷還會繼續循環下去, 0 – 1  = -1 , -1 的補碼錶示為 1111 1111 …. 1111 , 剛好是無符號數的最大值,會導致死循環。因此也需要注意一切與無符號類型數據的運算,以及強制類型轉換可能出現的問題。

    3. 浮點數

        終於來到了這一章的重點內容之一(其實感覺這本書哪裡都挺重要的),這裏主要介紹浮點數是如何表示的,並且介紹浮點數舍入的問題(和上面講到的舍入不大一樣),浮點數的表示及其運算標準稱為 IEEE754 標準,初看可能會讓你覺得有些晦澀難懂,但是理解之後會覺得設計的十分巧妙。

    3.1 定點表示法

        首先讓我們先看下十進制的浮點數是如何表示的,浮點數的定義與小數點息息相關,定義在小數點左邊的数字的權是 10 的正冪,右邊的数字為 10 的負冪,如 12.34 表示 1 * 10^ 1 + 2 * 10^0 + 3 * 10 ^-1 + 4 * 10 ^ -2 = 12又34/100,同理可以得到二進制的浮點數表示,即定義在小數點左邊的数字的權是 2 的正冪,右邊的数字為 2 的負冪,如 101.11 = 1 * 2^2 + 0 * 2^1 + 1 * 2^0 + 1 * 2^-1 + 1 * 2^-2 。這種浮點數的表示方法是有缺陷的,無法精準的表示特定的数字,以 1/5 為例,可以用 十進制数字 0.2 表示,但是我們無法用二進制數字錶示它,只能近似的表示它,通過增加二進製表示的長度可以提升表示的精度。如下圖所示。

    3.2 IEEE754標準

        在前面談到的定點表示法不能有效的表示一個比較大的数字,例如 5 x 2^100 是用 101 後面跟隨 100 個零的位模式,我們希望能夠通過給定 x 和 y 的值來表示如 x * 2 ^y 的数字。IEEE754 標準使用 V = ( – 1)^S * M * 2^E 的形式來表示一個數。

    • 符號(Sign): S 決定這個數是負數(S = 1 )還是正數 (S = 0), 對於數值為 0 的符號位做特殊解釋。
    • 尾數(Significand): M 是一個二進制小數,範圍為 1 ~ 2 – e , 或者是 0 ~ 1 – e 。
    • 階碼(Exponent): E 的作用是對浮點數進行加權,這個權重是 2 的 E 次冪(E 可能為負數)。

    通過將浮點數的位劃分為三個字段,分別對這些值進行編碼:

    • 一個單獨的符號位 S 。
    • k 位的階碼字段 ,exp = e(0) e(1) e(2) … e(k-1) ,exp 用來編碼階碼 E。
    • n 位的小数字段 ,   frac = f(n-1) … f(1) f(0) ,frac 用來編碼尾數 M。

    下圖是該標準下封裝到字中的兩種最常見的格式。

    此外,根據階碼值(exp),被編碼的值可以分為下圖幾種情況(階碼值全為 0 ,階碼值全為 1 , 階碼值不全為 0 也不全為 1):

    接下來對這幾種格式進行一一介紹~:

    • 規格化浮點數 : 這是最普遍的情況,當 exp 的值不全為 0 也不全為 1 時,就屬於這種情況,這種情況下,階碼值 E = e – bias ,其中 e 為無符號數,即 exp 的值,而 bias 是一個 2^(k-1) – 1 的偏置值(單精度為 127,雙精度為 1023),而小数字段 frac 被解釋為描述小數值 f ,其中 0 <= f < 1,其二進製表示為 0.f(n-1)…f(1)f(0) 的数字,也就是二進制小數點在最高有效位的左邊的形式。尾數定義為 M = 1 + f 。 有時候這種方式也叫做 隱含 1 開頭的表示(implied leading  1),因為這種定義我們可以把 M 看成一個二進製表示為 1.f(n-1) … f(1)f(0) 的数字。既然我們總是能調整階碼 E ,使得尾數 M 在範圍 1 <= M < 2 之中(假設沒有溢出),那麼這樣可以節約一個位,因為第一位總是為 1 。
    • 非規格化浮點數 : 當 exp 的值全為 0 的時候,所表示的浮點數為非規格化類型,E = 1 – bias ,而尾數的值為 M = f 。不含開頭的 1 。非規格化有兩種用途,首先它提供了表示  0 的方法,因為規格化數使得 M >=  1,所以不能表示 0 ,另外非規格化數另一個功能則是表示那些非常接近於 0.0 的數,他們提供了一種屬性,稱為逐漸溢出,其中,可能的數值均勻分佈接近於 0.0 。
    • 特殊值 : 最後一類數值是指當階碼全為 1 的時候出現的。當小數域全為 0 時,表示為無窮大/小,當我們將兩個非常大的數相乘時,或者除以零時,無窮能夠表示溢出的結果。當小數域為非 0 時,結果為 NaN(Not a Number),一些運算的結果不能為實數或者無窮時,會返回 NaN,比如 根號 -1 ,或者 無窮減無窮。此外,在某些應用中也可以用來表示未初始化的數值。

        首先,通過一個字長為 8 位的例子,來看一下IEEE754標準實際上使用時是如何表示的 :

        上圖為展示了假定 w = 8 的字長,k = 4 的階碼位以及 n = 3 的小數位。偏移量為 2 ^ ( k -1 ) -1 = (2 ^ 3) – 1 = 7。圖中分別展示了非規格化數,規格化數以及特殊值是如何編碼的,以及如何結合在一起表示 V = (2^E) * M。我們可以看到,從最大非規格化數到最小規格化數,其值的轉變十分平滑,從 7/512 到 8/512 。這得益於非規格化數的 E 定義為 1 – bias ,最大的非規格化數的階碼值 E 與最小的規格化數的階碼值 E 是相等的,兩者唯一的區別在於 M 值,規格化數尾數 M = 1 + f ,而非規格化的尾數 M = f ,因為非規格化值是用於表示 [0, 1] 區間的小數的,當 f 達到最大值時, f 接近於 1 ,此時最大的非規格化數再進一位,小數 M 只能表示為 1 ,因為此時限制於 f 的位數,沒有比 f 大又比 1 小的小數值 ,進位後轉換成了規格化數,此時 f = 0 , 在階碼值 E 相等的情況下,讓規格化的 M = 1 + f 恰好可以使兩者進行平滑的轉換。

        假如我們使非規格化數的 E = 0 – bias = -7 ,那麼會導致最大非規格化數和最小規格化數的粒度過大,兩者的值分別為 7/1024 和 8/512 。這種定義可以彌補非規格化數的尾數沒有隱含的 1 。通過上述的例子,我們可以發現 ,假如我們把上述的例子按無符號整數表示的話,會發現它的值是有序上升的,這不是偶然的,IEEE 格式如此設計就是為了浮點數能夠使用整數排序函數進行排序。

        通過練習將整數值轉換為浮點數值形式對理解浮點數很有用,以 12345(十進制) 為例,其二進製表示為 1100 0000 1110 01 . 0  ,通過將小數點左移 13 位得到 1.1000000111001 * 2^13 ,我們丟棄開頭的 1 (這裏的 1 就是規格化數隱含的 1),構造小数字段,當 f 不足 23 位的時候,往後填充 0 ,即 M = 1 + f = 1 + 1000 0001 1100 1000 0000 000 ,當 f 大於 23 位的時候,f 多出的位會被捨棄(這裏可以看出浮點數的兩個性質,以 int 類型和 float 類型舉例,當 int 值 大於 2^24 的時候,int 轉換成 float 兩者很有可能值會不相等,因為多出的部分被捨棄了,二是 float 可以表示的數值遠遠大於 int 類型,V =  (-1 ^ S)  * M * 2^E  ,E 最高可以等於 127 ,float 的最大值為 (2^127) * (1 + f),而 int 最大值為 (2^31) -1。

    3.3 舍入

        浮點數的舍入方式有四種,分別是向上舍入,向下舍入,向零舍入,向偶數舍入。下圖是幾種舍入方式的例子 :

    偶數舍入是浮點數默認的舍入方式,可以看到,向偶數舍入時,當小數值為中間值時,會使最低有效数字總為偶數,如 2.5 和 1.5 都舍入為 2 。為什麼使用向偶數舍入呢,假設我們採用向上舍入,用這種方法舍入一組數值,會在計算這些值的平均值中引入統計偏差。我們採用這種方式舍入得到的平均值總是比這些數本身的平均值要略高一些,反之向下舍入亦然,向偶數舍入則可以使在 50% 的時間內向上舍入,50% 的時間內向下舍入。

    4. 小結

      • 計算機將信息編碼為位(bit),通常組織成字節序列,有不同的編碼方式來表示整數,實數和字符串。不同的計算機模型在編碼数字和多字節數據中的字節順序時使用不同的約定。
      • 絕大部分機器使用補碼來編碼整數。對於浮點數使用 IEEE754 標準來編碼。
      • 在進行對無符號和有符號整數進行強制類型轉換時,底層的位模式是不變的。(浮點數與整數轉換則會進行 改變,如 float f = 1.25; int x = f; 此時打印兩者的十六進制值,可以分別輸出為 f = 92463258 ,x = 1 )
      • 由於編碼的長度有限,當超出表示範圍時,有限長度會引起數值溢出,如 x * x 可能會得到負數。當浮點數非常接近於 0.0 時,轉換成 0 時也會產生下溢。
      • 使用補碼運算 ~x + 1 = -x (不適用於 INT_MIN) 。可以通過 (2^k) – 1 生成一個 k 位的掩碼。
      • 浮點數不具備結合率,因為可能發生溢出或者舍入,從而失去精度。如(le20 * le20) * le-20 = 正無窮,而 le20 * (le20 * le-20) = le20 。此外也不具備分配性,如 le20 * (le20 – le20) = 0.0 ,而 le20 * le20 – le20 * le20 = NaN。

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

    【其他文章推薦】

    ※帶您來了解什麼是 USB CONNECTOR  ?

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

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

    ※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

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

  • 計算機專業學生常用網站

    計算機專業學生常用網站

    這篇文章我很早之前就想寫了,但奈何一直沒有合適的時間,也不知道應該怎樣去表達。現在自己是一個大三的學生,學習了兩年多的計算機,我不知道自己算不算合格,和那些初中就搞OI,大三實習就業的大佬們相比,我肯定是不行的,但我覺得我這兩年的學習經歷應該更符合一般的計算機專業學生,可能也更有參考性吧。這裏我主要來分享一下我常用的一些網站,這些網站主要受眾還是學生,關注我的人也大都是同輩的學生,大家可以在評論區討論或補充,這篇博客也會一直更新下去。

    1、Google

    這個網站就不必說了吧,我一直有一種觀點,搜索資料對計算機專業的學生來說是一種特別重要的能力。現在的世界有那麼多的知識,誰也不可能都學會,咱們的大腦只是一個cache級別的存儲器,想要獲得知識、解決問題,學會使用搜索引擎必不可少!至於google被牆,使用百度也是可以的,但注意對信息要辨偽存真,多去比較。

    2、GitHub

    這是開發者最為重要的網站了吧,代碼託管網站。大概大一下學期才注意到這個網站,各種資源應有盡有,想要什麼輪子,上去搜就好了。最令我震驚的是,裏面還有這種網課學習資料,清華北大浙大等名校計算機專業所有課程的資料都齊全,互聯網時代真的太便利了。

     

    3、Stack Overflow

    這個網站也挺有用的,之前我查找一個C++問題,搜索的結果都答不到點上,在上面看到一個美國人的回答才如夢初醒。後來學習過程中遇到什麼 問題,上去搜一下,大概率能搜到答案。缺點也很明顯,這是一個英語網站,大多數回答都是英文,所以要有點計算機英語基礎。至於同樣英文網站的GitHub,我只能感慨,中國程序員太厲害了,中文項目一點也不少,可以說GitHub的繁榮離不開中國人。

     

    4、bilibili

    或許最初這隻是一個二次元愛好者的聚集地,但現在B站應該可以算是中國的油管吧。我在高中的時候入坑B站,申請會員需要答題就讓我知道這個網站應該有着素質不錯的成員,果然這裡有無數的沙雕網友逗你開心,逛B站也成為我高中生活最快樂的消遣方式。上了大學才發現B站也可以是一個學習網站,甚至中央都表揚過B站。跟着B站上的老師學了高數、離散數學、線性代數、数字邏輯、概率論、計算機組成原理等等,我一般是在課堂上聽一遍,課後快到考試周了跟着B站視頻複習一遍,很穩!我愛死這個小破站了!(不知道大家有沒有注意到我博客背景上的22娘33娘呢?)

    5、中國大學MOOC

     這個算是對B站的一個補充的,其實B站中很多學校視頻都是轉自這裏的,很多網課其實是侵權的,隨時可能被B站刪掉。雖然有些可能需要收費,但也還能接受,至少質量有保證。

    6、知乎

    為什麼要提知乎這個看似很無關的網站?我主要覺得這是一個可以用來增長見識的社區,雖然現在它一點點的向微博貼吧靠近。

    知乎給自己的定位是一個網絡問答社區,用戶在上面分享自己專業的知識和見解。據統計與計算機相關的用戶佔總用戶很大的比重,可能大家上班都在划水?

    也有很多大佬直接在知乎上寫博客,平時逛逛知乎把握一下行業動向,聽聽大佬吹吹牛逼,很有意思。

    7、博客園

    這個我立足的地方嘛!我在大一下學期才正式開始寫博客,最初搞ACM,師哥讓我們寫題解,最開始真是不愛寫,隨便應付一下,後來發現寫博客的樂趣,一發不可收拾。每天逛一下,大家說話有好聽,我超級喜歡這裏!

    8、CSDN

    這應該是一個邁不過去的話題吧,我也不想修什麼優越感,但是現在CSDN真的令人不悅呀!以前聽前輩講起過它的輝煌,現在也不能說是沒落了吧,依舊有龐大的用戶,龐大的流量,也算是一個成功的商業網址了。CSDN最讓我反感的一點是找一個問題,所有答案都是一樣複製粘貼過來的,很難找到原作者,問轉載者詳情問題詳情也無法解答。我不反對轉載別人的博客,畢竟要有開源精神,但轉載能不能貼一個原文鏈接?還有積分下載,講一個笑話,我一個師哥發了一份JDK1.8,5C幣下載,一年賺了300C幣,這是在收智商稅?

    9、MSDN 我告訴你

    這個網站可就厲害了,整合了許多微軟原版系統鏡像,精簡版本,純凈系統。

    10、W3school

    就算不去學前端,我們多多少少也要了解一些相關知識,這也算是一個前端只是比較全的網站了。

    11、鳩摩搜索

    能夠搜索出不少專業書,配合pandownload使用吧,這個給出pandownload的使用說明

    12、菜鳥教程

     

     有朋友提到了菜鳥教程,這裏也來介紹一下吧,個人覺得菜鳥教程提供了較為全面,系統詳細的教程,廣告少,界面簡約,聽說網站最早是一個人寫的,很牛。這個網站挺適合新手做輔助,個人覺得就是有些教程獲取不到較為詳細的說明。

    13、各大OJ

    北京大學

    杭州电子科技大學

    浙江大學

    codeforces

    PAT

    藍橋杯

     

     

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

    【其他文章推薦】

    ※為什麼 USB CONNECTOR 是電子產業重要的元件?

    網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

    ※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

    ※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

    新北清潔公司,居家、辦公、裝潢細清專業服務

  • Java I/O體系從原理到應用,這一篇全說清楚了

    Java I/O體系從原理到應用,這一篇全說清楚了

    本文介紹操作系統I/O工作原理,Java I/O設計,基本使用,開源項目中實現高性能I/O常見方法和實現,徹底搞懂高性能I/O之道

    基礎概念

    在介紹I/O原理之前,先重溫幾個基礎概念:

    • (1) 操作系統與內核

    操作系統:管理計算機硬件與軟件資源的系統軟件
    內核:操作系統的核心軟件,負責管理系統的進程、內存、設備驅動程序、文件和網絡系統等等,為應用程序提供對計算機硬件的安全訪問服務

    • 2 內核空間和用戶空間

    為了避免用戶進程直接操作內核,保證內核安全,操作系統將內存尋址空間劃分為兩部分:
    內核空間(Kernel-space),供內核程序使用
    用戶空間(User-space),供用戶進程使用
    為了安全,內核空間和用戶空間是隔離的,即使用戶的程序崩潰了,內核也不受影響

    • 3 數據流

    計算機中的數據是基於隨着時間變換高低電壓信號傳輸的,這些數據信號連續不斷,有着固定的傳輸方向,類似水管中水的流動,因此抽象數據流(I/O流)的概念:指一組有順序的、有起點和終點的字節集合

    抽象出數據流的作用:實現程序邏輯與底層硬件解耦,通過引入數據流作為程序與硬件設備之間的抽象層,面向通用的數據流輸入輸出接口編程,而不是具體硬件特性,程序和底層硬件可以獨立靈活替換和擴展

    I/O 工作原理

    1 磁盤I/O

    典型I/O讀寫磁盤工作原理如下:

    tips: DMA:全稱叫直接內存存取(Direct Memory Access),是一種允許外圍設備(硬件子系統)直接訪問系統主內存的機制。基於 DMA 訪問方式,系統主內存與硬件設備的數據傳輸可以省去CPU 的全程調度

    值得注意的是:

    • 讀寫操作基於系統調用實現
    • 讀寫操作經過用戶緩衝區,內核緩衝區,應用進程並不能直接操作磁盤
    • 應用進程讀操作時需阻塞直到讀取到數據

    2 網絡I/O

    這裏先以最經典的阻塞式I/O模型介紹:

    tips:recvfrom,經socket接收數據的函數

    值得注意的是:

    • 網絡I/O讀寫操作經過用戶緩衝區,Sokcet緩衝區
    • 服務端線程在從調用recvfrom開始到它返回有數據報準備好這段時間是阻塞的,recvfrom返回成功后,線程開始處理數據報

    Java I/O設計

    1 I/O分類

    Java中對數據流進行具體化和實現,關於Java數據流一般關注以下幾個點:

    • (1) 流的方向
      從外部到程序,稱為輸入流;從程序到外部,稱為輸出流

    • (2) 流的數據單位
      程序以字節作為最小讀寫數據單元,稱為字節流,以字符作為最小讀寫數據單元,稱為字符流

    • (3) 流的功能角色

    從/向一個特定的IO設備(如磁盤,網絡)或者存儲對象(如內存數組)讀/寫數據的流,稱為節點流
    對一個已有流進行連接和封裝,通過封裝后的流來實現數據的讀/寫功能,稱為處理流(或稱為過濾流);

    2 I/O操作接口

    java.io包下有一堆I/O操作類,初學時看了容易搞不懂,其實仔細觀察其中還是有規律:
    這些I/O操作類都是在繼承4個基本抽象流的基礎上,要麼是節點流,要麼是處理流

    2.1 四個基本抽象流

    java.io包中包含了流式I/O所需要的所有類,java.io包中有四個基本抽象流,分別處理字節流和字符流:

    • InputStream
    • OutputStream
    • Reader
    • Writer

    2.2 節點流

    節點流I/O類名由節點流類型 + 抽象流類型組成,常見節點類型有:

    • File文件
    • Piped 進程內線程通信管道
    • ByteArray / CharArray (字節數組 / 字符數組)
    • StringBuffer / String (字符串緩衝區 / 字符串)

    節點流的創建通常是在構造函數傳入數據源,例如:

    FileReader reader = new FileReader(new File("file.txt"));
    FileWriter writer = new FileWriter(new File("file.txt"));

    2.3 處理流

    處理流I/O類名由對已有流封裝的功能 + 抽象流類型組成,常見功能有:

    • 緩衝:對節點流讀寫的數據提供了緩衝的功能,數據可以基於緩衝批量讀寫,提高效率。常見有BufferedInputStream、BufferedOutputStream
    • 字節流轉換為字符流:由InputStreamReader、OutputStreamWriter實現
    • 字節流與基本類型數據相互轉換:這裏基本數據類型數據如int、long、short,由DataInputStream、DataOutputStream實現
    • 字節流與對象實例相互轉換:用於實現對象序列化,由ObjectInputStream、ObjectOutputStream實現

    處理流的應用了適配器/裝飾模式,轉換/擴展已有流,處理流的創建通常是在構造函數傳入已有的節點流或處理流:

    FileOutputStream fileOutputStream = new FileOutputStream("file.txt");
    // 擴展提供緩衝寫
    BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
     // 擴展提供提供基本數據類型寫
    DataOutputStream out = new DataOutputStream(bufferedOutputStream);

    3 Java NIO

    3.1 標準I/O存在問題

    Java NIO(New I/O)是一個可以替代標準Java I/O API的IO API(從Java 1.4開始),Java NIO提供了與標準I/O不同的I/O工作方式,目的是為了解決標準 I/O存在的以下問題:

    • (1) 數據多次拷貝

    標準I/O處理,完成一次完整的數據讀寫,至少需要從底層硬件讀到內核空間,再讀到用戶文件,又從用戶空間寫入內核空間,再寫入底層硬件

    此外,底層通過write、read等函數進行I/O系統調用時,需要傳入數據所在緩衝區起始地址和長度
    由於JVM GC的存在,導致對象在堆中的位置往往會發生移動,移動後傳入系統函數的地址參數就不是真正的緩衝區地址了

    可能導致讀寫出錯,為了解決上面的問題,使用標準I/O進行系統調用時,還會額外導致一次數據拷貝:把數據從JVM的堆內拷貝到堆外的連續空間內存(堆外內存)

    所以總共經歷6次數據拷貝,執行效率較低

    • (2) 操作阻塞

    傳統的網絡I/O處理中,由於請求建立連接(connect),讀取網絡I/O數據(read),發送數據(send)等操作是線程阻塞的

    // 等待連接
    Socket socket = serverSocket.accept();
    
    // 連接已建立,讀取請求消息
    StringBuilder req = new StringBuilder();
    byte[] recvByteBuf = new byte[1024];
    int len;
    while ((len = socket.getInputStream().read(recvByteBuf)) != -1) {
        req.append(new String(recvByteBuf, 0, len, StandardCharsets.UTF_8));
    }
    
    // 寫入返回消息
    socket.getOutputStream().write(("server response msg".getBytes()));
    socket.shutdownOutput();

    以上面服務端程序為例,當請求連接已建立,讀取請求消息,服務端調用read方法時,客戶端數據可能還沒就緒(例如客戶端數據還在寫入中或者傳輸中),線程需要在read方法阻塞等待直到數據就緒

    為了實現服務端併發響應,每個連接需要獨立的線程單獨處理,當併發請求量大時為了維護連接,內存、線程切換開銷過大

    3.2 Buffer

    Java NIO核心三大核心組件是Buffer(緩衝區)、Channel(通道)、Selector

    Buffer提供了常用於I/O操作的字節緩衝區,常見的緩存區有ByteBuffer, CharBuffer, DoubleBuffer, FloatBuffer, IntBuffer, LongBuffer, ShortBuffer,分別對應基本數據類型: byte, char, double, float, int, long, short,下面介紹主要以最常用的ByteBuffer為例,Buffer底層支持Java堆內(HeapByteBuffer)或堆外內存(DirectByteBuffer)

    堆外內存是指與堆內存相對應的,把內存對象分配在JVM堆以外的內存,這些內存直接受操作系統管理(而不是虛擬機,相比堆內內存,I/O操作中使用堆外內存的優勢在於:

    • 不用被JVM GC線回收,減少GC線程資源佔有
    • 在I/O系統調用時,直接操作堆外內存,可以節省一次堆外內存和堆內內存的複製

    ByteBuffer底層堆外內存的分配和釋放基於malloc和free函數,對外allocateDirect方法可以申請分配堆外內存,並返回繼承ByteBuffer類的DirectByteBuffer對象:

    public static ByteBuffer allocateDirect(int capacity) {
        return new DirectByteBuffer(capacity);
    }

    堆外內存的回收基於DirectByteBuffer的成員變量Cleaner類,提供clean方法可以用於主動回收,Netty中大部分堆外內存通過記錄定位Cleaner的存在,主動調用clean方法來回收;
    另外,當DirectByteBuffer對象被GC時,關聯的堆外內存也會被回收

    tips: JVM參數不建議設置-XX:+DisableExplicitGC,因為部分依賴Java NIO的框架(例如Netty)在內存異常耗盡時,會主動調用System.gc(),觸發Full GC,回收DirectByteBuffer對象,作為回收堆外內存的最後保障機制,設置該參數之後會導致在該情況下堆外內存得不到清理

    堆外內存基於基礎ByteBuffer類的DirectByteBuffer類成員變量:Cleaner對象,這個Cleaner對象會在合適的時候執行unsafe.freeMemory(address),從而回收這塊堆外內存

    Buffer可以見到理解為一組基本數據類型,存儲地址連續的的數組,支持讀寫操作,對應讀模式和寫模式,通過幾個變量來保存這個數據的當前位置狀態:capacity、 position、 limit:

    • capacity 緩衝區數組的總長度
    • position 下一個要操作的數據元素的位置
    • limit 緩衝區數組中不可操作的下一個元素的位置:limit <= capacity

    3.3 Channel

    Channel(通道)的概念可以類比I/O流對象,NIO中I/O操作主要基於Channel:
    從Channel進行數據讀取 :創建一個緩衝區,然後請求Channel讀取數據
    從Channel進行數據寫入 :創建一個緩衝區,填充數據,請求Channel寫入數據

    Channel和流非常相似,主要有以下幾點區別:

    • Channel可以讀和寫,而標準I/O流是單向的
    • Channel可以異步讀寫,標準I/O流需要線程阻塞等待直到讀寫操作完成
    • Channel總是基於緩衝區Buffer讀寫

    Java NIO中最重要的幾個Channel的實現:

    • FileChannel: 用於文件的數據讀寫,基於FileChannel提供的方法能減少讀寫文件數據拷貝次數,後面會介紹
    • DatagramChannel: 用於UDP的數據讀寫
    • SocketChannel: 用於TCP的數據讀寫,代表客戶端連接
    • ServerSocketChannel: 監聽TCP連接請求,每個請求會創建會一個SocketChannel,一般用於服務端

    基於標準I/O中,我們第一步可能要像下面這樣獲取輸入流,按字節把磁盤上的數據讀取到程序中,再進行下一步操作,而在NIO編程中,需要先獲取Channel,再進行讀寫

    FileInputStream fileInputStream = new FileInputStream("test.txt");
    FileChannel channel = fileInputStream.channel();

    tips: FileChannel僅能運行在阻塞模式下,文件異步處理的 I/O 是在JDK 1.7 才被加入的 java.nio.channels.AsynchronousFileChannel

    // server socket channel:
    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    serverSocketChannel.bind(new InetSocketAddress(InetAddress.getLocalHost(), 9091));
    
    while (true) {
        SocketChannel socketChannel = serverSocketChannel.accept();
        ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
        int readBytes = socketChannel.read(buffer);
        if (readBytes > 0) {
            // 從寫數據到buffer翻轉為從buffer讀數據
            buffer.flip();
            byte[] bytes = new byte[buffer.remaining()];
            buffer.get(bytes);
            String body = new String(bytes, StandardCharsets.UTF_8);
            System.out.println("server 收到:" + body);
        }
    }

    3.4 Selector

    Selector(選擇器) ,它是Java NIO核心組件中的一個,用於檢查一個或多個NIO Channel(通道)的狀態是否處於可讀、可寫。實現單線程管理多個Channel,也就是可以管理多個網絡連接

    Selector核心在於基於操作系統提供的I/O復用功能,單個線程可以同時監視多個連接描述符,一旦某個連接就緒(一般是讀就緒或者寫就緒),能夠通知程序進行相應的讀寫操作,常見有select、poll、epoll等不同實現

    Java NIO Selector基本工作原理如下:

    • (1) 初始化Selector對象,服務端ServerSocketChannel對象
    • (2) 向Selector註冊ServerSocketChannel的socket-accept事件
    • (3) 線程阻塞於selector.select(),當有客戶端請求服務端,線程退出阻塞
    • (4) 基於selector獲取所有就緒事件,此時先獲取到socket-accept事件,向Selector註冊客戶端SocketChannel的數據就緒可讀事件事件
    • (5) 線程再次阻塞於selector.select(),當有客戶端連接數據就緒,可讀
    • (6) 基於ByteBuffer讀取客戶端請求數據,然後寫入響應數據,關閉channel

    示例如下,完整可運行代碼已經上傳github(https://github.com/caison/caison-blog-demo):

    Selector selector = Selector.open();
    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    serverSocketChannel.bind(new InetSocketAddress(9091));
    // 配置通道為非阻塞模式
    serverSocketChannel.configureBlocking(false);
    // 註冊服務端的socket-accept事件
    serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
    
    while (true) {
        // selector.select()會一直阻塞,直到有channel相關操作就緒
        selector.select();
        // SelectionKey關聯的channel都有就緒事件
        Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
    
        while (keyIterator.hasNext()) {
            SelectionKey key = keyIterator.next();
            // 服務端socket-accept
            if (key.isAcceptable()) {
                // 獲取客戶端連接的channel
                SocketChannel clientSocketChannel = serverSocketChannel.accept();
                // 設置為非阻塞模式
                clientSocketChannel.configureBlocking(false);
                // 註冊監聽該客戶端channel可讀事件,併為channel關聯新分配的buffer
                clientSocketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocateDirect(1024));
            }
    
            // channel可讀
            if (key.isReadable()) {
                SocketChannel socketChannel = (SocketChannel) key.channel();
                ByteBuffer buf = (ByteBuffer) key.attachment();
    
                int bytesRead;
                StringBuilder reqMsg = new StringBuilder();
                while ((bytesRead = socketChannel.read(buf)) > 0) {
                    // 從buf寫模式切換為讀模式
                    buf.flip();
                    int bufRemain = buf.remaining();
                    byte[] bytes = new byte[bufRemain];
                    buf.get(bytes, 0, bytesRead);
                    // 這裏當數據包大於byteBuffer長度,有可能有粘包/拆包問題
                    reqMsg.append(new String(bytes, StandardCharsets.UTF_8));
                    buf.clear();
                }
                System.out.println("服務端收到報文:" + reqMsg.toString());
                if (bytesRead == -1) {
                    byte[] bytes = "[這是服務回的報文的報文]".getBytes(StandardCharsets.UTF_8);
    
                    int length;
                    for (int offset = 0; offset < bytes.length; offset += length) {
                        length = Math.min(buf.capacity(), bytes.length - offset);
                        buf.clear();
                        buf.put(bytes, offset, length);
                        buf.flip();
                        socketChannel.write(buf);
                    }
                    socketChannel.close();
                }
            }
            // Selector不會自己從已selectedKeys中移除SelectionKey實例
            // 必須在處理完通道時自己移除 下次該channel變成就緒時,Selector會再次將其放入selectedKeys中
            keyIterator.remove();
        }
    }

    tips: Java NIO基於Selector實現高性能網絡I/O這塊使用起來比較繁瑣,使用不友好,一般業界使用基於Java NIO進行封裝優化,擴展豐富功能的Netty框架來優雅實現

    高性能I/O優化

    下面結合業界熱門開源項目介紹高性能I/O的優化

    1 零拷貝

    零拷貝(zero copy)技術,用於在數據讀寫中減少甚至完全避免不必要的CPU拷貝,減少內存帶寬的佔用,提高執行效率,零拷貝有幾種不同的實現原理,下面介紹常見開源項目中零拷貝實現

    1.1 Kafka零拷貝

    Kafka基於Linux 2.1內核提供,並在2.4 內核改進的的sendfile函數 + 硬件提供的DMA Gather Copy實現零拷貝,將文件通過socket傳送

    函數通過一次系統調用完成了文件的傳送,減少了原來read/write方式的模式切換。同時減少了數據的copy, sendfile的詳細過程如下:

    基本流程如下:

    • (1) 用戶進程發起sendfile系統調用
    • (2) 內核基於DMA Copy將文件數據從磁盤拷貝到內核緩衝區
    • (3) 內核將內核緩衝區中的文件描述信息(文件描述符,數據長度)拷貝到Socket緩衝區
    • (4) 內核基於Socket緩衝區中的文件描述信息和DMA硬件提供的Gather Copy功能將內核緩衝區數據複製到網卡
    • (5) 用戶進程sendfile系統調用完成並返回

    相比傳統的I/O方式,sendfile + DMA Gather Copy方式實現的零拷貝,數據拷貝次數從4次降為2次,系統調用從2次降為1次,用戶進程上下文切換次數從4次變成2次DMA Copy,大大提高處理效率

    Kafka底層基於java.nio包下的FileChannel的transferTo:

    public abstract long transferTo(long position, long count, WritableByteChannel target)

    transferTo將FileChannel關聯的文件發送到指定channel,當Comsumer消費數據,Kafka Server基於FileChannel將文件中的消息數據發送到SocketChannel

    1.2 RocketMQ零拷貝

    RocketMQ基於mmap + write的方式實現零拷貝:
    mmap() 可以將內核中緩衝區的地址與用戶空間的緩衝區進行映射,實現數據共享,省去了將數據從內核緩衝區拷貝到用戶緩衝區

    tmp_buf = mmap(file, len); 
    write(socket, tmp_buf, len);

    mmap + write 實現零拷貝的基本流程如下:

    • (1) 用戶進程向內核發起系統mmap調用
    • (2) 將用戶進程的內核空間的讀緩衝區與用戶空間的緩存區進行內存地址映射
    • (3) 內核基於DMA Copy將文件數據從磁盤複製到內核緩衝區
    • (4) 用戶進程mmap系統調用完成並返回
    • (5) 用戶進程向內核發起write系統調用
    • (6) 內核基於CPU Copy將數據從內核緩衝區拷貝到Socket緩衝區
    • (7) 內核基於DMA Copy將數據從Socket緩衝區拷貝到網卡
    • (8) 用戶進程write系統調用完成並返回

    RocketMQ中消息基於mmap實現存儲和加載的邏輯寫在org.apache.rocketmq.store.MappedFile中,內部實現基於nio提供的java.nio.MappedByteBuffer,基於FileChannel的map方法得到mmap的緩衝區:

    // 初始化
    this.fileChannel = new RandomAccessFile(this.file, "rw").getChannel();
    this.mappedByteBuffer = this.fileChannel.map(MapMode.READ_WRITE, 0, fileSize);

    查詢CommitLog的消息時,基於mappedByteBuffer偏移量pos,數據大小size查詢:

    public SelectMappedBufferResult selectMappedBuffer(int pos, int size) {
        int readPosition = getReadPosition();
        // ...各種安全校驗
        
        // 返回mappedByteBuffer視圖
        ByteBuffer byteBuffer = this.mappedByteBuffer.slice();
        byteBuffer.position(pos);
        ByteBuffer byteBufferNew = byteBuffer.slice();
        byteBufferNew.limit(size);
        return new SelectMappedBufferResult(this.fileFromOffset + pos, byteBufferNew, size, this);
    }

    tips: transientStorePoolEnable機制
    Java NIO mmap的部分內存並不是常駐內存,可以被置換到交換內存(虛擬內存),RocketMQ為了提高消息發送的性能,引入了內存鎖定機制,即將最近需要操作的CommitLog文件映射到內存,並提供內存鎖定功能,確保這些文件始終存在內存中,該機制的控制參數就是transientStorePoolEnable

    因此,MappedFile數據保存CommitLog刷盤有2種方式:

    • 1 開啟transientStorePoolEnable:寫入內存字節緩衝區(writeBuffer) -> 從內存字節緩衝區(writeBuffer)提交(commit)到文件通道(fileChannel) -> 文件通道(fileChannel) -> flush到磁盤
    • 2 未開啟transientStorePoolEnable:寫入映射文件字節緩衝區(mappedByteBuffer) -> 映射文件字節緩衝區(mappedByteBuffer) -> flush到磁盤

    RocketMQ 基於 mmap+write 實現零拷貝,適用於業務級消息這種小塊文件的數據持久化和傳輸
    Kafka 基於 sendfile 這種零拷貝方式,適用於系統日誌消息這種高吞吐量的大塊文件的數據持久化和傳輸

    tips: Kafka 的索引文件使用的是 mmap+write 方式,數據文件發送網絡使用的是 sendfile 方式

    1.3 Netty零拷貝

    Netty 的零拷貝分為兩種:

    • 1 基於操作系統實現的零拷貝,底層基於FileChannel的transferTo方法
    • 2 基於Java 層操作優化,對數組緩存對象(ByteBuf )進行封裝優化,通過對ByteBuf數據建立數據視圖,支持ByteBuf 對象合併,切分,當底層僅保留一份數據存儲,減少不必要拷貝

    2 多路復用

    Netty中對Java NIO功能封裝優化之後,實現I/O多路復用代碼優雅了很多:

    // 創建mainReactor
    NioEventLoopGroup boosGroup = new NioEventLoopGroup();
    // 創建工作線程組
    NioEventLoopGroup workerGroup = new NioEventLoopGroup();
    
    final ServerBootstrap serverBootstrap = new ServerBootstrap();
    serverBootstrap 
         // 組裝NioEventLoopGroup 
        .group(boosGroup, workerGroup)
         // 設置channel類型為NIO類型
        .channel(NioServerSocketChannel.class)
        // 設置連接配置參數
        .option(ChannelOption.SO_BACKLOG, 1024)
        .childOption(ChannelOption.SO_KEEPALIVE, true)
        .childOption(ChannelOption.TCP_NODELAY, true)
        // 配置入站、出站事件handler
        .childHandler(new ChannelInitializer<NioSocketChannel>() {
            @Override
            protected void initChannel(NioSocketChannel ch) {
                // 配置入站、出站事件channel
                ch.pipeline().addLast(...);
                ch.pipeline().addLast(...);
            }
        });
    
    // 綁定端口
    int port = 8080;
    serverBootstrap.bind(port).addListener(future -> {
        if (future.isSuccess()) {
            System.out.println(new Date() + ": 端口[" + port + "]綁定成功!");
        } else {
            System.err.println("端口[" + port + "]綁定失敗!");
        }
    });

    3 頁緩存(PageCache)

    頁緩存(PageCache)是操作系統對文件的緩存,用來減少對磁盤的 I/O 操作,以頁為單位的,內容就是磁盤上的物理塊,頁緩存能幫助程序對文件進行順序讀寫的速度幾乎接近於內存的讀寫速度,主要原因就是由於OS使用PageCache機制對讀寫訪問操作進行了性能優化:

    頁緩存讀取策略:當進程發起一個讀操作 (比如,進程發起一個 read() 系統調用),它首先會檢查需要的數據是否在頁緩存中:

    • 如果在,則放棄訪問磁盤,而直接從頁緩存中讀取
    • 如果不在,則內核調度塊 I/O 操作從磁盤去讀取數據,並讀入緊隨其後的少數幾個頁面(不少於一個頁面,通常是三個頁面),然後將數據放入頁緩存中

    頁緩存寫策略:當進程發起write系統調用寫數據到文件中,先寫到頁緩存,然後方法返回。此時數據還沒有真正的保存到文件中去,Linux 僅僅將頁緩存中的這一頁數據標記為“臟”,並且被加入到臟頁鏈表中

    然後,由flusher 回寫線程周期性將臟頁鏈表中的頁寫到磁盤,讓磁盤中的數據和內存中保持一致,最後清理“臟”標識。在以下三種情況下,臟頁會被寫回磁盤:

    • 空閑內存低於一個特定閾值
    • 臟頁在內存中駐留超過一個特定的閾值時
    • 當用戶進程調用 sync() 和 fsync() 系統調用時

    RocketMQ中,ConsumeQueue邏輯消費隊列存儲的數據較少,並且是順序讀取,在page cache機制的預讀取作用下,Consume Queue文件的讀性能幾乎接近讀內存,即使在有消息堆積情況下也不會影響性能,提供了2種消息刷盤策略:

    • 同步刷盤:在消息真正持久化至磁盤后RocketMQ的Broker端才會真正返回給Producer端一個成功的ACK響應
    • 異步刷盤,能充分利用操作系統的PageCache的優勢,只要消息寫入PageCache即可將成功的ACK返回給Producer端。消息刷盤採用後台異步線程提交的方式進行,降低了讀寫延遲,提高了MQ的性能和吞吐量

    Kafka實現消息高性能讀寫也利用了頁緩存,這裏不再展開

    參考

    《深入理解Linux內核 —— Daniel P.Bovet》

    更多精彩,歡迎關注公眾號 分佈式系統架構

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

    【其他文章推薦】

    USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

    ※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

    ※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

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

    ※幫你省時又省力,新北清潔一流服務好口碑

  • 程序員需要了解的硬核知識之操作系統入門

    程序員需要了解的硬核知識之操作系統入門

    對於程序員來說,最莫大的榮耀莫過於自己的軟件被大多數人使用了吧。

    歷史文章請戳

    本篇文章作為操作系統的入門文章,可能入門都算不上吧,畢竟操作系統太龐大和複雜了。本篇文章主要帶你了解一下我們常用的操作系統環境。

    操作系統環境

    程序中包含着運行環境這一內容,可以說 運行環境 = 操作系統 + 硬件 ,操作系統又可以被稱為軟件,它是由一系列的指令組成的。我們不介紹操作系統,我們主要來介紹一下硬件的識別。

    我們肯定都玩兒過遊戲,你玩兒遊戲前需要干什麼?是不是需要先看一下自己的筆記本或者電腦是不是能肝的起遊戲?下面是一個遊戲的配置(懷念一下 wow)

    圖中的主要配置如下

    • 操作系統版本:說的就是應用程序運行在何種系統環境,現在市面上主要有三種操作系統環境,Windows 、Linux 和 Unix ,一般我們玩兒的大型遊戲幾乎都是在 Windows 上運行,可以說 Windows 是遊戲的天堂。Windows 操作系統也會有區分,分為32位操作系統和64位操作系統,互不兼容。
    • 處理器:處理器指的就是 CPU,你的電腦的計算能力,通俗來講就是每秒鐘能處理的指令數,如果你的電腦覺得卡帶不起來的話,很可能就是 CPU 的計算能力不足導致的。想要加深理解,請閱讀博主的另一篇文章:

    • 顯卡:顯卡承擔圖形的輸出任務,因此又被稱為圖形處理器(Graphic Processing Unit,GPU),顯卡也非常重要,比如我之前玩兒的劍靈開五檔(其實就是圖像變得更清晰)會卡,其實就是顯卡显示不出來的原因。
    • 內存:內存即主存,就是你的應用程序在運行時能夠動態分析指令的這部分存儲空間,它的大小也能決定你電腦的運行速度,想要加深理解,請閱讀博主的另一篇文章

    • 存儲空間:存儲空間指的就是應用程序安裝所佔用的磁盤空間,由圖中可知,此遊戲的最低存儲空間必須要大於 5GB,其實我們都會遺留很大一部分用來安裝遊戲。

    從程序的運行環境這一角度來考量的話,CPU 的種類是特別重要的參數,為了使程序能夠正常運行,必須滿足 CPU 所需的最低配置。

    CPU 只能解釋其自身固有的語言。不同的 CPU 能解釋的機器語言的種類也是不同的。機器語言的程序稱為 本地代碼(native code),程序員用 C 等高級語言編寫的程序,僅僅是文本文件。文本文件(排除文字編碼的問題)在任何環境下都能显示和編輯。我們稱之為源代碼。通過對源代碼進行編譯,就可以得到本地代碼。下圖反映了這個過程。

    Windows 操作系統克服了CPU以外的硬件差異

    計算機的硬件並不僅僅是由 CPU 組成的,還包括用於存儲程序指令的數據和內存,以及通過 I/O 連接的鍵盤、显示器、硬盤、打印機等外圍設備。

    在 WIndows 軟件中,鍵盤輸入、显示器輸出等並不是直接向硬件發送指令。而是通過向 Windows 發送指令實現的。因此,程序員就不用注意內存和 I/O 地址的不同構成了。Windows 操作的是硬件而不是軟件,軟件通過操作 Windows 系統可以達到控制硬件的目的。

    不同操作系統的 API 差異性

    接下來我們看一下操作系統的種類。同樣機型的計算機,可安裝的操作系統類型也會有多種選擇。例如:AT 兼容機除了可以安裝 Windows 之外,還可以採用 Unix 系列的 Linux 以及 FreeBSD (也是一種Unix操作系統)等多個操作系統。當然,應用軟件則必須根據不同的操作系統類型來專門開發。CPU 的類型不同,所對應機器的語言也不同,同樣的道理,操作系統的類型不同,應用程序向操作系統傳遞指令的途徑也不同

    應用程序向系統傳遞指令的途徑稱為 API(Application Programming Interface)。Windows 以及 Linux 操作系統的 API,提供了任何應用程序都可以利用的函數組合。因為不同操作系統的 API 是有差異的。所以,如何要將同樣的應用程序移植到另外的操作系統,就必須要覆蓋應用所用到的 API 部分。

    鍵盤輸入、鼠標輸入、显示器輸出、文件輸入和輸出等同外圍設備進行交互的功能,都是通過 API 提供的。

    這也就是為什麼 Windows 應用程序不能直接移植到 Linux 操作系統上的原因,API 差異太大了。

    在同類型的操作系統下,不論硬件如何,API 幾乎相同。但是,由於不同種類 CPU 的機器語言不同,因此本地代碼也不盡相同。

    FreeBSD Port 幫你輕鬆使用源代碼

    不知道你有沒有這個想法:“既然 CPU 不同會導致本地代碼不同,那為何不將源代碼直接發送給程序呢?”這確實是一種解決辦法,Unix 系列的 FreeBSD 操作系統就使用了這種方式。

    Unix 系列操作系統 FreeBSD 中,存在一種名為 Ports 的機制。該機制能夠結合當前運行環境的硬件環境來編譯應用的源代碼,進而得到可以運行的本地代碼。如果目標應用的源代碼在硬件上找不到,Ports 就會自動使用 FTP 連接到相應站點下載代碼。

    全球有很多站點都提供適用於 FreeBSD 的應用源代碼。通過使用 Ports 可以利用的程序源代碼,大約有 16000 種。根據不同的領域進行分類,可以隨時使用。

    FreeBSD 上應用的源代碼,大部分是用 C 語言來標註的,C 編譯器可以結合 FreeBSD 的運行環境來生成合適的本地代碼。

    FTP( File Transfer Protocol) 是連接到互聯網上的計算機之間的傳送文件的協議。

    可以使用虛擬機獲取其他環境

    即使不通過應用程序的移植,在同一個操作系統上仍然可以使用其他的操作系統,那就是使用 虛擬機軟件。虛擬機(Virtual Machine)指通過軟件的具有完整硬件系統功能的、運行在一個完全隔離環境中的完整計算機系統。在實體計算機中能夠完成的工作在虛擬機中都能夠實現。

    提供相同運行環境的 Java 虛擬機

    總算是提到大 Java 了, Java 大法好,除了虛擬機的方法之外,還有一種方法能夠提供不依賴於特定硬件和操作系統的程序運行環境,那就是 Java。

    大家說的 Java 其實有兩層意思,一種是作為編程語言的 Java;一種是作為程序運行環境的 Java。Java 與其他語言相同,都是通過源代碼編譯后運行的。不過,編譯後生成的不是特定 CPU 使用的本地代碼,而是名為字節代碼 的程序。直接代碼的運行環境就稱為 Java 虛擬機(Java Virtual Machine)。Java 虛擬機是一邊把 Java 字節代碼逐一轉換為本地代碼一邊在運行着。

    程序運行時,將編譯后的字節代碼轉換為本地代碼,這樣的操作看上去有些迂迴,但由此可以實現相同的字節碼可以在不同的操作系統環境下運行。

    想象一下,你開發完成的應用部署到 Linux 環境下,是不是什麼都不用管?

    Windows 有專門的 Windows 虛擬機,Macintosh 有 Macintosh 專門的虛擬機。從操作系統來看,Java虛擬機就是一個應用,從運行環境上來看,Java 虛擬機就是運行環境。

    BIOS 和引導

    最後對一些比較基礎的部分做一些補充說明。程序的運行環境,存在着名為 BIOS(Basic Input/Output System)的系統。BIOS 存儲在 ROM 中,是預先內置在計算機主機內部的程序。BIOS 除了鍵盤、磁盤和顯卡等基本控制外,還有引導程序的功能。引導程序是存儲在啟動驅動器啟示區域的小程序。操作系統的啟動驅動器一般硬盤。不過有時也可能是 CD-ROM 或軟盤。

    電腦開機后,BIOS 會確認硬件是否正常運行,沒有異常的話會直接啟動引導程序。引導程序的功能是把在硬盤等記錄的 OS 加載到內存中運行。雖然啟動應用是 OS 的功能,但 OS 不能啟動自己,是通過引導程序來啟動的。

    文章參考:

    《程序是怎樣跑起來的》第七章

    關注公眾號後台回復 191106 即可獲得《程序是怎樣跑起來的》电子書

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

    【其他文章推薦】

    ※帶您來了解什麼是 USB CONNECTOR  ?

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

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

    ※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

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

  • MapReduce任務提交源碼分析

    MapReduce任務提交源碼分析

      為了測試MapReduce提交的詳細流程。需要在提交這一步打上斷點:

       F7進入方法:

       進入submit方法:

       注意這個connect方法,它在連接誰呢?我們知道,Driver是作為客戶端存在的,那麼客戶端連接的應該就是Yarn集群,但是在這個簡單的WordCount案例中,並沒有將任務提交到Yarn集群,而是在本機中執行的。所以先假設這裏連接的自然就是本機。

    進入這個connect方法,然後在裏面的Cluster(集群的意思)方法上打上斷點:

     

     很明顯,這是一個構造器,他把集群抽象成了一個對象。進入此方法:

     初始化了一個客戶端協議,進入這個create方法,看看他是如何初始化客戶端協議的:

       注意這個mapreduce.framework.name,他就是mapred-site.xml文件中的mapreduce.framework.name項的值,由於我並沒有提交到集群上,而是在本機,所以他會加載本機的mapred-site.xml文件,但是我本機下的該文件並沒有像集群上那樣配置了mapred-site.xml文件,只有一份mapred-site.xml.template文件,更沒有mapreduce.framework.name這一項,所以上面代碼中的值就默認為local了。

      本機上的配置:

     集群上的配置:

    create方法最終反回了一個LocalJobRunner對象,如果上面的值是yarn,返回的則是YarnJobRunner。繼續往下:

       會發現這個LocalJobRunner就是客戶端。然後一直往下,直到完成connect方法,會發現整個過程中在connect方法中創建了一個Cluster對象,然後在Cluster對象裏面獲取到了客戶端。

      緊接着,由於connect方法初始化了cluster對象,所以接下來創建了submitter對象,用於提交任務。進入submitJobInternal方法打上斷點

     

    這個checkSpecs方法就是用來檢查路徑的,當輸入路徑不存在或輸出路徑已存在時會報錯。進入此方法后再進入內部方法就一目瞭然了:

     

    繼續往下執行,完成checkSpecs方法后完初始化一個路徑:

    打開此路徑,此時還是空的文件夾:

    繼續往下,隨後做了一些獲取IP和往配置文件中設置IP和hostname的操作:

     繼續往下,隨後又在上面的路徑下隨機創建了一個目錄:

    繼續往下,見到copyAndConfigureFiles時,進入此方法,然後在進入內部方法uploadFiles():

    在uploadFiles方法中有,執行后的效果:

     

     

     

     

     這個文件夾暫時不知道幹嘛的,只知道copyAndConfigureFiles方法創建了這個路徑。此方法執行完后,執行到writeSplits進入此方法:

     這個方法是重點:切片是怎麼切的呢?

     maps是int型,這表示這啟動的maptask的數量也該和切片的數量保持一致。而具體切成多少片呢?

    上面的方法多態調用到子類的方法writeNewSplits,然後調用getSplits方法:

    getSplits方法中有這樣一段:

       也就是說:當剩餘長度比切片尺寸大於1.1時,就會產生新的切片,比如說文件65m,splitSize為32m,第一片切到32m,剩餘33m,由於33/32<1.1,所以就不再切片,65m被切成兩片,0~32、32~65,而不是0~32 、 32~64 、 64~65三片。

       當執行完writeSplits方法后,會在上面創建的目錄下生成幾個文件:

     上面生成的文件中保存了“切片的規劃信息”。繼續往下,當執行到writeConf方法后,會繼續在上面的目錄下生成與此Job相關的配置文件:

     

       繼續執行,當執行完submitJob方法后,job本身(即WordCount程序本身會被打成jar包被提交)就被提交了:

      但是由於我是直接在本地執行的,直接從main方法進來執行的,沒必要再打成jar包再從main方法進入,所以並不用提交,但是如果是是在yarn 上執行的時候,會把程序打成jar包放在上面的目錄下。

       當任務執行完畢后,上面的目錄會被清空:

       至此,整個任務的提交執行完成,試想一下,如果這個任務在Yarn上執行是什麼樣子呢?基本的邏輯不會變:

      首先,還是執行connect方法,初始化到Cluster對象,然後創建JobRunner,不過在Yarn上執行的JobRunner就不是LocalJobRunner了,而是YarnJobRunner。執行完connect方法後會在HDFS文件系統創建一個路徑,其作用與上面創建的路徑相同,用於保存切片方案信息和配置文件信息,同時會將任務本身的jar包放入其中,最後任務執行完,這些內容又將被銷毀。

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

    【其他文章推薦】

    ※為什麼 USB CONNECTOR 是電子產業重要的元件?

    網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

    ※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

    ※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

    新北清潔公司,居家、辦公、裝潢細清專業服務

  • python機器學習——隨機梯度下降

    python機器學習——隨機梯度下降

    上一篇我們實現了使用梯度下降法的自適應線性神經元,這個方法會使用所有的訓練樣本來對權重向量進行更新,也可以稱之為批量梯度下降(batch gradient descent)。假設現在我們數據集中擁有大量的樣本,比如百萬條樣本,那麼如果我們現在使用批量梯度下降來訓練模型,每更新一次權重向量,我們都要使用百萬條樣本,訓練時間很長,效率很低,我們能不能找到一種方法,既能使用梯度下降法,但是又不要每次更新權重都要使用到所有的樣本,於是隨機梯度下降法(stochastic gradient descent)便被提出來了。

    隨機梯度下降法可以只用一個訓練樣本來對權重向量進行更新:
    \[ \eta(y^i-\phi(z^i))x^i \]
    這種方法比批量梯度下降法收斂的更快,因為它可以更加頻繁的更新權重向量,並且使用當個樣本來更新權重,相比於使用全部的樣本來更新更具有隨機性,有助於算法避免陷入到局部最小值,使用這個方法的要注意在選取樣本進行更新時一定要隨機選取,每次迭代前都要打亂所有的樣本順序,保證訓練的隨機性,並且在訓練時的學習率也不是固定不變的,可以隨着迭代次數的增加,學習率逐漸減小,這種方法可以有助於算法收斂。

    現在我們有了使用全部樣本的批量梯度下降法,也有了使用單個樣本的隨機梯度下降法,那麼一種折中的方法,稱為最小批學習(mini-batch learning),它每次使用一部分訓練樣本來更新權重向量。

    接下來我們實現使用隨機梯度下降法的Adaline

    from numpy.random import seed
    class AdalineSGD(object):
        """ADAptive LInear NEuron classifier.
    
        Parameters
        ----------
        eta:float
            Learning rate(between 0.0 and 1.0
        n_iter:int
            Passes over the training dataset.
    
        Attributes
        ----------
        w_: 1d-array
            weights after fitting.
        errors_: list
            Number of miscalssifications in every epoch.
        shuffle:bool(default: True)
            Shuffle training data every epoch
            if True to prevent cycles.
        random_state: int(default: None)
            Set random state for shuffling
            and initalizing the weights.
    
        """
    
        def __init__(self, eta=0.01, n_iter=10, shuffle=True, random_state=None):
            self.eta = eta
            self.n_iter = n_iter
            self.w_initialized = False
            self.shuffle = shuffle
            if random_state:
                seed(random_state)
    
        def fit(self, X, y):
            """Fit training data.
    
            :param X:{array-like}, shape=[n_samples, n_features]
            :param y: array-like, shape=[n_samples]
            :return:
            self:object
    
            """
    
            self._initialize_weights(X.shape[1])
            self.cost_ = []
    
            for i in range(self.n_iter):
                if self.shuffle:
                    X, y = self._shuffle(X, y)
                cost = []
                for xi, target in zip(X, y):
                    cost.append(self._update_weights(xi, target))
                avg_cost = sum(cost)/len(y)
                self.cost_.append(avg_cost)
            return self
        
        def partial_fit(self, X, y):
            """Fit training data without reinitializing the weights."""
            if not self.w_initialized:
                self._initialize_weights(X.shape[1])
            if y.ravel().shape[0] > 1:
                for xi, target in zip(X, y):
                    self._update_weights(xi, target)
            else:
                self._update_weights(X, y)
            return self
        
        def _shuffle(self, X, y):
            """Shuffle training data"""
            r = np.random.permutation(len(y))
            return X[r], y[r]
        
        def _initialize_weights(self, m):
            """Initialize weights to zeros"""
            self.w_ = np.zeros(1 + m)
            self.w_initialized = True
        
        def _update_weights(self, xi, target):
            """Apply Adaline learning rule to update the weights"""
            output = self.net_input(xi)
            error = (target - output)
            self.w_[1:] += self.eta * xi.dot(error)
            self.w_[0] += self.eta * error
            cost = 0.5 * error ** 2
            return cost
        
        def net_input(self, X):
            """Calculate net input"""
            return np.dot(X, self.w_[1:]) + self.w_[0]
        
        def activation(self, X):
            """Computer linear activation"""
            return self.net_input(X)
        
        def predict(self, X):
            """Return class label after unit step"""
            return np.where(self.activation(X) >= 0.0, 1, -1)
    

    其中_shuffle方法中,調用numpy.random中的permutation函數得到0-100的一個隨機序列,然後這個序列作為特徵矩陣和類別向量的下標,就可以起到打亂樣本順序的功能。

    現在開始訓練

    ada = AdalineSGD(n_iter=15, eta=0.01, random_state=1)
    ada.fit(X_std, y)

    畫出分界圖和訓練曲線圖

    plot_decision_region(X_std, y, classifier=ada)
    plt.title('Adaline - Stochastic Gradient Desent')
    plt.xlabel('sepal length [standardized]')
    plt.ylabel('petal length [standardized]')
    plt.legend(loc = 'upper left')
    plt.show()
    plt.plot(range(1, len(ada.cost_) + 1), ada.cost_, marker='o')
    plt.xlabel('Epochs')
    plt.ylabel('Average Cost')
    plt.show()

    從上圖可以看出,平均損失下降很快,在大概第15次迭代后,分界線和使用批量梯度下降的Adaline分界線很類似。

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

    【其他文章推薦】

    USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

    ※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

    ※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

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

    ※幫你省時又省力,新北清潔一流服務好口碑

  • 研究:熱浪恐同時襲擊大糧倉 造成食物短缺

    摘錄自2019年12月10日中央社報導

    德國波茨坦氣候影響研究中心(Potsdam Institute for Climate Impact Research)氣候學家、也在荷蘭阿姆斯特丹自由大學(VU University Amsterdam)擔任副教授的庫姆(Dim Coumou)共同執筆在「自然氣候變遷」(Nature Climate Change)期刊,該報告警告,氣候變遷可能導致全球各地出現更強烈的熱浪,並在同一時間襲擊各大糧倉,造成食物短缺,引發動亂。

    庫姆表示,世界一部分地區的農作物因氣候災難等原因歉收,原本就會抵銷其他收成正常或豐收地區增加的交易量;但高懸地球上空的強烈噴射氣流風,加上氣候導致氣溫更加升高,可能會在同時間襲來,威脅到多個糧倉的作物。

    最無法抵擋這種熱浪的地區包括北美西部、西歐、西俄羅斯和烏克蘭,這裡種植的小麥、玉米、大豆、稻米產量占全球主食約1/4。

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

    【其他文章推薦】

    ※帶您來了解什麼是 USB CONNECTOR  ?

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

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

    ※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

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

  • 酒精製環保清潔劑 跨界合作友善環境

    摘錄自2019年12月13日公視報導

    「無酒精」啤酒近年來越來越受歡迎,不過大量酒精廢棄物何去何從,讓業者傷腦筋。有啤酒製造商和清潔劑公司合作,將這類廢棄物用來製造環保洗碗精。

    總部設在比利時魯汶的百威英博啤酒集團,是全球最大的啤酒製造商。為了迎合快速成長的無酒精啤酒市場的需求,業者生產這種喝了不醉的飲料時,得將酒精提取出來。不過大量廢棄酒精何去何從?一直讓業者很頭痛。

    百威英博後來找上強調使用天然配方的環保清潔產品製造商宜珂,發現將廢棄酒精淨化再處理後,可以用作活性清潔劑和防腐劑成分,雙方一拍即合。不過廢酒精再利用,仍舊會散發酒的味道,宜珂正想盡辦法克服。宜珂創新部主任多曼說:「如何蓋住這個味道,讓我們的產品在消費者使用時聞起來味道很好。因為消費者不希望餐具上留有啤酒味,所以這是我們主要的挑戰。」

    宜珂後來使用來自其他廢棄農產品提煉的香味,掩蓋啤酒味,成功推出這款環保洗碗精,上面標註有1/4成分來自啤酒廢棄物。從啤酒泡到清潔劑泡泡,百威英博成功將產業垃圾再利用,透過跨界合作,變成對環境更友善的商品。

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

    【其他文章推薦】

    ※為什麼 USB CONNECTOR 是電子產業重要的元件?

    網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

    ※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

    ※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

    新北清潔公司,居家、辦公、裝潢細清專業服務

  • 解鎖新夥伴關係 政府、企業與社會的氣候合作

    文:李翊僑(荷蘭馬斯垂克大學永續科學、政策與社會碩士生)

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

    【其他文章推薦】

    USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

    ※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

    ※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

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

    ※幫你省時又省力,新北清潔一流服務好口碑

  • 李克強:加快建設新能源汽車充電設施

    國務院總理李克強作政府工作報告時表示,今後要加快建設城市停車場和新能源汽車充電設施,提高清潔能源比重。

    李克強指出,重拳治理大氣霧霾和水污染。著力抓好減少燃煤排放和機動車排放。加強煤炭清潔高效利用,推進以電代煤、以氣代煤。全面實施燃煤電廠超低排放和節能改造,加快淘汰不符合強制性標準的燃煤鍋爐,增加天然氣供應,完善風能、太陽能、生物質能等發展扶持政策,提高清潔能源比重,鼓勵秸稈資源化利用,減少直接焚燒。

    全面推廣車用燃油國五標準,淘汰黃標車和老舊車380萬輛,在重點區域實行大氣污染聯防聯控。

    增強消費拉動經濟增長的基礎作用。適應消費升級趨勢,破除政策障礙,優化消費環境,維護消費者權益。支持發展養老、健康、家政、教育培訓、文化體育等服務消費。壯大網路資訊、智慧家居、個性時尚等新興消費。鼓勵線上線下互動,推動實體商業創新轉型。完善物流配送網路,促進快遞業健康發展。活躍二手車市場,加快建設城市停車場和新能源汽車充電設施。在全國開展消費金融公司試點,鼓勵金融機構創新消費信貸產品。

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※帶您來了解什麼是 USB CONNECTOR  ?

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

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

    ※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

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