分類
發燒車訊

市場電動車需求上升,Nissan將對現有車款EV化

日經新聞報導,日產汽車(Nissan)將大舉擴充電動車(EV)產品陣容,日產社長兼CEO西川廣人27日於橫濱市舉行的定期股東會上表示,「今年度將推出新型『Leaf』,且中期來看,將推動現行已進行量產販售的車款EV化」。

因北美、中國加強環保規範,帶動EV有望進一步普及。日產目前的EV車款僅有「Leaf」等少數幾款,而之後計畫將SUV、輕型汽車以及商用車進行EV化。

另外,日產會長Carlos Ghosn也在股東會上表示,「日產在EV界居領導位置。日產EV累計銷售量超過60萬台、為美國特斯拉(Tesla)的2倍」。

日本市調機構富士經濟(Fuji Keizai)6月22日公布銷售動向報告指出,EV在2025年以後需求將急速增加,預估2030年時EV年銷售量將增至407萬台、超越油電混合車(HV、2030年銷售量預估為391萬台),且之後雙方的差距將持續擴大。在中國需求增加加持下,2035年EV全球銷售量將擴大至630萬台、將達2016年的13.4倍(較2016年增加12.4倍)。

(本文內容由授權使用。圖片出處:)

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

【其他文章推薦】

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

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

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

分類
發燒車訊

樹莓派3B/3B+和4B安裝OpenCV教程 (屢試不爽)

    

安裝前準備

  1. 在樹莓派上拓展文件系統

    如果你使用的樹莓派為新裝的系統,那麼第一件事情就是擴展文件系統,以包括microSD卡上的所有空間。

    具體步驟如下:

      1.在樹莓派終端(或者SSH)上輸入:

1 $ sudo raspi-config

 

      2.然後選擇“高級選項”菜單項

   

       3.然後選擇“擴展文件系統”:

  

       4. 選擇第一個選項“A1.Expand Filesystem”,按鍵盤上的Enter鍵,完成後點擊“Finish”按鈕,重新啟動樹莓派。

    如果不能重啟,則可以執行以下操作:

1 $ sudo reboot

 

      重新啟動后,文件系統已經擴展為包括micro-SD卡上的所有空間。可以通過執行 df -h 檢查輸出來驗證磁盤是否已擴展。

1 $ df -h

 

 

  

 

      5. 此時我的樹莓派文件系統已擴展為包含16GB的micor-SD卡。如果您使用的是8GB卡,則可能使用了將近50%的可用空間,

    因此,一件簡單的事情就是刪除LibreOffice和Wolfram引擎以釋放Pi上的一些空間:

1 $ sudo apt-get purge wolfram-engine
2 $ sudo apt-get purge libreoffice*
3 $ sudo apt-get clean
4 $ sudo apt-get autoremove

 

 

  2.更換樹莓派源為清華鏡像源,防止後面下載GTK2.0失敗。

    換源方法參考:

安裝步驟

   1.更新系統

1 $ sudo apt-get update && sudo apt-get upgrade

   

   2.在樹莓派上安裝OpenCV所需要依賴的工具和一些圖像視頻庫

  • 安裝包括CMake的開發人員工具

    1 // 安裝build-essential、cmake、git和pkg-config
    2 sudo apt-get install build-essential cmake git pkg-config 

     

  • 安裝常用圖像工具包

    1 // 安裝jpeg格式圖像工具包
    2 sudo apt-get install libjpeg8-dev 

     

    1 // 安裝tif格式圖像工具包
    2 sudo apt-get install libtiff5-dev 

     

    1 // 安裝JPEG-2000圖像工具包
    2 sudo apt-get install libjasper-dev 

     

    1 // 安裝png圖像工具包
    2 sudo apt-get install libpng12-dev 

     

  • 安裝常用的視頻庫

1 //v4l中4後面的是 英文字母“l”
2 sudo apt-get install libavcodec-dev libavformat-dev libswscale-dev libv4l-dev

 

  

  • 安裝GTK2.0

    1 sudo apt-get install libgtk2.0-dev

     

  • 安裝OpenCV數值優化函數包

1 sudo apt-get install libatlas-base-dev gfortran

 

    

 

    3.下載編譯OpenCV源碼

  • 下載opencv3.4.3和opencv_contrib3.4.3

1 // 下載OpenCV
2 wget -O opencv-3.4.3.zip https://github.com/Itseez/opencv/archive/3.4.3.zip

 

   

1 // 解壓OpenCV
2 unzip opencv-3.4.3.zip

 

   

1 // 下載OpenCV_contrib庫:
2 wget -O opencv_contrib-3.4.3.zip https://github.com/Itseez/opencv_contrib/archive/3.4.3.zip

 

   

 

1 // 解壓OpenCV_contrib庫:
2 unzip opencv_contrib-3.4.3.zip

   

  • 配置CMake編譯OpenCV 3環境

    使用CMake設置編譯,然後運行 make 來編譯OpenCV。這是整個過程中耗時最長的步驟,大約4個小時。

    回到OpenCV存儲庫並創建 build 文件夾,用來存放 CMake 編譯時產生的臨時文件。

1 //具體路徑請以實際為準
2 cd ~/opencv-3.4.3
3 
4 // 新建build文件夾
5 mkdir build
6      
7 // 進入build文件夾
8 cd build
9   

   

 

  •  設置CMake編譯參數,安裝目錄默認為/usr/local

    注意參數名、等號和參數值之間不能有空格,每行末尾“\”之前有空格,這裏使用換行符“\”是為了看起來工整,參數值最後是兩個英文的點,意思是上級

  目錄(【注意】如果在root用戶下執行cmake命令,請將OPENCV_EXTRA_MODULES_PATH的值改為絕對路徑,如:/home/pi/opencv_contrib-3.4.3/modules):

/** CMAKE_BUILD_TYPE是編譯方式
* CMAKE_INSTALL_PREFIX是安裝目錄
* OPENCV_EXTRA_MODULES_PATH是加載額外模塊
* INSTALL_PYTHON_EXAMPLES是安裝官方python例程
* BUILD_EXAMPLES是編譯例程(這兩個可以不加,不加編譯稍微快一點點,想要C語言的例程的話,在最後一行前加參數INSTALL_C_EXAMPLES=ON,要C++例程的話在最後一行前加參數INSTALL_C_EXAMPLES=ONINSTALL_CXX_EXAMPLES=ON)
**/
 
sudo cmake -D CMAKE_BUILD_TYPE=RELEASE \
    -D CMAKE_INSTALL_PREFIX=/usr/local \
    -D OPENCV_EXTRA_MODULES_PATH=~/opencv_contrib-3.4.3/modules \
    -D INSTALL_PYTHON_EXAMPLES=ON \
    -D INSTALL_CXX_EXAMPLES=ON \
    -D BUILD_EXAMPLES=ON ..
 

   

 

     配置完后如下圖:

   

 

  • 備份build文件中的東西

     因為下一步的編譯會使用build文件中的東西,假如編譯失敗后還要重新進行cmake,比較耽誤時間,這裏可以直接備份一下cmake好的build文件夾,

  命名為build1,重新make的時候可以拿來用。

