歐洲電動自行車熱銷 美利達搶攻市場

歐洲人一向喜歡騎自行車旅遊、運動,看中銀髮族人數增加,讓退休老年人也能享受騎車樂趣的電動自行車,也成為最夯運動商品,各大車廠積極搶攻電動自行車市場。 比利時最大的自行車展於本周在庫特萊 (Kortrijk) 舉行,吸引超過 10 萬人次參觀。台灣自行車廠商美利達也從荷蘭前來參展。   看中銀髮商機,這幾年各大車廠都把焦點放到電動自行車領域,從一開始以通勤用為主,到現在不少車廠都針對高單價的公路車、登山車,推出電動車款,讓銀髮族也能輕鬆和朋友騎自行車出遊。   美利達荷蘭分公司總經理柯柏芮德表示,電動自行車將是未來歐洲自行車市場的一大主流商品,成長速度也相當快。歐洲 1 年的電動自行車銷售量達 20 萬輛,他樂觀預估市場至少可以擴大到數百萬輛。美利達也於 2 年前在德國生產基地擴充電動自行車生產線,搶攻歐洲電動自行車市場。        

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

【其他文章推薦】

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

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

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

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

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

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

第五屆中國國際新能源汽車論壇2015

第五屆中國國際新能源汽車論壇2015強勢回歸,5月18、19日重聚上海

2014年5月18-19日∣中國•上海

新能源汽車“智”造時代

為了全面促進我國新能源汽車產業的快速發展,給全球新能源汽車產業的參與者提供一個溝通交流的平臺,在連續四屆精彩紛呈的新能源汽車論壇的基礎上,由中國產業海外發展和規劃協會、德國電動車協會、上海交大密西根學院主辦,希邁商務諮詢承辦,並得到了亞太電動車協會的傾情指導的“第五屆中國國際新能源汽車論壇2015”將於2015年5月18日至19日在上海隆重召開。屆時將有三百位行業領導出席本次論壇。

互聯網智慧時代強勢來襲,且正悄然改變著汽車這個傳統的行業。汽車互聯網時代的到來也促進著新能源汽車、汽車租賃等新形態汽車業務有效開展。汽車智慧化、新能源、輕量化成為汽車發展的新趨勢。

在過去的四屆新能源汽車論壇上,主辦方成功的邀請了包括國家發改委能源研究所、世界電動車協會、亞太電動車協會、世界氫能協會、世界分散式能源聯盟、中國工程院、美國工程師學會、國家863重大項目組、北京公共交通集團、上海交通大學、清華大學、國家電網、中國南方電網、普天新能源等在內的政府單位與研究機構,以及包括寶馬、賓士、奇瑞捷豹路虎、大眾、奇瑞、奧迪、比亞迪、上汽、北汽、東風、本田、豐田、現代、吉利、宇通客車等在內的知名整車商,共同參與和討論,對新能源汽車行業政策趨勢、技術路線、技術難點、基礎設施建設、商業模式等進行了積極的探討,取得了豐碩的成果,獲得了業內外人士的一致好評。

在嶄新的2015年,組委會為感謝業內外人士對系列論壇長期以來的關注和支援,將傾情奉上第五屆中國國際新能源汽車論壇2015,本次論壇將以新能源汽車“智”造時代為主題。現誠摯邀請您參加本次會議共創輝煌。屆時將邀請全球範圍內的整車製造商、電網公司、電力公司、電池廠商、零部件供應商、核心技術提供商和政府官員近三百位行業嘉賓一起,就新能源汽車產業面臨的機遇、挑戰和對策進行為期兩天的富有建設性和戰略性的討論。

往屆會議精彩回顧

第一屆中國國際新能源汽車論壇 第二屆中國國際新能源汽車論壇
第三屆中國國際新能源汽車論壇 第四屆中國國際新能源汽車論壇

部分往屆知名發言人:

會議亮點

  • 參會嘉賓:
    270+高度滿意的企業決策者,90+業內知名企業,20+國家和地區
  • 參會嘉賓分析:
    17%+來自各國政府部門及權威機構,25%+來自知名整車商
  • 演講嘉賓:
    30+世界新能源汽車行業知名發言嘉賓
  • 交流機會:
    16+小時的交流機會:圓桌討論、VIP午宴和開放式問答
  • 會議形式:
    2天高品質的乾貨分享,整車廠商親臨電池企業對接洽談會

會議結構

  上午 下午
大會第一天

“智”造時代政策預測、製造趨勢及試點城市推廣情況

工信部、科技部、交通部、發改委及地方政府的政策支持
汽車智慧化、新能源、輕量化發展趨勢
國內外整車商新能源新動向
試點城市推廣情況介紹及遇到問題解決措施

新能源汽車動力總成電氣化及商業模式

新能源汽車開發特點及供應配套要求
動力總成電氣化
無人駕駛及無線充電

大會第二天

電池及儲能的研發、製造趨勢

動力電池未來商業模式、技術路線探索
燃料電池及材料研發
動力電池最新技術工藝及典型生產設備選型

微型電動車市場准入

政策准入前景
市場格局變化
電池技術路線選擇

整車廠商-電池企業

對接洽談會

第五屆中國國際新能源汽車論壇2015特色

新能源汽車“智”造時代,第五屆中國國際新能源汽車論壇2015將會邀請政府主管機構、整車商及行業各個領域的領先者,對行業現有的發展訴求及發展願景規劃做出積極的探討。

