環境資訊中心綜合外電;姜唯 編譯;林大利 審校
本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面
※南投搬家公司費用需注意的眉眉角角,別等搬了再說!
※新北清潔公司,居家、辦公、裝潢細清專業服務
※教你寫出一流的銷售文案?
CS-LogN思維導圖:記錄CS基礎 面試題
開源地址:https://github.com/FISHers6/CS-LogN
線程池相關類
能獲取子線程的運行結果
互斥同步(鎖)
非互斥同(原子類)
併發容器
無同步與不可變方案
降低資源消耗
提高響應速度
提高線程的可管理性
corePoolSize、maximumPoolSize、keepAliveTime、workQueue、threadFactory、handler
圖示
線程添加規則
1.如果線程數量小於corePoolSize,即使工作線程處於空閑狀態,也會創建一個新線程來運行新任務,創建方法是使用threadFactory
2.如果線程數量大於corePoolSize但小於maximumPoolSize,則將任務放入隊列
3.如果workQueue隊列已滿,並且線程數量小於maxPoolSize,則開闢一個非核心新線程來運行任務
4.如果隊列已滿,並且線程數大於或等於maxPoolSize,則拒絕該任務,執行handler
圖示(分別與3個參數比較)
常用線程池
newFixedThreadPool
newSingleThreadExecutor
newCachedThreadPool
newScheduledThreadPool
如何設置初始化線程池的大小?
可根據線程池中的線程
處理任務的不同進行分別估計
CPU 密集型任務
IO 密集型任務
使用線程池的注意事項
線程池的狀態
線程池停止
shutdown
shutdownNow
線程池的組成
1.線程池管理器
2.工作線程
3.任務隊列:無界、有界、直接交付隊列
4.任務接口Task
圖示
Executor家族
Executor頂層接口,只有一個execute方法
ExecutorService繼承了Executor,增加了一些新的方法,比如shutdown擁有了初步管理線程池的功能方法
Executors工具類,來創建,類似Collections
圖示
線程池實現任務復用的原理
線程池對線程作了包裝,不需要啟動線程,不需要重複start線程,只是調用已有線程固定數量的線程來跑傳進來的任務run方法
添加工作線程
重複利用線程執行不同的任務
每個 Thread 維護着一個 ThreadLocalMap 的引用;ThreadLocalMap 是 ThreadLocal 的內部類,用 Entry 來進行存儲;key就對應一個個ThreadLocal
get方法:取出當前線程的ThreadLocalMap,然後調用map.getEntry方法,把ThreadLocal作為key參數傳入,取出對應的value
set方法:往 ThreadLocalMap 設置ThreadLocal對應值
initalValue方法:延遲加載,get的時候設置初始化
圖示
value內存泄漏
原因:ThreadLocal 被 ThreadLocalMap 中的 entry 的 key 弱引用。如果 ThreadLocal 沒有被強引用, 那麼 GC 時 Entry 的 key 就會被回收,但是對應的 value 卻不會回收,就會造成內存泄漏
解決方案:每次使用完 ThreadLocal,都調用它的 remove () 方法,清除value數據。
源碼圖示
引入目的
解決Runnable的缺陷
是什麼如何使用
引入目的
常用方法
使用場景
注意點
Callable和Future的關係
Future相當於一個存儲器,它存儲未來call()任務方法的返回值結果
可以用Future.get方法來獲取Callable接口的執行結果,在call()未執行完畢之前沒調用get的線程會被阻塞
線程池傳入Callable,submit返回Future,get獲取值
FutureTask
FutureTask是一種包裝器,可以把Callable轉化成Future和Runnable,它同時實現了二者的接口。所以既可以作為Runnable任務被線程執行,又可以作為Future得到Callable的返回值
圖示
final修飾變量
被final修飾的變量,意味着值不能被修改。
如果變量是對象,那麼對象的引用不能變,但是對象自身的內容依然可以變化。
賦值時機
屬性被聲明為final后,該變量則只能被賦值一次。且一旦被賦值,final的變量就不能再被改變,如論如何也不會變。
區分為3種
final instance variable(類中的final屬性)
final static variable(類中的static final屬性)
final local variable(方法中的final變量)
為什麼規定時機
final修飾方法(構造方法除外)
final修飾類
ABA問題
CAS+自旋,導致自旋時間過長
改進:通過版本號的機制來解決。每次變量更新的時候,版本號加 1,如AtomicStampedReference的compareAndSet ()
簡介
Lock和Synchronized的異同點
相同點
不同點
Lock 有比 synchronized 更精確的線程語義和更好的性能;高級功能
1 實現原理不同
2 靈活性不同
3 等待時是否可以中斷
可見性
悲觀鎖(互斥同步鎖)
思想
實例
缺點
樂觀鎖
思想
實例
優缺點對比
對比
什麼是可重入
可重入的好處
可重入鎖ReentrantLock與非可重入鎖ThreadPoolExecutor的Worker類對比
公平鎖
介紹
優點
缺點
非公平鎖
介紹
優點
缺點
優缺點對比
源碼分析
排他鎖
介紹
共享鎖
介紹
ReentrantReadWriteLock
讀寫鎖的作用
讀寫鎖的規則
一把鎖兩種方式鎖定
讀線程插隊策略(非公平下)
鎖升級
引入場景
策略
適合場景
阻塞鎖
思想
開銷缺陷
自旋鎖
思想
開銷缺陷
源碼分析
atomic包下的類基本都是自旋鎖的實現
AtomicInteger的實現:自旋鎖實現原理是CAS,Atomic調用Unsafe進行自增add的源碼中的do-while循環就是一個自旋操作,使用CAS如果修改過程中遇到其它線程修改導致沒有秀嘎四成功,就在while里死循環,直至修改成功
圖示
適用場景
介紹
使用場景
JDK1.6 后對synchronized鎖的優化
JDK1.6 對鎖的實現引入了大量的優化,如偏向鎖、輕量級鎖、自旋鎖、適應性自旋鎖、鎖消除、鎖粗化等技術來減少鎖操作的開銷。
偏向鎖
輕量級鎖
重量級鎖
自旋鎖
自適應自旋鎖
鎖消除
鎖粗化
寫代碼時的優化
常用方法
實現原理
缺點
引入目的/改進思想
設計思想
集合類歷史
為什麼需要
為什麼不用HashMap
為什麼不用Collection.synchronizedMap
數據結構與併發策略
JDK1.7
JDK1.8
1.7到1.8改變後有哪些優點
注意事項
引入目的
適合場景
讀寫規則
實現原理
缺點
為什麼使用隊列
併發隊列關係圖示
BlockingQueue阻塞隊列
阻塞隊列是局由自動阻塞功能的隊列,線程安全;take方法移除隊頭,若隊列無數據則阻塞直到有數據;put方法插入元素,如果隊列已滿就無法繼續插入則阻塞直到隊列里有了空閑空間
ArrayBlockQueue
LinkedBlockingQueue
PriorityBlockingQueue
SynchronousQueue
DelayQueue
非阻塞隊列
ConcurrentLinkedQueue
選擇合適的隊列
邊界上看
內存上看
吞吐量上看
控制併發流程的工具類,作用是幫助程序員更容易讓線程之間相互配合,來滿足業務邏輯
併發工具類圖示
作用(事件)
常用方法
作用
常用方法
作用
常用方法
作用(線程)
常用方法
Exclusive(獨佔)
Share(共享)
核心三要素
1.sate
2.控制線程搶鎖和配合的FIFO隊列
3.期望協作工具類去實現的“獲取/釋放”等喚醒分配的方法策略
AQS的用法
AQS在CountDownLatch的應用
內部類Sync繼承AQS
1.state表示門閂倒數的count數量,對應getCount方法獲取
2.釋放方法,countDown方法會讓state減1,直到減為0時就喚醒所有線程。countDown方法調用releaseShared,它調用sync實現的tryReleaseShared,其使用CAS+自旋鎖,來實現安全的計數-1
3.阻塞方法,await會調用sync提供的aquireSharedInterruptly方法,當state不等於0時,最終調用LockUpport的park,它利用Unsafe的park,native方法,把線程加入阻塞隊列
總結
AQS在Semphore的應用
state表示信號量允許的剩餘許可數量
tryAcquire方法,判斷信號量大於0就成功獲取,使用CAS+自旋改變state狀態。如果信號量小於0了,再請求時tryAcquireShared返回負數,調用aquireSharedInterruptly方法就進入阻塞隊列
release方法,調用sync實現的releaseShared,會利用AQS去阻塞隊列喚醒一個線程
總結
AQS在ReentrantLock的應用
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整
※南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!
※教你寫出一流的銷售文案?
※超省錢租車方案
意法半導體推出新款車規碳化矽(SiC)二極體,以滿足電動汽車和插電式混合動力車(PHEVs,Plug-in Hybrids)等新能源汽車對車載充電器(OBCs,on-board battery chargers)在有限空間內處理大功率的苛刻要求。
| |
新款二極體採用先進的技術可防止高電流突波燒毀裝置,其過電流保護是額定電流的2.5倍,因此設計人員可選用更小、更經濟實惠且可靠性和效能都不會受到影響的電流更小的二極體。此新碳化矽二極體通過車規產品測試,反向擊穿電壓提高到650V,能滿足設計人員和汽車廠商欲降低電壓補償係數的要求,以確保車載充電半導體元件的標準與瞬間峰值電壓之間有充足的安全邊際。 這次推出的650V二極體包括TO-220AC功率封裝的10A STPSC10H065DY和TO-220AC封裝的12A STPSC12H065DY。此外,TO-220AB封裝的STPSC20H065CTY和TO-247封裝的STPSC20H065CWY是內建2個10A二極體的雙二極體(dual-diode )產品,可最大幅度地提升空間利用度並減少車載充電器的重量。
本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整
※南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!
※教你寫出一流的銷售文案?
※超省錢租車方案
微信公眾號:碼農充電站pro
個人主頁:https://codeshellme.github.io
那些能用計算機迅速解決的問題,就別用手做了。
—— Tom Duff
目錄
上一節 我們介紹了Python 面向對象的相關概念,我們已經知道類與對象是面向對象編程中非常重要的概念。
類就是一個模板,是抽象的。對象是由類創建出來的實例,是具體的。由同一個類創建出來的對象擁有相同的方法和屬性,但屬性的值可以是不同的。不同的對象是不同的實例,互不干擾。
如下,是一個最簡單的類,實際上是一個空類,不能做任何事情:
class People:
pass
在Python 中定義一個類,需要用到class 關鍵字,後邊是類名,然後是一個冒號:,然後下一行是類中的代碼,注意要有縮進。
People 雖然是一個空類,但依然可以創建對象,創建一個對象的語法為:
對象名 = 類名(參數列表)
參數列表是跟__init__ 構造方法相匹配的,如果沒有編寫__init__ 方法,創建對象時,就不需要寫參數,如下:
>>> p = People()
>>> p
<__main__.People object at 0x7fd30e60be80>
>>>
>>> p1 = People()
>>> p1
<__main__.People object at 0x7fd30e60be48>
p 和 p1 都是People類的對象。0x7fd30e60be80 是p 的地址,0x7fd30e60be48 是p1 的地址。可以看到不同的對象的地址是不同的,它們是兩不同的實例,互不干擾。
類中可以包含屬性(類中的變量),創建出來的對象就會擁有相應的屬性,每個對象的屬性的值可以不同。
創建好對象后,可以用如下方法給對象添加屬性:
>>> p = People()
>>> p.name = '小明' # 添加 name 屬性
>>> p.sex = '男' # 添加 sex 屬性
>>> p.name # 訪問對象的屬性
'小明'
>>> p.sex # 訪問對象的屬性
'男'
雖然在技術上可以這樣做,但是一般情況下,我們並不這樣為對象添加屬性,這樣會破壞類的封裝性,使得代碼混亂,不利於維護。
當訪問一個不存在的屬性時,會出現異常:
>>> p.job # 一個不存在的屬性
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'People' object has no attribute 'job'
我們一般會在__init__ 方法中為類添加屬性並賦值。
__init__ 方法在Python 的類中,以雙下劃線__開頭和結尾的方法,被稱為魔法方法,每個魔法方法都有特定的含義。Python 為我們規定了一些魔法方法,讓我們自己實現這些方法。
__init__ 方法叫做構造方法,用來初始化對象。Python 解釋器會在生成對象時,自動執行構造方法,而無需用戶显示調用。
__init__ 方法不需要有返回值。
類中的所有實例方法 方法,都至少有一個參數,就是self。Python 中的self 相當於C++ 和Java 中的this 指針,都是代表當前對象。只是Python 中的self 需要显示寫在方法的第一個參數,而this 指針則不需要寫在方法參數中。
構造方法一般用於初始化對象的一些屬性,構造函數可以不寫,也可以只有一個self 參數。
當構造函數只有一個self 參數時,創建該類的對象時,不需要添加參數。當構造函數除了self 參數還有其它參數時,創建該類的對象時,則需要添加相匹配的參數。
比如,我們定義一個People 類,它有三個屬性,分別是name,sex,age:
class People:
def __init__(self, name, sex, age):
self.name = name
self.sex = sex
self.age = age
print('執行了 __init__ 方法')
def print_info(self):
print('people:%s sex:%s age:%s' % (
self.name, self.sex, self.age))
在這個People 類中除了有一個__init__ 方法外,還有一個print_info 方法,每個方法中的都有self 參數,並且是第一個參數,self 代表當前對象。
在創建該類的對象時,需要傳遞匹配的參數(self 參數不用傳遞):
>>> p = People('小明', '男', 18)
執行了 __init__ 方法
>>> p
<People.People object at 0x7feb6276bda0>
>>> p.print_info()
people:小明 sex:男 age:18
>>>
>>> p1 = People('小美', '女', 18)
執行了 __init__ 方法
>>> p1
<People.People object at 0x7fd54352be48>
>>> p1.print_info()
people:小美 sex:女 age:18
可以看到,在創建p 和p1 對象時,字符串執行了 __init__ 方法 被打印了出來,而我們並沒有显示調用該方法,說明__init__ 方法被默認執行了。
對象p 和p1 是兩個不同的對象,擁有相同的屬性和方法,但是屬性值是不一樣的。兩個對象互不干擾,對象p 的地址為0x7feb6276bda0,p1 的地址是0x7fd54352be48。
執行代碼p.print_info(),是調用p 對象的print_info() 方法,因為,在定義該方法的時候,只有一個self 參數,所以在調用該方法的時候,不需要有參數。
私有屬性
普通的屬性,就像上面的name,sex和age 屬性,都是公有屬性,在類的外部都可以被任意的訪問,就是可以用對象.屬性名的方式來訪問屬性,如下:
>>> p = People('小明', '男', 18)
執行了 __init__ 方法
>>> p.name # 訪問屬性
'小明'
>>> p.name = '小麗' # 修改屬性
>>> p.name # 訪問屬性
'小麗'
這樣就破壞了數據的封裝性,這種訪問方式是不可控(會不受限制的被任意訪問)的,不利於代碼的維護,不符合面向對象的編程規範。
所以,通常我們會將類中的屬性,改為私有屬性,就是不能以對象.屬性名 這樣的方式訪問類屬性。
在Python 中,通過在屬性名的前邊添加雙下劃線__,來將公有屬性變為私有屬性,如下:
#! /usr/bin/env python3
class People:
def __init__(self, name, sex, age):
self.__name = name # 兩個下劃線
self.__sex = sex # 兩個下劃線
self._age = age # 一個下劃線
print('執行了 __init__ 方法')
def print_info(self):
print('people:%s sex:%s age:%s' % (
self.__name, self.__sex, self._age))
這樣就無法通過對象.屬性名的方式來訪問屬性了,如下:
>>> p = People('小美', '女', 18)
執行了 __init__ 方法
>>> p.__name # 出現異常
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'People' object has no attribute '__name'
但是,Python 中這種私有屬性的方式,並不是真正的私有屬性,Python 只是將__name 轉換為了_People__name,即是在__name 的前邊加上了_類名(_People),我們依然可以這樣訪問__name 屬性:
>>> p._People__name
'小美'
但我們並不提倡這種方式,這會讓代碼變得混亂難懂。
可以注意到,People 類中的_age 屬性是以單下劃線開頭的,這種以單下劃線開頭的屬性是可以在類的外部被訪問的:
>>> p._age
18
但是根據Python 規範,以單下劃線開頭的屬性,也被認為是私有屬性,也不應該在類的外部訪問(雖然在技術上是可以訪問的)。
注意:以雙下劃線
__開頭且結尾的屬性__xxx__,是特殊屬性,是公有的,可在類的外部訪問
私有方法
私有方法與私有屬性類似,也可以在方法名的前邊加上雙下劃線__,來將某個方法變成私有的,一般不需要被外部訪問的方法,應該將其設置為私有方法。
set 和 get 方法為了數據的封裝性,我們不應該直接在類的外部以對象.屬性名的方式訪問屬性,那麼如果我們需要訪問類的屬性該怎麼辦呢?
這時我們需要為每個私有屬性都提供兩個方法:
為了減少代碼量,這裏只為__name 屬性設置了這兩個方法,代碼如下:
#! /usr/bin/env python3
class People:
def __init__(self, name, sex, age):
self.__name = name
self.__sex = sex
self._age = age
print('執行了 __init__ 方法')
def print_info(self):
print('people:%s sex:%s age:%s' % (
self.__name, self.__sex, self._age))
# set 和 get 方法
def set_name(self, name):
self.__name = name
def get_name(self):
return self.__name
用戶可以這樣設置和訪問類的屬性:
>>> from People import People
>>> p = People('小美', '女', 18)
執行了 __init__ 方法
>>> p.get_name() # 獲取 name 值
'小美'
>>> p.set_name('小麗') # 設置新的值
>>> p.get_name() # 再次獲取name 值
'小麗'
因為這種set 和 get 方法,是由類的開發者提供的,是被開發者控制的。
類的開發者會根據需要,來控制類的使用者如何使用該類,即哪些類的屬性和方法應該被使用者訪問,以及如何被使用者訪問。
如此,類的使用者就不能隨便的訪問類中的屬性,這就達到了封裝的目的。
(完。)
推薦閱讀:
Python 簡明教程 — 14,Python 數據結構進階
Python 簡明教程 — 15,Python 函數
Python 簡明教程 — 16,Python 高階函數
Python 簡明教程 — 17,Python 模塊與包
Python 簡明教程 — 18,Python 面向對象
歡迎關注作者公眾號,獲取更多技術乾貨。
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整
※南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!
※教你寫出一流的銷售文案?
※超省錢租車方案

