Python字典 你必須知道的用法系列

本文Python版本為3.7.X,閱讀本文之前需了解python字典的基本用法。

介紹

字典(dict)是Python中內置的一個數據結構,由多個鍵值對組成,鍵(key)和值(value)用冒號分隔,每個鍵值對之間用逗號(,)分隔,整個字典包括在大括號中({}),鍵必須是唯一的,值可以取任何類型,但是鍵必須是不可變類型,如字符串,数字或元組。

底層使用了hash表來關聯key和value,dict是無序的。特點包括:

  1. 查找和插入的速度極快,不會隨着key的增加而變慢;
  2. 需要佔用的內存較多

所以,dict是一種以空間換取時間的數據結構,應用於需要快速查找的場景。

操作

常用方法

get()

返回指定鍵的值,如果key不存在,則返回默認值(默認為None),而不會報錯,語法為dict.get(key)。

dict_1['age'] = 24

In [7]: print(dict_1.get('age'))
24

In [11]: print(dict_1.get('nama'))
None

In [12]: print(dict_1['nama'])
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-12-ef61a380920e> in <module>
----> 1 print(dict_1['nama'])

KeyError: 'nama'

key in dict

使用in操作符來判斷鍵是否存在於字典中,存在則返回True,否則返回False,語法為:key in dict。

In [15]: dict_1
Out[15]: {'name': None, 'age': 24, 'sex': None}

In [16]: print('name' in dict_1)
True

In [17]: print('nama' in dict_1)
False

在python 2中該功能使用has_key()方法實現。

items()

以列表形式返回可遍歷的(鍵, 值)元組數組,語法為dict.items()。

In [18]: dict_1
Out[18]: {'name': None, 'age': 24, 'sex': None}

In [19]: print(dict_1.items())
dict_items([('name', None), ('age', 24), ('sex', None)])

In [20]: for key, value in dict_1.items():
    ...:     print(key, value)
    ...:
name None
age 24
sex None

keys()

以列表返回一個字典的所有鍵:dict.keys()

In [21]: dict_1
Out[21]: {'name': None, 'age': 24, 'sex': None}

In [22]: print(dict_1.keys())
dict_keys(['name', 'age', 'sex'])

values()

以列表形式返回字典中的所有值:dict.values()

In [27]: dict_1
Out[27]: {'name': None, 'age': 24, 'sex': None, 'sub_name': 'Tony'}

In [28]: print(dict_1.values())
dict_values([None, 24, None, 'Tony'])

setdefault()

和get()類似,用戶獲得與給頂尖相關聯的值,不同的是,該方法如果鍵不存在時會添加鍵並將值設為默認值,語法為:dict.setdefault(key, default=None)。

In [23]: dict_1
Out[23]: {'name': None, 'age': 24, 'sex': None}

In [24]: print(dict_1.setdefault('name'))
None

In [25]: print(dict_1.setdefault('name', 'Tony'))
None

In [26]: print(dict_1.setdefault('sub_name', 'Tony'))
Tony

In [27]: dict_1
Out[27]: {'name': None, 'age': 24, 'sex': None, 'sub_name': 'Tony'}

update()

語法為:dict_1.update(dict_2),用於把dict_2的鍵值對更新到dict_1中,如果有相同的鍵會被覆蓋。

In [31]: dict_1
Out[31]: {'name': None, 'age': 24, 'sex': None, 'sub_name': 'Tony'}

In [32]: dict_2
Out[32]: {'name': 'Mary', 'age': 18, 'sex': None, 'sub_name': ''}

In [33]: dict_1.update(dict_2)

In [34]: dict_1
Out[34]: {'name': 'Mary', 'age': 18, 'sex': None, 'sub_name': ''}

clear()

刪除字典中的所有項,dict.clear(),舉個例子:

In [1]: dict_1 = dict(name="Tony", age=24)

In [2]: dict_2 = dict_1

In [3]: print(dict_2)
{'name': 'Tony', 'age': 24}

In [4]: dict_2.clear()

In [5]: dict_2
Out[5]: {}

In [6]: dict_1
Out[6]: {}

copy()

淺拷貝原始字典,返回一個具有相同鍵值對的新字典,dict.copy(),舉個例子:

In [1]: dict_1 = dict(name='Tony', info=['boy', 24])

In [2]: dict_3 = dict_1.copy()

In [3]: dict_3['name'] = "Ring"

In [4]: dict_3['info'].remove('boy')

In [5]: dict_3
Out[5]: {'name': 'Ring', 'info': [24]}

In [6]: dict_1
Out[6]: {'name': 'Tony', 'info': [24]}

fromkeys()

創建一個新字典,dict.fromkeys(seq[, value]),以序列seq中的元素做字典的鍵,value為字典所有鍵對應的初始值,其中value為可選參數, 默認為None。適用於數據初始化,舉個例子:

In [1]: info = ['name', 'age', 'sex']

In [2]: dict_1 = dict.fromkeys(info)

In [3]: dict_1
Out[3]: {'name': None, 'age': None, 'sex': None}

常見操作

合併字典

有四種方式:

  1. 常規處理
In [15]: dict_1
Out[15]: {'Tony': 24}

In [16]: dict_2
Out[16]: {'ben': 18}

In [17]: dict3 = dict()

In [18]: for key, value in dict_1.items():
    ...:     dict_3[key] = value
    ...:

In [19]: for key, value in dict_2.items():
    ...:     dict_3[key] = value
    ...:

In [20]: dict_3
Out[20]: {'Tony': 24, 'ben': 18}
  1. update()
In [9]: dict_1
Out[9]: {'Tony': 24}

In [10]: dict_2
Out[10]: {'ben': 18}

In [12]: dict_3 = dict_1.copy()

In [13]: dict_3.update(dict_2)

In [14]: dict_3
Out[14]: {'Tony': 24, 'ben': 18}
  1. 藉助字典的dict(d1, **d2)方法
In [33]: dict_1
Out[33]: {'Tony': 24}

In [34]: dict_2
Out[34]: {'ben': 18}

In [35]: dict_3 = dict(dict_1, **dict_2)

In [36]: dict_3
Out[36]: {'Tony': 24, 'ben': 18}

進階

字典推導式

和列表推導式類似,優點是底層用C實現,會快很多,推薦使用。

對換字典的鍵值

使用字典推導式可以輕鬆對換一個字典的鍵值:

In [42]: dict_4
Out[42]: {24: 'Tony', 18: 'ben'}

In [43]: dict_3
Out[43]: {'Tony': 24, 'ben': 18}

In [44]: dict_4 = {k:v for v, k in dict_3.items()}

In [45]: dict_4
Out[45]: {24: 'Tony', 18: 'ben'}

從字典中提取子集

想創建一個字典,其本身是另一個字典的子集。

舉個例子:

In [88]: a = {'Ben': 18, 'Jack': 12, 'Ring': 23, 'Tony': 24}

In [89]: b = {k:v for k, v in a.items() if v > 18}

In [90]: b
Out[90]: {'Ring': 23, 'Tony': 24}

生成有序字典

在Python3.6之前的字典是無序的,但是有時候我們需要保持字典的有序性,orderDict可以在dict的基礎上實現字典的有序性,這裏的有序指的是按照字典key插入的順序來排列,這樣就實現了一個先進先出的dict,當容量超出限制時,先刪除最早添加的key。
舉例:

In [49]: from collections import OrderedDict

In [50]: ordered_dict = OrderedDict([('a', 2), ('b', 4), ('c', 5)])

In [51]: for key, value in ordered_dict.items():
    ...:     print(key, value)
    ...:
a 2
b 4
c 5

可以看到OrderedDict是按照字典創建時的插入順序來排序。

原理:OrderedDict內部維護了一個雙向鏈表,它會根據元素加入的順序來排列鍵的位置,這也就導致OrderedDict的大小是普通字典的2倍多。

合併列表中key相同的字典

也就是生成所謂的一鍵多值字典,需要將對應的多個值保存在其它容器比如列表或集合,取決於多值是否需要保證唯一性。

舉個例子:

In [64]: from collections import defaultdict

In [65]: a = [{'a': 1}, {'b': 3}, {'c': 4}, {'a':5}, {'b':2}, {'b': 4}]

In [66]: b = defaultdict(list)

In [67]: [b[k].append(v) for item in a for k, v in item.items()]
Out[67]: [None, None, None, None, None, None]

In [68]: b
Out[68]: defaultdict(list, {'a': [1, 5], 'b': [3, 2, 4], 'c': [4]})

In [69]: b['a']
Out[69]: [1, 5]

尋找兩個字典的異同

場景:尋找兩個字典中的異同,包括相同的鍵或者相同的值。

分析:字典是一系列鍵值之間的映射集合,有以下特點:

  1. keys()會返回字典中的所有鍵,並且字典的鍵是支持集合操作的,所以利用集合的交叉並補即可對字典的鍵進行處理;
  2. items()返回(key, value)組成的對象,支持集合操作;
  3. values()並不支持集合操作,因為並不能保證所有的值是唯一的,但是如果必須要判斷操作,可以先將值轉化為集合來實現。

舉例:

In [78]: a = {'a':1, 'b':2, 'c':3}

In [79]: b = {'b':3, 'c':3, 'd':4}

In [80]: a.keys() & b.keys()
Out[80]: {'b', 'c'}

In [81]: a.keys() - b.keys()
Out[81]: {'a'}

In [82]: a.items() & b.items()
Out[82]: {('c', 3)}