想瞭解詳細內容,請登陸官方網站:
連絡人:Hill ZENG(曾先生)
電話:+86-21-6045 1760
傳真:+86-21-6047 5887
郵箱:

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

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

2014年歐洲電動汽車銷量榜出爐 日產leaf市占率26%拔頭籌

據統計,2014年歐洲電動汽車總銷量達5.6萬輛,其中日產聆風(Leaf)的銷量佔據了主導地位,達14658輛,市場佔有率為26%。聆風目前與Juke在英國同一條生產線生產,因此價格有所降低,供應量也更大。2014年聆風電動汽車僅在英國就售出4051輛,比2013年的1812輛多出一倍以上。這款車型佔據了55%的英國純電動汽車市場。   另外,雷諾Zoe在歐洲的銷量為11227輛,排在電動汽車市場的第二位,市場佔有率為20%。特斯拉Model S電動汽車以8734輛的成績排名第三,考慮到其與奧迪A6相當的高昂售價,這一表現算得上十分出色。而BMWi3售出5804輛排在第四位,市場佔有率10%。但是i3在2014年年末剛剛上市而且受生產難度限制產量有限,也算是在電動車市場上為德國企業的表現扳回一成。   而電動車銷量榜的第5和第6位分別為大眾汽車的兩款車型。其中e-Up!電動車以5363輛的銷量幾乎比肩BMWi3。可能受限於34900歐元的售價和較晚的發售時間,其全新的電動版高爾夫(e-Golf)只售出3328輛。

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

【其他文章推薦】

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

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

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

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

※超省錢租車方案

Java工具類—包裝類

Java工具類——包裝類

我們都知道,JDK 其實給我們提供了很多很多 Java 開發者已經寫好的現成的類,他們其實都可以理解成工具類,比如我們常見的集合類,日期相關的類,數學相關的類等等,有了這些工具類,你會發現它能很大程度的幫你節省時間,能很方便的實現你的需求。當然,沒有這些包,你也能實現你的需求,但是你需要時間,今天我們主要是來學習一下包裝類。

一、包裝類介紹

1、為什麼需要包裝類?

我們知道 Java 語言是一個面向對象的編程語言,但是 Java 中的基本數據類型卻不是面向對象的,但是我們在實際使用中經常需要將基本數據類型轉換成對象,便於操作,比如,集合的操作中,這時,我們就需要將基本類型數據轉化成對象,所以就出現了包裝類。

2、包裝類是什麼呢?

包裝類,顧名思義就是將什麼經過包裝的類,那麼是將什麼包裝起來的呢,顯然這裡是將基本類型包裝起來的類。包裝類的作用就是將基本類型轉成對象,將基本類型作為對象來處理。

Java 中我們知道,基本數據類型有8個,所以對應的包裝類也是8個,包裝類就是基本類型名稱首字母大寫。但Integer 和 Character 例外,它們显示全稱,如下面表格所示:

基本數據類型 對應包裝類
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character
boolean Boolean

二、包裝類的繼承關係

通過閱讀 Java8 的 API 官方文檔或者看源代碼我們可以得知8個包裝類的繼承關係如下:

通過以上的繼承關係圖,我們其實可以這樣記憶,包裝類裏面有6個與数字相關的都是繼承自 Number 類,而其餘兩個不是與数字相關的都是默認繼承 Object 類。通過看 API 官方文檔,我們還可以得知這8個包裝類都實現了Serializable , Comparable 接口。比如下圖的 Integer 類

public final class Integer extends Number implements Comparable<Integer> {}

三、包裝類的使用方法(基本操作)

接下來關於包裝類的講解我就講Integer包裝類,其他的都依此類推,用法和操作都是差不多的,只是名字不一樣而已。

1、包裝類的構造方法

8個包裝類都有帶自己對應類型參數的構造方法,其中8個包裝類中除了Character還有構造方法重載,參數是String類型的。

Integer one = new Integer(666);
Integer two = new Integer("666");

2、包裝類的自動拆裝箱

在了解自動拆裝箱之前,我們得先知道什麼是拆箱和裝箱。其實拆裝箱主要應對基本類型與包裝類型的相互轉換問題。

  • 裝箱:將基本類型轉換成包裝類型的過程叫做裝箱。

  • 拆箱:將包裝類型轉換成基本類型的過程叫做拆箱。

其實,在 JDK1.5 版本之前,是沒有自動拆裝箱的,開發人員要手動進行裝拆箱:

//手動裝箱,也就是將基本類型10轉換為引用類型
Integer integer = new Integer(10);
//或者
Integer integer1 = Integer.valueOf(10);

//手動拆箱,也就是將引用類型轉換為基本類型
int num = integer.intValue();

而在在 JDK1.5 版本之後,為了減少開發人員的工作,提供了自動裝箱與自動拆箱的功能。實現了自動拆箱和自動裝箱,如下方代碼所示:

//自動裝箱
Integer one = 1;
//自動拆箱
int two = one + 10;

其實以上兩種方式本質上是一樣得,只不過一個是自動實現了,一個是手動實現了。至於自動拆裝箱具體怎麼實現的我這裏不做深入研究。

四、包裝類的緩存機制

我們首先來看看以下代碼,例1:

public static void main(String[] args) {
  Integer i1 = 100;
  Integer i2 = 100;
  Integer i3 = new Integer(100);
  Integer i4 = new Integer(100);
  System.out.println(i1 == i2);//true
  System.out.println(i1 == i3);//false
  System.out.println(i3 == i4);//false
  System.out.println(i1.equals(i2));//true
  System.out.println(i1.equals(i3));//true
  System.out.println(i3.equals(i4));//true
}