1 //返回上層目錄
2 cd ..
3 //備份release文件夾
4 cp -r release ./release1

  

  • 為樹莓派增加SWAP

    在開始編譯之前,建議你增加交換空間。這將使你使用樹莓派的所有四個內核來編譯OpenCV,而不會由於內存耗盡導致編譯掛起。

    打開 etc dphys – swapfile   文件:

1 $ sudo nano /etc/dphys-swapfile

    然後編輯 CONF_SWAPSIZE  變量:

   

 

      注意:此處我將交換空間從100MB增加到2048MB;如果你不執行此步驟,你的樹莓派編譯時很可能掛起。

    重新啟動交換服務:

1 $ sudo /etc/init.d/dphys-swapfile stop
2 $ sudo /etc/init.d/dphys-swapfile start

   

 

     注意:增加交換空間的大小是燒壞樹莓派 microSD卡的好方法。基於閃存的存儲只能執行有限數量的寫操作,直到該卡基本不能夠容納1和0。我們只能在短時間內

  啟動大型交換,所以這沒什麼大問題的。

  • 編譯OpenCV 3

1 /**
2 * 以管理員身份,否則容易出錯
3 * make命令參數-j4指允許make使用最多4個線程進行編譯,這樣編譯速度會更快
4 * 可以根據自己機器的情況進行更改
5 * 使用tee命令可以將編譯過程中終端显示的信息保存到make.log文件中,便於查看,這樣即使VNC斷線,終端的* 信息太多看不到,也可以通過make.log文件查看編譯過程。
6 **/
7  
8 sudo make -j4 2>&1 | tee make.log

   

 

    如果看到進度編譯到100%,那麼說明編譯安裝成功。

   

1 // 安裝
2 sudo make install
3  
4 // 更新動態鏈接庫
5 sudo ldconfig

    

    注意: 不要忘記回到 /etc/dphysswapfile 文件:

       1.將 CONF_SWAPSIZE 重置為 100MB

       2.重新啟動交換服務

其他配置

   設置庫的路徑,相當於windows下的環境變量,便於使用OpenCV庫,也可以不進行設置,使用的時候說明路徑也可。例如在編譯時說明使用庫的路徑是 -L/usr/local/lib 

 

  1.配置opencv.conf 

   打開opencv.conf配置文件,在末端加入如下內容: 

 1 //這裏我使用的是樹莓派默認的nano,也可以使用vim、gedit.打開opencv.conf文件
 2 sudo nano /etc/ld.so.conf.d/opencv.conf
 3 
 4 /**
 5 *在末端添加如下內容
 6 *注意:?表示一個空格,可能原因是有的語言要求最後有一個空格才可以編譯通過。
 7 **/
 8 /usr/local/lib
 9 ?
10 
11 //加載一下
12sudo ldconfig

   

  2.打開 bash.bashrc 配置文件 

1 //打開bash.bashrc配置文件
2 sudo gedit /etc/bash.bashrc
3 
4 
5 // 在最後添加如下內容
6 PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig       
7 export PKG_CONFIG_PATH

    

  3.重啟樹莓派

1 sudo reboot

 

 檢測OpenCV使用是否正常

  python程序

 1 import cv2
 2 import numpy as np
 3 cv2.namedWindow("gray")
 4 img = np.zeros((512,512),np.uint8)#生成一張空的灰度圖像
 5 cv2.line(img,(0,0),(511,511),255,5)#繪製一條白色直線
 6 cv2.imshow("gray",img)#显示圖像
 7 #循環等待,按q鍵退出
 8 while True:
 9     key=cv2.waitKey(1)
10     if key==ord("q"):
11         break
12 cv2.destoryWindow("gray")

  保存文件為 test.py ,並在終端運行程序

1 sudo python3 test.py

  運行結果如下:

  

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

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

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

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

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

分類
發燒車訊

精通awk系列(8):awk劃分字段的3種方式

回到:

詳細分析awk字段分割

awk讀取每一條記錄之後,會將其賦值給$0,同時還會對這條記錄按照預定義變量FS劃分字段,將劃分好的各個字段分別賦值給$1 $2 $3 $4...$N,同時將劃分的字段數量賦值給預定義變量NF

引用字段的方式

$N引用字段:

  • N=0:即$0,引用記錄本身
  • 0<N<=NF:引用對應字段
  • N>NF:表示引用不存在的字段,返回空字符串
  • N<0:報錯

可使用變量或計算的方式指定要獲取的字段序號。

awk '{n = 5;print $n}' a.txt
awk '{print $(2+2)}' a.txt   # 括號必不可少,用於改變優先級
awk '{print $(NF-3)}' a.txt

分割字段的方式

讀取record之後,將使用預定義變量FS、FIELDWIDTHS或FPAT中的一種來分割字段。分割完成之後,再進入main代碼段(所以,在main中設置FS對本次已經讀取的record是沒有影響的,但會影響下次讀取)。

劃分字段方式(一):FS或-F

FS或者-F:字段分隔符

  • FS為單個字符時,該字符即為字段分隔符
  • FS為多個字符時,則採用正則表達式模式作為字段分隔符
  • 特殊的,也是FS默認的情況,FS為單個空格時,將以連續的空白(空格、製表符、換行符)作為字段分隔符
  • 特殊的,FS為空字符串””時,將對每個字符都進行分隔,即每個字符都作為一個字段
  • 設置預定義變量IGNORECASE為非零值,正則匹配時表示忽略大小寫(隻影響正則,所以FS為單字時無影響)
  • 如果record中無法找到FS指定的分隔符(例如將FS設置為”\n”),則整個記錄作為一個字段,即$1$0相等
# 字段分隔符指定為單個字符
awk -F":" '{print $1}' /etc/passwd
awk 'BEGIN{FS=":"}{print $1}' /etc/passwd

# 字段分隔符指定為正則表達式
awk 'BEGIN{FS=" +|@"}{print $1,$2,$3,$4,$5,$6}' a.txt

劃分字段方式(二):FIELDWIDTHS

指定預定義變量FIELDWIDTHS按字符寬度分割字段,這是gawk提供的高級功能。在處理某字段缺失時非常好用。

用法:

示例1:

# 沒取完的字符串DDD被丟棄,且NF=3
$ awk 'BEGIN{FIELDWIDTHS="2 3 2"}{print $1,$2,$3,$4}' <<<"AABBBCCDDDD"
AA BBB CC 

# 字符串不夠長度時無視
$ awk 'BEGIN{FIELDWIDTHS="2 3 2 100"}{print $1,$2,$3,$4"-"}' <<<"AABBBCCDDDD"
AA BBB CC DDDD-

# *號取剩餘所有,NF=3
$ awk 'BEGIN{FIELDWIDTHS="2 3 *"}{print $1,$2,$3}' <<<"AABBBCCDDDD"      
AA BBB CCDDDD

# 字段數多了,則取完字符串即可,NF=2
$ awk 'BEGIN{FIELDWIDTHS="2 30 *"}{print $1,$2,NF}' <<<"AABBBCCDDDD"  
AA BBBCCDDDD 2

示例2:處理某些字段缺失的數據。

