標籤: 網頁設計公司

  • 不畏油價跌 美電動車 12 月創最高單月銷售紀錄

    儘管美國汽油零售價格跟隨國際原油同步下挫到每加侖 2 美元的低水準,2014 年美國電動車銷售強勁增加 23%,12 月並創下單月最佳紀錄;顯見電動車已蔚為一股持久的趨勢,不受油價影響。   根據電動車專業網站 InsideEVs 統計,2014 年 12 月美國賣出 12,874 輛電動車,為統計以來最高單月銷售紀錄。至於 2014 年全年,電動車銷售勁揚 23%,達到 119,710 輛。   就個別品牌來看,全美電動車銷售冠軍是日產 Leaf,2014 年總計售出 30,200 輛,遙遙領先亞軍通用汽車雪佛蘭 Volt 的 18,805 輛。起價介於 3.5 萬到 4 萬美元的特斯拉 Model S 排名第 3,全年銷量為 17,300 輛,市占不及 15%。不過,在 12 月 Model S 首度擊敗售價較低的日產 Leaf(約 3 萬美元)。   2014 年美國電動車市場唯一新上市車款是 BMW,分別在 5 月及 8 月推出 i3 與 i8 ,加總的銷量僅 6,647 輛。展望 2015 年,將有數款新型電動車上路,尤其是在休旅車和越野車市場。而特斯拉的 Model X 則預計 2015 年下半年登場,是今年最受期待的電動車。

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

    【其他文章推薦】

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

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

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

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

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

    ※超省錢租車方案

  • 2014中國高新汽車國際峰會圓滿閉幕,廣受業界好評

    第三屆中國高新汽車國際峰會已於2014年12月9日在上海浦東嘉里大酒店完美落幕。本屆峰會由法蘭克福展覽(上海)有限公司和中國國家發展和改革委員會國際合作中心聯合主辦,共邀請到包括政府官員、行業協會代表和知名整車企業在內的14位重量級演講嘉賓出席,共同探討未來汽車產業革新的重要創新技術、市場趨勢和發展戰略方針。為期一整天的峰會由一個主論壇和兩個分論壇組成,共迎來386位與會代表,專業領域橫跨汽車全產業鏈。

    法蘭克福展覽(上海)有限公司總經理李慶新先生表示:“由於整體定位和結構調整,峰會由往年的兩天精簡為一天時間,但是本屆峰會的參會代表人數仍再創新高。由此印證了中國高新汽車國際峰會作為汽車行業的年度盛會,為業內重要政策決策者、企業高管和投資者建立了交流契機和合作機會。與會代表對我們表達了支持和鼓勵,我們非常高興可以收到如此正面的肯定。我們相信中國高新汽車國際峰會將會成為討論汽車行業發展趨勢、最新技術以及業界精英交流討論的高端平台。”

    參會代表和演講嘉賓滿意會議定位,共同關注汽車行業未來合作契機

    本屆峰會以「關注未來汽車發展趨勢,聚焦零部件產業升級」為主題,共邀請到多位政府領導蒞臨並致辭。其中包括:

    • 鄭惠強先生,全國政協常委、上海市人大副主任
    • 李鋼先生,國家發改委產業協調司處長
    • 徐秉金先生,中歐協會會長、商務部原副部長

    峰會由上海市新能源汽車推進辦公室主任劉建華先生擔任首位演講嘉賓,就「發起『新能源汽車走進社區』倡議」議題發表真知灼見。除此之外,強大演講嘉賓陣容包括來自寶馬、特斯拉、沃爾沃、上汽和北汽等國內外知名整車企業。首次擔任演講嘉賓的上海汽車集團股份有限公司汽車工程中心車身開發總監邱國華博士此次主要圍繞上汽集團電動汽車發展戰略展開介紹,並評價道:“中國高新汽車國際峰會是一個高端的汽車行業交流平台,議題定位準確。主題覆蓋整車、能源和材料等一系熱門話題。峰會聚集了汽車行業的技術戰略研究和市場分析等高端人才,我認為這是一個非常出色的活動。”

    特斯拉中國區公共政策和充電基礎設施總監高翔先生同樣也分享了他的感想,並表示:“本屆峰會邀請了很多政府政策制定者,他們的政策導向對於未來電動車的發展影響很大。我認為電動汽車產業正得到大眾越來越多的關注。我希望通過中國高新汽車國際峰會這個深入的交流平台,在政策環境和行業環境得到突破,未來能夠制定完善的行業標準,希望有更多的車企投入到電動汽車的開發中。”

    上海大眾汽車有限公司信息系統部高級經理邱振捷先生擔任”用互聯網思維造車”主題的演講嘉賓,關注電動汽車行業的前瞻性概念。他認為:“去年我是峰會的參會代表,而今年我非常榮幸可以擔任演講嘉賓出席本屆峰會。中國高新汽車國際峰會是一個齊聚行業精英的平台,活動的整體規劃和安排超出我的預期。在這裏我遇到了很多零部件企業和整車廠,我非常願意和他們互相交流經驗,這給我工作帶來很多幫助。”

    已經連續第二年出席的三菱綜合材料管理(上海)有限公司戰略企劃部經理徐冬榮先生對峰會在新能源汽車未來發展前景產生積極作用表示肯定,說道:“演講嘉賓介紹了最新的電動汽車技術和資訊,通過本屆峰會我瞭解到很多高新汽車的最新行業發展趨勢和扶持政策。我認為新能源汽車的未來發展前景是非常巨大的,因此發展速度和技術瓶頸將是最需要關注的問題。”

    首次參會的參會代表德國ThyssenKrupp System Engineering GmbH的業務發展專員Elena Kaplun女士希望通過中國高新汽車國際峰會洞察中國汽車行業的最新潛在投資機會,她表示道:“我對中國汽車市場有了深入的認識和瞭解。我對新能源汽車和輕量化汽車技術發展的話題很感興趣,嘉賓的演講內容對市場研究和業務發展戰略規劃很有幫助。除此之外,我還有機會在這裏拓展我的人脈,更可以和卓越的業界人士有進一步的合作機會如特斯拉、BMW和上汽等,我非常高興能夠參加此次活動!”

    2014 年中國高新汽車國際峰會同期舉行的 Automechanika Shanghai — 上海國際汽車零配件、維修檢測診斷設備及服務用品展覽會,是亞洲規模最大的汽車零部件、維修檢測診斷設備及汽車用品展覽會。

    欲瞭解更多有關峰會詳情,敬請訪問官方網站

    或發郵件至。

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

    【其他文章推薦】

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

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

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

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

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

  • 北京路燈變充電樁 望每天為一萬輛車充電

    北京市首批路燈充電樁改造已經展開,位於昌平區京密北路上的八十八盞路燈,從傳統高壓鈉燈改造為LED燈,可讓十幾輛電動汽車同時充電;此外,昌平區內還設置了八個純電動汽車慢充樁,專供電動車充電,充電時間約為四、五個小時,且白天晚上皆可充電。北京市科委指出,希望今年內能達成北京五環內建成五公里半徑的快速充電網路和每天為一萬輛車充電的目標。   除北京市內,連接北京與上海全程1262公里的京滬高速公路也將開通全線快速充電系統。平均每五十公里將設一座充電站,最快三十分鐘可充電完成。同等里程電費支出為燃油車的一半,全程充電費不到人民幣400元(約合新臺幣2052元)。   目前中國大陸的國家電網已完成2.4萬個充電樁,形成「兩縱一橫」網路,規模為世界之最。但據報導,其目前已建成的四百多座充電站幾乎沒有盈利,處於全線虧損,而在深圳市營運的七座充電站,每年虧損額超過1000萬人民幣(約5132.3萬元)。

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

    【其他文章推薦】

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

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

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

    南投搬家公司費用需注意的眉眉角角,別等搬了再說!

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

  • 通用推雪佛蘭 Bolt 欲與定價相當的 Tesla Model 3 抗衡

    通用推雪佛蘭 Bolt 欲與定價相當的 Tesla Model 3 抗衡

      美國底特律汽車展於 1 月 12 日開幕,通用汽車(GM)新款電動車雪佛蘭 Bolt(Chevrolet Bolt)首度亮相,預料未來將與電動車大廠特斯拉(Tesla)激烈競爭。   雪佛蘭 Bolt 每次充電可行駛 200 英里,達到先前油電混合車款 Volt 的 4 倍之多, 電池由韓國樂金化學(LG Chem)所製造,預計於 2017 年上市,售價約為 3 萬美元,可望與同樣預定 2017 年上市,售價約 3.5 萬美元的 Model 3 車款競相抗衡。根據知情人士表示,Bolt 為掀背式(Hatchback)設計,外型傾向跨界車款。   通用汽車盼能藉雪佛蘭 Bolt,加強消費者對該品牌具備完整產品線的印象,也希望能將此車款擴及至包括中國等全球市場。     (Source:)

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

    【其他文章推薦】

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

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

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

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

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

  • python高級-閉包-裝飾器

    閉包內容:

    1. 匿名函數:能夠完成簡單的功能,傳遞這個函數的引用,只有功能

    2. 普通函數:能夠完成複雜的功能,傳遞這個函數的引用,只有功能

    3. 閉包:能夠完成較為複雜的功能,傳遞這個閉包中的函數以及數據,因此傳遞是功能+數據

    4. 對象:能夠完成最複雜的功能,傳遞很多數據+很多功能,因此傳遞的是數據+功能

    ———————————————————

    1. 對全局函數進行修改:在函數當中加global,在閉包中外邊中的變量加nonlocal

    2. 閉包定義:有兩個函數嵌套使用,裏面的函數可以使用外面函數所傳輸的參數,最後可傳遞的是裏面函數的結構與數據(個人理解)。

    3. 最後閉包可以在python中引申出裝飾器 ———————————————————

     1 def closure():
     2     # 在函數內部再定義一個函數,
     3     # 並且這個函數用到了外邊函數的變量,那麼將這個函數以及用到的一些變量稱之為閉包
     4     def closure_in(x):
     5         print('---------我是打不死的%s--------' %x)
     6     return closure_in
     7  8 x = closure()
     9 x('小強')
    10 11 print('*'*20)
    12 # -----加餐---------
    13 def closure_1(a,b,c):
    14     def closure_on(x):
    15         print('-----%s加餐-------' %b)
    16         print(a*x + c)
    17     return closure_on
    18 19 demo = closure_1(2,'小強',3) #傳closure_1函數
    20 demo(4) #傳clsure_on函數
    21 22 #注:函數不加括號,調用的是函數本身【function】;函數加括號,調用的是函數的return結果。

     

    裝飾器內容:

    代碼要遵守‘開放封閉’原則;對已經寫好的函數遵守封閉,對功能擴展遵守開放;

     1 # 裝飾器的作用:為了對原來的代碼上進行擴展
     2 def decoration(func):
     3     def call_func():
     4         print('-------正在裝飾 -------' )
     5         func()
     6     return call_func
     7  8 #@decoration     #--->demo_new = decoration(demo)
     9 def demo():
    10    print('demo----')
    11 12 demo_new = decoration(demo)
    13 demo_new()

     

    使用裝飾器來測試一個函數的運行時:

     1 import time
     2 def set_func(func):
     3     def call_func():
     4         start_time = time.time()
     5         func()
     6         stop_func = time.time()
     7         print(‘alltimes is %f’ %(stop_func-start_fun))
     8     return call_func
     9 @set_func
    10 def test1():
    11     print(‘——-test1———’)    
    12 test1()
    13 14 #等價於:
    15 @set_func==test1 = set_func(test1)
    16

     

    1. 沒有參數,沒有返回值的函數進行裝飾:
     1 def set_func(func):
     2     def call_func():
     3         print(‘———test2——-’)
     4         print(‘———-test3——’)
     5         func()
     6     return call_func
     7     
     8 @set_func
     9 def test1():
    10     print(‘——test1——-   ’)

     

    2. 對有參數無返回值的函數進行裝飾:
     1 def set_func(func):
     2     def call_func(a):  #
     3         print(‘———test2——-’)
     4         print(‘———-test3——’)
     5         func(a) #
     6     return call_func
     7     
     8 @set_func
     9 def test1(num):
    10     print(‘——test1——- %d    ’ %num)
    11 12 test1(100) —->call_func(100)
    13 test1(200)——>call_func(200)

     

    復現裝飾器原理:

    ————————————————————————-

    只要遇到@函數 裝飾器(這句話),在程序中就已經執行了!!
    3. 不定長參數的函數裝飾:
     1 def set_func(func):
     2     def call_func(*args,**kwargs):  #
     3         print(‘———test2——-’)
     4         print(‘———-test3——’)
     5         func(*args,**kwargs) #(拆包)將元祖拆開,每個進行傳輸;
     6         #func(args,kwargs)—>不行,相當於傳遞了兩個參數:一個元祖,一個字典。
     7     return call_func
     8     
     9 @set_func
    10 def test1(num,*args,**kwargs):
    11     print(‘——test1——- %d    ’ %num)
    12     print(‘——test1——-   ’ , args)
    13     print(‘——test1——- ’ ,kwargs )
    14     
    15 test1(100)  
    16 test1(100,200)
    17 test1(100,200,300,mm=100)

    注意:*args保存不定長參數,以元祖保存,**kwargs保存字典形式(mm=…)

    4.對應的返回值參數進行裝飾、通用裝飾器:
     1 #通用裝飾器
     2 def set_func(func):
     3     print(“開始進行裝飾———-”)
     4     def call_func(*args,**kwargs):  #
     5         print(‘———test2——-’)
     6         print(‘———-test3——’)
     7         return func(*args,**kwargs) #(拆包)將元祖拆開,每個進行傳輸;如果沒有return ret返回none。
     8         #func(args,kwargs)—>不行,相當於傳遞了兩個參數:一個元祖,一個字典。
     9     return call_func
    10     
    11 @set_func
    12 def test1(num,*args,**kwargs):
    13     print(‘——test1——- %d    ’ %num)
    14     print(‘——test1——-   ’ , args)
    15     print(‘——test1——- ’ ,kwargs )
    16     return ‘ok’    #—-返回給上面的func(),然後return func—ret
    17     
    18 ret = test1(100)
    19

     

    5. 多個裝飾器對同一個函數進行裝飾:
     1 def add_qx(func):
     2     print(“——開始進行裝飾權限1———-”)
     3     def call_func(*args,**kwargs):  #
     4         print(‘這是權限驗證1’)
     5         return func(*args,**kwargs)
     6     return call_func
     7     
     8  9 def add_xx(func):
    10     print(“——開始進行裝飾xx功能———-”)
    11     def call_func(*args,**kwargs):  #
    12         print(‘這是xx權限驗證’)
    13         return func(*args,**kwargs)
    14     return call_func
    15     
    16 @add_qx
    17 @add_xx
    18 def test1():
    19     print(‘——test1——-’) 
    20 21 test1()
    22

     

    首先執行第一個,但是第一個裝飾器下面不是函數(裝飾器原則:下面必須是函數,否則不執行),所以第一個函數先等待,等第二個裝飾器執行后形成函數在交給第一個裝飾器;所以運行結果是:

    1. 開始進行裝飾xx的功能,

    2. 開始進行裝飾權限1,

    3. 這是權限驗證1,

    4. 這是xx權限驗證,

    5. ——-test1——-,

    ——————裝飾器練習—————- 輸出格式:<td><h1>haha</h1></td>

     1 def set_func_1(func):
     2     def call_func():
     3         return ‘<h1>’ + func() + ’</h1> 4     return call_func
     5     
     6  7 def set_func_2(func):
     8     def call_func():
     9         return ‘<td>’ + func() + ’</td>10     return call_func
    11     
    12 @set_func_1()
    13 @set_func_2()
    14 def get_str():
    15     return ‘haha’
    16     
    17 print(get_str()) 

    18 最後執行的效果: <h1><td>haha</td></h1>
    6. 用類對函數進行裝飾(了解):
     1 class Test(object):
     2     def __init__(self,func):
     3         self.func = fun
     4         
     5     def __call__(self):
     6         print(‘這裡是裝飾器的功能。。。。’)
     7         return self.func()
     8         
     9 @Test
    10 def get_str():
    11     return ‘haha’
    12     
    13 print(get_str())

     

    以上就是裝飾器與閉包的全部內容,希望有所收穫,如果有錯誤,希望指出,感謝!!

     

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

    【其他文章推薦】

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

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

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

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

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

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

  • 常見的索引模型淺析

    常見的索引模型淺析

      索引的出現是為了提高數據庫查詢的效率,就像書的目錄一樣。常見的索引模型有哈希表、有序數組、B+樹。

    • 自適應哈希索引(AHI)

      哈希表是一種常見的數據結構,即通過哈希算法計算出一個数字在表中的位置,並將数字存入該表。哈希索引就是通過哈希表來實現的,一般情況下查找時間複雜度為O(1)。InnoDB會監控對錶上各索引頁的查詢,會自動根據訪問的頻率和模式為某些熱點頁建立哈希索引,所以又叫自適應哈希索引,訪問模式一樣指查詢的條件一樣。

      比如我們維護一張身份證信息和用戶姓名的表,需要根據身份證號查詢姓名,哈希索引大概是這樣的:

      哈希索引適合只有等值查詢的場景,例如select * from T where index_col = ‘##’。哈希索引是無序的,如果需要區間查詢,那就要把所有數據掃描一遍。

    • 有序數組索引

      有序數組在等值查詢和區間查詢場景中效率都很高,同樣用上面的表舉例,索引大概是這樣的:

      要查詢某條數據或者區間的時候,使用二分法時間複雜度為O(logN)。但如果需要在中間更新數據時,那麼就要移動後面所有的數據。有序數組索引只適用於靜態存儲引擎,比如保存2019年度學校所有學生信息。

    • B+樹索引

      B+樹是為磁盤或其他直接存取輔助設備設計的一種平衡查找樹。下面是一顆高度為2的B+樹,所有記錄都在恭弘=叶 恭弘子結點上順序存放,恭弘=叶 恭弘子結點通過指針相連。

      B+樹索引就是B+樹在數據庫中的實現,由於B+索引在數據庫中具有高扇出性,在數據庫中B+樹的高度一般為2~4層。查找某一鍵值的行記錄時最多只需要2~4次IO。以InnoDB的一個整数字段索引為例,這顆B+樹大概是1200叉樹,這裏N叉樹的N取決於數據塊的大小。高度為4的時候就可以存1200的3次方個值,大概為17億。考慮到樹根的數據塊總是在內存中,一個10億行的表上一個整数字段的索引,查找一個值最多只需要訪問3次磁盤。

      在InnoDB存儲引擎中,表是根據主鍵順序存放的。根據恭弘=叶 恭弘子結點內容,B+樹索引又分為聚簇索引和輔助索引。

      • 聚簇索引:按照每張表的主鍵構造一顆B+樹,恭弘=叶 恭弘子結點的key是主鍵值, value是該行的其他字段值,聚簇索引的恭弘=叶 恭弘子結點也稱為數據頁。
      • 輔助索引:恭弘=叶 恭弘子結點的內容是主鍵的值。

    我們用一個例子來說明上面的概念,創建一張表,在字段k上有索引:

    create table T(
    id int primary key, 
    k int not null, 
    name varchar(16),
    index (k))engine=InnoDB;

      表中R1~R5的(ID,k)值分別為(100,1)、(200,2)、(300,3)、(500,5)和(600,6),兩顆B+樹如下,可以明顯看到這兩個顆樹的區別。

      使用普通索引查詢時,例如 select * from T where k = 2,需要先搜索k索引樹,得到ID的值為200;再到ID索引搜索一次。這個過程就叫做回表,非主鍵索引查詢需要多搜索一棵樹。

      參考資料:《MySQL實戰45講》

           《MySQL技術內幕:InnoDB存儲引擎》第二版

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

    【其他文章推薦】

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

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

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

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

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

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

  • 實時web應用方案——SignalR(.net core)

    實時web應用方案——SignalR(.net core)

    何為實時

    先從理論上解釋一下兩者的區別。

    大多數傳統的web應用是這樣的:客戶端發起http請求到服務端,服務端返回對應的結果。像這樣:

     

    也就是說,傳統的web應用都是客戶端主動發起請求到服務端。

    那麼實時web應用呢?它不需要主動發起請求,服務端可以主動推送信息到客戶端。

    舉栗子的話,實時聊天工具、web遊戲等都可以算是實時應用。

    什麼是SignalR

    如果想做一個實時應用,最好用web socket。很早以前我也寫過web socket的實現方式,但不夠全面,這裏再補上一篇。

    來說說signalR,它是一款開源的實時框架,可以使用三種方式實現通信(long polling、server sent events、web socket)。它很好的整合了底層技術,讓我們可以不用關注底層技術實現而把精力聚焦在業務實現上。一個完整的signalR包括客戶端和服務端,服務端支持net core/net framework,還支持大部分客戶端,比如瀏覽器和桌面應用。

    回落機制

    為了兼容不同瀏覽器(客戶端)和服務端,signalR採用了回落機制,使得它可以根據情況協商使用不同的底層傳輸方式。假如瀏覽器不支持web socket,就自動降級使用sse,再不行就long polling。當然,也可以禁用這種機制,指定其中一種。

    三種通信方式

    long polling(長輪詢)

    長輪詢是客戶端發起請求到服務端,服務器有數據就會直接返回。如果沒有數據就保持連接並且等待,一直到有新的數據返回。如果請求保持到一段時間仍然沒有返回,這時候就會超時,然後客戶端再次發起請求。

    這種方式優點就是簡單,缺點就是資源消耗太多,基本是不考慮的。

    server sent events(sse)

    如果使用了sse,服務器就擁有了向客戶端推送的能力,這些信息和流信息差不多,期間會保持連接。

    這種方式優點還是簡單,也支持自動重連,綜合來講比long polling好用。缺點也很明顯,不支持舊的瀏覽器不說,還只能發送本文信息,而且瀏覽器對sse還有連接數量的限制(6個)。

    web socket

    web socket允許客戶端和服務端同時向對方發送消息(也就是雙工通信),而且不限制信息類型。雖然瀏覽器同樣有連接數量限制(可能是50個),但比sse強得多。理論上最優先使用。

    進入正題

    開始之前,還需要了解RPC和Hub的概念。

    RPC:全程Remote Procedure Call,字面意思遠程服務調用,可以像調用本地方法一樣調用遠程服務。前端可以調用後端方法,後端也可以調用前端方法。

    Hub:基於RPC,接受從客戶端發過來的消息,也同時負責把服務端的消息發送給客戶端。客戶端可以調用Hub裏面的方法,服務端可以通過Hub調用客戶端裏面的方法。

    好了,概念已經理解清楚了,接下來上代碼。

    在項目里新增Hub類:

    using Microsoft.AspNetCore.SignalR;
    using System.Threading.Tasks;
    
    namespace SignalRDemo.Server
    {
        public class SignalRHub : Hub
        {
            /// <summary>
            /// 客戶連接成功時觸發
            /// </summary>
            /// <returns></returns>
            public override async Task OnConnectedAsync()
            {
                var cid = Context.ConnectionId;
    
                //根據id獲取指定客戶端
                var client = Clients.Client(cid);
    
                //向指定用戶發送消息
                await client.SendAsync("Self", cid);
    
                //像所有用戶發送消息
                await Clients.All.SendAsync("AddMsg", $"{cid}加入了聊天室");
            }
        }
    }

    為了讓外部可以訪問,我們還需要一個控制器。在控制器里聲明隨便建一個:

    using Microsoft.AspNetCore.Mvc;
    using Microsoft.AspNetCore.SignalR;
    using SignalRDemo.Server;
    using System.Threading.Tasks;
    
    namespace SignalRDemo.Controllers
    {
        public class HomeController : Controller
        {
            private readonly IHubContext<SignalRHub> _countHub;
    
            public HomeController(IHubContext<SignalRHub> countHub)
            {
                _countHub = countHub;
            }
    
            /// <summary>
            /// 發送信息
            /// </summary>
            /// <param name="msg"></param>
            /// <param name="id"></param>
            /// <returns></returns>
            public async Task Send(string msg, string id)
            {
                await _countHub.Clients.All.SendAsync("AddMsg", $"{id}:{msg}");
            }
        }
    }

    再然後進入StartUp設置端點:

    endpoints.MapHub<SignalRHub>("/hub");

    完成以後,配置signalr客戶端:

    setupConn = () => {
        conn = new signalR.HubConnectionBuilder()
            .withUrl("/hub")
            .build();
    
        conn.on("AddMsg", (obj) => {
            $('#msgPanel').append(`<p>${obj}</p>`);
        });
    
        conn.on("Finished", () => {
            conn.stop();
            $('#msgPanel').text('log out!');
        });
    
        conn.on("Self", (obj) => {
            $('#userId').text(obj);
        });
    
        conn.start()
            .catch(err => console.log(err));
    }

    要注意withUrl裏面的路徑就是之前設置好的端點。

    運行效果:

     

     

     Hub還支持組操作,比如:

    //將用戶添加到A組
    await
    Groups.AddToGroupAsync(Context.ConnectionId, "GroupA");
    //將用戶踢出A組
    await Groups.RemoveFromGroupAsync(Context.ConnectionId, "GroupA");
    //向A組所有成員廣播消息
    await Clients.Group("GroupA").SendAsync("AddMsg", "群組消息");

    更多操作請參考官方文檔。

    本文演示demo的源碼見git,地址:https://gitee.com/muchengqingxin/SignalRDemo.git

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

    【其他文章推薦】

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

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

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

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

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

    ※超省錢租車方案

  • 2020/6/11 JavaScript高級程序設計 DOM

    2020/6/11 JavaScript高級程序設計 DOM

    DOM(文檔對象模型)是針對HTML和XML文檔的一個API(應用程序接口)。他描繪了一個層次化的節點樹,允許開發人員添加、移除和修改頁面的某一部分。

    10.1 節點層次

    DOM將任何HTML和XML文檔描繪成一個由多層節點構成的結構。

    文檔節點(Document)是每個文檔的根節點。文檔節點只有一個子節點(HTML文檔中實終是<html>),我們稱之為文檔元素(每個文檔只能有一個文檔元素)。文檔元素是文檔的最外層元素,其他所有元素都包含在文檔元素中。

    每一段標記都能通過樹中一個節點來表示,包括特性、文檔類型、註釋等,共有12種節點類型。這些類型都繼承自一個基類型

    10.1.1 Node類型

     JavaScript中的所有節點類型都繼承自Node類型,所有的節點類型都共享相同的基本屬性和方法。

    nodeType屬性:表明節點的類型(12種)

    eg:Node.ELEMENT_NODE(1);  //元素節點

    通過該屬性可以確定一個節點的類型,可以通過類型字面量判等,也可以通過数字值比較。

    if (someNode.nodeType == Node.ELEMENT_NODE){  //在IE中無效
        alert("Node is element.");
    }
    
    if (someNode.nodeType == 1){  //適用於任何瀏覽器
        alert("Node is element.");
    }

    1. nodeName和nodeValue屬性

    可以了解節點的具體信息。

    對於元素節點,nodeName保存的始終是標籤名,nodeValue的值始終是null。

    2. 節點關係

     

    • 屬性
    1 childNodes屬性 保存NodeList對象(類數組,但不是數組),這個對象也有length屬性。可以通過方括號,也可以通過item()方法訪問節點。可以通過Array.prototype.slice()方法將其轉換為數組。
    2 parentNode屬性 指向父節點。
    3 previousSibling / nextSibling屬性 訪問同一列表中的其他屬性。即前一個和后一個同胞節點。
    4 firstChild / lastChild屬性 指向childNodes列表的第一個和最後一個節點。
    • 方法
    1 hasChildNodes() 檢驗是否存在子節點。存在時返回true。
    2 ownerDocument() 指向整個文檔的文檔節點。方便直接到達頂端。

    3. 操作節點

    1 appendChild() 向childNodes列表的末尾添加一個節點。返回新的節點。由於任何一個節點不能同時出現在多個位置上,所以當傳入的節點是父節點的子節點時,這個節點會變成最後一個子節點。
    2 insertBefore()

    將節點插入到childNodes列表中一個特定的位置。接收兩個參數:要插入的節點和作為參照的節點。

    插入節點后,被插入節點會成為參照節點的前一個同胞節點,同時被方法返回。省略參照節點時與appendChild()執行相同的操作。

    3 replaceChild() 替換節點(複製所有的關係指針)。接收兩個參數:要插入的節點和要替換的節點。被替換的節點將從文檔樹中移除,但仍然在文檔中,只是沒有了自己的位置(指針)
    4 removeChild() 移除節點。返回被移除的節點。同樣被移除的節點仍然在文檔中。

    PS1:使用這幾個方法必須取得父節點(使用parentNode屬性)。

    PS2:不是所有類型的節點都有子節點。在不支持子節點的節點上調用這些方法,會拋出錯誤。

    4. 其他方法

    1 cloneNode()

    創建調用這個方法的節點的一個完全相同的副本。接受一個布爾值參數,表示是否執行深複製(true則執行深複製)。

    • 深複製:複製節點以及整個子節點樹
    • 淺複製:只複製節點本身

    複製后返回的節點歸文檔所有,沒有為他指定父節點。要通過其他的方法把他加入到文檔中。

    IE>9及其他瀏覽器會計入空白節點。

    2 normalize()

    處理文檔樹中的文本節點。

    • 出現文本節點不包含文本 => 刪除空白文本節點
    • 接連出現兩個文本節點 => 合併為一個文本節點

    10.1.2 Document類型

    Document類型表示文檔。

    • document對象是HTMLDocument的一個實例,表示整個HTML頁面。
    • document對象是window對象的一個屬性,可以作為全局對象來訪問。

    Document節點的特徵:

    • nodeType的值為9;
    • nodeName的值是”#document”;
    • nodeValue和parentNode的值為null;
    • ownerDocument的值為null。

    1. 文檔的子節點

    1 DocumentType(最多一個) <!DOCTYPE>標籤,可以通過document.doctype屬性來訪問他的信息。
    2 Element(最多一個)

    文檔元素<html>。

    通過documentElement屬性childNodes列表(在無處理指令的情況下是firstChild)訪問可快速找到html元素。

    document.body屬性可以指向<body>元素(因為該元素使用頻率高,為了便於開發增添該屬性)。

    3 ProcessingInstruction 表示處理指令。
    4 Comment 註釋。

    2. 文檔信息

    作為HTMLDocument的實例,document對象還有一些特殊的屬性。

    1 title <title>元素中的文本,是當前頁面的標題。
    2 URL 完整的URL。
    3 domain

    頁面的域名。僅domain可以設置。但有一定的限制:

    • 不能設置為URL中不包含的域
    • 如果域名一開始是“鬆散的”(wrox.com),那麼就不能再將其設置回“緊繃的”(p2p.wrox.com)

    作用:將每個頁面的document.domain設置為相同的值,就可以互相訪問對方包含的JavaScript對象了。(解決跨域問題)

    4 referrer 鏈接到當前頁面的那個頁面的URL。在沒有來源頁面的情況下是空字符串。

    3. 查找元素

    1 getElementById() 參數為要取得元素的ID。找到返回該元素,沒有找到返回null。如果有多個id值相同,則只會返回第一個。
    2 getElementByTagName() 參數為要取得元素的標籤名。返回元素的NodeList。在HTML文檔中,返回的是HTMLCollection對象。可以通過方括號或者item()方法來訪問。
    3 nameItem() HTMLCollection對象的方法。通過元素的name屬性取得集合中的項(第一項)。同時HTMLCollection對象還支持按名稱訪問項。
    4 getElementByName() 返回帶有給定name屬性的所有元素(一個HTMLCollection)。

    4. DOM的一致性檢測

    ducument.implementation屬性:提供關於實現了DOM 哪些部分的信息的對象。

     他有一個方法,hasFeature()。接收兩個參數:要檢測的DOM功能的名稱和版本號。如果支持給定名稱和版本的功能,則返回true。

    檢驗結果true不意味着現實與規範一致,最好除了檢測hasFeature()之外,同時使用能力檢測

    5. 文檔寫入

    write() / writeln():接受一個字符串,即寫入輸出流中的文本。write()會原樣寫入,writeln()會在字符串末尾添加一個換行符(\n)。這兩個方法可以向頁面中動態的加入內容。

    //在頁面加載過程中輸出當前的日期和時間
    document.write("<strong>" + (new Date()).toString() + "</strong>");

    同時還可以用來動態的包含外部資源,例如JavaScript文件等。

    document.write("<script type=\"text/javascript\" src=\"file.js\"> + "<\/script>");

    PS:由於不能直接包含字符串”</script>”(這樣會導致該字符串被解釋為腳本的結束,後面的代碼無法運行),所以要將這個字符串分開寫。

    在頁面被呈現的過程中,會直接輸出內容。如果在文檔加載結束后(window.onload)再調用write(),那麼輸出的內容會重寫整個頁面。

    方法open()close()分別用於打開和關閉網頁的輸出流。

    10.1.3 Element類型

    Element類型提供了對元素標籤名、子節點及特性的訪問。Element節點具有以下特徵:

    • nodeType值為1;
    • nodeName的值為元素的標籤名;
    • nodeValue的值為null;
    • parentNode可能是Document或Element;

    tagName屬性:返回訪問元素的標籤名(與nodeName相同)。 => 在HTML中標籤名始終以全部大寫表示,需要檢驗標籤類型時最好調用toLowerCase()方法。

    1. HTML元素

    所有HTML元素都由HTMLElement類型表示。HTMLElement類型直接繼承自Element並添加了一些屬性。

    • id,元素在文檔中的唯一標識;
    • title,有關元素的附加說明信息,一般通過工具提示條显示出來;
    • dir,語言的方向(”ltr”,即left-to-right)或“rtl”;
    • className,與元素的class特性對應,為元素制定的CSS類。

    2. 取得特性

     

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

    【其他文章推薦】

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

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

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

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

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

  • IOT設備SmartConfig實現

    IOT設備SmartConfig實現

    一般情況下,IOT設備(針對wifi設備)在智能化過程中需要連接到家庭路由。但在此之前,需要將wifi信息(通常是ssid和password,即名字和密碼)發給設備,這一步驟被稱為配網。移動設備如Android、iOS等扮演發送wifi信息的角色,簡單來說就是移動應用要與IOT設備建立通信,進而交換數據。針對配網這一步驟,市面上一般有兩種做法:

    • AP連接方式:IOT設備發出AP(Access Point,可理解為路由器,可發出wifi)信息;移動設備STA(Station,可以連接wifi)連接到IOT設備AP,接着就可以發送wifi(家庭路由的wifi)信息給設備了。另外,也可互換角色,及移動設備釋放熱點,IOT設備進行連接。
    • SmartConfig(一鍵配置)方式:不需要建立連接,移動設備將wifi信息(需提前獲取)寫入數據包,組播循環發出此數據包;IOT設備處於監聽所有網絡的模式,接收到UDP包后解析出wifi信息拿去連網。

    可以發現,SmartConfig不需建立連接,步驟較少,實現起來也較容易,並且用戶也無需進行過多的操作。本文的IOT設備基於ESP32開發板,解釋原理及實現如何通過Android APP發出UDP包實現SmartConfig。知識點:計算機網絡、UDP、組播、DatagramSocket

    一、網絡知識回顧

    計算機網絡分層結構如下:

    • 應用層:體系中的最高層。任務是通過應用程序間的交互來完成特定網絡應用。不同的網絡應用對應不同的協議:如HTTPDNSSMTP。其交互的數據單元稱為報文。
    • 運輸層:複雜向兩台主機中進程直接的通信提供通用的數據傳輸服務,使用端口作為向上傳遞的進程標識,主要有TCP和UDP。
    • 網絡層:負責為分組交換網絡上的不同主機提供通信服務,使用IP協議。
    • 網絡接口層:包括數據鏈路層和物理層,傳輸單位分別是幀和比特。

    1. IP協議

    IP(Internet Protocol)協議是網絡層的主要協議。其版本有IPv4(32位)、IPv6(128位)。與IP協議配套使用的還有地址解析協議(ARP)、網際控制報文協議(ICMP,重要應用即常見的PING,測試連通性)、網際組管理協議(IGMP)。

    IP數據報格式,由首部和數據部分兩部分組成:

    IP地址分類如下:

    1.1 兩級IP地址

    IP地址是每一台主機唯一的標識符,由網絡號和主機號組成。A、B、C三類均為單播地址(一對一),D類為多播地址(一對多)。

    1.2 三級IP地址

    在兩級中新增了子網號字段,也稱為劃分子網。其方法是從主機號借用若干位作為子網號。

    子網掩碼:是一個網絡或子網的重要屬性,可通過子網掩碼計算出目的主機所處於哪一個子網。若沒有劃分子網,則使用默認子網掩碼(A類255.0.0.0、B類255.255.0.0、C類255.255.255.0)

    1.3 無分類編址

    無分類編址(CIDR)也稱為構造超網,使用網絡前綴+主機號規則,並使用斜線標明前綴位數,如:

    128.15.34.77/20
    
    1.4 IP多播

    多播又稱為組播,提供一對多的通信,大大節約網絡資源。IP數據報中不能寫入某一個IP地址,需寫入多播組的標識符(需要接收的主機與此標識符關聯)。D類地址即為多播組的標識符,所以多播地址(D類)只能作為目的地址。分為本局域網上的硬件多播、互聯網多播兩種。

    多播使用到的協議

    • IGMP(網際組管理協議):讓連接在本地局域網上的多播路由器(能夠運行多播協議的路由器)知道本局域網上是否有主機(進程)參加或退出了某個多播組。
    • 多播路由選擇協議:用於多播路由器之間的協同工作,以便讓多播數據報以最小的代價傳輸。

    2. UDP協議

    運輸層向上面的應用層提供通信服務,通信的端點並不是主機而是主機中進程,使用協議端口號標識進程(如HTTP為80)。UDP協議是運輸層中重要的兩個協議之一。

    • UDP是無連接的
    • UDP使用盡最大努力交付,不保證可靠交付
    • UDP是面向報文的
    • UDP沒有擁塞控制
    • UDP支持一對一,一對多,多對一和多對多
    • UDP首部開銷小

    二、Java中的UDP

    1. Socket

    socket是在應用層和傳輸層之間的一個抽象層,它把TCP/IP層複雜的操作抽象為幾個簡單的接口供應用層調用已實現進程在網絡中通信。簡單來說,socket是一種接口,對傳輸層(TCP/UPD協議)進行了的封裝。

    socket通信

    • TCP socket:需建立連接,TCP三次握手,基於流的通信(InputStrea和OutputStream)
    • UDP socket:無需建立連接,基於報文的通信。可以組播的形式發出報文,適合本場景中的配網步驟。

    2. Java中的socket

    2.1 類解釋

    Java為Socket編程封裝了幾個重要的類(均為客戶端-服務端模式):

    Socket
    實現了一個客戶端socket,作為兩台機器通信的終端,默認採用TCP。connect()方法請求socket連接、getXXXStream()方法獲取輸入/出流、close()關閉流。

    ServerSocket
    實現了一個服務器的socket,等待客戶端的連接請求。bind()方法綁定一個IP地址和端口、accept()方法監聽並返回一個Socket對象(會阻塞)、close()關閉一個socket

    SocketAddress # InetSocketAddress
    前者是一個抽象類,提供了一個socket地址,不關心傳輸層協議;後者繼承自前者,表示帶有IP地址和端口號的socket地址。

    DatagramSocket
    實現了一個發送和接收數據報的socket,使用UDP。send()方法發送一個數據報(DatagramPacket)、receive()方法接收一個數據報(一直阻塞接至收到數據報或超時)、close()方法關閉一個socket。

    DatagramPacket
    使用DatagramSocket時的數據報載體。

    2.2 UDP實例

    SmartConfig採用UDP實現,所以在前述知識的基礎下,先編寫一個例子熟悉java udp的使用,首先建立服務端的代碼:

    public class UDPServer {
        /**
         * 設置緩衝區的長度
         */
        private static final int BUFFER_SIZE = 255;
        /**
         * 指定端口,客戶端需保持一致
         */
        private static final int PORT = 8089;
    
        public static void main(String[] args) {
            DatagramSocket datagramSocket = null;
            try {
                datagramSocket = new DatagramSocket(PORT);
                DatagramPacket datagramPacket = new DatagramPacket(new byte[BUFFER_SIZE], BUFFER_SIZE);
                while (true) {
                    // 接收數據報,處於阻塞狀態
                    datagramSocket.receive(datagramPacket);
                    System.out.println("Receive data from client:" + new String(datagramPacket.getData()));
                    // 服務器端發出響應信息
                    byte[] responseData = "Server response".getBytes();
                    datagramPacket.setData(responseData);
                    datagramSocket.send(datagramPacket);
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (datagramSocket != null) {
                    datagramSocket.close();
                }
            }
        }
    }
    

    客戶端發出數據報:

    public class UDPClient {
        /**
         * 指定端口,與服務端保持一致
         */
        private static final int PORT = 8089;
        /**
         * 超時重發時間
         */
        private static final int TIME_OUT = 2000;
        /**
         * 最大重試次數
         */
        private static final int MAX_RETRY = 3;
    
        public static void main(String[] args) throws IOException {
            try {
                byte[] sendMsg = "Client msg".getBytes();
                // 創建數據報
                DatagramSocket socket = new DatagramSocket();
                // 設置阻塞超時時間
                socket.setSoTimeout(TIME_OUT);
                // 創建server主機的ip地址(此處使用了本機地址)
                InetAddress inetAddress = InetAddress.getByName("192.168.xxx.xxx");
                // 發送和接收的數據報文
                DatagramPacket sendPacket = new DatagramPacket(sendMsg, sendMsg.length, inetAddress, PORT);
                DatagramPacket receivePacket = new DatagramPacket(new byte[sendMsg.length], sendMsg.length);
                // 數據報文可能丟失,設置重試計數器
                int tryTimes = 0;
                boolean receiveResponse = false;
                // 將數據報文發送出去
                socket.send(sendPacket);
                while (!receiveResponse && (tryTimes < MAX_RETRY)) {
                    try {
                        // 阻塞接收數據報文
                        socket.receive(receivePacket);
                        // 檢查返回的數據報文
                        if (!receivePacket.getAddress().equals(inetAddress)) {
                            throw new IOException("Unknown server's data");
                        }
                        receiveResponse = true;
                    } catch (InterruptedIOException e) {
                        // 重試
                        tryTimes++;
                        System.out.println("TimeOut, try " + (MAX_RETRY - tryTimes) + " times");
                    }
                }
                if (receiveResponse) {
                    System.out.println("Receive from server:" + new String(receivePacket.getData()));
                } else {
                    System.out.println("No data!");
                }
                socket.close();
            } catch (SocketException e) {
                e.printStackTrace();
            }
        }
    }
    

    運行結果:

    * 發現客戶端收到的數據被截斷了,這是因為沒有重置接收包的長度,在服務端datagramPacket.setLength()可解決。

    三、SmartConfig

    根據前面的socket相關應用,基本想到如何實現一鍵配置。在實際應用中,原理一樣,只是增加了組播(這一點需要和IOT設備端共同確定,數據的格式也需協定)。在實現中,需要針對不同IP組播地址發出循環的UDP報文,增加設備端接收到的可能性;同時APP也要開啟服務端程序監聽發出數據報的響應,以此更新UI或進行下一步的數據通信。相關核心代碼如下:

    // 對每一個組播地址循環發出報文 
    while (!mIsInterrupt && System.currentTimeMillis() - currentTime < mParameter
             .getTimeoutGuideCodeMillisecond()) {
         mSocketClient.sendData(gcBytes2,
                 mParameter.getTargetHostname(),
                 mParameter.getTargetPort(),
                 mParameter.getIntervalGuideCodeMillisecond());
         // 跳出條件,發出UDP報文達到一定時間
         if (System.currentTimeMillis() - startTime > mParameter.getWaitUdpSendingMillisecond()) {
             break;
         }
    }
    

    組播地址設置:

    public String getTargetHostname() {
        if (mBroadcast) {
            return "255.255.255.255";
        } else {
            int count = __getNextDatagramCount();
            return "234." + (count + 1) + "." + count + "." + count;
        }
    }
    

    完整代碼省略(利益相關,代碼匿了^_^),基本思路很簡單。最終的實現是IOT設備收到UDP發出的wifi信息,並以此成功連接wifi,連接服務器,進而綁定賬號。

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

    【其他文章推薦】

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

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

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

    南投搬家公司費用需注意的眉眉角角,別等搬了再說!

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

  • mybatis緩存之一級緩存(二)

    mybatis緩存之一級緩存(二)

    這篇文章介紹下mybatis的一級緩存的生命周期

    一級緩存的產生

    一級緩存的產生,並不是看mappper的xml文件的select方法,看下面的例子

    mapper.xml

        <select id="getById" resultType="entity.TempEntity">
           select * from  temp where id = #{id}
        </select>
    

    test

        @Test
        public  void testSelectAsUpdate() throws IOException {
            InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
            SqlSessionFactory build = new SqlSessionFactoryBuilder().build(inputStream);
            SqlSession sqlSession = build.openSession();
            sqlSession.update("dao.Temp03Dao.getById", 1);
            sqlSession.update("dao.Temp03Dao.getById", 1);
        }
    
    

    執行結果

    2020-06-26 17:33:27,899 DEBUG [dao.Temp03Dao.getById] - ==>  Preparing: select * from temp where id = ? 
    2020-06-26 17:33:27,922 DEBUG [dao.Temp03Dao.getById] - ==> Parameters: 1(Integer)
    2020-06-26 17:33:27,923 DEBUG [dao.Temp03Dao.getById] - ==>  Preparing: select * from temp where id = ? 
    2020-06-26 17:33:27,923 DEBUG [dao.Temp03Dao.getById] - ==> Parameters: 1(Integer)
    

    我們可以看到執行了2次查詢。說明並沒有產生緩存。說明和sqlsession調用的方法是有關係的

    只有調用上圖中的方法才會產生一級緩存

    一級緩存的銷毀

    1.關閉session

    這個是根據debug看到的一級緩存的最終結構。下面是整個依賴的類圖

    test

     @Test
        public  void test() throws IOException, NoSuchFieldException, IllegalAccessException {
            InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
            SqlSessionFactory build = new SqlSessionFactoryBuilder().build(inputStream);
            SqlSession sqlSession = build.openSession();
            TempEntity tempEntity1 = sqlSession.selectOne("dao.Temp03Dao.getById", 1);
            logger.info(tempEntity1);
    
            Field executorField = sqlSession.getClass().getDeclaredField("executor");
            executorField.setAccessible(true);
            CachingExecutor  cachingExecutor = (CachingExecutor) executorField.get(sqlSession);
    
            Field declaredField = cachingExecutor.getClass().getDeclaredField("delegate");
            declaredField.setAccessible(true);
            SimpleExecutor simpleExecutor  = (SimpleExecutor) declaredField.get(cachingExecutor);
    
            Field localCacheField = simpleExecutor.getClass().getSuperclass().getDeclaredField("localCache");
            localCacheField.setAccessible(true);
            PerpetualCache perpetualCache = (PerpetualCache) localCacheField.get(simpleExecutor);
    
            Field cacheField = perpetualCache.getClass().getDeclaredField("cache");
            cacheField.setAccessible(true);
            Map<Object,Object> map= (Map<Object, Object>) cacheField.get(perpetualCache);
            logger.info("緩存關閉前");
            for (Map.Entry<Object,Object> objectObjectEntry:map.entrySet()){
                logger.info(objectObjectEntry.getKey() + "===" + objectObjectEntry.getValue());
            }
            sqlSession.close();
            logger.info("緩存關閉后");
    
            for (Map.Entry<Object,Object> objectObjectEntry:map.entrySet()){
                logger.info(objectObjectEntry.getKey() + "=" + objectObjectEntry.getValue());
            }
        }
    

    運行結果

    2020-06-26 17:38:52,777 DEBUG [dao.Temp03Dao.getById] - ==>  Preparing: select * from temp where id = ? 
    2020-06-26 17:38:52,801 DEBUG [dao.Temp03Dao.getById] - ==> Parameters: 1(Integer)
    2020-06-26 17:38:52,824 DEBUG [dao.Temp03Dao.getById] - <==      Total: 1
    2020-06-26 17:38:52,824 INFO [TempTest] - TempEntity{id=1, value1='11111', value2='aaaaa'}
    2020-06-26 17:38:52,825 INFO [TempTest] - 緩存關閉前
    2020-06-26 17:38:52,826 INFO [TempTest] - -1654591322:461730790:dao.Temp03Dao.getById:0:2147483647:select * from  temp where id = ?:1:dev===[TempEntity{id=1, value1='11111', value2='aaaaa'}]
    2020-06-26 17:38:52,827 INFO [TempTest] - 緩存關閉后
    

    可以看到session關閉后,緩存就不存在了

    2.Commit提交

    test

        @Test
        public  void testCommit() throws IOException {
            InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
            SqlSessionFactory build = new SqlSessionFactoryBuilder().build(inputStream);
            SqlSession sqlSession = build.openSession();
            TempEntity tempEntity1 = sqlSession.selectOne("dao.Temp03Dao.getById", 1);
            logger.info(tempEntity1);
            sqlSession.commit();
            TempEntity tempEntity2 = sqlSession.selectOne("dao.Temp03Dao.getById", 1);
            logger.info(tempEntity2);
            logger.info(tempEntity1 == tempEntity2);
    
        }
    

    運行結果

    2020-06-26 17:40:40,821 DEBUG [dao.Temp03Dao.getById] - ==>  Preparing: select * from temp where id = ? 
    2020-06-26 17:40:40,846 DEBUG [dao.Temp03Dao.getById] - ==> Parameters: 1(Integer)
    2020-06-26 17:40:40,862 DEBUG [dao.Temp03Dao.getById] - <==      Total: 1
    2020-06-26 17:40:40,862 INFO [TempTest] - TempEntity{id=1, value1='11111', value2='aaaaa'}
    2020-06-26 17:40:40,863 DEBUG [dao.Temp03Dao.getById] - ==>  Preparing: select * from temp where id = ? 
    2020-06-26 17:40:40,863 DEBUG [dao.Temp03Dao.getById] - ==> Parameters: 1(Integer)
    2020-06-26 17:40:40,864 DEBUG [dao.Temp03Dao.getById] - <==      Total: 1
    2020-06-26 17:40:40,864 INFO [TempTest] - TempEntity{id=1, value1='11111', value2='aaaaa'}
    2020-06-26 17:40:40,864 INFO [TempTest] - false
    

    說明sqlSession.commit時會清空緩存

    3.Rollback

    test

        @Test
        public  void testRollback() throws IOException {
            InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
            SqlSessionFactory build = new SqlSessionFactoryBuilder().build(inputStream);
            SqlSession sqlSession = build.openSession();
            TempEntity tempEntity1 = sqlSession.selectOne("dao.Temp03Dao.getById", 1);
            logger.info(tempEntity1);
            sqlSession.rollback();
            TempEntity tempEntity2 = sqlSession.selectOne("dao.Temp03Dao.getById", 1);
            logger.info(tempEntity2);
            logger.info(tempEntity1 == tempEntity2);
    
        }
    

    執行結果

    2020-06-26 17:42:23,793 DEBUG [dao.Temp03Dao.getById] - ==>  Preparing: select * from temp where id = ? 
    2020-06-26 17:42:23,833 DEBUG [dao.Temp03Dao.getById] - ==> Parameters: 1(Integer)
    2020-06-26 17:42:23,843 DEBUG [dao.Temp03Dao.getById] - <==      Total: 1
    2020-06-26 17:42:23,843 INFO [TempTest] - TempEntity{id=1, value1='11111', value2='aaaaa'}
    2020-06-26 17:42:23,844 DEBUG [dao.Temp03Dao.getById] - ==>  Preparing: select * from temp where id = ? 
    2020-06-26 17:42:23,844 DEBUG [dao.Temp03Dao.getById] - ==> Parameters: 1(Integer)
    2020-06-26 17:42:23,845 DEBUG [dao.Temp03Dao.getById] - <==      Total: 1
    2020-06-26 17:42:23,845 INFO [TempTest] - TempEntity{id=1, value1='11111', value2='aaaaa'}
    2020-06-26 17:42:23,845 INFO [TempTest] - false
    

    sqlSession.rollback()也會清空緩存

    4.update更新

    這裡是在第一次查詢后,緊接着進行update操作。這裏與表無關。就是操作其它表,也會清空緩存。

    test

        @Test
        public  void testForUpdate() throws IOException {
            InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
            SqlSessionFactory build = new SqlSessionFactoryBuilder().build(inputStream);
            SqlSession sqlSession = build.openSession();
            TempEntity tempEntity1 = sqlSession.selectOne("dao.Temp03Dao.getById", 1);
            logger.info(tempEntity1);
            sqlSession.update("dao.Temp03Dao.updateById", 1);
            TempEntity tempEntity2 = sqlSession.selectOne("dao.Temp03Dao.getById", 1);
            logger.info(tempEntity2);
            logger.info(tempEntity1 == tempEntity2);
    
        }
    

    運行結果

    2020-06-26 17:45:43,997 DEBUG [dao.Temp03Dao.getById] - ==>  Preparing: select * from temp where id = ? 
    2020-06-26 17:45:44,034 DEBUG [dao.Temp03Dao.getById] - ==> Parameters: 1(Integer)
    2020-06-26 17:45:44,048 DEBUG [dao.Temp03Dao.getById] - <==      Total: 1
    2020-06-26 17:45:44,049 INFO [TempTest] - TempEntity{id=1, value1='11111', value2='aaaaa'}
    2020-06-26 17:45:44,049 DEBUG [dao.Temp03Dao.updateById] - ==>  Preparing: update temp set value1 = 'ffffff' where id = ? 
    2020-06-26 17:45:44,049 DEBUG [dao.Temp03Dao.updateById] - ==> Parameters: 1(Integer)
    2020-06-26 17:45:44,050 DEBUG [dao.Temp03Dao.updateById] - <==    Updates: 1
    2020-06-26 17:45:44,051 DEBUG [dao.Temp03Dao.getById] - ==>  Preparing: select * from temp where id = ? 
    2020-06-26 17:45:44,051 DEBUG [dao.Temp03Dao.getById] - ==> Parameters: 1(Integer)
    2020-06-26 17:45:44,052 DEBUG [dao.Temp03Dao.getById] - <==      Total: 1
    2020-06-26 17:45:44,053 INFO [TempTest] - TempEntity{id=1, value1='ffffff', value2='aaaaa'}
    2020-06-26 17:45:44,053 INFO [TempTest] - false
    

    這裏還是在一個session會話中。記得之前有人給我說只要在一個session會話中,執行update不會清空緩存。這裏的代碼就證明了

    5.clearCache 主動清除

    test

        @Test
        public  void testClearCatch() throws IOException {
            InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
            SqlSessionFactory build = new SqlSessionFactoryBuilder().build(inputStream);
            SqlSession sqlSession = build.openSession();
            TempEntity tempEntity1 = sqlSession.selectOne("dao.Temp03Dao.getById", 1);
            logger.info(tempEntity1);
            sqlSession.clearCache();
            TempEntity tempEntity2 = sqlSession.selectOne("dao.Temp03Dao.getById", 1);
            logger.info(tempEntity2);
            logger.info(tempEntity1 == tempEntity2);
    
        }
    

    運行結果

    2020-06-26 17:48:42,085 DEBUG [dao.Temp03Dao.getById] - ==>  Preparing: select * from temp where id = ? 
    2020-06-26 17:48:42,110 DEBUG [dao.Temp03Dao.getById] - ==> Parameters: 1(Integer)
    2020-06-26 17:48:42,124 DEBUG [dao.Temp03Dao.getById] - <==      Total: 1
    2020-06-26 17:48:42,124 INFO [TempTest] - TempEntity{id=1, value1='11111', value2='aaaaa'}
    2020-06-26 17:48:42,125 DEBUG [dao.Temp03Dao.getById] - ==>  Preparing: select * from temp where id = ? 
    2020-06-26 17:48:42,125 DEBUG [dao.Temp03Dao.getById] - ==> Parameters: 1(Integer)
    2020-06-26 17:48:42,126 DEBUG [dao.Temp03Dao.getById] - <==      Total: 1
    2020-06-26 17:48:42,126 INFO [TempTest] - TempEntity{id=1, value1='11111', value2='aaaaa'}
    2020-06-26 17:48:42,126 INFO [TempTest] - false
    

    一級緩存 臟讀問題

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

    【其他文章推薦】

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

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

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

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

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