為了全面促進我國新能源汽車產業的快速發展,給全球新能源汽車產業的參與者提供一個溝通交流的平臺,在連續四屆精彩紛呈的新能源汽車論壇的基礎上,由中國產業海外發展和規劃協會、德國電動車協會、上海交大密西根學院主辦,希邁商務諮詢承辦,並得到了亞太電動車協會的傾情指導的“第五屆中國國際新能源汽車論壇2015”將於2015年5月18日至19日在上海隆重召開。屆時將有三百位行業領導出席本次論壇。
互聯網智慧時代強勢來襲,且正悄然改變著汽車這個傳統的行業。汽車互聯網時代的到來也促進著新能源汽車、汽車租賃等新形態汽車業務有效開展。汽車智慧化、新能源、輕量化成為汽車發展的新趨勢。
在過去的四屆新能源汽車論壇上,主辦方成功的邀請了包括國家發改委能源研究所、世界電動車協會、亞太電動車協會、世界氫能協會、世界分散式能源聯盟、中國工程院、美國工程師學會、國家863重大項目組、北京公共交通集團、上海交通大學、清華大學、國家電網、中國南方電網、普天新能源等在內的政府單位與研究機構,以及包括寶馬、賓士、奇瑞捷豹路虎、大眾、奇瑞、奧迪、比亞迪、上汽、北汽、東風、本田、豐田、現代、吉利、宇通客車等在內的知名整車商,共同參與和討論,對新能源汽車行業政策趨勢、技術路線、技術難點、基礎設施建設、商業模式等進行了積極的探討,取得了豐碩的成果,獲得了業內外人士的一致好評。
在嶄新的2015年,組委會為感謝業內外人士對系列論壇長期以來的關注和支援,將傾情奉上第五屆中國國際新能源汽車論壇2015,本次論壇將以新能源汽車“智”造時代為主題。現誠摯邀請您參加本次會議共創輝煌。屆時將邀請全球範圍內的整車製造商、電網公司、電力公司、電池廠商、零部件供應商、核心技術提供商和政府官員近三百位行業嘉賓一起,就新能源汽車產業面臨的機遇、挑戰和對策進行為期兩天的富有建設性和戰略性的討論。
往屆會議精彩回顧
| 第一屆中國國際新能源汽車論壇 | 第二屆中國國際新能源汽車論壇 |
![]() |
![]() |
| 第三屆中國國際新能源汽車論壇 | 第四屆中國國際新能源汽車論壇 |
![]() |
![]() |
部分往屆知名發言人:





會議亮點
會議結構
| 上午 | 下午 | ||
| 大會第一天 |
“智”造時代政策預測、製造趨勢及試點城市推廣情況 工信部、科技部、交通部、發改委及地方政府的政策支持 |
新能源汽車動力總成電氣化及商業模式 新能源汽車開發特點及供應配套要求 |
|
| 大會第二天 |
電池及儲能的研發、製造趨勢 動力電池未來商業模式、技術路線探索 |
微型電動車市場准入 政策准入前景 |
整車廠商-電池企業 對接洽談會 |
第五屆中國國際新能源汽車論壇2015特色
新能源汽車“智”造時代,第五屆中國國際新能源汽車論壇2015將會邀請政府主管機構、整車商及行業各個領域的領先者,對行業現有的發展訴求及發展願景規劃做出積極的探討。
想瞭解詳細內容,請登陸官方網站:
連絡人:Hill ZENG(曾先生)
電話:+86-21-6045 1760
傳真:+86-21-6047 5887
郵箱:
本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整
※南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!
※教你寫出一流的銷售文案?
※超省錢租車方案
從春哥(章亦春)那看到的關於SystemTap的介紹,讓我對動態追蹤這個功能頗為感興趣,覺得這是一個非常有用的功能。
SystemTap對內核及用戶態程序提供了動態追蹤功能,用戶可以自定探測事件來跟蹤程序的運行情況,如函數的調用路徑、CPU佔用和磁盤IO等一系列可以探測的情況。有了systemtap,可以在程序不修改代碼,甚至不用重啟就能分析出程序的運行情況。
配合火焰圖的可視化,對程序的性能分析極其有利。(放一個FlameGraph的官方圖片)
SystemTap 基本思想是命名事件,併為它們提供處理程序。每當發生指定的事件時,內核都會將處理程序視為子例程運行,然後繼續運行。有一系列的事件,例如進入或退出函數,計時器到期或整個SystemTap會話的開始和停止。處理程序是一系列腳本語言語句,用於指定事件發生時要完成的工作。這項工作通常包含從事件上下文中提取數據,將其存儲到內部變量或打印結果。
SystemTap 的工作原理是將腳本翻譯成C語言,執行C編譯器創建一個內核模塊。當模塊被加載后,通過掛載到內核來激活所有的探測事件。然後,當事件發生再任何處理器上時,編譯后的處理程序就運行,最終,SystemTap會話停止,Hook取消,內核模塊被移除,整個過程由命令行程序stap驅動。
原理圖如下:
需要內核級別的支持,依賴了三個rpm下載鏈接,
這三個包的版本必須匹配當前內核的版本,比如我自己的內核版本是 3.10.0-327, 那麼以上三個包版本都必須保持一致。
安裝 SystemTap:
$ yum install systemtap
測試是否成功安裝:
$ stap -ve ‘probe begin{printf(“Hello, World\n”); exit();}’
正常的話會輸出 Hello, World,但是不出意外會出現版本不一致的情況:
ERROR: module version mismatch (#1 SMP Fri Nov 20 11:12:42 CST 2015 vs #1 SMP Thu Nov 19 22:10:57 UTC 2015), release 3.10.0-327.el7.x86_64
出現這個的情況是版本相同但是打包事件不相同的情況,修改這個時間和uname -a 中的時間保持一致。
$ rpm -ql kernel-devel | xargs grep UTS_VERSION 2>/dev/null
/usr/src/kernels/3.10.0-327.el7.x86_64/include/generated/compile.h:#define UTS_VERSION "#1 SMP Fri Nov 20 11:12:42 CST 2015"
再次運行那個hello測試,出現緩存的錯誤,刪除緩存文件
最簡單的探測類型就是跟蹤事件。Systemtap支持許多內置事件,所有的事件家族見 tapset
可以探測的的常用事件:
常用的可打印值(具體見 tapset):
$$var, 上下文中存在 $var,可以使用該變量stap 腳本簡單,語法類似C;
# fuck
// fuck
/* fuck */
function foo() {
// exit(); // 退出 systemtap 會話
}
function if_expr() {
i = 0
if (i == 1)
printf("[if] i = %d\n", i);
else
printf("[else] i = %d\n", i);
}
function while_expr() {
i = 0;
while (i != 2)
printf("[while] i = %d\n", i++);
}
function for_expr() {
for (i = 0; i < 2; i++)
printf("[for] i = %d\n", i);
}
function str() {
uid = uid();
s_uid = sprint(uid);
f_uid = "fuck" . s_uid
printf("uid: %d-%s-%s\n", uid, s_uid, f_uid); // uid: 0-0-fuck0
// exit();
}
global t; // 聲明元組
global tpl[400]; // 聲明一個400容量的元組
t["fuck"]++; // t["fuck"] 初始值默認為0, ++ 變成 1
t["fuck"] = 4396; // 賦值為4396
tpl["fuck", pid()]++; // 兩個元素
tpl["shit", tid()]++;
// 包含4個維度 @count @avg @min @max
global t;
t["fuck", tid()] <<< 1
t["fuck", pid()] <<< 1
t[execname(), tid()] <<< 1
t["fuck", 5487] <<< 2
t["fuck", 5487] <<< 3
t["fuck", 5487] <<< 1
具體結構如下:
t["fuck",5487] @count=3 @min=1 @max=3 @sum=6 @avg=2
t["fuck",26060] @count=2 @min=1 @max=1 @sum=2 @avg=1
t["stapio",26060] @count=1 @min=1 @max=1 @sum=1 @avg=1
// 遍歷(升序), 限制5次循環
foreach([key, value] in t+ limit 5)
printf("%s: %d\n", key, value)
// 結果
stapio: 2571
fuck: 2571
fuck: 5487
Usage: stap [options] FILE Run script in file.
or: stap [options] -e SCRIPT Run given script.
or: stap [options] -l PROBE List matching probes.
or: stap [options] -L PROBE List matching probes and local variables.
[options]
-T TIME terminate the script after TIME seconds
除了直接執行腳本文件外,另外一個比較有用的功能 -L -l 現象,列出可探測點及局部變量
// 截取部分~
[root@localhost stp]# stap -l 'process("/tmp/limlog/build/tests/LogTest").function("*")'
process("/tmp/limlog/build/tests/LogTest").function("write@/tmp/limlog/limlog/Log.cpp:107")
process("/tmp/limlog/build/tests/LogTest").function("~LimLog@/tmp/limlog/limlog/Log.cpp:213")
process("/tmp/limlog/build/tests/LogTest").function("~LogLine@/tmp/limlog/limlog/Log.cpp:341")
process("/tmp/limlog/build/tests/LogTest").function("~LogSink@/tmp/limlog/limlog/Log.cpp:59")
process("/tmp/limlog/build/tests/LogTest").function("~_Impl@/usr/include/c++/4.8.2/thread:107")
process("/tmp/limlog/build/tests/LogTest").function("~_Impl_base@/usr/include/c++/4.8.2/thread:97")
$)[root@localhost stp]# stap -L 'process("/tmp/limlog/build/tests/LogTest").function("*")'
process("/tmp/limlog/build/tests/LogTest").function("id@/usr/include/c++/4.8.2/thread:73") $this:class id* const
process("/tmp/limlog/build/tests/LogTest").function("incConsumable@/tmp/limlog/limlog/Log.cpp:313") $this:class LimLog* const $n:uint32_t
process("/tmp/limlog/build/tests/LogTest").function("incConsumablePos@/tmp/limlog/limlog/Log.cpp:135") $this:class BlockingBuffer* const $n:uint32_t
process("/tmp/limlog/build/tests/LogTest").function("incConsumablePos@/tmp/limlog/limlog/Log.cpp:460") $n:uint32_t
process("/tmp/limlog/build/tests/LogTest").function("insert@/usr/include/c++/4.8.2/bits/basic_string.h:1319") $__c:char $__n:size_type $__pos:size_type $this:class basic_string<char, std::char_traits<char>, std::allocator<char> >* const
有這個功能,我們就可以看到函數所在源文件中的位置及可以根據的局部變量。
探針事件的關鍵字是 probe, 由 probe 引出需要跟蹤的事件
// 腳本執行后显示 === begin ===
probe begin {
printf("=== begin ===\n");
}
根據上述的可跟蹤的事件
[root@localhost stp]# cat foo.stp
#!/usr/bin/env stap
probe begin {
printf("=== begin ===\n");
}
probe end {
printf("=== end ===\n");
}
// 執行腳本
[root@localhost stp]# stap foo.stp -T 1
=== begin ===
=== end ===
// 調用open系統調用
[root@localhost stp]# cat foo.stp
#!/usr/bin/env stap
probe kernel.function("sys_open").call {
printf("%s call %s\n", execname(), ppfunc());
}
// open系統調用返回
probe kernel.function("sys_open").call {
printf("%s call %s over\n", execname(), ppfunc());
}
[root@localhost stp]# stap foo.stp -T 1
sh call SyS_open
sh call SyS_open over
sh call SyS_open
sh call SyS_open over
sh call SyS_open
sh call SyS_open over
[root@localhost stp]# cat foo.stp
#!/usr/bin/env stap
// 定時調用函數
probe timer.ms(500) {
printf("now: %d\n", gettimeofday_s());
}
[root@localhost stp]# stap foo.stp -T 3
now: 1593141081
now: 1593141081
now: 1593141082
now: 1593141082
now: 1593141083
// 取自春哥的sample-bt示例代碼,定時調用棧取樣
global bts;
probe timer.profile {
if (pid() == 5291)
bts[backtrace(), ubacktrace()] <<< 1
}
probe timer.s(10) {
foreach([k, u] in bts-) {
print_stack(k);
print_ustack(u);
printf("\\t%d\\n", @count(bts[k, u]));
}
exit();
}
// 結果為16進制地址,截取一部分數據,這部分數據需要進一步加工統計
[root@localhost stp]# stap foo.stp
0xffffffff810d6244 : 0xffffffff810d6244
0xffffffff810475fa : 0xffffffff810475fa
\t1\n 0xffffffffa0139f0d : 0xffffffffa0139f0d [xfs]
// 打印程序 LogTest 所有執行的函數
[root@localhost stp]# cat foo.stp
probe process("/tmp/limlog/build/tests/LogTest").function("*") {
printf("function: %s\n", ppfunc());
}
// 庫函數也加載進來了
[root@localhost stp]# stap foo.stp
function: offsetOfPos
function: __distance<char*>
function: operator+<long int, std::ratio<1l, 1000000000l>, long int, std::ratio<1l, 1000000l> >
function: operator<<
//打印 LogTest 程序屬於 Log.cpp 中的函數信息
[root@localhost stp]# cat foo.stp
probe process("/tmp/limlog/build/tests/LogTest").statement("*@Log.cpp") {
printf("function: %s\n", ppfunc());
}
// 過濾了庫函數,只留下Log.cpp中的函數執行
[root@localhost stp]# stap foo.stp
function: singleton
function: produce
function: produce
function: produce
function: produce
function: used
function: used
function: consumable
function: operator<<
function: ~LogLine
// 獲取上下文變量
[root@localhost stp]# stap -L 'process("/tmp/limlog/build/tests/LogTest").statement("*@Log.cpp")'
process("/tmp/limlog/build/tests/LogTest").statement("LimLog@/tmp/limlog/limlog/Log.cpp:185") $this:class LimLog* const
process("/tmp/limlog/build/tests/LogTest").statement("LogLine@/tmp/limlog/limlog/Log.cpp:336") $this:class LogLine* const $level:enum LogLevel $loc:struct LogLoc const&
process("/tmp/limlog/build/tests/LogTest").statement("append@/tmp/limlog/limlog/Log.cpp:346") $this:class LogLine* const $data:char const* $n:size_t
process("/tmp/limlog/build/tests/LogTest").statement("consumable@/tmp/limlog/limlog/Log.cpp:140") $this:class BlockingBuffer const* const
process("/tmp/limlog/build/tests/LogTest").statement("consume@/tmp/limlog/limlog/Log.cpp:146") $this:class BlockingBuffer* const $to:char* $n:uint32_t
// 這裏我們追蹤 consume 函數中的兩個參數
[root@localhost stp]# cat foo.stp
probe process("/tmp/limlog/build/tests/LogTest").function("consume") {
printf("func = %s, $var(n) = %d, $var(to) = %p\n", ppfunc(), $n, $to);
}
[root@localhost stp]# stap foo.stp
func = consume, $var(n) = 406, $var(to) = 0x7f902a94d010
func = consume, $var(n) = 203, $var(to) = 0x7f902a94d1a6
func = consume, $var(n) = 790, $var(to) = 0x7f902a94d010
func = consume, $var(n) = 3319, $var(to) = 0x7f902a94d326
func = consume, $var(n) = 4235, $var(to) = 0x7f902a94d010
func = consume, $var(n) = 4235, $var(to) = 0x7f902a94d010
func = consume, $var(n) = 2326, $var(to) = 0x7f902a94d010
func = consume, $var(n) = 8470, $var(to) = 0x7f902a94d010
在探測點被執行時打印這種簡單的用法外,還能進一步做一些其它的事情。
[root@localhost stp]# cat foo.stp
#!/usr/bin/env stap
global top_funcs;
probe begin {
printf("=== begin ===\n");
}
probe process("/tmp/limlog/build/tests/LogTest").statement("*@Log.cpp") {
top_funcs[ppfunc()]++;
}
probe end {
foreach(func in top_funcs- limit 10)
printf("%s: %d\n", func, top_funcs[func]);
printf("=== end ===\n");
}
// LogTest 在3秒內執行最多的10個函數及次數
[root@localhost stp]# stap foo.stp -T 3
=== begin ===
produce: 1152
append: 720
singleton: 646
operator<<: 608
produceLog: 576
used: 576
consumable: 359
incConsumablePos: 64
getLogLevel: 32
LogLine: 32
=== end ===
[root@localhost stp]# cat foo.stp
#!/usr/bin/env stap
global top_exec;
probe begin {
printf("=== begin ===\n");
}
probe kernel.function("sys_write") {
top_exec[execname()]++;
}
probe end {
foreach(exec in top_exec- limit 10)
printf("%s: %d\n", exec, top_exec[exec]);
printf("=== end ===\n");
}
[root@localhost stp]# stap foo.stp -T 3
=== begin ===
qemu-kvm: 2948
sync: 2174
virsh: 673
libvirtd: 305
route: 28
grep: 21
ps: 21
ssh: 16
python: 16
nginx: 16
=== end ===
調用層次需要藉助 thread_indent 來打印合適的縮進,另外需要設置函數入口(call)和返回(return)的探測點。函數命名不足以使用function通配符來匹配,而 statement 雖然可以匹配我們想要的結果(過濾第三方函數),但是不支持 call 和return。雖然有一個比較傻但是的確可行的方式是,在腳本裏面手動寫入這些函數。
// 先取出需要關注的函數名稱
stap -L 'process("/tmp/limlog/build/tests/LogTest").statement("*@*.cpp")' | sed 's/.*statement(\"\(.*\)@.*\".*/\1/g' | sort -u
腳本如下:
#!/usr/bin/env stap
global f;
function init() {
f["append"] = 1;
f["benchmark"] = 1;
f["consumable"] = 1;
f["consume"] = 1;
f["date"] = 1;
f["datetime"] = 1;
f["formatTimestamp"] = 1;
f["getLogLevel"] = 1;
f["gettid"] = 1;
f["i16toa"] = 1;
f["i2a"] = 1;
f["i32toa"] = 1;
f["i64toa"] = 1;
f["incConsumable"] = 1;
f["incConsumablePos"] = 1;
f["LimLog"] = 1;
f["~LimLog"] = 1;
f["listStatistic"] = 1;
f["log_10_diff_element_len"] = 1;
f["log_10_diff_element_str"] = 1;
f["log_10_diff_element_x1"] = 1;
f["log_16_same_element_x6"] = 1;
f["log_1_same_element_x6"] = 1;
f["log_4_same_element_x6"] = 1;
f["LogLine"] = 1;
f["~LogLine"] = 1;
f["LogSink"] = 1;
f["~LogSink"] = 1;
f["main"] = 1;
f["now"] = 1;
f["operator<<"] = 1;
f["produce"] = 1;
f["produceLog"] = 1;
f["rollFile"] = 1;
f["setLogFile"] = 1;
f["setLogLevel"] = 1;
f["setRollSize"] = 1;
f["singleton"] = 1;
f["sink"] = 1;
f["sinkThreadFunc"] = 1;
f["stringifyLogLevel"] = 1;
f["test_blocking_buffer"] = 1;
f["test_itoa"] = 1;
f["test_timestamp"] = 1;
f["time"] = 1;
f["u16toa"] = 1;
f["u2a"] = 1;
f["u32toa"] = 1;
f["u64toa"] = 1;
f["used"] = 1;
f["write"] = 1;
}
probe begin {
printf("=== begin ===\n");
init();
}
probe process("/tmp/limlog/build/tests/LogTest").function("*").call {
fn = ppfunc()
if (f[fn] == 1)
printf("%s -> %s\n", thread_indent(4), ppfunc());
}
probe process("/tmp/limlog/build/tests/LogTest").function("*").return {
fn = ppfunc()
if (f[fn] == 1)
printf("%s <- %s\n", thread_indent(-4), ppfunc());
}
probe end {
printf("=== end ===\n");
}
函數的部分調用樹如下所示:
=== begin ===
0 LogTest(25381): -> main
101 LogTest(25381): -> setLogFile
111 LogTest(25381): -> singleton
121 LogTest(25381): -> LimLog
129 LogTest(25381): -> LogSink
270 LogTest(25381): -> now
282 LogTest(25381): <- now
288 LogTest(25381): -> date
296 LogTest(25381): -> datetime
453 LogTest(25381): <- datetime
472 LogTest(25381): <- date
478 LogTest(25381): <- LogSink
576 LogTest(25381): <- LimLog
586 LogTest(25381): <- singleton
603 LogTest(25381): -> rollFile
0 LogTest(25382): -> sinkThreadFunc
650 LogTest(25381): <- rollFile
···
樹狀調用可以很清楚的展示出來程序的執行邏輯,甚至可以看到多線程的處理:sinkThreadFunc 和 rollFile 之間的跨度。
觀察 libguestfs 的啟動過程及耗時,libvirt 支持function 和mark(有5個,大致邏輯上的埋點)
#! /usr/bin/env stap
global start_time, prev;
function display_time () {
now = gettimeofday_us ()
delta = (prev > 0 ) ? now - prev : 0
printf ("%18d (+%10d): ", now - start_time, delta)
prev = now
}
probe begin {
start_time = gettimeofday_us ()
prev = 0
printf ("%-18s (+%10s): %s\n", "# time_usec",
"delta usec", "event")
}
probe process("/usr/lib*/libguestfs.so.0*").function("*")
{
display_time()
printf ("%s\n", ppfunc())
}
打印的結果,如果使用 libguestfs-test-tools 執行失敗,可以根據產生的函數進入源代碼中進行排查,縮小排查的範圍
[root@localhost stp]# stap libguestfs.stp
# time_usec (+delta usec): event
1880754 (+ 0): _init
1880765 (+ 11): frame_dummy
1880768 (+ 3): register_tm_clones
1880772 (+ 4): compile_regexp_re_hostname_port
1880804 (+ 32): init_libguestfs
1881074 (+ 270): guestfs_int_init_direct_backend
1881079 (+ 5): guestfs_int_register_backend
1881083 (+ 4): guestfs_int_init_libvirt_backend
1881085 (+ 2): guestfs_int_register_backend
1881088 (+ 3): guestfs_int_init_uml_backend
···
而 libvirt 中 example/ 目錄中就有包含一些關於 systemtap 的腳本文件。
在函數調用樹之上更進一步,加入樣本的頻率得到svg格式的火焰圖,用戶可以點擊不同的圖層來查看更詳細的信息。
火焰圖工具為brendangregg開發的開源項目FlameGraph,配合春哥寫的systemtap工具,用一段簡單的shell腳本如下,可以得到一張svg的圖片
#!/bin/bash
# 火焰圖工具的路徑
FlameGraph_PATH="/FlameGraph"
# 春哥的棧收集工具
OR_Stap_PATH="/home/zxh/dev/openresty-systemtap-toolkit"
if [ $# != 1 ] ; then
echo "Usage: sh $0 PID"
echo " e.g.: sh $0 4396"
exit 1;
fi
${OR_Stap_PATH}/sample-bt -p $1 -t 180 -u > /tmp/bt-sample
${FlameGraph_PATH}/stackcollapse-stap.pl /tmp/bt-sample > /tmp/bt_stap.out
${FlameGraph_PATH}/flamegraph.pl /tmp/bt_stap.out > backtrace.svg
rm -rf /tmp/bt-sample
rm -rf /tmp/bt_stap.out
CPU定時採樣,根據採樣生成的 backtrace.svg 如下,橫軸為CPU佔用的時間比例,縱軸為函數調用的深度:
這是一個日誌庫的benchmark(博客園不支持上傳svg格式),而主要耗時也在阻塞隊列的入隊上,符合預期。
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整
※南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!
※教你寫出一流的銷售文案?
※超省錢租車方案
在巴賽隆納舉辦的2015世界移動通信大會上,福特汽車公司宣佈了「智慧騎行」的試驗專案,首推智慧電動車,這也是一項通過在車載連接技術、移動出行、自動駕駛汽車、消費者體驗以及大資料方面的創新,説明改變世界出行方式的計畫。 首推的倆款智能電動車為MoDe:Pro與MoDe:Me。MoDe:Pro由福特團隊打造完成,用於商業用途。MoDe:Me在自行車製造商Dahon的協助下打造完成,便於摺疊存放。
(圖片來源:騰訊) |
這兩款電動自行車都配備了200瓦的馬達和9安培小時的電池,提供電動踏板輔助,最高時速可達2萬5千公尺。原型電動自行車的技術可實現行車預警系統,在汽車超車時,透過車把手的震動向騎士們發出警告,還可通過開啟把手上的警示燈,提醒汽車司機注意電動自行車的存在。 MoDe:Me和MoDe:Pro可與一款名為MoDe:Link的原型應用程式配合使用,此款應用程式可與iPhone 6手機相容。通過應用程式中的即時資訊,電動自行車可實現導航、線路選擇、速度和舒適度調整等功能。
本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整
※南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!
※教你寫出一流的銷售文案?
※超省錢租車方案
各位小夥伴們好哇!從今日起,我將開始更新《秒懂Java》系列文章,從0開始講解Java的方方面面,完全零基礎也可以看懂。後面也將推出配套的視頻版,歡迎大家保持關注!
一說到計算機(Computer),大家首先想到的可能是電腦(台式電腦、手提電腦)。
實際上,我們日常生活中使用的平板電腦、智能手機也屬於計算機,它們的功能也跟電腦一樣非常強大。
還有KTV的點歌機、超市的自助收銀機等很多設備也都屬於計算機。
毋庸置疑,計算機是人類歷史上最偉大的發明之一,極大地提高了社會的生產力,目前它已經延伸到了生活、學習、工作等各個領域,無處不在。如今,我們會在計算機上安裝各種各樣的軟件(應用、遊戲),學習工作、衣食住行、吃喝玩樂一網打盡,極大地改變了我們的生活、學習、工作方式。
語言,是雙方進行溝通交流的主要表達方式。
那如果我要跟計算機進行交流呢?那就應該用計算機編程語言。
注意:我們要學習的很多IT技術都源自西方國家(比如美國),因此,很多技術名詞,都是從英文翻譯過來的。為了保證描述的嚴謹性,當首次提及某個技術名詞時,我都會在它旁邊標註原本的英文單詞。
那如何利用編程語言與計算機進行交流呢?
編程語言,顧名思義,就是可以用來“編寫程序”的語言。
我們平時使用的各種軟件(應用、遊戲)都是通過編程語言開發出來的,它們都由一大堆的代碼組成。當打開軟件時(將軟件運行到計算機上時),計算機就會開始識別執行軟件中包含的代碼,完成相應的功能。
所以,編程語言有一個非常重要的作用,那就是:開發軟件!
世界上有上百種各式各樣的編程語言,目前比較常見的主流編程語言有
每一門編程語言的作用都不太一樣,比如
當然,上面列舉的僅僅是它們的部分功能,並沒有說完整。比如:
其他的就不一一說明了,了解一下即可
每當我們要學習一門新的語言時,都要先學習它的語法。
不同編程語言的語法是不一樣的,每一門編程語言都有自己特有的語法。比如,想讓計算機播放一個視頻,不同編程語言可能會有不同的寫法。
不難看出它們之間的一些差異(了解一下即可,不用去深究)
雖然它們的寫法不一樣,但是都完成了一樣的功能:讓計算機播放一個視頻。如果想利用編程語言開發出強大優秀的軟件、控制計算機做更多的事情,首先要踏踏實實學好編程語言的語法。如果你連語法都不懂,怎麼可能寫出正確的代碼呢?
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整
※南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!
※教你寫出一流的銷售文案?
※超省錢租車方案
特斯拉執行長 Elon Musk 透露未來將透過 7.0 版本升級,為 Model S 車款帶來全新自動駕駛(self-driving)模式「autopilot」,預計在 3 至 4 個月內釋出軟體更新。
在新聞發佈會上,Elon Musk 公佈下一波 6.2 版本升級,新增里程保證(Range Assurance)和旅程規劃(Trip Planner)兩大關鍵功能。里程保證在於行駛過程中,系統會背景監控電力狀況並即時告知車主;旅程規劃則是在車主設定目的地之後,系統自動判斷行駛過程中需要充電的地點,規劃出最佳路線,這都是為了減輕部分車主的「里程焦慮」(Range Anxiety)問題。除了上述新功能之外,Elon Musk 還介紹了一些和主動安全相關的輔助功能,例如自動緊急剎車、盲區警告以及側面碰撞警告等。 不過,新聞發佈會上最令人興奮的是,Elon Musk 透露未來將透過 7.0 版本升級,為 Model S 車款帶來全新自駕模式「autopilot」。 Elon Musk 指出,擁有 Model S 車款的車主基本上可透過 autopilot 模式,做到往返舊金山和西雅圖之間無須任何操控動作。不過他也謹慎地表示,這還不算是真正的自動駕駛技術,只不過是強大的自動轉向系統。autopilot 不足之處,在於它僅適用於行駛在高速公路上,以該公司目前的技術,可能還無法安全地駛入郊區,畢竟街上難免還是會有孩童或路人竄出。欲使用 autopilot 模式,車主還需要選配特斯拉所提供價值 4,250 美元的 optional tech 方案。 本文全文授權轉載自《科技新報》─〈〉
本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整
※南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!
※教你寫出一流的銷售文案?
※超省錢租車方案
說起回家,路途漫漫,行李滿滿,尤其我等村裡交通不發達的地方,可能連直達的票都沒有,雖說條條大陸通羅馬,但畢竟還是想找個換乘最少的路線,畢竟誰不想回家更輕鬆點呢(*^_^*),下面就是我回家的所有路線。
思路很簡單,先找起點看是否能到,不能到的話,看起點能到的點的下一步是否能到
話不多說,擼代碼:
public static void main(String[] args) {
HashMap<String,List<String>> data = new HashMap<String, List<String>>();
List<String> list1 = new ArrayList<String>();
data.put("起點",list1);
list1.add("A");
list1.add("B");
List<String> list2 = new ArrayList<String>();
data.put("A",list2);
list2.add("終點");
List<String> list3 = new ArrayList<String>();
data.put("B",list3);
list3.add("A");
list3.add("終點");
query(data,"終點","起點");
}
public static void query(Map<String,List<String>> data, String queryValue, String start){
if(data==null || queryValue ==null){
return;
}
Queue<String> queue = new LinkedList<String>();
Map quaryLog = new HashMap();
Map<String,List<String>> routes = new HashMap<String, List<String>>();
queue.offer(start);
quaryLog.put(start,"");
String parent = null;
while (!queue.isEmpty()){
parent = queue.poll();
List<String> values = data.get(parent);
for(String value:values){
List<String> r = new ArrayList<String>();
if(routes.containsKey(parent)){
r.addAll(routes.get(parent));
}
r.add(parent);
routes.put(value,r);
if(queryValue.equals(value)){
routes.get(value).add(value);
System.out.println(routes.get(value));
return;
}
if(!quaryLog.containsKey(value)){
queue.offer(value);
quaryLog.put(value,"");
}
}
}
return ;
}
run 一把,結果出來了
[起點, A, 終點]
終於,結果出來了,先到A地,再從A到終點,其實這就是廣度優先搜索,so easy興沖衝去買票,發現錢不夠,哎,沒有考慮票價啊!!!我的票價是這樣的:
按照現在的規劃需要700元,可是我只有650元,不夠啊,沒辦法,修改算法把,這次需要把價錢考慮進去,我需要最便宜的路線
思路也類似,先從起點開始走,分別計算最便宜的路線
終點暫時到不了,我們把到終點的距離記作無窮,接着我們從B點開始往下找,計算最便宜的價錢如下:
然後再計算A點走的話,最便宜的路線,比從B點走便宜的話我們就更新,不便宜的話代表原來的價錢已經是最便宜的了
找到了,最便宜的路線是600,但是程序要如何做呢,畢竟我以後不僅要回家,還要去旅遊,還要去丈母娘家,我要每次都最便宜!!!,擼碼如下:
public static void main(String[] args) {
HashMap<String,HashMap<String,Integer>> data = new HashMap<String, HashMap<String, Integer>>();
HashMap<String,Integer> map1 = new HashMap<String, Integer>();
data.put("起點",map1);
map1.put("A",600);
map1.put("B",200);
HashMap<String,Integer> map2 = new HashMap<String, Integer>();
data.put("A",map2);
map2.put("終點",100);
HashMap<String,Integer> map3 = new HashMap<String, Integer>();
data.put("B",map3);
map3.put("終點",500);
map3.put("A",300);
queryMinPrice(data,"起點","終點");
}
public static void queryMinPrice(HashMap<String,HashMap<String,Integer>> data,String start,String end){
HashMap<String,Integer> costs = new HashMap<String, Integer>();
HashMap<String,List<String>> route = new HashMap<String, List<String>>();
for(Map.Entry<String,Integer> entry: data.get(start).entrySet()){
costs.put(entry.getKey(),entry.getValue());
List<String> list = new ArrayList<String>();
list.add(entry.getKey());
route.put(entry.getKey(),list);
}
costs.put(end,Integer.MAX_VALUE);
HashMap<String,String> queryLog = new HashMap<String, String>();
String key = findMinPriceKey(costs,queryLog);
while (key != null){
queryLog.put(key,"");
if(data.get(key) == null){
break;
}
for(Map.Entry<String,Integer> entry:data.get(key).entrySet()){
if(costs.containsKey(entry.getKey())){
if(entry.getValue()+costs.get(key)<costs.get(entry.getKey())){
costs.put(entry.getKey(),entry.getValue()+costs.get(key));
List<String> list = new ArrayList<String>();
list.addAll(route.get(key));
list.add(entry.getKey());
route.put(entry.getKey(),list);
}
}else {
costs.put(entry.getKey(),entry.getValue()+costs.get(key));
List<String> list = new ArrayList<String>();
list.addAll(route.get(key));
route.put(entry.getKey(),list);
}
}
key = findMinPriceKey(costs,queryLog);
}
System.out.println("最小花費:"+costs.get(end));
System.out.println("最小花費路徑:"+route.get(end));
}
private static String findMinPriceKey(HashMap<String,Integer> data,HashMap<String,String> queryLog){
String key = null;
for(Map.Entry<String,Integer> entry : data.entrySet()){
if(!queryLog.containsKey(entry.getKey()) && key == null ){
key = entry.getKey();
}
if(!queryLog.containsKey(entry.getKey()) && entry.getValue()<data.get(key)){
key = entry.getKey();
}
}
return key;
}
運行結果:
最小花費:600
最小花費路徑:[B, A, 終點]
結果出來了,先買到B的票,然後在到A,再回家,只要600塊,還能省50塊,完美!!這就是大名鼎鼎的狄克斯特拉算法。
PS:這不是標準的狄克斯特拉算法,只是本例中的數據少,數據多的話遍歷成本是很高的,標準的狄克斯特拉算法利用的是貪婪算法思想,求局部最優解,要在可以到達終點時就返回(此註釋基於評論,本來自己寫着玩兒的,沒想會有人認真來看,避免產生誤解,誤導他人,水平有限深感慚愧)
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整
※南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!
※教你寫出一流的銷售文案?
※超省錢租車方案