如果按照常規的FS進行字段分割,則對於缺失字段的行和沒有缺失字段的行很難統一處理,但使用FIELDWIDTHS則非常方便。

假設a.txt文本內容如下:

ID  name    gender  age  email          phone
1   Bob     male    28   abc@qq.com     18023394012
2   Alice   female  24   def@gmail.com  18084925203
3   Tony    male    21   aaa@163.com    17048792503
4   Kevin   male    21   bbb@189.com    17023929033
5   Alex    male    18                  18185904230
6   Andy    female  22   ddd@139.com    18923902352
7   Jerry   female  25   exdsa@189.com  18785234906
8   Peter   male    20   bax@qq.com     17729348758
9   Steven  female  23   bc@sohu.com    15947893212
10  Bruce   female  27   bcbd@139.com   13942943905

因為email字段有的是空字段,所以直接用FS劃分字段不便處理。可使用FIELDWIDTHS。

# 字段1:4字符
# 字段2:8字符
# 字段3:8字符
# 字段4:2字符
# 字段5:先跳過3字符,再讀13字符,該字段13字符
# 字段6:先跳過2字符,再讀11字符,該字段11字符
awk '
BEGIN{FIELDWIDTHS="4 8 8 2 3:13 2:11"}
NR>1{
    print "<"$1">","<"$2">","<"$3">","<"$4">","<"$5">","<"$6">"
}' a.txt

# 如果email為空,則輸出它
awk '
BEGIN{FIELDWIDTHS="4 8 8 2 3:13 2:11"}
NR>1{
    if($5 ~ /^ +$/){print $0}
}' a.txt

劃分字段方式(三):FPAT

FS是指定字段分隔符,來取得除分隔符外的部分作為字段。

FPAT是取得匹配的字符部分作為字段。它是gawk提供的一個高級功能。

FPAT根據指定的正則來全局匹配record,然後將所有匹配成功的部分組成$1、$2...,不會修改$0

  • awk 'BEGIN{FPAT="[0-9]+"}{print $3"-"}' a.txt
  • 之後再設置FS或FPAT,該變量將失效

FPAT常用於字段中包含了字段分隔符的場景。例如,CSV文件中的一行數據如下:

Robbins,Arnold,"1234 A Pretty Street, NE",MyTown,MyState,12345-6789,USA

其中逗號分隔每個字段,但雙引號包圍的是一個字段整體,即使其中有逗號。

這時使用FPAT來劃分各字段比使用FS要方便的多。

echo 'Robbins,Arnold,"1234 A Pretty Street, NE",MyTown,MyState,12345-6789,USA' |\
awk '
    BEGIN{FPAT="[^,]*|(\"[^\"]*\")"}
    {
        for (i=1;i<NF;i++){
            print "<"$i">"
        }
    }
'

最後,patsplit()函數和FPAT的功能一樣。

檢查字段劃分的方式

有FS、FIELDWIDTHS、FPAT三種獲取字段的方式,可使用PROCINFO數組來確定本次使用何種方式獲得字段。

PROCINFO是一個數組,記錄了awk進程工作時的狀態信息。

如果:

  • PROCINFO["FS"]=="FS",表示使用FS分割獲取字段
  • PROCINFO["FPAT"]=="FPAT",表示使用FPAT匹配獲取字段
  • PROCINFO["FIELDWIDTHS"]=="FIELDWIDTHS",表示使用FIELDWIDTHS分割獲取字段

例如:

if(PROCINFO["FS"]=="FS"){
    ...FS spliting...
} else if(PROCINFO["FPAT"]=="FPAT"){
    ...FPAT spliting...
} else if(PROCINFO["FIELDWIDTHS"]=="FIELDWIDTHS"){
    ...FIELDWIDTHS spliting...
}

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

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

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

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

分類
發燒車訊

心裏有紅黑樹

Why 紅黑樹

為什麼大家都這麼推崇紅黑樹呢? 這就是數據結構的魅力!!! 下面我簡述一下常用數據結構的優缺點

  • 數組

大家對數組很熟悉, 都知道對數組來說,它底層的存儲空間是連續的,因此如果我們根據index去獲取元素,速度是相當快, 但是對於數組來說有時候查詢也不見得就一定塊, 比如我們查詢數組中名字叫張三的人, 也不得不從開始遍歷這個數組

如果我們想往數組中插入一個元素, 也不見得一定就慢, 比如我們往數組中最後的位置插入就很快, 但是要是往開始的位置插入的話, 肯定會很慢, 需要將現有數組中所有的元素往後移動一位, 才能空出開始的位置,給新元素用

  • 鏈表

一說鏈式存儲, 大家也都知道, 這種數據結構僅僅是邏輯連續, 物理存儲不連續, 因此我們有可以通過玩指針或者引用很快的完成元素的刪除和添加

對鏈表的查詢來說, 一定是慢的, 無論查詢誰, 查哪個, 都得從第一個節點開始遍歷

  • AVL樹

AVL樹, 就是二叉平衡樹, 這種有序的樹形結果就將鏈式存儲添加刪除塊, 順序存儲的查找快兩大有點進行了一次中和, 在絕大部分情況下, AVL樹在增刪改查方面的性能都原超過數組和鏈表

  • 紅黑樹

紅黑樹是對AVL樹是又一次重大升級, AVL樹,對於樹的平衡要求太嚴格了, 每當添加,刪除節點時,都不得不進行調整

對於AVL樹個紅黑樹來說, 每次添加一個新的節點都是最多進行兩次旋轉(左旋右旋)就能重新使樹變的平衡,

但是當我們刪除一個恭弘=叶 恭弘子節點時, AVL樹重新調整成平衡狀態時最多需要進行旋轉O(logN)次, 而紅黑樹最多旋轉3次就能重新平衡,時間複雜度是O(1)

還有就是紅黑樹並不是完全意義上的AVL樹, 也就是說它其實並不是真的像AVL樹那樣嚴格要求對一個節點來說左右子樹的高度差不能超過1, 而是選擇使用染成紅色和黑色進行維護

簡單來說, 因為紅黑樹並不像AVL樹那樣完全平衡, 可能會導致紅黑樹的讀性能略遜於AVL, 但是紅黑樹的維護成本絕對是遠遠低於AVL, 在空間上的開銷和AVL樹基本持平, 因此紅黑樹被大家極力推崇, 和學習java的同學直接相關的就是jdk8的 hashmap

紅黑樹的特性

紅黑樹主要存在下面的7條性質

  1. 節點非紅即黑
  2. 根節點必定是黑色
  3. 恭弘=叶 恭弘子節點全部是黑色, (這裏說的恭弘=叶 恭弘子節點是我們想象在肉眼看到的節點上再多加一層子節點)
  4. 紅節點的子節點必定是黑色
  5. 紅節點的父節點必定是黑色
  6. 從根節點到任意子節點的路徑上,都要經歷相同數目的黑節點
  7. 從根節點到任意子節點的路徑上不可能存在兩個連續相同的紅節點

常見的誤區

如上圖, 看着挺像紅黑樹, 其實他不是, 看它node10, 並不滿足上面的性質6. 因為我們認為node10的左子節點是黑色的節點, 這樣的話, 從node20到node10的左子節點就經歷了兩個黑節點, 而其他的 node15, node25, node35 經歷的黑色子節點數都是三個