再舉一個例子,在創建一個字典時,期望可以去除某些鍵:

In [85]: a
Out[85]: {'a': 1, 'b': 2, 'c': 3}

In [86]: c = {k: a[key] for k in a.keys() - {'b'}}

In [87]: c
Out[87]: {'a': 3, 'c': 3}

以上。

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

【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

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

※帶您來看台北網站建置台北網頁設計,各種案例分享

紅毛猩猩家園不保 印尼水壩爭議 凸顯聯合國碳排放計算漏洞

環境資訊中心綜合外電;黃鈺婷 翻譯;彭瑞祥 審校;稿源:Mongabay

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

【其他文章推薦】

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

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

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

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

財政部長樓繼偉:2020年針對新能源汽車的補貼將退出

中國財政部長樓繼偉日前在中國電動汽車百人會論壇上表示,汽車企業對消費補貼過度依賴,2020年針對新能源汽車的補貼將退出。

樓繼偉稱2020年針對新能源汽車的補貼將退出;2017-2018年的補貼標準比2016年水準下調20%。

樓繼偉稱對優勢企業的補貼將提高。十三五期間中國新能源汽車市場進一步開放,將加快建立新能源汽車積分交易機制。

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

【其他文章推薦】

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

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

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

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

王傳福:到2030年私家車將全面電動化

近日,第二屆中國電動汽車百人會論壇在京召開。比亞迪股份有限公司董事長王傳福在本屆大會上指出:國家政策對新能源車發展具有巨大推動作用。在這體種體制優勢的推動下,到2020年大巴和計程車將全面電動化,到2025年,貨車(物流車)全面電動化,到2030年私家車將全面電動化。

縱觀全球新能源車市場,歐洲新能源車在2015年有所增長,美國則因為油價下降導致新能源車增速放緩,而中國則是增長最為迅速的新能源車市場。“可以看出兩種機制下增長的態勢,中國是體制在推動,國外是市場資源的配製在推動,兩個手造成的結果是完全不一樣的。中國新能源汽車市場產銷快速增長,最後的推手凸顯了政府的體制優勢。”王傳福表示。

王傳福稱,我國體制機制的優勢在於強有力的政策支持。一個標誌性事件就是2013年7月份國務院頒佈了新能源汽車發展指導意見,後來被人們稱為“三十條”。王傳福認為,“三十條”是全球最系統、最科學、最全面、最有效的政策體系,囊括了財稅、政策、法規、標準、基礎設施等一系列政策。“三十條”已經逐項落實,帶來了如今新能源車市場飛速發展的現狀。

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

【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

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

※帶您來看台北網站建置台北網頁設計,各種案例分享

電動車生意難做?電池廠三星SDI淨損飆、股價遭熊吻