當我們修改了值為200的時候,例2:

public static void main(String[] args) {
  Integer i1 = 200;
  Integer i2 = 200;
  Integer i3 = new Integer(200);
  Integer i4 = new Integer(200);
  System.out.println(i1 == i2);//false
  System.out.println(i1 == i3);//false
  System.out.println(i3 == i4);//false
  System.out.println(i1.equals(i2));//true
  System.out.println(i1.equals(i3));//true
  System.out.println(i3.equals(i4));//true
}

通過上面兩端代碼,我們發現修改了值,第5行代碼的執行結果竟然發生了改變,為什麼呢?首先,我們需要明確第1行和第2行代碼實際上是實現了自動裝箱的過程,也就是自動實現了 Integer.valueOf 方法,其次,比較的是地址,而 equals 比較的是值(這裏的 eauals 重寫了,所以比較的是具體的值),所以顯然最後五行代碼的執行結果沒有什麼疑惑的。既然比較的是地址,例1的第5行代碼為什麼會是true呢,這就需要我們去了解包裝類的緩存機制。

其實看Integer類的源碼我們可以發現在第780行有一個私有的靜態內部類,如下:

private static class IntegerCache {
    static final int low = -128;
    static final int high;
    static final Integer cache[];

    static {
        // high value may be configured by property
        int h = 127;
        String integerCacheHighPropValue =
            sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
        if (integerCacheHighPropValue != null) {
            try {
                int i = parseInt(integerCacheHighPropValue);
                i = Math.max(i, 127);
                // Maximum array size is Integer.MAX_VALUE
                h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
            } catch( NumberFormatException nfe) {
                // If the property cannot be parsed into an int, ignore it.
            }
        }
        high = h;

        cache = new Integer[(high - low) + 1];
        int j = low;
        for(int k = 0; k < cache.length; k++)
            cache[k] = new Integer(j++);

        // range [-128, 127] must be interned (JLS7 5.1.7)
        assert IntegerCache.high >= 127;
    }

    private IntegerCache() {}
}

我們知道,靜態的內部類是在整個 Integer 加載的時候就已經加載完成了,以上代碼初始化了一個 Integer 類型的叫 cache 的數組,取值範圍是[-128, 127]。緩存機制的作用就是提前實例化相應範圍數值的包裝類對象,只要創建處於緩存範圍的對象,就使用已實例好的對象。從而避免重複創建多個相同的包裝類對象,提高了使用效率。如果我們用的對象範圍在[-128, 127]之內,就直接去靜態區找對應的對象,如果用的對象的範圍超過了這個範圍,會幫我們創建一個新的 Integer 對象,其實下面的源代碼就是這個意思:

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

所以 例1 代碼里,i1 和i2 是100,值的範圍在[-128, 127],所以直接區靜態區找,所以i1和i2指向的地址是同一個,所以 i1==i2;而在例2的代碼里,i1 和i2 是200,值的範圍不在在[-128, 127],所以分別創建了一個新的對象,放在了堆內存里,各自指向了不同的地址,所以地址都不同了,自然 i1 不等於 i2。

通過分析源碼我們可以發現,只有 double 和 float 的自動裝箱代碼沒有使用緩存,每次都是 new 新的對象,其它的6種基本類型都使用了緩存策略。
使用緩存策略是因為,緩存的這些對象都是經常使用到的(如字符、-128至127之間的数字),防止每次自動裝箱都創建一次對象的實例。

五、包裝類和基本數據類型的區別

  • 默認值不同

包裝類的默認值是null,而基本數據類型是對應的默認值(比如整型默認值是0,浮點型默認值是0.0)

  • 存儲區域不同

基本數據類型是把值保存在棧內存里,包裝類是把對象放在堆中,然後通過對象的引用來調用他們

  • 傳遞方式不同

基本數據類型變量空間裏面存儲的是值,傳遞的也是值,一個改變,另外一個不變,而包裝類屬於引用數據類型,變量空間存儲的是地址(引用),傳遞的也是引用,一個變,另外一個跟着變。

五、小結