如上圖它也不是紅黑樹, 因為我們認為node30的右節點是黑色的節點, 這樣的話從node60到node30的右節點就經歷了三個黑色的節點, 而其他的所有子節點都經歷了4個, 故, 他不是紅黑樹

紅黑樹與2-3-4樹等價

如上圖中,當我們將一個紅黑樹中的黑色節點和紅色節點融合在一起時,我們會發現, 這個紅黑樹其實就是一顆2-3-4樹, 一顆四階B樹

並且, 紅黑樹中黑色節點的每一個合併完成后的節點中都有一個黑色的節點, 換句話說就是紅黑樹中黑色節點的個數等於2-3-4樹中節點的個數

添加

添加節點其實就是構造紅黑樹的過程, 只要我們嚴格遵循上面的7條限制, 構造出來的樹就是紅黑樹

通過上圖其實我們發現, 紅黑樹真的可以和四階B樹之間進行等價代換, 換句話說就是 4階B樹的性質對於紅黑樹來書其實也是存在的, 主要是如下兩條性質

  • 所有新添加進去的節點都被放在了恭弘=叶 恭弘子節點上
  • 2-3-4樹中每一個節點中允許承載的元素的個數 [1,3]

經驗推薦: 就是新添加的節點盡量全部是紅色, 如果你畫一畫就會發現, 如果我們新添加的節點是紅色的話,上面所說的7條性質中, 除了第四條(紅節點的子節點必定的黑節點). 其他的限制都可以滿足

於是看一下一顆四階B樹插入節點時有哪些種情況

數一數: 一共 4+3+3+2 = 12種情況, 換句話說, 只要我們處理好了這12種情況, 我們就完成了添加節點的邏輯

  • 情況1, 就是假設我們添加進去的是紅色的節點, 並且這個紅色節點的父節點是黑色節點時, 直接添加進行,不需要其他任何變換, 就想下圖這樣, 直接簡單粗暴的添加就行

除去第一種情況外, 還剩下8中情況出現了紅紅節點相鄰, 於是繼續往下看, 我們對他進行一次修復

  • 情況2: 如下圖

插入的node57, node64, 什麼情況呢? 就是當前節點是node5556, 首先這個節點中現存兩個元素, 並且是往這個黑色的節點的左側的左側插入, 或者是右側的右側插入一個紅色節點

看上圖出現了兩個紅色節點相鄰,於是我們第一件事就是進行重新染色,

  1. 將插入節點的父節點染成黑色
  2. 將插入節點的祖父節點染成紅色
  3. 將祖父節點進行旋轉, 如果這個新節點被插入在父節點的右側. 左旋轉它的祖父節點

經過上面的變換后, 我們重新得到標準的紅黑樹如下

  • 情況3: 新添加的節點的叔叔節點不是紅色

第三種情況和第二種情況相似, 還是插入 node57和node64. 判斷的條件是 插入節點的叔叔節點(父節點的兄弟節點)不是紅節點,

簡稱 LR , 或者是RL , 需要進行如下的調整

  1. 染色: 將自己染成黑色,祖節點染成紅色
  2. LR: 父節點左旋轉, 祖父節點右旋轉
  3. RL: 祖父點右旋轉, 父節點左旋轉

LR舉例:

經過上面的變化,我們重新得到平衡的紅黑樹

接着往下看剩下的四種情況

  • 情況4: 新添加的節點的叔叔節點是紅色, 其實就是需要上溢的情況, 也很好處理

像上圖這樣, 新添加的紅色節點 node15, 它本身的父節點是node20, 父節點的叔叔節點是紅色的node25, 我們比較node15和node20的大小, 發現node15本來是應該放在node20的左邊的, 但是對於一顆2-3-4樹來說, 單個節點最多就有3個元素, 如果再加上node15 就會出現上溢的情況, 怎麼辦呢? 我們上溢調整, 選擇這個節點中間位置的元素向上和父節點合併, 選擇node20, node30其實都是可以的, 為了方便我們選擇node30

好,下面開始修復這個紅黑樹

  1. 將插入的節點的父節點和它的叔叔節點染成黑色
  2. 發生了上溢, 將他的父節點的染成紅色, 遞歸插入到根節點上, 這時候根節點可能又會發生上溢

然後上溢

當我們將新插入的節點的父節點node30染成紅色時, 再插入到根節點, 實際上就是重複我們枚舉出來的這12種情況中的一種. 紅黑樹一定會被修復, 當然這時候很可能會出現根節點也容納不了新的元素, 需要根節點也進行上溢, 然後將根節點染黑

還有一種情況是像下面這樣, 同樣是在情況4下的新插入的節點的叔叔節點是紅色

像下面這樣調整:

  1. 將父節點和叔叔節點染成黑色
  2. 祖父節點上溢

然後就是這種情況

調整的思路和前面一樣

  1. 將父節點和叔叔節點染成黑色
  2. 將祖父節點上溢

至此紅黑樹的添加的12種情況就全部枚舉完成了

刪除

對於刪除來說總共兩大種四小種情況

  • 第一種就是刪除的節點就是紅色節點, 如果真是這樣的話,直接刪除就ok
  • 第二種是刪除的節點是黑色節點
    • 刪除擁有1個red節點的黑色節點
    • 刪除擁有2個red節點的黑色節點,
    • 刪除黑色節點

如果一個像下面這樣, 下面的黑色節點有兩個子節點, 這種情況下,黑色節點肯定不會直接被刪除的, 需要進行變換,讓他的恭弘=叶 恭弘子節點去替換他,進而實現刪除的目的

  • 情況1: 刪除擁有1個紅節點的黑色節點,像下圖這樣

怎麼判斷這就是我們想刪除的情況呢? 當我們確定用來替代這個被刪除的黑節點是紅色,則符合當前的情況

也就是說我們想刪除 node40 和 node70, 於是我們這樣做

  1. 讓這個指向被刪除的節點的指針指向這個被刪除的節點的子節點
  2. 將替代它的節點染成黑色

於是我們接得到下圖這樣的結果

  • 情況2: 刪除的節點是黑色的恭弘=叶 恭弘子節點, 並且可向兄弟節點借

首先,如果這個恭弘=叶 恭弘子節點就是根節點的話,直接刪除就ok

看下面的這個圖, 我們就刪除其中node90, 即,刪除黑色恭弘=叶 恭弘子節點

如果想刪除上圖中的node90也是由竅門的,規律和2-3-4樹是擦不多的

假設它就是2-3-4樹, 如果我們將node90刪了, 我們計算一下, 對於2-3-4樹來說, 每一個節點位置上至少有 ⌈ 4/2 ⌉ -1 = 1個元素, 但是把node90刪除了這個位置上的節點中沒有元素, 因此產生了 下溢