三星集團的電池大廠三星SDI(Samsung SDI)公布2015年第4季財報,虧損擴大。不過三星SDI的化學部門即將分拆出售給樂天集團(Lotte),或許有助未來獲利。   韓聯社報導,三星SDI 25日盤後公布,去年Q4營收年減2.53%至1.86兆韓圜,營損為808億韓圜。淨損為2,313億韓圜(1.94億美元),虧損高於2014年同期的淨損1,288億美元,也不及Q3出現淨利5,024億韓圜。   此外,三星SDI股東25日同意,將分拆化學部門成立獨立公司,暫名為「SDI Chemical」,其中90%股權將在今年上半之前,出售給Lotte Chemical。去年十月樂天集團以3兆韓圜(25億美元)入股三星SDI化學部門,並購買Samsung Fine Chemicals的31.5%股權。   嘉實XQ全球贏家系統報價顯示,三星SDI 25日上漲0.49%收在102,500韓圜。和去年12月16日波段收盤高點(129,000韓圜),重挫21%之多。   三星集團積極卡位汽車市場,旗下電池大廠三星SDI (Samsung SDI)展示最新的車用電池,充電一次可跑600公里,預定2020年量產。   韓聯社11日報導,三星SDI在底特律車展秀出新的車用電池原型,充電一次可跑600公里,高於先前的500公里,預定2020年量產。該公司估計,2020年全球電動車銷量將增至116萬輛,遠高於2015年的27.8萬輛。   三星SDI已成為純粹的車用電池廠,該公司目前在南韓與中國擁有五座生產線、總產能多達4.5GWh。到了2020年,該公司還打算把產能擴充到35GWh。barron`s.com 去年11月4日報導,Bernstein Research分析師Mark Newman發表研究報告指出,三星SDI在2020年的車用電池產能,正好與特斯拉預定的Gigafactory產能完全一致。   (本文內容由授權使用)

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

【其他文章推薦】

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

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

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

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

常隆客車和AESC公司達成了電動客車錳酸鋰離子電池供應協定

2016年1月14日, 江蘇常隆客車有限公司(江蘇省江陰市)和AESC公司(神奈川縣座間市)達成了電動客車錳酸鋰離子電池供應協定,雙方高層出席了隆重的簽字儀式。本次合作協定初期電池供給規模為30MWh,可以滿足100台12米電動客車的錳酸鋰電池。雙方還預計到2017年可以持續供貨900MWh規模的錳酸鋰電池,滿足3000輛純電動客車生產需求。

通過雙方合作,常隆電動客車將更加安全可靠、經久耐用,努力為市場提供最好的電動客車、最優質的服務、最值得信賴的品牌。同時雙方約定今後持續探討本次合作的動力電池在儲能領域應用的可行性。雙方計畫同步開拓中國市場和國際市場。

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

【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

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

※帶您來看台北網站建置台北網頁設計,各種案例分享

手把手教你實現熱更新功能,帶你了解 Arthas 熱更新背後的原理

文章來源:

一、前言

一天下午正在摸魚的時候,測試小姐姐走了過來求助,說是需要改動測試環境 mock 應用。但是這個應用一時半會又找不到源代碼存在何處。但是測試小姐姐的活還是一定要幫,突然想起了 Arthas 可以熱更新應用代碼,按照網上的步驟,反編譯應用代碼,加上需要改動的邏輯,最後熱更新成功。對此,測試小姐姐很滿意,並表示下次會少提 Bug。

嘿嘿,以前一直對熱更新背後原理很好奇,藉著這個機會,研究一下熱更新的原理。

二、Arthas 熱更新

我們先來看下 Arthas 是如何熱更新的。

詳情參考:

假設我們現在有一個 HelloService 類,邏輯如下,現在我們使用 Arthas 熱更新代碼,讓其輸出 hello arthas

public class HelloService {

    public static void main(String[] args) throws InterruptedException {

        while (true){
            TimeUnit.SECONDS.sleep(1);
            hello();
        }
    }

    public static void hello(){
        System.out.println("hello world");
    }

}

2.1、jad 反編譯代碼

首先運行 jad 命令反編譯 class 文件獲取源代碼,運行命令如下:。

jad --source-only com.andyxh.HelloService > /tmp/HelloService.java

2.2、修改反編譯之後的代碼

拿到源代碼之後,使用 VIM 等文本編輯工具編輯源代碼,加入需要改動的邏輯。

2.3、查找 ClassLoader

然後使用 sc 命令查找加載修改類的 ClassLoader,運行命令如下:

$ sc -d  com.andyxh.HelloService | grep classLoaderHash
 classLoaderHash   4f8e5cde

這裏運行之後將會得到 ClassLoader 哈希值。

2.4、 mc 內存編譯源代碼

使用 mc 命令編譯上一步修改保存的源代碼,生成最終 class 文件。

$ mc -c 4f8e5cde  /tmp/HelloService.java  -d /tmp
Memory compiler output:
/tmp/com/andyxh/HelloService.class
Affect(row-cnt:1) cost in 463 ms.

2.5、redefine 熱更新代碼

運行 redefine 命令:

$ redefine /tmp/com/andyxh/HelloService.class
redefine success, size: 1

熱更新成功之後,程序輸出結果如下:

一般情況下,我們本地將會有源代碼,上面的步驟我們可以進一步省略,我們可以先在自己 IDE 上改動代碼,編譯生成 class 文件。這樣我們只需要運行 redefine 命令即可。也就是說實際上起到作用只是 redefine

三、 Instrumentation 與 attach 機制

Arthas 熱更新功能看起來很神奇,實際上離不開 JDK 一些 API,分別為 instrument API 與 attach API。

3.1 Instrumentation

Java Instrumentation 是 JDK5 之後提供接口。使用這組接口,我們可以獲取到正在運行 JVM 相關信息,使用這些信息我們構建相關監控程序檢測 JVM。另外, 最重要我們可以替換修改類的,這樣就實現了熱更新。

Instrumentation 存在兩種使用方式,一種為 pre-main 方式,這種方式需要在虛擬機參數指定 Instrumentation 程序,然後程序啟動之前將會完成修改或替換類。使用方式如下:

java -javaagent:jar Instrumentation_jar -jar xxx.jar

有沒有覺得這種啟動方式很熟悉,仔細觀察一下 IDEA 運行輸出窗口。

另外很多應用監控工具,如:zipkin、pinpoint、skywalking。

這種方式只能在應用啟動之前生效,存在一定的局限性。

JDK6 針對這種情況作出了改進,增加 agent-main 方式。我們可以在應用啟動之後,再運行 Instrumentation 程序。啟動之後,只有連接上相應的應用,我們才能做出相應改動,這裏我們就需要使用 Java 提供 attach API。

3.2 Attach API

Attach API 位於 tools.jar 包,可以用來連接目標 JVM。Attach API 非常簡單,內部只有兩個主要的類,VirtualMachineVirtualMachineDescriptor

VirtualMachine 代表一個 JVM 實例, 使用它提供 attach 方法,我們就可以連接上目標 JVM。

 VirtualMachine vm = VirtualMachine.attach(pid);

VirtualMachineDescriptor 則是一個描述虛擬機的容器類,通過該實例我們可以獲取到 JVM PID(進程 ID),該實例主要通過 VirtualMachine#list 方法獲取。

        for (VirtualMachineDescriptor descriptor : VirtualMachine.list()){

            System.out.println(descriptor.id());
        }

介紹完熱更新涉及的相關原理,接下去使用上面 API 實現熱更新功能。

四、實現熱更新功能

這裏我們使用 Instrumentation agent-main 方式。

4.1、實現 agent-main

首先需要編寫一個類,包含以下兩個方法:

public static void agentmain (String agentArgs, Instrumentation inst);          [1]
public static void agentmain (String agentArgs);            [2]

上面的方法只需要實現一個即可。若兩個都實現, [1] 優先級大於 [2],將會被優先執行。

接着讀取外部傳入 class 文件,調用 Instrumentation#redefineClasses,這個方法將會使用新 class 替換當前正在運行的 class,這樣我們就完成了類的修改。

public class AgentMain {
    /**
     *
     * @param agentArgs 外部傳入的參數,類似於 main 函數 args
     * @param inst
     */
    public static void agentmain(String agentArgs, Instrumentation inst) {
        // 從 agentArgs 獲取外部參數
        System.out.println("開始熱更新代碼");
        // 這裏將會傳入 class 文件路徑
        String path = agentArgs;
        try {
            // 讀取 class 文件字節碼
            RandomAccessFile f = new RandomAccessFile(path, "r");
            final byte[] bytes = new byte[(int) f.length()];
            f.readFully(bytes);
            // 使用 asm 框架獲取類名
            final String clazzName = readClassName(bytes);

            // inst.getAllLoadedClasses 方法將會獲取所有已加載的 class
            for (Class clazz : inst.getAllLoadedClasses()) {
                // 匹配需要替換 class
                if (clazz.getName().equals(clazzName)) {
                    ClassDefinition definition = new ClassDefinition(clazz, bytes);
                    // 使用指定的 class 替換當前系統正在使用 class
                    inst.redefineClasses(definition);
                }
            }

        } catch (UnmodifiableClassException | IOException | ClassNotFoundException e) {
            System.out.println("熱更新數據失敗");
        }


    }

    /**
     * 使用 asm 讀取類名
     *
     * @param bytes
     * @return
     */
    private static String readClassName(final byte[] bytes) {
        return new ClassReader(bytes).getClassName().replace("/", ".");
    }
}

完成代碼之後,我們還需要往 jar 包 manifest 寫入以下屬性。

## 指定 agent-main 全名
Agent-Class: com.andyxh.AgentMain
## 設置權限,默認為 false,沒有權限替換 class
Can-Redefine-Classes: true

我們使用 maven-assembly-plugin,將上面的屬性寫入文件中。

<plugin>
    <artifactId>maven-assembly-plugin</artifactId>
    <version>3.1.0</version>
    <configuration>
        <!--指定最後產生 jar 名字-->
        <finalName>hotswap-jdk</finalName>
        <appendAssemblyId>false</appendAssemblyId>
        <descriptorRefs>
            <!--將工程依賴 jar 一塊打包-->
            <descriptorRef>jar-with-dependencies</descriptorRef>
        </descriptorRefs>
        <archive>
            <manifestEntries>
                <!--指定 class 名字-->
                <Agent-Class>
                    com.andyxh.AgentMain
                </Agent-Class>
                <Can-Redefine-Classes>
                    true
                </Can-Redefine-Classes>
            </manifestEntries>
            <manifest>
                <!--指定 mian 類名字,下面將會使用到-->
                <mainClass>com.andyxh.JvmAttachMain</mainClass>
            </manifest>
        </archive>
    </configuration>
    <executions>
        <execution>
            <id>make-assembly</id> <!-- this is used for inheritance merges -->
            <phase>package</phase> <!-- bind to the packaging phase -->
            <goals>
                <goal>single</goal>
            </goals>
        </execution>
    </executions>
</plugin>

到這裏我們就完成熱更新主要代碼,接着使用 Attach API,連接目標虛擬機,觸發熱更新的代碼。

public class JvmAttachMain {
    public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException {
        // 輸入參數,第一個參數為需要 Attach jvm pid 第二參數為 class 路徑
        if(args==null||args.length<2){
            System.out.println("請輸入必要參數,第一個參數為 pid,第二參數為 class 絕對路徑");
            return;
        }
        String pid=args[0];
        String classPath=args[1];
        System.out.println("當前需要熱更新 jvm pid 為 "+pid);
        System.out.println("更換 class 絕對路徑為 "+classPath);
        // 獲取當前 jar 路徑
        URL jarUrl=JvmAttachMain.class.getProtectionDomain().getCodeSource().getLocation();
        String jarPath=jarUrl.getPath();

        System.out.println("當前熱更新工具 jar 路徑為 "+jarPath);
        VirtualMachine vm = VirtualMachine.attach(pid);//7997是待綁定的jvm進程的pid號
        // 運行最終 AgentMain 中方法
        vm.loadAgent(jarPath, classPath);
    }
}

在這個啟動類,我們最終調用 VirtualMachine#loadAgent,JVM 將會使用上面 AgentMain 方法使用傳入 class 文件替換正在運行 class。

4.2、運行

這裏我們繼續開頭使用的例子,不過這裏加入一個方法獲取 JVM 運行進程 ID。

public class HelloService {

    public static void main(String[] args) throws InterruptedException {
        System.out.println(getPid());
        while (true){
            TimeUnit.SECONDS.sleep(1);
            hello();
        }
    }

    public static void hello(){
        System.out.println("hello world");
    }

    /**
     * 獲取當前運行 JVM PID
     * @return
     */
    private static String getPid() {
        // get name representing the running Java virtual machine.
        String name = ManagementFactory.getRuntimeMXBean().getName();
        System.out.println(name);
        // get pid
        return name.split("@")[0];
    }

}

首先運行 HelloService,獲取當前 PID,接着複製 HelloService 代碼到另一個工程,修改 hello 方法輸出 hello agent,重新編譯生成新的 class 文件。

最後在命令行運行生成的 jar 包。

HelloService 輸出效果如下所示:

4.3、調試技巧

普通的應用我們可以在 IDE 直接使用 Debug 模式調試程序,但是上面的程序無法直接使用 Debug。剛開始運行的程序碰到很多問題,無奈之下,只能選擇最原始的辦法,打印錯誤日誌。後來查看 arthas 的文檔,發現上面一篇文章介紹使用 IDEA Remote Debug 模式調試程序。

首先我們需要在 HelloService JVM 參數加入以下參數:

-Xrunjdwp:transport=dt_socket,server=y,address=8001  

此時程序將會被阻塞,直到遠程調試程序連接上 8001 端口,輸出如下:

然後在 Agent-main 這個工程增加一個 remote 調試。

圖中參數如下:

-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8001

Agent-main 工程打上斷點,運行遠程調試, HelloService 程序將會被啟動。

最後在命令行窗口運行 Agent-main 程序,遠程調試將會暫停到相應斷點處,接下來調試就跟普通 Debug 模式一樣,不再敘述。

4.4、相關問題

由於 Attach API 位於 tools.jar 中,而在 JDK8 之前 tools.jar 與我們常用JDK jar 包並不在同一個位置,所以編譯與運行過程可能找不到該 jar 包,從而導致報錯。

如果 maven 編譯與運行都使用 JDK9 之後,不用擔心下面問題。

maven 編譯問題

maven 編譯過程可能發生如下錯誤。

解決辦法為在 pom 下加入 tools.jar 。

        <dependency>
            <groupId>jdk.tools</groupId>
            <artifactId>jdk.tools</artifactId>
            <scope>system</scope>
            <version>1.6</version>
            <systemPath>${java.home}/../lib/tools.jar</systemPath>
        </dependency>

或者使用下面依賴。

        <dependency>
            <groupId>com.github.olivergondza</groupId>
            <artifactId>maven-jdk-tools-wrapper</artifactId>
            <version>0.1</version>
            <scope>provided</scope>
            <optional>true</optional>
        </dependency>

程序運行過程 tools.jar 找不到

運行程序時拋出 java.lang.NoClassDefFoundError,主要原因還是系統未找到 tools.jar 導致。

在運行參數加入 -Xbootclasspath/a:${java_home}/lib/tools.jar,完整運行命令如下:

4.5、熱更新存在一些限制

並不是所有改動熱更新都將會成功,當前使用 Instrumentation#redefineClasses 還是存在一些限制。我們僅只能修改方法內部邏輯,屬性值等,不能添加,刪除方法或字段,也不能更改方法的簽名或繼承關係。

五、彩蛋

寫完熱更新代碼,收到一封系統郵件提示 xxx bug 待修復。恩,說好的少提 Bug 呢 o(╥﹏╥)o。

六、幫助

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

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

【其他文章推薦】

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

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

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

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

MySQL InnoDB 事務

MySQL 原理篇

MySQL5.5 及以後版本的默認存儲引擎使用的是 InnoDB,接下來針對事務的講解都是基於 InnoDB 存儲引擎的。

事務的定義

事務:數據庫操作的最小工作單元,是作為單個邏輯工作單元執行的一系列操作; 事務是一組不可再分割的操作集合(工作邏輯單元)。

典型事務使用場景:轉賬

update user_account set balance = balance - 1000 where userID = 3;
update user_account set balance = balance + 1000 where userID = 1;

MySQL 開啟事務

/*
  BEGIN / START TRANSACTION        --手工
  COMMIT / ROLLBACK                --事務提交或回滾
  SET SESSION autocommit = ON/OFF  --設定會話級別事務是否自動開啟
*/

MySQL 默認是開啟事務的,通過 SHOW VARIABLES like 'autocommit'; 可以查看 MySQL 的事務開啟情況。

  • 在 autocommit = ON(自動提交事務)的情況下,可以執行 BEGIN; 或者 START TRANSACTION; 命令,改為手動提交事務,執行完 SQL 語句后,需要通過 COMMIT 命令提交事務,或者通過 ROLLBACK 命令回滾事務。
  • 在 autocommit = OFF(手動提交事務)的情況下,執行完 SQL 語句后,需要通過 COMMIT 命令提交事務,或者通過 ROLLBACK 命令回滾事務。

JDBC 編程

connection.setAutoCommit(boolean);

測試命令

SHOW VARIABLES like 'autocommit';

/* autocommit為ON */
update teacher set name ='seven' where id =1;
insert teacher (name,age) value ('james',22);
delete from teacher where name = 'james';

/* autocommit為ON如何開啟事務 */
BEGIN;
START TRANSACTION;

update teacher set name ='seven' where id =1;
insert teacher (name,age) value ('james',22);
delete from teacher where name = 'james';

COMMIT;
ROLLBACK;

/* 將autocommit改成OFF */
set session autocommit = OFF;
update teacher set name ='seven' where id =1;
insert teacher (name,age) value ('james',22);
delete from teacher where name = 'james';

COMMIT;
ROLLBACK;

事務 ACID 特性

原子性(Atomicity):最小的工作單元,整個工作單元要麼一起提交成功,要麼全部失敗回滾。

一致性(Consistency):事務中操作的數據及狀態改變是一致的,即寫入資料的結果必須完全符合預設的規則,不會因為出現系統意外等原因導致狀態的不一致。

隔離型(Isolation):一個事務所操作的數據在提交之前,對其他事務的可見性設定(一般設定為不可見)。

持久性(Durability):事務所做的修改就會永久保存,不會因為系統意外導致數據的丟失。

參考()這篇博文,講了一下原子性和一致性的區別。

事務併發帶來了哪些問題

臟讀

比如 user 表中有一條用戶數據,執行了如下操作:

  1. 事務B更新 id=1 的數據,age 更新為18,不提交事務
  2. 事務A查詢 id=1 的數據
  3. 事務B回滾剛才的更新操作

這個時候,事務A中查詢出的 id=1 的數據,age 的值是16還是18?

不可重複讀

 

比如 user 表中有一條用戶數據,執行了如下操作:

  1. 事務A查詢 id=1 的數據
  2. 事務B更新 id=1 的數據,age 更新為18,並提交事務
  3. 事務A再次查詢 id=1 的數據

這個時候,事務A兩次查詢出的 id=1 的數據,age 的值是16還是18?

幻讀

比如在 user 表中執行了如下操作:

  1. 事務A查詢 age>15 的數據
  2. 事務B新增一條數據,age=22,並提交事務
  3. 事務A再次查詢 age>15 的數據

這個時候,事務A兩次查詢出的數據,數量是1條還是2條?

事務四種隔離級別

SQL92 ANSI/ISO標準:http://www.contrib.andrew.cmu.edu/~shadow/sql/sql1992.txt

Read Uncommitted(未提交讀未解決併發問題

事務未提交對其他事務也是可見的,臟讀(dirty read)

Read Committed(提交讀)解決臟讀問題

一個事務開始之後,只能看到自己提交的事務所做的修改,不可重複讀(non repeatable read)

Repeatable Read(可重複讀解決不可重複讀問題

在同一個事務中多次讀取同樣的數據結果是一樣的,這種隔離級別未定義解決幻讀的問題

Serializable(串行化)解決所有問題

最高的隔離級別,通過強制事務的串行執行

InnoDB 引擎對隔離級別的支持程度

事務隔離級別

臟讀

不可重複讀

幻讀

未提交讀(Read Uncommitted)

可能

可能

可能

已提交讀(Read Committed)

不可能

可能

可能

可重複讀(Repeatable Read)

不可能

不可能

對 InnoDB 不可能

串行化(Serializable)

不可能

不可能

不可能

事務隔離級別的併發能力:未提交讀 > 已提交讀 > 可重複讀 > 串行化

InnDB 引擎默認的事務隔離級別是可重複讀(Repeatable Read),在該級別中它把幻讀的問題也解決了。InnDB 中事務隔離級別通過鎖、MVCC 實現。

通過如下語句查看 InnoDB 的默認事務隔離級別:

/* 全局、當前會話的隔離級別 */
SELECT @@global.tx_isolation, @@tx_isolation;

通過如下語句設置 InnoDB 的事務隔離級別:

/* 設置全局隔離級別 */
set global transaction isolation level read committed;
/* 設置當前會話的隔離級別 */
set session transaction isolation level read committed;

接下來我們來測試一下 InnoDB 的默認事務隔離級別(Repeatable Read)是否解決了臟讀、不可重複讀、幻讀的問題。

數據準備:

CREATE TABLE `user` (
  `id` int(11) NOT NULL,
  `name` varchar(32) NOT NULL,
  `age` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

insert into `user` (`id`, `name`, `age`) values('1','Ada','16');

是否解決了臟讀的問題?

開啟兩個 MySQL 客戶端,模擬事務A和事務B的操作,執行步驟如下:

  1. 事務B開啟手動事務,更新 id=1 的數據,age 更新為18,不提交事務
  2. 事務A查詢 id=1 的數據
  3. 事務B回滾剛才的更新操作

圖中的数字是執行步驟,通過下圖可以看出事務A的執行結果是16,InnDB 的默認事務隔離級別完美的解決了臟讀的問題。

是否解決了不可重複讀的問題?

開啟兩個 MySQL 客戶端,模擬事務A和事務B的操作,執行步驟如下:

  1. 事務A開啟手動事務,查詢 id=1 的數據
  2. 事務B更新 id=1 的數據,age 更新為18,並提交事務
  3. 事務A再次查詢 id=1 的數據

圖中的数字是執行步驟,通過下圖可以看出事務A兩次的執行結果都是16,沒有受事務B更新操作的影響,InnDB 的默認事務隔離級別完美的解決了不可重複讀的問題。

是否解決了幻讀的問題?

開啟兩個 MySQL 客戶端,模擬事務A和事務B的操作,執行步驟如下:

  1. 事務A開啟手動事務,查詢 age>15 的數據
  2. 事務B新增一條數據,age=22,並提交事務
  3. 事務A再次查詢 age>15 的數據

圖中的数字是執行步驟,通過下圖可以看出事務A兩次的執行結果都是一條數據,沒有受事務B新增操作的影響,InnDB 的默認事務隔離級別完美的解決了幻讀的問題。

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

【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

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

※帶您來看台北網站建置台北網頁設計,各種案例分享

微型電動車或納入摩托車管理範疇 政策或將2016年出臺

據瞭解,工信部、發改委、公安部、環境部等部委已經對微型電動汽車進行調研,通過高標準放行一小部分產品後,其他微型電動車或將納入新的摩托車管理範疇。

中國電動汽車百人會微型電動車課題組組長介紹,通過多次微型電動車發展問題的調研與討論,並形成了相關的政策管理建議方案。他表示,馬凱總理對微型電動車的發展已有批示,要納入正常的機動車管理。

這是一個積極正面的信號,而不是否認微型電動車的存在價值。

在具體的管理標準上,一位從事新能源汽車研究的相關人士告訴記者,目前政策傾向于按照類似歐盟的摩托車歸類管理微型電動車。同時,在歐盟標準之上,按照中國不同城市特色區分管理,比如,在北上廣深等一線城市禁止微型電動車上路,但遠郊區可放開,三四線及以下的城市也可以上路,這就規避了實際管理中的很多矛盾。
 

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

【其他文章推薦】

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

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

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

台灣海運大陸貨務運送流程

兩岸物流進出口一站式服務

南美洲成立新網站 鼓勵舉報投訴貪污及破壞環境

摘錄自2020年2月20日星島日報報導

三個非政府組織在南美洲成立一個新網站,鼓勵公眾舉報及投訴貪污及破壞環境的行為。由智利民權組織 Ciudadania Inteligente 、美國 Proyecto PODER 組織及西班牙國際巴爾塔薩.加爾松基金會(International Baltasar Garzon Foundation)合力促成。

三個組織發表聲明稱,成立這個網站的目的是提供一個重要工具和途徑,鼓勵公眾舉報不公義的事情和行為,藉以建立更公平的社會,為那些權利和生活都面對危害的拉丁美洲人提供保障。

三個組織指出,過往一些震動拉丁美洲的政治和金錢醜聞,都是因為有人察覺到詐騙和犯罪行為而揭發的,包括涉及巴西大型工程公司 Odebrecht 的醜聞。該公司被指在區內數個國家行賄,獲取大型基建工程合約,涉及款項數以10億美元計。

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

【其他文章推薦】

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

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

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

台灣海運大陸貨務運送流程

兩岸物流進出口一站式服務