​ 以上就是我對於Java包裝類的個人理解,其實學習這些工具類還有一個更好的學習方式,就是去看官方文檔(API官方文檔地址:https://docs.oracle.com/javase/8/docs/api/)

公眾號:良許Linux

有收穫?希望老鐵們來個三連擊,給更多的人看到這篇文章

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

【其他文章推薦】

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

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

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

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

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

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

動態追蹤技術之SystemTap

SystemTap

從春哥(章亦春)那看到的關於SystemTap的介紹,讓我對動態追蹤這個功能頗為感興趣,覺得這是一個非常有用的功能。

SystemTap對內核及用戶態程序提供了動態追蹤功能,用戶可以自定探測事件來跟蹤程序的運行情況,如函數的調用路徑、CPU佔用和磁盤IO等一系列可以探測的情況。有了systemtap,可以在程序不修改代碼,甚至不用重啟就能分析出程序的運行情況。

配合火焰圖的可視化,對程序的性能分析極其有利。(放一個FlameGraph的官方圖片)

原理

SystemTap 基本思想是命名事件,併為它們提供處理程序。每當發生指定的事件時,內核都會將處理程序視為子例程運行,然後繼續運行。有一系列的事件,例如進入或退出函數,計時器到期或整個SystemTap會話的開始和停止。處理程序是一系列腳本語言語句,用於指定事件發生時要完成的工作。這項工作通常包含從事件上下文中提取數據,將其存儲到內部變量或打印結果。

SystemTap 的工作原理是將腳本翻譯成C語言,執行C編譯器創建一個內核模塊。當模塊被加載后,通過掛載到內核來激活所有的探測事件。然後,當事件發生再任何處理器上時,編譯后的處理程序就運行,最終,SystemTap會話停止,Hook取消,內核模塊被移除,整個過程由命令行程序stap驅動。

原理圖如下:

安裝

需要內核級別的支持,依賴了三個rpm下載鏈接,

  1. kernel-debuginfo-common
  2. kernel-debuginfo
  3. kernel-devel

這三個包的版本必須匹配當前內核的版本,比如我自己的內核版本是 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測試,出現緩存的錯誤,刪除緩存文件

  1. /root/.systemtap/cache/34/stap_34443d4ad1fe1d37c0352b7b8c691aee_975.c
  2. /root/.systemtap/cache/34/stap_34443d4ad1fe1d37c0352b7b8c691aee_975.ko

追蹤

最簡單的探測類型就是跟蹤事件。Systemtap支持許多內置事件,所有的事件家族見 tapset

可以探測的的常用事件:

  • begin, systemtap 會話開始
  • end, systemtap 會話結束
  • kernel.function(“sys_xxx”), 系統調用xx的入口
  • kernel.function(“sys_xxx”).return, 系統調用xx的返回
  • timer.ms(300), 每300毫秒的定時器
  • timer.profile, 每個CPU上周期觸發的定時器
  • process(“a.out”).function(“foo*”), a.out 中函數名前綴為foo的函數信息
  • process(“a.out”).statement(“*@main.c:200”), a.out中文件main.c 200行處的狀態

常用的可打印值(具體見 tapset):

  • tid(), 當前線程id
  • pid(), 當前進程id
  • uid(), 當前用戶id
  • execname(), 當前進程名稱
  • cpu(), 當前cpu編號
  • gettimeofday_s(), 秒時間戳
  • get_cycles(), 硬件周期計數器快照
  • pp(), 探測點事件名稱
  • ppfunc(), 探測點觸發的函數名稱
  • $$var, 上下文中存在 $var,可以使用該變量
  • print_backtrace(), 打印內核棧
  • print_ubacktrace(), 打印用戶空間棧

SystemTap 腳本

stap 腳本簡單,語法類似C;

  • 註釋
# fuck
// fuck
/* fuck */
  • 函數
function foo() {
    // exit(); // 退出 systemtap 會話
}
  • 基本的 if/else/while/for 控制結構
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

應用

stap 常用命令

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

有這個功能,我們就可以看到函數所在源文件中的位置及可以根據的局部變量。

systemtap 腳本

探針事件的關鍵字是 probe, 由 probe 引出需要跟蹤的事件

// 腳本執行后显示 === begin ===
probe begin {
    printf("=== begin ===\n");
}

根據上述的可跟蹤的事件

  • begin/end, 分別是systemtap會話的起始和結尾
[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 ===
  • kernel.function(“sys_xxx”), 系統調用
// 調用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
  • 定時cpu採樣
// 取自春哥的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

示例分析

在探測點被執行時打印這種簡單的用法外,還能進一步做一些其它的事情。

  1. 統計某一時間段內運行次數top k的函數
[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 ===
  1. 統計系統調用某一時間段內被調用最頻繁的k個程序及次數
[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 ===
  1. 打印函數調用層次(用戶程序)

調用層次需要藉助 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 之間的跨度。

  1. 開源庫對systemtap的集成
    像libguestfs 和libvirt都有支持,恰好工作環境這兩個庫也經常有一些難以排查的問題出現

觀察 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格式),而主要耗時也在阻塞隊列的入隊上,符合預期。

rpm 鏈接

  1. https://mirrors.ocf.berkeley.edu/centos-debuginfo/7/x86_64/kernel-debuginfo-common-x86_64-3.10.0-327.el7.x86_64.rpm
  2. https://mirrors.ocf.berkeley.edu/centos-debuginfo/7/x86_64/kernel-debuginfo-3.10.0-327.el7.x86_64.rpm
  3. ftp://ftp.pbone.net/mirror/ftp.scientificlinux.org/linux/scientific/7.0/x86_64/updates/security/kernel-devel-3.10.0-327.el7.x86_64.rpm

參考

  1. systemtap 原理圖, https://www.ibm.com/developerworks/cn/linux/l-cn-systemtap3/index.html?ca=drs-
  2. systemtap 官方教程(推薦),https://sourceware.org/systemtap/tutorial/1_Introduction.html
  3. systemtap 內置函數庫 tapset,https://sourceware.org/systemtap/tapsets/index.html
  4. 春哥的一系列stap工具,https://github.com/openresty/openresty-systemtap-toolkit
  5. 火焰圖工具,https://github.com/brendangregg/FlameGraph

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

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

互聯網是如何連接的:計網概述

當你在瀏覽器輸入網址之後會發生什麼

最直觀的感受當然是跳轉到網址所指向的頁面啦,但在網絡比較卡的時候,你可能注意到過,瀏覽器的左下角通常會有一些等待什麼什麼請求之類的小字。這時候,一個問題讓你搜索到了這篇博文,我輸入網址之後,瀏覽器到底幹了什麼?更要命的是,我想知道互聯網到底是如何把每個人連接起來的?

我們先來認識一下你的瀏覽器,雖然大多數時間你都在使用它愉快的網上衝浪。可能在你的印象里,這個玩意兒就是只能開開網頁,看看視頻,功能強大,但略顯單一。現在,隨便找個PDF文件然後打開,你的默認方式應該是你心愛的瀏覽器。如果不是,可以右鍵選擇打開方式,再用瀏覽器打開。

應該都能打開吧?這麼一說,好像我們從來沒注意過這傢伙居然還有別的用處。為什麼潛意思里覺得它就是看看網頁呢?這裏要更新一下名詞,實際上我們輸入的網址,更準確的叫法應該是URL。

URL: Uniform Resource Locator,統一資源定位符。詞聽起來有點繞口,你可以看看現在打開PDF標籤頁的地址欄,看看它的開頭是不是跟網址不一樣

URL有各種各樣的寫法“http:”、“https:”、“ftp:”、“file:”、“mailto:”,開頭部分表示了瀏覽器應當採取的訪問方式。那麼輸入網址之後,首先發生了什麼事就呼之欲出了。

瀏覽器先得解析URL

比如我們要訪問博客園的首頁,那麼應該在地址欄輸入https://www.cnblogs.com。瀏覽器看到URL的開頭是https,就會明白這是一個web請求,後面的www.cnblogs.com指的是Web服務器域名。

域名可以看成是IP地址的別名,你可以在搜索引擎里輸入IP,你會看到一連串的数字。沒有人會希望上個網還要先拿小本本把經常用到的IP地址記下來,再說,輸入這一連串的数字也很麻煩,一不小心就輸錯了。所以我們使用域名來代替IP地址,幫助記憶。

IP地址:你可以類比於現實中的住房地址,因特網上的計算機也擁有一個惟一標識符來區別它的網上鄰居(其他的電腦)。

APP支持IPv6網絡是怎麼回事?你可能在APP啟動界面的底部看到過這行小字,實際上IPv6之前是IPv4(互聯網通信協議第四版),就是一個給大家分配地址的協議,在這個版本里IP用32位的整數表示。32位?看起來很長的樣子,然而2011年的時候,IANA就宣布IPv4地址43億IP地址已經分配完畢。

後續的IPv6可謂是超級加強版,32位擴展到了128位意味着能給地球上每一粒沙子編號,他能分配大約3.4×10^38個IP地址!

向DNS查詢Web服務器的IP地址

域名對應着IP地址,那麼如何才能知道這個域名對應的IP地址是什麼呢?自然是得問拿小本本記下這些東西的人啦。

DNS : 一個可以將域名和IP地址相互映射的一個分佈式數據庫,能夠使人更方便的訪問互聯網,而不用去記住能夠被機器直接讀取的IP數串。

只用一台DNS服務器去存儲數不勝數的IP地址是不可能的,所以在分散保存的情況下,一台DNS是如何處理它查詢不到的域名信息呢?

實際上,如同域名一樣,DNS也是分級的。域名的最左邊部分表示機器名字,它隨後的部分則是該計算機所屬的組織,可能還會有子組織。最右邊則代表頂級域名,指定該計算機所屬組織的類型。大部分的頂級域都被用於美國的公司和組織,其他國家的計算機一般是用國家的特有頂級域,比如cn(中國)。

頂級域 主要應用
edu 教育機構
com 商業公司
org 非盈利組織
net 網絡供應商
cn 中國國家頂級域名

負責管理下級域的DNS服務器找不到該域名對應的IP地址就會發送一個請求到它們的上級DNS服務器中去尋找,然後上級DNS還找不到就到更上一級的DNS服務器中,以此類推。最上一級叫做根域,根域服務器的地址在配置DNS服務器的時候就已經寫入其配置文件裏面了。

分配給根域DNS服務器的IP地址在全世界僅有13個,很遺憾中國沒有。日本佔據一個,其他的全是歐美。

雖然IP地址只有13個,但實際上服務器的數量遠遠多於13個,因為根域DNS是使用多台服務器對應一個IP地址的。

不知道你是否有過瀏覽器突然不能訪問網頁的經歷,更離譜的是QQ居然能用,就他瀏覽器不行。這是為什麼呢?多半是DNS服務器地址的問題,在 控制面板\網絡和 Internet\網絡連接 右鍵屬性選擇IPv4 改成下圖一般就好了

為什麼QQ沒問題呢?因為QQ是直接通過IP連接的。

你訪問的其實是文件

現在不如點開一下我首頁的第一篇博客吧,或者直接訪問https://www.cnblogs.com/AD-milk/p/13171059.html然後按下F12打開開發者模式,讓我們看看都有些什麼?

我們請求了一個html文件,1317…好像就是輸入的網址里的那個。這麼說的話,這一個一個斜桿好像確實是像文件路徑的結構!

但如果你訪問博客園的首頁,你會發現路徑名那裡只有一個/,這表示根目錄,好像並沒有指定文件呀。這種省略文件名的情況下,一般是默認訪問根目錄下的index.html或者default.html這樣的文件。

html:超文本標記語言,你可以簡單的認為是可以在文本中嵌入音頻,圖片和鏈接的語言

按理說我們只是請求了13171059.html這個文件,但從圖中來看我們好像請求了不止一個文件,這是為什麼?回想一下,你會有過因為網卡而導致加載的頁面只有文字的情況?這一般是因為網絡問題,css文件以及圖片沒有被傳輸過來。瀏覽器在加載文件之前並不知道這裏還需要什麼圖片或者文件,只有加載之後才會繼續發送請求。

css文件是一種用來指定html樣式的文件。缺少了它頁面就沒有好看的風格了。

1 條請求消息中只能寫 1 個 URI。如果需要獲取多個文件,必須對每個文件單獨發送 1 條請求。

URI:Uniform Resource Identifier,統一資源標識符,URL是特殊的URI

眨眼間的旅途

你說的我都知道了,可你還是沒有回答我,互聯網到底是如何互聯的?

首先來考慮一個簡單的場景:兩台電腦之間如何互相通訊?當然是接跟線把兩個都連上啦,那再來五台怎麼辦?往電腦上再打五個接口?這顯然不現實,我們可以讓集線器幫我們的忙。它具有多個端口,可以實現其內部所有網絡設備的連通。不過集線器是個榆木腦袋,它唯一知道的事情就是:當某一個端口發出數據時,它就把這個數據複製到與其連接的其他端口,這樣所有連接到集線器的設備都可以獲得這個數據包。這跟個大喇叭一樣,太煩人了,說點悄悄話都不行。

之所以電纜不如光纖通信速率高,是因為電信號在提升通信速率的同時,其衰減率也會提高(信號在傳播過程中減弱),導致信號無法傳到目的地。相對地,光信號本來的衰減率就很低,提高通信速率也並不會提高衰減率。此外,光纖還不受電磁噪聲的影響,因此光纖能夠進行高速通信。

不是光信號本來就比電信號快啦!!!

為了解決廣播所帶來的安全問題,以及減少不必要的流量,我們可以換個聰明點的傢伙:交換機。交換機比集線器智能的地方在於,它記下了與其相連的主機的物理地址MAC,他把這些存儲在地址表裡面。那麼現在,當數據報從一個端口發出時,它能根據數據報提供的目標MAC,直接將數據發往預期的目的端口。終於可以和喜歡的女同學說悄悄話了,呼~~。

但集線器和交換機只能在局域網中交換信息,無法在外部網絡交換。因為要在互聯網上通訊需要識別IP地址,顯然集線器和交換機沒有這個功能。所以我們需要用到路由器:它是一個能根據IP地址,將數據報從一個路由轉發到另外一個路由的設備。這意味着它具有過濾功能,但一個數據報達到該路由時,它會檢查這是否是發給自己的,如果不是的話就將它轉發給其他路由。所以它相當於一個網絡的網關。

現在的路由器大多集成了三者的功能,成了“瑞士軍刀”。讓人想不到的是,儘管交換機比路由器簡單,但實際上路由器早於交換機問世。

現在從我們電腦發出的請求數據報就沿着電路,被多個路由器轉發,最後到達目的地。

至此,這眨眼間的漫長旅途迎來了終點。但其實網絡通訊遠不止這些,這裏省去了很多東西,其中最重要的就是各式各樣的協議了。缺少協議會使得網絡世界通訊困難,就好比現實生活中你覺得點頭表示OK,搖頭表示NO,但另外一個人卻持相反的認識,這時候你們倆的交流就會遇上大麻煩!

協議:定義了在兩個或多個通信實體之間交換的報文格式和順序,以及報文發送和接收一條報文或其他事件採取的動作

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

【其他文章推薦】

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

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

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

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

※超省錢租車方案

四川綿陽2020年產15萬輛新能源車 總產值達5059億

中國四川省綿陽市政府常務會議審議並原則通過了《綿陽市新能源汽車產業發展規劃(2015—2020年)》。規劃提出欲在綿陽科技城集中發展區建設集新能源汽車核心技術研發、核心零件生產製造、整車製造與體驗、國家級專業孵化器、中國國家級工程技術實驗與檢測中心為一體的國家新能源汽車研究與產業發展基地。到2020年,實現年產15萬輛新能源汽車的產能,新能源汽車及關鍵零部件產業總產值1000億元人民幣(折合新台幣約5059億元)。   綿陽新能源汽車產業已擁有川汽、華晨汽車南方基地、中國重汽綿陽分公司等整車製造企業,還擁有長虹、安縣英志鋰電池等一批新能源汽車零配件企業。規劃提出,綿陽要「全域規劃」,加快新能源汽車在公共交通、公務、出租等領域的示範應用,政府採購的特種車輛也要優先選擇新能源汽車;大力發展農用低速電動車,不斷擴大新能源汽車應用範圍。

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

【【其他文章推薦】

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

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

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

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

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

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

福特攜兩款新智慧電動車 亮相MWC2015

在巴賽隆納舉辦的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/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

解惑3:時間頻度,算法時間複雜度

一、概述

先放百科上的說法:

算法的時間複雜度(Time complexity)是一個函數,它定性描述該算法的運行時間。這是一個代表算法輸入值的字符串的長度的函數。

時間複雜度常用大O符號表述,不包括這個函數的低階項和首項係數。使用這種方式時,時間複雜度可被稱為是漸近的,亦即考察輸入值大小趨近無窮時的情況。

例如,如果一個算法對於任何大小為 n (必須比 n0 大)的輸入,它至多需要 5n3 + 3n 的時間運行完畢,那麼它的漸近時間複雜度是 O(n3).

二、時間頻度

要理解時間複雜度,需要先理解時間頻度,而時間頻度簡單的說,就是算法中語句的執行次數。

舉個例子:

要計算1+2+…+100,現在有兩種算法

public int fun1(int n){
    int total;
    for(int i = 0; i <= n; i++){
        total+=i;
    }
    return total;
}

public int fun2(int n){
    int total = (1 + n)*n/2;
    return total;
}

我們可以看見,對於fun1()這個方法,不管n多大,永遠需要執行n+1次,也就是說他的時間頻度是T(n)=n+1,

而對與fun2()來說,不管n多大都只需要執行1次,所以他的時間頻度T(n)=1。

當n趨向無窮大時,有三個忽略:

1.忽略常數項

比如T(n)=2n+1,當n趨向無窮大時,可以忽略常數項1;

參見下圖:

  • 2n+20 和 2n 隨着n 變大,執行曲線無限接近, 20可以忽略
  • 3n+10 和 3n 隨着n 變大,執行曲線無限接近, 10可以忽略

2.忽略低次項

比如T(n)=2n+3n^8,當n趨向無窮大時,可以忽略低次項及其係數2n;

參見下圖:

  • 2n^2+3n+10 和 2n^2 隨着n 變大, 執行曲線無限接近, 可以忽略 3n+10
  • n^2+5n+20 和 n^2 隨着n 變大,執行曲線無限接近, 可以忽略 5n+20

3.忽略係數

比如T(n)=2n^8,當n趨向無窮大時,可以忽略係數2。

參見下圖:

  • 隨着n值變大,5n^2+7n 和 3n^2 + 2n ,執行曲線重合, 說明 這種情況下, 5和3可以忽略。
  • 而n^3+5n 和 6n^3+4n ,執行曲線分離,說明多少次方式關鍵

三、時間複雜度

我們現在理解了時間頻度的T(n)的含義,假設當有一個輔助函數f(n),使得當n趨近無窮大時,T(n)/f(n)的極限值為不等於0的常數,就叫f(n)為T(n)的同量級函數,記作T(n)=O(f(n)),

稱O(f(n))為算法的時間漸進複雜度,也就是時間複雜度。

又根據時間頻度T(n)的“三個忽略”原則,我們可以知道時間複雜度是這樣得到的:

  1. 忽略所有常數
  2. 只保留函數中的最高階項
  3. 去掉最高階項的係數

舉個例子:

某算法T(n)=2n^3+4n-5,按步驟走:

  1. T(n)=2n^3+4n
  2. T(n)=2n^3
  3. T(n)=n^3

即可得該算法時間複雜度為O(n^3)

四、常見時間複雜度

這裏按複雜度從低到高列舉常見的時間複雜度:

  1. 常數階O(1)

    // 無論代碼執行了多少行,只要是沒有循環等複雜結構,那這個代碼的時間複雜度就都是O(1) 。
    public void fun(int n){
        n+=1;
    }
    
  2. 對數階O(log2n)

    // 根據公式有 n = 2^x,也就是 x = log2n,x即為循環代碼執行次數,所以時間複雜度為O(log2n)
    public void fun(int n){
        int i = 1;
        while(i < n){
            i = i *2
        }
    }
    
  3. 線性階O(n)

    // 一般來說,只要代碼里只有一個循環結構,即輸入規模和執行次數呈線性相關,那這個代碼的時間複雜度就都是O(n) 。
    public void fun(int n){
        for(int i = 0; i < n; i++){
            n+=i;
        }
    }
    
  4. 線性對數階O(nlogn)

    // 可以簡單理解為對數階的程序被放入了循環結構中,也就是n*O(logn),下面的代碼的複雜度就是O(nlog2n)
    public void fun(int n){
        int j = 1;
        for(int i = 0; i < n; i++){
            while(i < n){
                j = j *2
            }
        }
    }
    
  5. 平方階O(n²),立方階O(n3),K次方階O(nk)

    // 平方階可以簡單理解為線性階中嵌套一個線性階,也就是O(logn)*O(logn),下面的代碼複雜度就是O(n^2)
    // 立方階同理,就是三個線性階的嵌套,K次方階同理
    public void fun(int n){
        for(int i = 0; i < n; i++){
            for(int j = 0; j < n; i++){
    			i=i+j;
            } 
        }
    }
    

五、複雜度的四個概念

  1. 最壞情況時間複雜度:代碼在最理想情況下執行的時間複雜度。
  2. 最好情況時間複雜度:代碼在最壞情況下執行的時間複雜度。
  3. 平均時間複雜度:用代碼在所有情況下執行的次數的加權平均值表示
  4. 均攤時間複雜度:在代碼執行的所有複雜度情況中絕大部分是低級別的複雜度,個別情況是高級別複雜度且發生具有時序關係時,可以將個別高級別複雜度均攤到低級別複雜度上。基本上均攤結果就等於低級別複雜度。

舉個例子:

長度為n的數組查找一個給定元素k

public void fun(int[] arr,int k){
    for(int i = 0; i < arr.length; i++){
        if(arr[i] == k){
            //找到了
        }
    }
}

上面這個方法,最好的情況下元素k就在數組第一位,複雜度為O(1),但是最壞的情況下,元素k在數組最後一位,複雜度為O(n)。

同一段代碼在不同情況下時間複雜度會出現量級差異,為了更全面,更準確的描述代碼的時間複雜度,我們引入這4個概念,當然,在大多數時候我們是不用特意區分這四種情況的。

六、總結

總結一下如何快速判斷程序的時間複雜度:

  • 只關注循環最多的那部分代碼
  • 總複雜度等於量級最大的那段代碼的複雜度
  • 嵌套代碼的複雜度等於嵌套內外代碼複雜度的乘積

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

【【其他文章推薦】

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

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

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

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

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

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

【秒懂Java】【第1章_初識Java】01_編程語言

各位小夥伴們好哇!從今日起,我將開始更新《秒懂Java》系列文章,從0開始講解Java的方方面面,完全零基礎也可以看懂。後面也將推出配套的視頻版,歡迎大家保持關注!

  • 我會儘力辦到:在保證通俗易懂的同時,不丟失知識的嚴謹性和完整性
  • 不管你是否有編程經驗,只要你感興趣、細心閱讀,就能學會
  • 本文是《秒懂Java》系列的第1篇文章,主要認識幾個基礎概念

什麼是計算機?

一說到計算機(Computer),大家首先想到的可能是電腦(台式電腦、手提電腦)。

實際上,我們日常生活中使用的平板電腦、智能手機也屬於計算機,它們的功能也跟電腦一樣非常強大。

還有KTV的點歌機、超市的自助收銀機等很多設備也都屬於計算機。

毋庸置疑,計算機是人類歷史上最偉大的發明之一,極大地提高了社會的生產力,目前它已經延伸到了生活、學習、工作等各個領域,無處不在。如今,我們會在計算機上安裝各種各樣的軟件(應用、遊戲),學習工作、衣食住行、吃喝玩樂一網打盡,極大地改變了我們的生活、學習、工作方式。

什麼是編程語言?

語言,是雙方進行溝通交流的主要表達方式。

  • 如果我要跟咱們中國人進行交流,應該用漢語
  • 如果我要跟美國人進行交流,應該用英語
  • 如果我要跟韓國人進行交流,應該用韓語

那如果我要跟計算機進行交流呢?那就應該用計算機編程語言。

  • 是的,計算機編程語言,就是用來跟計算機進行溝通交流的語言
  • 一般把計算機編程語言簡稱為:編程語言(Programming Language)

注意:我們要學習的很多IT技術都源自西方國家(比如美國),因此,很多技術名詞,都是從英文翻譯過來的。為了保證描述的嚴謹性,當首次提及某個技術名詞時,我都會在它旁邊標註原本的英文單詞。

與計算機進行交流

那如何利用編程語言與計算機進行交流呢?

編程語言,顧名思義,就是可以用來“編寫程序”的語言。

  • 首先,利用編程語言編寫一段程序。例如上圖所示的程序,只包含了3行代碼,這裏簡單說一下代碼的大概意思(了解一下即可,不用去深究)
    • 第1行:創建了一個播放器
    • 第2行:設置播放器的音量為100
    • 第3行:開始播放某個mp4視頻
  • 最後,將程序運行到計算機上,計算機就會開始識別執行程序中的每一句代碼,完成相應的功能,最後成功播放視頻

我們平時使用的各種軟件(應用、遊戲)都是通過編程語言開發出來的,它們都由一大堆的代碼組成。當打開軟件時(將軟件運行到計算機上時),計算機就會開始識別執行軟件中包含的代碼,完成相應的功能。

所以,編程語言有一個非常重要的作用,那就是:開發軟件!

主流編程語言

世界上有上百種各式各樣的編程語言,目前比較常見的主流編程語言有

  • Java、C、C++、C#、PHP、Python、Go
  • JavaScript(簡稱JS)、TypeScript(簡稱TS)
  • Objective-C(簡稱OC)、Swift、Kotlin
  • Scala、Assembly Language(彙編語言) 等

每一門編程語言的作用都不太一樣,比如

  • Java、Kotlin:可以用來開發Android系統中的軟件
  • Objective-C、Swift:可以用來開發iOS、Mac系統中的軟件
  • C、C++、C#:可以用來開發Windows系統中的軟件

當然,上面列舉的僅僅是它們的部分功能,並沒有說完整。比如:

  • C、C++也可以開發Android、iOS、Mac、Linux等系統中的軟件
  • Java也可以開發Mac、Linux、Windows等系統中的軟件

其他的就不一一說明了,了解一下即可

語法

每當我們要學習一門新的語言時,都要先學習它的語法。

  • 比如,學習英語就要先學習它的語法,英語的語法規定了:英文句子可以怎麼寫、不可以怎麼寫
  • 同樣的,每一門編程語言都有自己的語法,編程語言的語法規定了:你的代碼可以怎麼寫、不可以怎麼寫
    • 比如,Java語言的語法規定:每一句代碼後面都必須以分號(;)結束
    • 代碼一旦出現了語法錯誤,就無法成功運行到計算機上

不同編程語言的語法是不一樣的,每一門編程語言都有自己特有的語法。比如,想讓計算機播放一個視頻,不同編程語言可能會有不同的寫法。

不難看出它們之間的一些差異(了解一下即可,不用去深究)

  • Java、OC都以分號(;)結束,而Python不需要以分號(;)結束
  • Java、Python中都使用了點(.)、小括號(()),而OC中使用了中括號([])、冒號(:)、@符號
  • Java、OC中都使用雙引號(“”),而Python中可以使用單引號(”)

雖然它們的寫法不一樣,但是都完成了一樣的功能:讓計算機播放一個視頻。如果想利用編程語言開發出強大優秀的軟件、控制計算機做更多的事情,首先要踏踏實實學好編程語言的語法。如果你連語法都不懂,怎麼可能寫出正確的代碼呢?

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

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案