出現下溢,我們首先考慮的情況就是看看可不可以向它的兄弟節點借一個,但是和B樹是有取別的, 多了下面的限制

  1. 被刪除的這個節點的兄弟節點必須是黑色的
  2. 被刪除的這個節點的兄弟節點一定的有紅色的子節點才ok, 就像上圖那樣, 可以在左邊,右邊,或者都有
  3. 直接刪除掉指定的node(因為它在恭弘=叶 恭弘子節點的位置上)
  4. 進行旋轉,旋轉時注意, 兩點:第一點: 比如下面的原來根節點位置上的元素88是紅色的, 經過旋轉上來替換它的節點的顏色必須染成紅色, 如果node88是黑色, 那麼經過旋轉上來替換他的節點的顏色必須染成黑色 ,第二點: 旋轉完成后,新的跟節點的直接左右子節點的顏色轉換為黑色

怎麼進行旋轉呢? 就像下圖這樣

  • 情況3: 刪除的節點是黑色的恭弘=叶 恭弘子節點, 並且它的兄弟是黑色,而且它的兄弟節點不能借給他元素

像這種情況:我像刪除node99,但是沒辦法像他的兄弟節點借元素,於是

  1. 將父節點向下合併,父節點染成黑色
  2. 將它的兄弟節點染成紅色

也有特殊的情況, 就是它的父節點只有一個,還是黑色

這時候,我們將他的父節點下溢, 原位置的節點捨棄

  • 還有最後一種情況就是, 刪除的是黑色的節點, 它的兄弟節點的是紅色的節點

就像上圖那樣,我們想刪除node99, 但是node99的兄弟節點其實是node55, 而不是node77, 我們怎麼樣才能轉換為前面說的那些情況呢?

  1. 將被刪除節點的父節點染成紅色, 兄弟節點染黑

  2. 讓被刪除的父節點進行右旋轉(node88右轉)

    得到下圖

於是我們就將這種兄弟節點為紅節點的情況轉化成了兄弟節點為黑色節點的樣子, 按照原來的方式進行刪除修整即可

  1. 讓原父節點下溢
  2. 原染成黑色
  3. 兄弟節點,染成紅色

至此本文就結束, 歡迎關注我,後續我更新更多的關於開發相關的筆記

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

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

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

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

分類
發燒車訊

特斯拉啟用臺南奇美博物館超級充電站,下一站臺中七期將啟用

特斯拉(Tesla) 今日(7/20) 啟用臺灣第二組超級充電站,為全亞洲第一組設在博物館園區的超級充電站。而第三組超級充電站則會在下週於臺中七期啟用。

此次啟用的台南超級充電站位於台南奇美博物館,是全亞洲第一組設立於博物館的超級充電設施,共有8 組超級充電設備,足以應付開車環島遊,以及跨縣市長途旅遊等的需要。這次的項目更獲得台南政府大力支持,鼓勵特斯拉對於環保理念的實踐。特斯拉在台的首座超級充電站設於台北花博園區,該充電站提供6 組超級充電設備。

下週特斯拉將啟用位於臺中七期的第三組超級充電站,鄰近國道一號臺中交流,方便南來北往的特斯拉車主休息同時,快速充電再上路。

特斯拉的超級充電站充電30 分鐘,就能供應行駛270 公里所需電力,為臺北臺南間七成路程。而設在百貨公司的目的地充電站,已經從臺灣頭的翡翠灣,到臺灣尾的墾丁,遍佈14 個縣市,超過80 個地點,250 支的目的地充電座。

(合作媒體:。圖片出處:特斯拉提供)  

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

【其他文章推薦】

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

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

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

分類
發燒車訊

日產傳退出EV電池生產業務、子公司AES賣中國GSR

朝日新聞22日報導,日產汽車(Nissan)將退出電動車(EV)用電池的生產業務,旗下車用電池生產子公司「Automotive Energy Supply(以下簡稱AES)」將出售給中國投資基金「GSR」,出售額預估為1,000億日圓,雙方預計會在今年夏天達成共識並對外發表。日產在退出EV電池生產業務後,會將資源集中在最先端電池的研發上。

報導指出,鋰離子電池是決定EV競爭力的關鍵,而日系廠商原先是該領域的霸主,不過近年來,南韓、中國廠商呈現顯著成長,加上AES銷售通路有限,因此日產研判擁有EV電池生產業務不具備太多好處。

AES為日產、NEC分別出資51%、49%於2007年設立的公司,主要生產日產電動車Leaf或油電混合車(HV)所需的鋰離子電池。

根據調查公司Techno Systems Research指出,2014年Panasonic車用鋰離子電池全球市佔率高達47%,而2015年雖維持首位、但市佔率萎縮至34%;反觀「其他廠商」市佔率從14%飆增至33%,而在「其他廠商」中、比亞迪(BYD)等中國廠商佔了大多數,LG化學、三星SDI等南韓廠商也在後猛追。

中國對電池市場虎視眈眈,成長最快的電池廠「寧德時代新能源科技股份」(簡稱寧德時代或CATL)已誓言要在2020年底前,使電池產量超越特斯拉(Tesla)、Panasonic合資的「Gigafactory」超大電池廠。

英國金融時報3月5日報導,北京已呼籲企業在2020年底前將車用電池產能拉高一倍,還鼓勵業者前往海外投資。高盛預估,鋰電池將是未來10年的關鍵技術,估計到了2025年市場總值上看400億美元、且主要會由中國主導。

寧德時代行銷主任Neil Yang預測,雖然超越日本、南韓業者並非易事,但預估未來10年內,全球只會剩下10家鋰電池製造商,其中60%的市場會由前三大業者拿下。他也透露寧德時代要將觸角探至全球的野心,聲稱該公司希望跟特斯拉合作,也一直在跟通用汽車(GM)接洽,而福斯(Volkswagen)、BMW則已都是他們的客戶。

寧德時代預計要在2020年將鋰電池總產能拉升至50GWh,而中國整體的鋰電池產能則合計會達121GWh。相較之下,特斯拉則希望在明(2018)年將鋰電池產能拉升至35GWh。每1個GWh的電力,可供應40,000輛電動車、每部行駛100公里所需。中國已在2013年超越南韓成為全世界最大的鋰電池供應國。

(本文內容由MoneyDJ授權使用。圖片出處:public domain CC0)

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

【其他文章推薦】

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

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

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

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

分類
發燒車訊

EV弱點一次解決!豐田傳推全固體電池車、數分鐘充飽電

東京新聞25日報導,豐田汽車(Toyota)計畫在2022年於日本國內開賣新型電動車(EV),其電池將採用充電量可達鋰離子電池2倍的「全固體電池」,將大幅提升EV的續航距離,且僅需數分鐘時間就可充飽電。在EV的研發上,由歐美廠商跑在前頭,而豐田期望藉由導入革新性技術扳回劣勢。

報導指出,現行市售的EV所搭載的電池以鋰離子電池為主,不過其弱點在於充飽一次電所能行駛的距離約300-400km、遜於汽油車,且即便使用快速充電技術、也需花費數十分鐘才能充飽電,而採用全固體電池的話,就有望一口氣將上述弱點全數解決。

據報導,全固體電池是將電解質從液體變更為固體,而豐田長年來持續進行全固體電池的研發,並於去年宣佈已攜手東京工業大學成功發現可適用於電解質的固體材料,並將在今年開始進行量產研發。

德國BMW、福斯(Volkswagen)等車廠也正研發全固體電池,不過量產相關計畫仍未明。

日經新聞曾於2011年10月報導,豐田汽車已和東京工業大學及高能源加速器研究機構攜手研發出一款使用新化合物的次世代電動車(EV)用「全固體電池」,其充飽一次電所能行駛的距離最長有望達1,000km左右的水準。

富士經濟6月22日公布調查報告指出,預估2030年時EV年銷售量將增至407萬台、超越油電混合車(HV、2030年銷售量預估為391萬台),且之後雙方的差距將持續擴大。富士經濟預估,在中國需求增加加持下,2035年EV全球銷售量將擴大至630萬台、將達2016年的13.4倍(較2016年增加12.4倍)。

(本文內容由授權使用。圖片出處:Toyota)

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

【其他文章推薦】

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

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

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

分類
發燒車訊

Gogoro 太陽能電池交換站,在新北八里驅動示範

新創科技公司 Gogoro 睿能創意股份有限公司,28 日宣布其與新北市政府合作的首座太陽能電池交換站,正式在新北市八里區開始營運。隨著這個搭配太陽能面板的電池交換站的啟動,Gogoro 對永續能源的承諾邁出更關鍵的一步。   這座位於八里十三行博物館附近(新北市八里區文昌路與文昌一街交叉口)的 Gogoro 八里公兒四電池交換站配置 2.3kW 太陽能面板,依據天氣環境等因素,每天可以產生大約 6.21 kWH 的電力,是第一座以再生能源提供電力的 GoStation 電池交換站。太陽能電力不但可以提供電池交換站的 Gogoro 電池使用,降低發電時所產生的二氧化碳排放,更可以與全台電力網路連結,參與整體網路的電量調節。   Gogoro 執行長陸學森表示 :「Gogoro 致力發展潔淨的智慧能源,希望透過具備能源調度能力的智慧電網,成為城市的電力調節樞紐,以促成電力平衡。與新北市政府合作的太陽能八里電池交換示範站為我們上述的目標邁出了重要的一步。」   新北市政府則表示,市府一直在積極尋找降低溫室氣體排放、改善空氣污染的積極方法,與 Gogoro 合作成立全世界第一座太陽能電池交換站具有標竿式的意義。電動機車零排放的特性不但可以提升市區的空氣品質,以再生能源為來源的電力,更可以降低交通工具所帶來的碳排放。未來也將持續努力發展以再生能源補充電力的相關設施,做為新北市重要的基礎設施。   這座太陽能電池交換示範站,設有物聯網智慧平台,透過分析供電情況的螢幕,說明了包括減少碳排量,減少樹木砍伐面積,綠能總儲電量,城市電網 ,太陽能發電量等訊息,讓每名換電的民眾,清楚的知道,自己對環境的貢獻度,也具有相當程度的教育意義。   Gogoro 目前擁有 25,000 名車主,總共累計將近 500 萬次的電池交換,9,000 萬公里的總里程數,已經替地球減少 720 萬公斤的二氧化碳排放,隨著未來再生能源比例逐漸提升,Gogoro 的車主們將更對地球與環境產生更多的正面影響力。Gogoro 為鼓勵車主響應綠能電池交換站,也設計了特別的機制。凡是造訪八里公兒四站,並完成電池交換的車主,即可在 Gogoro App 上獲得「阿波羅的力量」徽章。   (合作媒體:。圖片出處:科技新報)  

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

【其他文章推薦】

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

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

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

分類
發燒車訊

.NET資源泄露與處理方案

.NET雖然擁有強大易用的垃圾回收機制,但並不是因為這樣,你就可以對資源管理放任不管,其實在稍不注意的時候,可能就造成了資源泄露,甚至因此導致系統崩潰,到那時再來排查問題就已經是困難重重。

==========    ==========

一、知識點簡單介紹

常見的資源泄露有:

  • 內存泄漏:非託管資源沒有釋放、非靜態對象註冊了靜態實例。
  • GDI泄露:字體。
  • 句柄泄露:Socket或線程。
  • 用戶對象泄露:移除的對象未釋放。

二、具體實例

1. 內存泄漏

很常見的現象是分不清哪些對象需要釋放,對於控件、Stream等一些非託管資源也只管新增,卻沒有釋放,功能是實現了,卻埋了顆不小的雷。

private void button1_Click(object sender, EventArgs e)
{
    for(int i=0;i<1000;i++)
        this.Controls.Add(new TabPage());
}
private void button1_Click(object sender, EventArgs e)
{
    new Form2.ShowDialog();
}

如果你覺得寫這樣的代碼很Cool,很簡潔,你在項目中也有這麼寫代碼,那你就碰到大麻煩了,你試試在上面Form2中開個大一點的數組來檢查內存,然後運行,按幾下按鈕,你就會發現,內存一直增加,即使你調用了GC也無濟於事。所以,對於此類非託管資源要記住釋放,用完即廢可以採用using關鍵字。

public Form2()
{
    InitializeComponent();
    MyApp.FormChanged += FormChanged;
}

上面這個例子中,MyApp是一個靜態類,如果在實例對象中向這種類裏面註冊了事件,而又沒有取消註冊,這樣也會遇到大麻煩,即使在外部已經記得調用了Form2的Dispose也是沒用的。

解決方案

  • 注意託管資源和非託管資源的釋放區別,非託管資源是需要手動釋放的。
  • ,如上面的ShowDialog問題。(using中還起到了try-catch的作用,避免由於異常未調用Dispose的情況)
  • 使用UnLoad事件或者析構函數,對註冊的全局事件進行取消註冊。
  • 特別注意自定義組件的穩定性更重要,發生問題時影響也更廣。

2. GDI泄露

一般會跟字體相關,例如我曾在Android上用Cocos2d做一個小遊戲時頻繁地切換字體、Dev控件的Font屬性賦值也會有這種現象。

XXX.Font = new Font(...)

解決方案

  • 這個問題我目前是採用字體池來解決,類似線程池的概念,相同Key值取同一個對象。若有更好方案歡迎留言討論

3. 句柄泄露

一般跟Socket和Thread(線程)有關

for(int i=0;i<1000;i++){
    new Thread(()=>{
        Thread.Sleep(1000);
    }).Start();
}

解決方案

  • Socket的場景暫時沒遇到。
  • 線程問題採用線程池相關的輔助類能有效解決,例如ThreadPool、Task、Parallel。

4. 用戶對象泄露

一般跟移除的對象未釋放有關

private void button1_Click(object sender, EventArgs e)
{
    tab.Remove(tabPage);
}

三、最後特別奉送一個內存釋放的大招

[DllImport("kernel32.dll", EntryPoint = "SetProcessWorkingSetSize")]
public static extern int SetProcessWorkingSetSize(IntPtr process, int minSize, int maxSize);
/// <summary>    
/// 釋放內存    
/// </summary>    
public static void ClearMemory()
{
    GC.Collect();
    GC.WaitForPendingFinalizers();
    if (Environment.OSVersion.Platform == PlatformID.Win32NT)
    {
        SetProcessWorkingSetSize(System.Diagnostics.Process.GetCurrentProcess().Handle, -1, -1);
    }
}

調用以上API能讓你的內存一下爆減,是不是很給力,一調用內存就降下來了。But,先別高興太早,這其實是偽釋放,只是暫時解決內存大量泄漏導致系統崩潰的應急處理方案。具體原因參考:,關鍵信息:物理內存轉虛擬內存,涉及磁盤讀寫。好處壞處都貼出來了,是否需要使用請君自己斟酌。

四、總結

實際上由於各個開發人員的水平跟接觸面不同,又沒有經過統一的培訓(各個人對資源釋放的理解與關注度不同,或者寫代碼時就沒考慮內存未被釋放這種問題),發現問題的時候項目往往已經做到了一個階段,系統也比較龐大了,這種時候才發現內存泄露的問題確實是很頭疼的。

  • 資源泄露的場景往往是相互關聯的,發生最多的就是內存泄漏,而除了寫法可能有問題外,也可能是因為句柄泄露或用戶對象泄露引起的。

五、參考資料

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

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

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

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

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

分類
發燒車訊

java中的string對象深入了解

這裏來對Java中的String對象做一個稍微深入的了解。

Java對象實現的演進

String對象是Java中使用最頻繁的對象之一,所以Java開發者們也在不斷地對String對象的實現進行優化,以便提升String對象的性能。

Java6以及之前版本中String對象的屬性

在Java6以及之前版本中,String對象是對char數組進行了封裝實現的對象,其主要有4個成員成員變量,分別是char數組、偏移量offset、字符數量count和哈希值hash。String對象是通過offset和count兩個屬性來定位char[]數組,獲取字符串。這樣做可以高效、快速地共享數組對象,同時節省內存空間,但是這種方式卻可能會導致內存泄漏的發生。

Java7、8版本中String對象的屬性

從Java7版本開始,Java對String類做了一些改變,具體是String類不再有offset和count兩個變量了。這樣做的好處是String對象佔用的內存稍微少了點,同時String.substring()方法也不再共享char[]了,從而解決了使用該方法可能導致的內存泄漏問題。

Java9以及之後版本中String對象的屬性

從Java9版本開始,Java將char[]數組改為了byte[]數組。我們都知道,char是兩個字節的,如果用來存一個字節的情況下就會造成內存空間的浪費。而為了節約這一個字節的空間,Java開發者就改成了一個使用一個字節的byte來存儲字符串。

另外,在Java9中,String對象維護了一個新的屬性coder,這個屬性是編碼格式的標識,在計算字符串長度或者調用indexOf()方法的時候,會需要根據這個字段去判斷如何計算字符串長度。coder屬性默認有0和1兩個值,其中0代表Latin-1(單字節編碼),1則表示UTF-16編碼。

String對象的創建方式與在內存中的存放

在Java中,對於基本數據類型的變量和對對象的引用,保存在棧內存的局部變量表中;而通過new關鍵字和Constructor創建的對象,則是保存在堆內存中。而String對象的創建方式一般為兩種,一種是字面量(字符串常量)的方式,一種則是構造函數(String())的方式,兩種方式在內存中的存放有所不同。

字面量(字符串常量)的創建方式

使用字面量的方式創建字符串時,JVM會在字符串常量池中先檢查是否存在該字面量,如果存在,則返回該字面量在內存中的引用地址;如果不存在,則在字符串常量池中創建該字面量並返回引用。使用這種方式創建的好處是避免了相同值的字符串在內存中被重複創建,節約了內存,同時這種寫法也會比較簡單易讀一些。

String str = "i like yanggb.";

字符串常量池

這裏要特別說明一下常量池。常量池是JVM為了減少字符串對象的重複創建,特別維護了一個特殊的內存,這段內存被稱為字符串常量池或者字符串字面量池。在JDK1.6以及之前的版本中,運行時常量池是在方法區中的。在JDK1.7以及之後版本的JVM,已經將運行時常量池從方法區中移了出來,在Java堆(Heap)中開闢了一塊區域用來存放運行時常量池。而從JDK1.8開始,JVM取消了Java方法區,取而代之的是位於直接內存的元空間(MetaSpace)。總結就是,目前的字符串常量池在堆中。

我們所知道的幾個String對象的特點都來源於String常量池。

1.在常量池中會共享所有的String對象,因此String對象是不可被修改的,因為一旦被修改,就會導致所有引用此String對象的變量都隨之改變(引用改變),所以String對象是被設計為不可修改的,後面會對這個不可變的特性做一個深入的了解。

2.String對象拼接字符串的性能較差的說法也是來源於此,因為String對象不可變的特性,每次修改(這裡是拼接)都是返回一個新的字符串對象,而不是再原有的字符串對象上做修改,因此創建新的String對象會消耗較多的性能(開闢另外的內存空間)。

3.因為常量池中創建的String對象是共享的,因此使用雙引號聲明的String對象(字面量)會直接存儲在常量池中,如果該字面量在之前已存在,則是會直接引用已存在的String對象,這一點在上面已經描述過了,這裏再次提及,是為了特別說明這一做法保證了在常量池中的每個String對象都是唯一的,也就達到了節約內存的目的。

構造函數(String())的創建方式

使用構造函數的方式創建字符串時,JVM同樣會在字符串常量池中先檢查是否存在該字面量,只是檢查后的情況會和使用字面量創建的方式有所不同。如果存在,則會在堆中另外創建一個String對象,然後在這個String對象的內部引用該字面量,最後返回該String對象在內存地址中的引用;如果不存在,則會先在字符串常量池中創建該字面量,然後再在堆中創建一個String對象,然後再在這個String對象的內部引用該字面量,最後返回該String對象的引用。

String str = new String("i like yanggb.");

這就意味着,只要使用這種方式,構造函數都會另行在堆內存中開闢空間,創建一個新的String對象。具體的理解是,在字符串常量池中不存在對應的字面量的情況下,new String()會創建兩個對象,一個放入常量池中(字面量),一個放入堆內存中(字符串對象)。

String對象的比較

比較兩個String對象是否相等,通常是有【==】和【equals()】兩個方法。

在基本數據類型中,只可以使用【==】,也就是比較他們的值是否相同;而對於對象(包括String)來說,【==】比較的是地址是否相同,【equals()】才是比較他們內容是否相同;而equals()是Object都擁有的一個函數,本身就要求對內部值進行比較。

String str = "i like yanggb.";
String str1 = new String("i like yanggb.");

System.out.println(str == str1); // false
System.out.println(str.equals(str1)); // true

因為使用字面量方式創建的String對象和使用構造函數方式創建的String對象的內存地址是不同的,但是其中的內容卻是相同的,也就導致了上面的結果。

String對象中的intern()方法

我們都知道,String對象中有很多實用的方法。為什麼其他的方法都不說,這裏要特別說明這個intern()方法呢,因為其中的這個intern()方法最為特殊。它的特殊性在於,這個方法在業務場景中幾乎用不上,它的存在就是在為難程序員的,也可以說是為了幫助程序員了解JVM的內存結構而存在的(?我信你個鬼,你個糟老頭子壞得很)。

/**
* When the intern method is invoked, if the pool already contains a
* string equal to this {@code String} object as determined by
* the {@link #equals(Object)} method, then the string from the pool is
* returned. Otherwise, this {@code String} object is added to the
* pool and a reference to this {@code String} object is returned.
**/
public native String intern();

上面是源碼中的intern()方法的官方註釋說明,大概意思就是intern()方法用來返回常量池中的某字符串,如果常量池中已經存在該字符串,則直接返回常量池中該對象的引用。否則,在常量池中加入該對象,然後返回引用。然後我們可以從方法簽名上看出intern()方法是一個native方法。

下面通過幾個例子來詳細了解下intern()方法的用法。

第一個例子

String str1 = new String("1");
System.out.println(str1 == str1.intern()); // false
System.out.println(str1 == "1"); // false

在上面的例子中,intern()方法返回的是常量池中的引用,而str1保存的是堆中對象的引用,因此兩個打印語句的結果都是false。

第二個例子

String str2 = new String("2") + new String("3");
System.out.println(str2 == str2.intern()); // true
System.out.println(str2 == "23"); // true

在上面的例子中,str2保存的是堆中一個String對象的引用,這和JVM對【+】的優化有關。實際上,在給str2賦值的第一條語句中,創建了3個對象,分別是在字符串常量池中創建的2和3、還有在堆中創建的字符串對象23。因為字符串常量池中不存在字符串對象23,所以這裏要特別注意:intern()方法在將堆中存在的字符串對象加入常量池的時候採取了一種截然不同的處理方案——不是在常量池中建立字面量,而是直接將該String對象自身的引用複製到常量池中,即常量池中保存的是堆中已存在的字符串對象的引用。根據前面的說法,這時候調用intern()方法,就會在字符串常量池中複製出一個對堆中已存在的字符串常量的引用,然後返回對字符串常量池中這個對堆中已存在的字符串常量池的引用的引用(就是那麼繞,你來咬我呀)。這樣,在調用intern()方法結束之後,返回結果的就是對堆中該String對象的引用,這時候使用【==】去比較,返回的結果就是true了。同樣的,常量池中的字面量23也不是真正意義的字面量23了,它真正的身份是堆中的那個String對象23。這樣的話,使用【==】去比較字面量23和str2,結果也就是true了。

第三個例子

String str4 = "45";
String str3 = new String("4") + new String("5");
System.out.println(str3 == str3.intern()); // false
System.out.println(str3 == "45"); // false

這個例子乍然看起來好像比前面的例子還要複雜,實際上卻和上面的第一個例子是一樣的,最難理解的反而是第二個例子。

所以這裏就不多說了,而至於為什麼還要舉這個例子,我相信聰明的你一下子就明白了(我有醫保,你來打我呀)。

String對象的不可變性

先來看String對象的一段源碼。

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];

    /** Cache the hash code for the string */
    private int hash; // Default to 0

    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    private static final long serialVersionUID = -6849794470754667710L;
}

從類簽名上來看,String類用了final修飾符,這就意味着這個類是不能被繼承的,這是決定String對象不可變特性的第一點。從類中的數組char[] value來看,這個類成員變量被private和final修飾符修飾,這就意味着其數值一旦被初始化之後就不能再被更改了,這是決定String對象不可變特性的第二點。

Java開發者為什麼要將String對象設置為不可變的,主要可以從以下三個方面去考慮:

1.安全性。假設String對象是可變的,那麼String對象將可能被惡意修改。

2.唯一性。這個做法可以保證hash屬性值不會頻繁變更,也就確保了唯一性,使得類似HashMap的容器才能實現相應的key-value緩存功能。

3.功能性。可以實現字符串常量池(究竟是先有設計,還是先有實現呢)。

String對象的優化

字符串是常用的Java類型之一,所以對字符串的操作是避免不了的。而在對字符串的操作過程中,如果使用不當的話,性能可能會有天差地別,所以有一些地方是要注意一下的。

拼接字符串的性能優化

字符串的拼接是對字符串的操作中最頻繁的一個使用。由於我們都知道了String對象的不可變性,所以我們在開發過程中要盡量減少使用【+】進行字符串拼接操作。這是因為使用【+】進行字符串拼接,會在得到最終想要的結果前產生很多無用的對象。

String str = 'i';
str = str + ' ';
str = str + 'like';
str = str + ' ';
str = str + 'yanggb';
str = str + '.';

System.out.println(str); // i like yanggb.

事實上,如果我們使用的是比較智能的IDE編寫代碼的話,編譯器是會提示將代碼優化成使用StringBuilder或者StringBuffer對象來優化字符串的拼接性能的,因為StringBuilder和StringBuffer都是可變對象,也就避免了過程中產生無用的對象了。而這兩種替代方案的區別是,在需要線程安全的情況下,選用StringBuffer對象,這個對象是支持線程安全的;而在不需要線程安全的情況下,選用StringBuilder對象,因為StringBuilder對象的性能在這種場景下,要比StringBuffer對象或String對象要好得多。

使用intern()方法優化內存佔用

前面吐槽了intern()方法在實際開發中沒什麼用,這裏又來說使用intern()方法來優化內存佔用了,這人真的是,嘿嘿,真香。關於方法的使用就不說了,上面有詳盡的用法說明,這裏來說說具體的應用場景好了。有一位Twitter的工程師在Qcon全球軟件開發大會上分享了一個他們對String對象優化的案例,他們利用了這個String.intern()方法將以前需要20G內存存儲優化到只需要幾百兆內存。具體就是,使用intern()方法將原本需要創建到堆內存中的String對象都放到常量池中,因為常量池的不重複特性(存在則返回引用),也就避免了大量的重複String對象造成的內存浪費問題。

什麼,要我給intern()方法道歉?不可能。String.intern()方法雖好,但是也是需要結合場景來使用的,並不能夠亂用。因為實際上,常量池的實現是類似於一個HashTable的實現方式,而HashTable存儲的數據越大,遍歷的時間複雜度就會增加。這就意味着,如果數據過大的話,整個字符串常量池的負擔就會大大增加,有可能性能不會得到提升卻反而有所下降。

字符串分割的性能優化

字符串的分割是字符串操作的常用操作之一,對於字符串的分割,大部分人使用的都是split()方法,split()方法在大部分場景下接收的參數都是正則表達式,這種分割方式本身沒有什麼問題,但是由於正則表達式的性能是非常不穩定的,使用不恰當的話可能會引起回溯問題並導致CPU的佔用居高不下。在以下兩種情況下split()方法不會使用正則表達式:

1.傳入的參數長度為1,且不包含“.$|()[{^?*+\”regex元字符的情況下,不會使用正則表達式。

2.傳入的參數長度為2,第一個字符是反斜杠,並且第二個字符不是ASCII数字或ASCII字母的情況下,不會使用正則表達式。

所以我們在字符串分割時,應該慎重使用split()方法,而首先考慮使用String.indexOf()方法來進行字符串分割,在String.indexOf()無法滿足分割要求的時候再使用Split()方法。而在使用split()方法分割字符串時,需要格外注意回溯問題。

總結

雖然說在不了解String對象的情況下也能使用String對象進行開發,但是了解String對象可以幫助我們寫出更好的代碼。

 

“只希望在故事的最後,我還是我,你也還是你。”

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

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

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

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