奔馳下一代汽車將棄用特斯拉電動傳動系統 採用自家技術

戴姆勒電動汽車主管哈拉爾德•克勒格爾(Harald Kroeger)在接受德國媒體採訪時說,為了開發自有傳動系統和電池技術,奔馳已經投資了5.5億美元,下一代B級Electric Drive電動汽車不會使用特斯拉傳動系統。

奔馳這樣做合情合理。2014年年末時,奔馳基本上已經清空了特斯拉持股。在2014年的年報中特斯拉也說B級車研發合作已經基本完成。到了2015年年底,特斯拉已經銷售10萬輛電動汽車。以前特斯拉是矽谷新崛起的明星企業,一些大型汽車公司有興趣與它合作,現在汽車公司已經將特斯拉當成了競爭對手。比如豐田,在特斯拉成立之初,豐田也曾參與投資,特斯拉還為豐田電動版RAV4提供傳動系統。

那時的特斯拉還很年輕,它還沒有推出Model S轎車,如果能與大型汽車商合作對公司的發展無疑是有利的。特斯拉需要增加營收,只有有了營收,公司才有可能轉變為主流汽車製造商。另外,在公司沒有上市之前,它需要吸引早期投資者來支援自己。今天的特斯拉已經完全不同了,Model S大獲成功,Model X剛剛開始向用戶交貨。本月晚些時候Model 3也將上市,這是一款低價電動汽車。可以說現在的特斯拉已經是一家規模雖小但是卻很成熟的電動汽車製造商了。

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

【其他文章推薦】

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

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

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

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

小三通物流營運型態?

※快速運回,大陸空運推薦?

【集合系列】- 深入淺出分析LinkedHashMap

一、摘要

在集合系列的第一章,咱們了解到,Map的實現類有HashMap、LinkedHashMap、TreeMap、IdentityHashMap、WeakHashMap、Hashtable、Properties等等。

本文主要從數據結構和算法層面,探討LinkedHashMap的實現。

二、簡介

LinkedHashMap可以認為是HashMap+LinkedList,它既使用HashMap操作數據結構,又使用LinkedList維護插入元素的先後順序,內部採用雙向鏈表(doubly-linked list)的形式將所有元素( entry )連接起來。

LinkedHashMap繼承了HashMap,允許放入key為null的元素,也允許插入value為null的元素。從名字上可以看出該容器是LinkedList和HashMap的混合體,也就是說它同時滿足HashMap和LinkedList的某些特性,可將LinkedHashMap看作採用Linked list增強的HashMap。

打開 LinkedHashMap 源碼,可以看到主要三個核心屬性:

public class LinkedHashMap<K,V>
    extends HashMap<K,V>
    implements Map<K,V>{

    /**雙向鏈表的頭節點*/
    transient LinkedHashMap.Entry<K,V> head;

    /**雙向鏈表的尾節點*/
    transient LinkedHashMap.Entry<K,V> tail;

    /**
      * 1、如果accessOrder為true的話,則會把訪問過的元素放在鏈表後面,放置順序是訪問的順序
      * 2、如果accessOrder為false的話,則按插入順序來遍歷
      */
      final boolean accessOrder;
}

LinkedHashMap 在初始化階段,默認按插入順序來遍歷

public LinkedHashMap() {
        super();
        accessOrder = false;
}

LinkedHashMap 採用的 Hash 算法和 HashMap 相同,不同的是,它重新定義了數組中保存的元素Entry,該Entry除了保存當前對象的引用外,還保存了其上一個元素before和下一個元素after的引用,從而在哈希表的基礎上又構成了雙向鏈接列表。

源碼如下:

static class Entry<K,V> extends HashMap.Node<K,V> {
        //before指的是鏈表前驅節點,after指的是鏈表后驅節點
        Entry<K,V> before, after;
        Entry(int hash, K key, V value, Node<K,V> next) {
            super(hash, key, value, next);
        }
}

可以直觀的看出,雙向鏈表頭部插入的數據為鏈表的入口,迭代器遍歷方向是從鏈表的頭部開始到鏈表尾部結束。

除了可以保迭代歷順序,這種結構還有一個好處:迭代LinkedHashMap時不需要像HashMap那樣遍歷整個table,而只需要直接遍歷header指向的雙向鏈表即可,也就是說LinkedHashMap的迭代時間就只跟entry的個數相關,而跟table的大小無關。

三、常用方法介紹

3.1、get方法

get方法根據指定的key值返回對應的value。該方法跟HashMap.get()方法的流程幾乎完全一樣,默認按照插入順序遍歷。

public V get(Object key) {
        Node<K,V> e;
        if ((e = getNode(hash(key), key)) == null)
            return null;
        if (accessOrder)
            afterNodeAccess(e);
        return e.value;
}

如果accessOrdertrue的話,會把訪問過的元素放在鏈表後面,放置順序是訪問的順序

void afterNodeAccess(Node<K,V> e) { // move node to last
        LinkedHashMap.Entry<K,V> last;
        if (accessOrder && (last = tail) != e) {
            LinkedHashMap.Entry<K,V> p =
                (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
            p.after = null;
            if (b == null)
                head = a;
            else
                b.after = a;
            if (a != null)
                a.before = b;
            else
                last = b;
            if (last == null)
                head = p;
            else {
                p.before = last;
                last.after = p;
            }
            tail = p;
            ++modCount;
        }
}

測試用例:

public static void main(String[] args) {
        //accessOrder默認為false
        Map<String, String> accessOrderFalse = new LinkedHashMap<>();
        accessOrderFalse.put("1","1");
        accessOrderFalse.put("2","2");
        accessOrderFalse.put("3","3");
        accessOrderFalse.put("4","4");
        System.out.println("acessOrderFalse:"+accessOrderFalse.toString());
        
        //accessOrder設置為true
        Map<String, String> accessOrderTrue = new LinkedHashMap<>(16, 0.75f, true);
        accessOrderTrue.put("1","1");
        accessOrderTrue.put("2","2");
        accessOrderTrue.put("3","3");
        accessOrderTrue.put("4","4");
        accessOrderTrue.get("2");//獲取鍵2
        accessOrderTrue.get("3");//獲取鍵3
        System.out.println("accessOrderTrue:"+accessOrderTrue.toString());
}

輸出結果:

acessOrderFalse:{1=1, 2=2, 3=3, 4=4}
accessOrderTrue:{1=1, 4=4, 2=2, 3=3}

3.2、put方法

put(K key, V value)方法是將指定的key, value對添加到map里。該方法首先會調用HashMap的插入方法,同樣對map做一次查找,看是否包含該元素,如果已經包含則直接返回,查找過程類似於get()方法;如果沒有找到,將元素插入集合。

/**HashMap 中實現*/
public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
}

LinkedHashMap 中覆寫的方法

// LinkedHashMap 中覆寫
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
    LinkedHashMap.Entry<K,V> p =
        new LinkedHashMap.Entry<K,V>(hash, key, value, e);
    // 將 Entry 接在雙向鏈表的尾部
    linkNodeLast(p);
    return p;
}

private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
    LinkedHashMap.Entry<K,V> last = tail;
    tail = p;
    // last 為 null,表明鏈表還未建立
    if (last == null)
        head = p;
    else {
        // 將新節點 p 接在鏈表尾部
        p.before = last;
        last.after = p;
    }
}

3.3、remove方法

remove(Object key)的作用是刪除key值對應的entry,該方法實現邏輯主要以HashMap為主,首先找到key值對應的entry,然後刪除該entry(修改鏈表的相應引用),查找過程跟get()方法類似,最後會調用 LinkedHashMap 中覆寫的方法,將其刪除!

/**HashMap 中實現*/
public V remove(Object key) {
    Node<K,V> e;
    return (e = removeNode(hash(key), key, null, false, true)) == null ?
        null : e.value;
}

final Node<K,V> removeNode(int hash, Object key, Object value,
                           boolean matchValue, boolean movable) {
    Node<K,V>[] tab; Node<K,V> p; int n, index;
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (p = tab[index = (n - 1) & hash]) != null) {
        Node<K,V> node = null, e; K k; V v;
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            node = p;
        else if ((e = p.next) != null) {
            if (p instanceof TreeNode) {...}
            else {
                // 遍歷單鏈表,尋找要刪除的節點,並賦值給 node 變量
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key ||
                         (key != null && key.equals(k)))) {
                        node = e;
                        break;
                    }
                    p = e;
                } while ((e = e.next) != null);
            }
        }
        if (node != null && (!matchValue || (v = node.value) == value ||
                             (value != null && value.equals(v)))) {
            if (node instanceof TreeNode) {...}
            // 將要刪除的節點從單鏈表中移除
            else if (node == p)
                tab[index] = node.next;
            else
                p.next = node.next;
            ++modCount;
            --size;
            afterNodeRemoval(node);    // 調用刪除回調方法進行後續操作
            return node;
        }
    }
    return null;
}

LinkedHashMap 中覆寫的 afterNodeRemoval 方法

void afterNodeRemoval(Node<K,V> e) { // unlink
    LinkedHashMap.Entry<K,V> p =
        (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
    // 將 p 節點的前驅后後繼引用置空
    p.before = p.after = null;
    // b 為 null,表明 p 是頭節點
    if (b == null)
        head = a;
    else
        b.after = a;
    // a 為 null,表明 p 是尾節點
    if (a == null)
        tail = b;
    else
        a.before = b;
}

四、總結

LinkedHashMap 繼承自 HashMap,所有大部分功能特性基本相同,二者唯一的區別是 LinkedHashMap 在HashMap的基礎上,採用雙向鏈表(doubly-linked list)的形式將所有 entry 連接起來,這樣是為保證元素的迭代順序跟插入順序相同。

主體部分跟HashMap完全一樣,多了header指向雙向鏈表的頭部,tail指向雙向鏈表的尾部,默認雙向鏈表的迭代順序就是entry的插入順序。

五、參考

1、JDK1.7&JDK1.8 源碼
2、

作者:炸雞可樂
出處:

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

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

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

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

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

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

不止面試—jvm類加載面試題詳解

面試題

帶着問題學習是最高效的,本次我們將嘗試回答以下問題:

  1. 什麼是類的加載?
  2. 哪些情況會觸發類的加載?
  3. 講一下JVM加載一個類的過程
  4. 什麼時候會為變量分配內存?
  5. JVM的類加載機制是什麼?
  6. 雙親委派機制可以打破嗎?為什麼

答案放在文章的最後,來不及看原理也可以直接跳到最後直接看答案。

深入原理

類的生命周期

類的生命周期相信大家已經耳熟能詳,就像下面這樣:

不過這東西總是背了就忘,忘了又背,就像馬什麼梅一樣,對吧?

其實理解之後,基本上就不會再忘了。

加載

加載主要做三件事:

  1. 找到類文件(通過類的全限定名來獲取定義此類的二進制字節流)
  2. 放入方法區(將這個字節流所代表的靜態存儲結構轉化為方法區的運行時數據結構)
  3. 開個入口(生成一個代表此類的java.lang.Class對象,作為訪問方法區這些數據結構的入口)

總的來講,這一步就是通過類加載器把類讀入內存。需要注意的是,第三步雖然生成了對象,但並不在堆里,而是在方法區里。

連接

連接分為三步,一般面試都比較喜歡問準備這一步。

校驗

顧名思義,檢查Class文件的字節流中包含的信息是否符合當前虛擬機的要求。

準備

這一步中將為靜態變量和靜態常量分配內存,並賦值。

需要注意的是,靜態變量只會給默認值。比如下面這個:

public static int value = 123;

此時賦給value的值是0,不是123。

靜態常量(static final修飾的)則會直接賦值。比如下面這個:

public static final int value = 123;

此時賦給value的值是123。

解析

解析階段就是jvm將常量池的符號引用替換為直接引用。

恩……啥是常量池?啥是符號引用?啥是直接引用?

常量池我們放在jvm內存結構里說。先來說下什麼是符號引用和直接引用。

符號引用和直接引用

假設有一個Worker類,包含了一個Car類的run()方法,像下面這樣:

class Worker{
    ......
    public void gotoWork(){
        car.run(); //這段代碼在Worker類中的二進製表示為符號引用        
    }
    ......
}

在解析階段之前,Worker類並不知道car.run()這個方法內存的什麼地方,於是只能用一個字符串來表示這個方法。該字符串包含了足夠的信息,比如類的信息,方法名,方法參數等,以供實際使用時可以找到相應的位置。

這個字符串就被稱為符號引用

在解析階段,jvm根據字符串的內容找到內存區域中相應的地址,然後把符號引用替換成直接指向目標的指針、句柄、偏移量等,這之後就可以直接使用了。

這些直接指向目標的指針、句柄、偏移量就被成為直接引用

初始化

類的初始化的主要工作是為靜態變量賦程序設定的初值。

還記得上面的靜態變量嗎:

public static int value = 123;

經過這一步,value的值終於是123了。

總結如下圖:

類初始化的條件

Java虛擬機規範中嚴格規定了有且只有五種情況必須對類進行初始化:

  1. 使用new字節碼指令創建類的實例,或者使用getstatic、putstatic讀取或設置一個靜態字段的值(放入常量池中的常量除外),或者調用一個靜態方法的時候,對應類必須進行過初始化。
  2. 通過java.lang.reflect包的方法對類進行反射調用的時候,如果類沒有進行過初始化,則要首先進行初始化。
  3. 當初始化一個類的時候,如果發現其父類沒有進行過初始化,則首先觸發父類初始化。
  4. 當虛擬機啟動時,用戶需要指定一個主類(包含main()方法的類),虛擬機會首先初始化這個類。
  5. 使用jdk1.7的動態語言支持時,如果一個java.lang.invoke.MethodHandle實例最後的解析結果REF_getStatic、REF_putStatic、RE_invokeStatic的方法句柄,並且這個方法句柄對應的類沒有進行初始化,則需要先觸發其初始化。

除了以上這五種情況,其他任何情況都不會觸發類的初始化。

比如下面這幾種情況就不會觸發類初始化:

  1. 通過子類調用父類的靜態字段。此時父類符合情況一,而子類不符合任何情況。所以只有父類被初始化。
  2. 通過數組來引用類,不會觸發類的初始化。因為new的是數組,而不是類。
  3. 調用類的靜態常量不會觸發類的初始化,因為靜態常量在編譯階段就會被存入調用類的常量池中,不會引用到定義常量的類。

類加載機制

類加載器

在上面咱們曾經說到,加載階段需要“通過一個類的全限定名來獲取描述此類的二進制字節流”。這件事情就是類加載器在做。

jvm自帶三種類加載器,分別是:

  1. 啟動類加載器。
  2. 擴展類加載器。
  3. 應用程序類加載器

他們的繼承關係如下圖:

雙親委派

雙親委派機制工作過程如下:

  1. 當前ClassLoader首先從自己已經加載的類中查詢是否此類已經加載,如果已經加載則直接返回原來已經加載的類。每個類加載器都有自己的加載緩存,當一個類被加載了以後就會放入緩存,等下次加載的時候就可以直接返回了。

  2.  當前classLoader的緩存中沒有找到被加載的類的時候,委託父類加載器去加載,父類加載器採用同樣的策略,首先查看自己的緩存,然後委託父類的父類去加載,一直到bootstrp ClassLoader.

  3.  當所有的父類加載器都沒有加載的時候,再由當前的類加載器加載,並將其放入它自己的緩存中,以便下次有加載請求的時候直接返回。

為啥要搞這麼複雜?自己處理不好嗎?

雙親委派的優點如下:

  1. 避免重複加載。當父親已經加載了該類的時候,就沒有必要子ClassLoader再加載一次。
  2. 為了安全。避免核心類,比如String被替換。

打破雙親委派

“雙親委派”機制只是Java推薦的機制,並不是強制的機制。

比如JDBC就打破了雙親委派機制。它通過Thread.currentThread().getContextClassLoader()得到線程上下文加載器來加載Driver實現類,從而打破了雙親委派機制。

至於為什麼,以後再說吧。

答案

現在,我們可以回答文章開頭提出的問題了。盡量在理解的基礎上回答,不需要死記硬背。

  1. 什麼是類的加載?

    JVM把通過類名獲得類的二進制流之後,把類放入方法區,並創建入口對象的過程被稱為類的加載。經過加載,類就被放到內存里了。

  2. 哪些情況會觸發類的初始化?

    類在5種情況下會被初始化:

    第一,假如這個類是入口類,他會被初始化。

    第二,使用new創建對象,或者調用類的靜態變量,類會被初始化。不過靜態常量不算。

    第三,通過反射獲取類,類會被初始化

    第四,如果子類被初始化,他的父類也會被初始化。

    第五,使用jdk1.7的動態語言支持時,調用到靜態句柄,也會被初始化。

  3. 講一下JVM加載一個類的過程

    同問題1。不過這裏也可以問下面試官是不是想問類的生命周期。如果是問類的生命周期,可以回答有”加載、連接、初始化、使用、卸載“五個階段,連接又可以分為”校驗、準備、解析“三個階段。

  4. 什麼時候會為變量分配內存?

    在準備階段為靜態變量分配內存。

  5. JVM的類加載機制是什麼?

    雙親委派機制,類加載器會先讓自己的父類來加載,父類無法加載的話,才會自己來加載。

  6. 雙親委派機制可以打破嗎?為什麼

    可以打破,比如JDBC使用線程上下文加載器打破了雙親委派機制。原因是JDBC只提供了接口,並沒有提供實現。這個問題可以再看下引用文獻的內容。

引用文獻

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

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

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

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

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

小三通物流營運型態?

※快速運回,大陸空運推薦?

延燒八個月 澳洲新南威爾斯省野火終於撲滅

摘錄自2020年3月10日大紀元報導

澳洲新南威爾斯省日前正式宣布,當地延燒了大約八個月、造成無數損失的野火,終於完全撲滅了。據CNN報導,新南威爾斯省的消防部門在2月13日宣布,該省所有野火已經受到控制。而現在,所有野火都已經被撲滅。

新南威爾斯鄉村消防署(NSW Rural FIre Service)在3月2日發推文說:「目前沒有在延燒中的野火。這是自2019年7月初以來的第一次。」該署還提到,這場野火持續了超過240天。這場延燒了幾個月的野火最終導致至少28人死亡,大約3,000棟房屋被毀,多達10億隻動物受到波及。

現在,在澳洲政府正式宣布野火被撲滅之後,該國將開始其災後重建的工作,他們正在討論是否應該針對高風險的地區制定重建的限制。而對於在何處和如何重建房屋而言,屋主正面臨困難的選擇。

森林
災害
生態保育
土地水文
國際新聞
澳洲
大火
澳洲野火

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

【其他文章推薦】

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

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

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

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

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

特斯拉將在蘇州成立生產基地?

作為全球最具潛力的電動車市場之一,特斯拉(Tesla)也持續傳出有在中國設置本土化生產基地的消息。日前中國流出一份文件,內容是關於蘇州召開會議討論特斯拉汽車項目入主蘇州的相關事宜,似乎代表特斯拉已選定了地點。

根據中國網路媒體《第一電動網》所獲得的資訊,蘇州相關政府部門於3月13日召開專題會議,研究協調特斯拉的汽車生產工廠成立相關事宜。通知內容如下:

經研究,市委副書記、常務副市長周偉強定於3月13日(星期天)上午9:00,在市政府1號會議室召開專題會議,研究協調特斯拉汽車項目落戶蘇州的相關事宜。會議主要有三項議程:一是請商務局通報該項目整體情況;二是請各有關地區匯報爭取該項目落地的相關意見和措施(如土地供應等方面,書面材料請帶5份至會);三是市各有關部門就推進該項目提出有關建議(項目情況見附件)。

請各地政府(管委會)、各有關部門主要領導或分管領導準時參會(可攜帶具體業務負責同志),特此通知。

據了解,特斯拉高層於3月17、18日拜訪蘇州進行實地考察,但《第一電動網》對此事進行深入訪談,包括蘇州商務局等相關人士均沒有正面回應。特斯拉近來一直被傳言將在中國尋找合資夥伴並設立工廠,也有許多中國公司主動表達合作意願,不過特斯拉對於在中國設廠一事一直沒有明確的回答。

台灣特斯拉汽車公司日前正式登記成案,特斯拉在中國的動向也值得關切。

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

【其他文章推薦】

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

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

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

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

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

日本黑科技!豐橋大學研發無電池電動車

電動車沒有電池也能行駛?日本豐橋科技大學成功開發世界首輛無電池電動車,並於3月18日在日本愛知縣豐橋市公開試車。這款電動車採用特殊輪胎,行駛在特別設計的電氣化車道上,可直接透過輪胎供電,作為電動車的驅動電力。

這個無電池電動車計畫由豐橋技術科學大學汽車城市研究中心(Vehicle City Research Center)主任大平孝教授主持,與大成建設公司共同研究。研究人員在校園內的柏油路下埋入兩條通電鋼板來打造電氣化道路,為電動車輸送高功率電力;同時,汽車的輪胎也是特殊設計,可直接接收電氣化道路傳來的電力並用以行駛。豐橋科技大學指出,本次實驗採用了13.56MHz、輸出功率5kW的電流。

試車時,這款無電池電動車共以時速10公里的速度移動了30公尺。大平孝表示,這是全球第一筆無電池電動車上路行駛的紀錄。

電動車的續航力一直是一大技術難關。受限於電池容量等問題,一般的電動車普遍有行駛距離偏短、充電所需時間較長的困擾。若這個無電池電動車技術成熟,則這些問題未來都將不再是問題,電動車也能輕鬆長距離行駛。

大平孝表示,未來研究的方向將是電氣化道路的功能改良與成本降低,同時也會加緊研究安全性與標準化等相關問題。下一步將嘗試在汽車專用道路上進行實地測試。這項技術也能應用於工廠的貨物運送或物流技術等,具商業化的應用的領域。

(照片來源:豐橋科技大學)

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

【其他文章推薦】

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

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

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

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

小三通物流營運型態?

※快速運回,大陸空運推薦?

整數的表示與編碼

補一下CSAPP的筆記

計算機中整數的表示與編碼

計算機中的整數主要包括兩種:有符號數與無符號數。

無符號數的編碼

其中有符號數的表示方法與傳統二進制一致。
假設有一個整數數據類型有w位。我們可以將位向量寫成

在這個編碼中,每個xi都取值為0或1。我們用一個函數來表示B2Uw來表示:

無符號數的編碼方式實際上與我們所知道的二進制編碼方式是一致的。唯一要注意的是無符號數的編碼具有唯一性,也就是說一個数字只能有一個無符號數編碼。這是因為B2Uw是一個雙射。

有符號數的編碼

有符號數的編碼主要有三種方式:原碼、補碼與反碼。我曾經寫過一篇博客來進行探究,這裏不贅述。

需要說明的是:補碼也具有唯一性,原碼與反碼不具備這種性質,因為0在原碼與反碼中有兩種解釋。

有符號數與無符號數之間的轉換

有符號數轉無符號數

C語言中提供了在不同數據類型中做強制類型轉換的方法,對於無符號整數與有符號整數之間的轉換方式,大多數系統上默認的是底層的位不變,由此我們能推出有符號數與無符號數之間的轉換。
關於這些的轉換的的過程和原理,在此不贅述。這裏直接給出公式:
一個數的編碼方式從無符號編碼(補碼)轉換為有符號編碼后的數值公式為:

如果有符號數的真值小於0那麼,把真值加上2w即為其無符號真值,如果真值大於0,那麼不變。
我們用一段C語言代碼舉例:

#include<stdio.h>
#include<stdlib.h>
#include<limits.h>
int main(void)
{
    int i = -1;
    unsigned int j = (unsigned int)i;
    printf("%u\n", j);
    printf("%u\n", UINT_MAX);
    system("pause");
}

VS2017下的運行結果:

數據類型int的大小為8字節,32位,把-1轉換成無符號數需要加上232,結果為232-1,正好為無符號數編碼的最大值,所以與UINT_MAX的值一致。

無符號數轉有符號數

直接給出公式:

C語言代碼測試實例:

#include<stdio.h>
#include<stdlib.h>
#include<limits.h>
int main(void)
{
    unsigned int i = UINT_MAX;
    int j=(int)i;
    printf("%d", j);
    system("pause");
}

VS2017下的運行結果:

需要說明的是,在VS2017的環境下,上面兩個程序經過測試即使不使用強制類型轉換也可以得到正確的結果,其一是C語言中如果發現左右兩邊數據類型不一致會自動把數據往左邊的類型轉換,其二是,printf中的格式說明符也會自動執行類型轉換,這裏使用強制類型轉換隻是為了讓轉換看起來更加清晰。

無符號整數與有符號整數互相轉換可能遇到的問題

由於C語言對同時包含有符號和無符號數表達式的這種處理方式,出現了一些奇特的行為。當執行一個運算時如果它的一個運算數是有符號的而另一個是無符號的,那麼C語言會隱式地把有符號參數強制類型轉換為無符號,並假設這兩個數都是非負的
對於 這樣的關係運算符來說,它會導致非直觀的結果。我們同樣用一個C語言程序來作為測試:

#include<stdio.h>
#include<stdlib.h>
int main(void)
{
    printf("%d", -1 < 0U);
    printf("%d",(unsigned)-1 > -2);
}

VS2017運行結果:

第一個表達式中,由於0是無符號數,所以-1默認變成無符號數,即為232-1,這個數必然比0要大。所以第一個表達式為假。
第二個表達式中,通過把-1強制轉換成無符號數,-1變為232-1,-2變為232-2,所以第二個表達式為真。

擴展一個数字的位表示

有時我們會把一個佔用空間較小的數據類型轉換為佔用空間較大的數據類型(如果把佔用空間較大的數據類型轉換為佔用空間較小的數據類型,可能會丟失數據,我們一般不推薦這麼做)。

無符號數的零擴展

定義寬度為w位的位向量:

和寬度為w’的位向量:

其中w’>w。則:

要將一個無符號數轉換為一個更大的無符號數數據類型,我們只要簡單的在前面加上足夠的0即可,這種運算被稱為零擴展。

有符號數的符號拓展

定義寬度為w位的位向量:

和寬度為w’的位向量:

其中w’>w。則:

要將補碼数字轉換為一個更大的數據類型,可以執行一個符號擴展(sign-extension),在前面添加最高有效位的值。
具體證明略。

值得注意的點:在C語言中,把類型不同、大小不同的兩個數據類型相互轉換,先改變數據類型的大小,然後在執行類型轉換。
比如說:在C語言中,把一個short類型的變量轉換為unsigned類型的變量,我們要先把short類型的變量擴展到8個字節,然後再執行有符號數到無符號數的轉換。

截斷数字

一些特殊情況下,儘管這樣做會帶來風險,但我們仍然有時候會需要把一個高位的數據類轉換為低位的數據類型,這時候我們就需要截斷這個数字。

無符號數的截斷

定義寬度為w位的位向量:

而它截斷為k位的結果為:

令x=B2U_w(\vec x),x’=B2U_(\vec x’),則x’=x mod 2^k。
截斷為k為實際上就是對原數的真值用2^k取模。具體證明過程略。

有符號數的截斷

要理解有符號數的截斷,我們首先要明白,無論是有符號數還是無符號數真正區別他們的不是他們的真值,而是他們的編碼方式,實際上無論是有符號數,還是無符號數,在內存中都表示為串二進制數,有了編碼對他們真值的解釋,他們才能表示不同的數據。
我們都知道,截斷實際上就是截去前面冗餘的位,只留下我們需要的位,既然無符號數和有符號數在內存中表示的方法實際上都是一串二進制數,我們為什麼不可以把一個有符號數的位模式,看做是無符號數的編碼,用無符號數的方式將其截斷後得到的真值,再用把無符號數轉換為有符號數,最終得到將有符號數階段的真值。
總而言之,有符號數編碼的截斷結果是:

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

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

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

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

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

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

心裏有點樹

why 樹形結構

  • 順序存儲

順序存儲的特點是各個存儲單位在邏輯和物理內存上都是相鄰的,典型的就是代表就是數組,物理地址相鄰因此我們可以通過下標很快的檢索出一個元素

我們想往數組中添加一個元素最快的方式就是往它的尾部添加.如果往頭部添加元素的話,效率就很低,因為需要將從第一個元素開始依次往後移動一位,這樣就能空出第一位的元素,然後才能我們指定的數據插入到第一個的位置上

  • 鏈式存儲

鏈式存儲的特點是,各個節點之間邏輯是相鄰的,但是物理存儲上不相鄰,每一個節點都存放一個指針或者是引用用來指向它的前驅或者後繼節點, 因此我們想插入或者刪除一個元素時速度就會很塊,只需要變動一下指針的指向就行

但是對鏈表來說查找是很慢的, 因此對任意一個節點來書,他只知道自己的下一個節點或者是上一個節點在哪裡,再多的他就不不知道了,因此需要從頭結點開始遍歷…

樹型存儲結構有很多種,比如什麼二叉樹,滿二叉樹,紅黑樹,B樹等, 對於樹形結構來說,它會相對中和鏈式存儲結構和順序存儲結構的優缺點 (其中二叉排序樹最能直接的體會出樹中和鏈式存儲和線性存儲的特性,可以通過右邊的導航先去看看二叉排序樹)

樹的概述

如上圖是一個二叉樹, 當然樹還能有三叉,四叉等等…

  • 根節點: 最頂上的節點 即A
    層: 根節點在第一層 BE在第二層
    高度: 最大的層數
    森林: 多個樹的組合
    權: 節點上的值 如根節點的權是 A
    恭弘=叶 恭弘子節點: 下層上的節點是上一層的恭弘=叶 恭弘子節點
    雙親節點: 上層的節點是下層的節點的雙親節點(單個節點又是爸又是媽)
    路徑: 找到C的路徑是 A-B-C
    度: 就是直接子節點的個數

普通二叉樹

  • 什麼是二叉樹?

顧名思義就是度最大為2的樹就是二叉樹.而且對二叉樹來說,是嚴格區分左子樹和右子樹的,看上圖,雖然兩個樹的根節點都是1,但是他們的左右子樹不同,因此他們並不是相同的樹

  • 什麼是滿二叉樹?

像上圖這樣,所有的恭弘=叶 恭弘子節點都在最後一層,所有的且除了最後一層其他層的節點都有兩個子節點

二叉樹的全部節點計算公式是 2^n-1 , n是層數

  • 什麼是完全二叉樹?

像上圖這樣, 所有的恭弘=叶 恭弘子點都在最後一層或者是倒數第二層, 並且從左往右數是連續的

java&二叉樹

  • 封裝二叉樹節點
public class TreeNode {
    // 權
    private int value;
    // 左節點
    private TreeNode leftNode;
    // 右節點
    private TreeNode rightNode;
}
  • 封裝二叉樹
public class BinaryTree {
    TreeNode root;

    public void setRoot(TreeNode root) {
        this.root = root;
    }

    public TreeNode getRoot() {
        return this.getRoot();
    }
}

遍歷

像這樣一顆二叉樹,通過不同的書序遍歷會得到不同的結果

前中后的順序說的是root節點的順序,前序的話就是先遍歷父節點, 中序就是左父右 後續就是左右父

  • 前序遍歷
 public void frontShow() {
        System.out.println(this.value);
        if (leftNode != null)
            leftNode.frontShow();

        if (rightNode != null)
            rightNode.frontShow();
    }
  • 中序遍歷
    public void middleShow() {
        if (leftNode != null)
            leftNode.middleShow();

        System.out.println(value);

        if (rightNode != null)
            rightNode.middleShow();
    }
  • 後續遍歷
    public void backShow() {
        if (leftNode != null)
            leftNode.backShow();

        if (rightNode != null)
            rightNode.backShow();

        System.out.println(value);
    }

查找

其實有了上面三種遍歷的方式, 查找自然存在三種, 一遍遍歷一遍查找

    public TreeNode frontSeach(int num) {
        TreeNode node = null;
        // 當前節點不為空,返回當前節點
        if (num == this.value) {
            return this;
        } else {
            // 查找左節點
            if (leftNode != null) {
                node = leftNode.frontSeach(num);
            }
            if (node != null)
                return node;
            // 查找右節點
            if (rightNode != null)
                node = rightNode.frontSeach(num);
        }
        return node;
    }

刪除節點

刪除節點也是, 不考慮特別複雜的情況, 刪除節點就有兩種情況, 第一種要刪除的節點就是根節點, 那麼讓根節點=null就ok, 第二種情況要刪除的節點不是根節點,就處理它的左右節點, 左右節點還不是需要刪除的元素的話那麼就得遞歸循環這個過程

   // 先判斷是否是根節點,在調用如下方法
    
   public void deleteNode(int i) {
        TreeNode parent = this;
        // 處理左樹
        if (parent.leftNode!=null&&parent.leftNode.value==i){
            parent.leftNode=null;
            return;
        }
        // 處理左樹
        if (parent.rightNode!=null&&parent.rightNode.value==i){
            parent.rightNode=null;
            return;
        }
        // 遞歸-重置父節點
        parent=leftNode;
        if (parent!=null)
            parent.deleteNode(i);
        // 遞歸-重置父節點
        parent=rightNode;
        if (parent!=null)
            parent.deleteNode(i);

    }

順序存儲二叉樹

文章一開始剛說了, 順序存儲的數據結構的典型代表就是數組, 就像這樣

[1,2,3,4,5,6,7]

什麼是順序存儲的二叉樹呢? 其實就是將上面的數組看成了一顆樹,就像下圖這樣

數組轉換成二叉樹是有規律的, 這個規律就體現在他們的 下標的關聯上, 比如我們想找2節點的左子節點的下標就是 2*n -1 = 3 , 於是我們從數組中下標為3的位置取出4來

  • 第n個元素的左子節點是 2n-1
  • 第n個元素的右子節點是 2n-2
  • 第n個元素的父節點是 (n-1)/2

  • 遍歷順序存儲的二叉樹

    public void frontShow(int start){
        if (data==null||data.length==0){
            return;
        }
        // 遍歷當前節點
        System.out.println(data[start]);
        // 遍歷左樹
        if (2*start+1<data.length)
            frontShow(2*start+1);
        // 遍歷右樹
        if (2*start+2<data.length)
            frontShow(2*start+2);
    }

線索二叉樹

假設我們有下面的二叉樹, 然後我們可以使用中序遍歷它, 中序遍歷的結果是 4,2,5,1,3,6 但是很快我們就發現了兩個問題, 啥問題呢?

  • 問題1: 雖然可以正確的遍歷出 4,2,5,1,3,6 , 但是當我們遍歷到2時, 我們是不知道2的前一個是誰的,(哪怕我們剛才遍歷到了它的前一個節點就是4)

  • 問題2: node4,5,6,3的左右節點的引用存在空閑的情況

針對這個現狀做出了改進就是線索化二叉樹, 它可以充分利用各個節點中剩餘的node這個現狀…線索化后如下圖

  • 如果這個節點的右節點為空,我們就讓它讓它指向自己的後繼節點, 例如上圖的紅線
  • 如何節點的左節點為空, 就讓這個空閑的節點指向它的前驅節點,例如上圖的藍色線

這樣的話, 就實現了任意獲取出一個節點我們都能直接的得知它的前驅節點后後繼節點到底是誰

java&中序化二叉樹;

思路: 按照原來中序遍歷樹的思路,對樹進行中序遍歷,一路遞歸到4這個節點, 檢查到它的左節點為空,就將他的左節點指向它的前驅節點, 可是4本來就是最前的節點,故4這個節點的左節點自然指向了null

然後看它的右節點也為空,於是將他的右節點指向它的後繼節點, 可是這時依然沒獲取到2節點的引用怎麼辦呢? 於是先找個變量將4節點臨時存起來, 再往後遞歸,等遞歸到2節點時,取出臨時變量的4節點, 4節點.setRightNode(2節點)

然後重複這個過程

    // 臨時保存上一個節點
    private TreeNode preNode;

    // 中序線索化二叉樹
    void threadNode(TreeNode node) {
        if (node == null)
            return;

        // 處理左邊
        threadNode(node.getLeftNode());

        // 左節點為空,說明沒有左子節點, 讓這個空出的左節點指向它的上一個節點
        if (node.getLeftNode() == null) {
            // 指向上一個節點
            node.setLeftNode(preNode);
            // 標識節點的類型
            node.setLeftType(1);
        }

        // 處理前驅節點的右指針
        // 比如現在遍歷到了1, 1的上一個節點是5, 5的右邊空着了, 於是讓5的有節點指向1
        if (preNode != null && preNode.getRightNode() == null) {
            preNode.setRightNode(node);
            preNode.setRightType(1);
        }

        // 每次遞歸調用一次這個方法就更新前驅節點
        preNode = node;
        // 處理右邊
        threadNode(node.getRightNode());
    }

遍歷二叉樹

    public void threadIterator() {
        TreeNode node = root;
        while (node != null) {
            // 循環找
            while (node.getLeftType() == 0)
                node = node.getLeftNode();
            // 打印當前節點
            System.out.println(node.getValue());
            // 如果當前的節點的右type=1說明它有指針指向自己的前一個節點
            // 比如現在位置是4, 通過下面的代碼可以讓node=2
            while (node.getRightType() == 1) {
                node = node.getRightNode();
                System.out.println(node.getValue());
            }

            // 替換遍歷的節點, 可以讓 node從2指向 5, 或者從3指向1
            node = node.getRightNode();
        }

    }

赫夫曼樹(最優二叉樹)

定義: 什麼是赫夫曼樹

赫夫曼樹又稱為最優二叉樹

定義: 在N個帶權的恭弘=叶 恭弘子節點的所組成的所有二叉樹中,如果你能找出那個帶權路徑最小的二叉樹,他就是赫夫曼樹

一說起來赫夫曼樹,其實我們可以只關心它的恭弘=叶 恭弘子節點, 權, 路徑這三個要素

  • 什麼是恭弘=叶 恭弘子節點的帶權路徑?

所謂權,其實就是節點的值, 比如上圖中node4的權是8 , node5的權是6 ,node3的權是1, 而且我們只關心恭弘=叶 恭弘子節點的權

啥是帶權路徑呢? 比如上圖中 node4的帶權路徑是 1-2-4

  • 樹的帶權路徑長度(weight path length) 簡稱 WPL

其實就是這個樹所有的恭弘=叶 恭弘子節點的帶權路徑長度之和,

計算左樹的WPL =2*8+2*6+1*1 = 29

計算左樹的WPL =2*1+2*6+1*8 = 22

總結: 權值越大的節點,離根節點越近的節點是最優二叉樹

### 實戰: 將數組轉換為赫夫曼樹

  • 思路:

假設我們現在已經有了數組 [3,5,7,8,11,14,23,29], 如何將這個數組轉換成赫夫曼樹呢?

取出這裏最小的node3 和 倒數第二小的node5 ,構建成新的樹, 新樹的根節點是 node3,5的權值之和, 將構建完成的樹放回到原數組中

重複這個過程, 將最小的node7,node8取出,構建新樹, 同樣新樹的權重就是 node7,8的權重之和, 再將構建完成的樹放回到原數組中

如此往複,最終得到的樹就是huffman樹

  • java實現:

封裝TreeNode, 看上面的過程可以看到,需要比較權重的大小,因此重寫它的compareTo方法

public class TreeNode implements Comparable{
    // 權
    private int value;
    private TreeNode leftNode;
    private TreeNode rightNode;

    @Override
    public int compareTo(Object o) {
        TreeNode node = (TreeNode) o;
        return this.value-node.value;
    }

構建赫夫曼樹, 思路就是上圖的過程, 將數組中的各個元素轉換成Node. 然後存放在List容器中,每輪構建新樹時需要排序, 當集合中僅剩下一個節點,也就是根節點時完成樹的構建

    // 創建赫夫曼樹
    private static TreeNode buildHuffmanTree(int[] arr) {
        // 創建一個集合,存放將arr轉換成的二叉樹
        ArrayList<TreeNode> list = new ArrayList<>();
        for (int i : arr) {
            list.add(new TreeNode(i));
        }
        // 開始循環, 當集合中只剩下一棵樹時
        while (list.size() > 1) {
            // 排序
            Collections.sort(list);
            // 取出權值最小的數
            TreeNode leftNode = list.get(list.size() - 1);
            // 取出權值次要小的數
            TreeNode rightNode = list.get(list.size() - 2);
            // 移除取出的兩棵樹
            list.remove(leftNode);
            list.remove(rightNode);

            // 創建新的樹根節點
            TreeNode parentNode = new TreeNode(leftNode.getValue() + rightNode.getValue(), leftNode, rightNode);
            // 將新樹放到原樹的集合中
            list.add(parentNode);
        }
        return list.get(0);
    }

實戰: 赫夫曼樹與數據壓縮

通過上面的介紹我們能直觀的看出來,赫夫曼樹很顯眼的特徵就是它是各個節點能組成的樹中,那顆WPL,帶權路徑長度最短的樹, 利用這條性質常用在數據壓縮領域, 即我們將現有的數據構建成一個赫夫曼樹, 其中出現次數越多的字符,就越靠近根節點, 經過這樣的處理, 就能用最短的方式表示出原有字符

假設我們有這條消息can you can a can as a canner can a can.

數據對計算機來說不過是0-1這樣的数字, 我們看看將上面的字符轉換成01這樣的二進制數它長什麼樣子

1. 將原字符串的每一個char強轉換成 byte == ASCII
99 97 110 32 121 111 117 32 99 97 110 32 97 32 99 97 110 32 97 115 32 97 32 99 97 110 110 101 114 32 99 97 110 32 97 32 99 97 110
    
2. 將byte toBinaryString 轉換成01串如下:
1100011110000111011101000001111001110111111101011
0000011000111100001110111010000011000011000001100
0111100001110111010000011000011110011100000110000
1100000110001111000011101110100000110001111000011
1011101101110110010111100101000001100011110000111
011101000001100001100000110001111000011101110101110

也就是說,如果我們不對其進行壓縮時, 它將會轉換成上面那一大坨在網絡上進行傳輸

使用赫夫曼進行編碼:

思路: 我們將can you can a can as a canner can a can. 中的每一個符號,包括 點 空格,全部封裝進TreeNode

TreeNode中屬性如下: 包含權重: 也就是字符出現的次數, 包含data: 字符本身

public class TreeNode implements Comparable{
    // 存放權重就是字符出現的次數
    private int weight;
    // 存放英文數值
    private Byte data; //
    private TreeNode leftNode;
    private TreeNode rightNode;

封裝完成后, 按照權重的大小倒序排序,各個節點長成這樣:

a:11  :11   n:8   c:7   o:1  .:1  y:1   e:1  u:1  s:1  r:1  

將赫夫曼樹畫出來長這樣:

特徵,我們讓左側的路徑上的值是0, 右邊是1. 因此通過這個赫夫曼樹其實我們可以得到一張赫夫曼編碼錶,

比如像下面這樣:

n: 00
 : 01
a: 10
c: 111
// 每一個字符的編碼就是從根節點到它的路徑

有了這樣編碼錶, 下一步就是對數據進行編碼, 怎麼編碼呢? 不就是做一下替換嗎? 我們現在開始循環遍歷一開始的字符串, 挨個取出裏面的字符, 比如我們取出第一個字符是c, 拿着c來查詢這個表發現,c的編碼是111,於是我們將c替換成111, 遍歷到第二個字符是a, 拿着a查詢表,發現a的值是10, 於是我們將a替換成10, 重複這個過程, 最終我們得到的01串明顯比原來短很多

怎麼完成解碼呢? 解碼也不複雜, 前提也是我們得獲取到huffman編碼錶, 使用前綴匹配法, 比如我們現在接收到了

1111000xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

使用前綴就是先取出1 去查查編碼錶有沒有這個數? 有的話就返回對應的字符, 沒有的話就用11再去匹配

大家可以看看上面的那顆霍夫曼樹, 所有的data都在恭弘=叶 恭弘子節點上,所以使用前綴匹配完全可以,絕對不會出現重複的情況

  • 使用java實現這個過程

思路概覽:

  1. 將原生的字節數組轉化成一個個的TreeNode
  2. 取出所有的TreeNode封裝成赫夫曼樹
  3. 通過赫夫曼樹踢去出赫夫曼編碼錶
  4. 使用這個編碼錶進行編碼
  5. 解碼

  private static byte[] huffmanZip(byte[] bytes) {
        // 先統計每個byte出現的次數,放入集合中
        List<TreeNode> treeNodes = buildNodes(bytes);
        // 創建赫夫曼樹
        TreeNode node = createHuffmanTree(treeNodes);
        // 創建huffman編碼錶
        Map<Byte, String> codes = createHuffmanCodeTable(node);
        // 編碼, 將每一個byte替換成huffman編碼錶中的V
        byte[] encodeBytes = encodeHuffmanByte(bytes, codes);
        
        // 使用huffman編碼進行解碼
        byte[] decodeBytes = decode(encodeBytes);
        return decodeBytes;
    }

將原生的byte數組,封裝成一個個的TreeNode節點,保存在一個容器中,並且記錄下這個節點出現的次數, 因此我們需要將出現次數多的節點靠近根節點

    /**
     * 將byte轉換成node集合
     *
     * @param bytes
     * @return
     */
    private static List<TreeNode> buildNodes(byte[] bytes) {
        ArrayList<TreeNode> list = new ArrayList<>();
        HashMap<Byte, Integer> countMap = new HashMap<>();
        // 統計每一個節點的出現的次數
        for (byte aByte : bytes) {
            Integer integer = countMap.get(aByte);
            if (integer == null) {
                countMap.put(aByte, 1);
            } else {
                countMap.put(aByte, integer + 1);
            }
        }
        // 將k-v轉化成node
        countMap.forEach((k, v) -> {
            list.add(new TreeNode(v, k));
        });
        return list;
    }

構建赫夫曼樹

  /**
     * 創建huffman樹
     *
     * @param treeNodes
     * @return
     */
    private static TreeNode createHuffmanTree(List<TreeNode> treeNodes) {
        // 開始循環, 當集合中只剩下一棵樹時
        while (treeNodes.size() > 1) {
            // 排序
            Collections.sort(treeNodes);
            // 取出權值最小的數
            TreeNode leftNode = treeNodes.get(treeNodes.size() - 1);
            // 取出權值次要小的數
            TreeNode rightNode = treeNodes.get(treeNodes.size() - 2);
            // 移除取出的兩棵樹
            treeNodes.remove(leftNode);
            treeNodes.remove(rightNode);

            // 創建新的樹根節點
            TreeNode parentNode = new TreeNode(leftNode.getWeight() + rightNode.getWeight(), leftNode, rightNode);
            // 將新樹放到原樹的集合中
            treeNodes.add(parentNode);
        }
        return treeNodes.get(0);
    }

從赫夫曼樹中提取出編碼錶, 思路: 下面是完了個遞歸, 我們規定好左樹是0,右邊是1, 通過一個SpringBuilder, 每次迭代都記錄下原來走過的路徑,當判斷到它的data不為空時,說明他就是恭弘=叶 恭弘子節點,立即保存這個節點曾經走過的路徑,保存在哪裡呢? 保存在一個map中, Key就是byte value就是走過的路徑

  static StringBuilder stringBuilder = new StringBuilder();
  static Map<Byte, String> huffCode = new HashMap<>();

    /**
     * 創建huffman便編碼錶
     *
     * @param node
     * @return
     */
    private static Map<Byte, String> createHuffmanCodeTable(TreeNode node) {
        if (node == null)
            return null;
        getCodes(node.getLeftNode(), "0", stringBuilder);
        getCodes(node.getRightNode(), "1", stringBuilder);
        return huffCode;
    }

    /**
     * 根據node, 獲取編碼
     *
     * @param node
     * @param code
     * @param stringBuilder
     */
    private static void getCodes(TreeNode node, String code, StringBuilder stringBuilder) {
        StringBuilder sb = new StringBuilder(stringBuilder);
        sb.append(code);
        // 如果節點的data為空,說明根本不是恭弘=叶 恭弘子節點,接着遞歸
        if (node.getData() == null) {
            getCodes(node.getLeftNode(), "0", sb);
            getCodes(node.getRightNode(), "1", sb);
        } else {
            // 如果是恭弘=叶 恭弘子節點,就記錄它的data和路徑
            huffCode.put(node.getData(), sb.toString());
        }
    }

根據赫夫曼編碼錶進行編碼:

思路:

舉個例子: 比如,原byte數組中的一個需要編碼的字節是a

a的ASCII==97

97正常轉成二進制的01串就是 0110 0001

但是現在我們有了編碼錶,就能根據97從編碼錶中取出編碼: 10

換句話說,上面 0110 0001 和 10 地位相同

若干個需要編碼的數append在一起,於是我們就有了一個比原來短一些的01串, 但是問題來了,到這裏就結束了嗎? 我們是將這些01串轉換成String, 在getBytes()返回出去嗎? 其實不是的,因為我們還需要進行解碼,你想想解碼不得編碼map中往外取值? 取值不得有key? 我們如果在這裏將這個01串的byte數組直接返回出去了,再按照什麼樣的方式將這個byte[]轉換成String串呢? ,因為我們要從這個String串中解析出key

然後這裏我們進行約定, 將現在得到的01串按照每8位為一組轉換成int數, 再將這個int強轉成byte, 解碼的時候我們就知道了.就按照8位一組進行解碼. 解析出來數組再轉換成01串,我們就重新拿到了這個編碼后的01串,它是個String串

每遇到8個0或者1,就將它強轉成Int, 再強轉成type, 經過這樣的轉換可能會出現負數,因此01串的最前面有個符號位,1表示負數

比如說: 如果你打印一下面代碼中的encodeByte,你會發現打印的第一個數是-23, 這個-23被保存在新創建的byte數組的第一個位置上, 後續解碼時,就從這個byte數組中的第一個位置上獲取出這個-23, 將它轉換成01二進制串

怎麼轉換呢? 比如不是-23, 而是-1
真值 1
原碼:1,0001
補碼: 2^(4+1) +1 = 100000 + (-1) = 1,1111
我們獲取到的結果就是1111
 /**
     * 進行編碼
     *
     * @param bytes
     * @param codes
     * @return
     */
    private static byte[] encodeHuffmanByte(byte[] bytes, Map<Byte, String> codes) {
        StringBuilder builder = new StringBuilder();
        for (byte aByte : bytes) {
            builder.append(codes.get(aByte));
        }

        // 將這些byte按照每8位一組進行編碼
        int length = 0;
        if (builder.length() % 8 == 0) {
            length = builder.length() / 8;
        } else {
            length = builder.length() / 8 + 1;
        }
        // 用於存儲壓縮后的byte
        byte[] resultByte = new byte[length];
        // 記錄新byte的位置
        int index = 0;
        // 遍歷新得到的串
        for (int i = 0; i < builder.length(); i += 8) {
            String str = null;
            if (i + 8 > builder.length()) {
                str = builder.substring(i);
            } else {
                str = builder.substring(i, i + 8);
            }
            // 將八位的二進制轉換成byte
            // 這裏出現負數了....  涉及到補碼的問題
            byte encodeByte = (byte) Integer.parseInt(str, 2);
            // 存儲起來
            resultByte[index] = encodeByte;
            index++;
        }
        return resultByte;
    }

解碼: 前面我們知道了,約定是按照8位轉換成的int 再轉換成type[] , 現在按照這個約定,反向轉換出我們一開始的01串

/**
     * 按照指定的赫夫曼編碼錶進行解碼
     *
     * @param encodeBytes
     * @return
     */
    private static byte[] decode(byte[] encodeBytes) {
        List<Byte> list = new ArrayList();
        StringBuilder builder = new StringBuilder();
        for (byte encodeByte : encodeBytes) {
            // 判斷是否是最後一個,如果是最後一次不用用0補全, 因此最後一位本來就不夠8位
            boolean flag = encodeByte == encodeBytes[encodeBytes.length - 1];
            String s = byteToBitStr(!flag, encodeByte);
            builder.append(s);
        }
        // 調換編碼錶的k-v
        Map<String, Byte> map = new HashMap<>();
        huffCode.forEach((k, v) -> {
            map.put(v, k);
        });
        // 處理字符串
        for (int i = 0; i < builder.length(); ) {
            int count = 1;
            boolean flag = true;
            Byte b = null;
            while (flag){
                String key = builder.substring(i,i+count);
                b=map.get(key);
                if (b==null){
                    count++;
                }else {
                    flag=false;
                }
            }
            list.add(b);
            i+=count;
        }

        // 將list轉數組
        byte[] bytes = new byte[list.size()];
        int i=0;
        for (Byte aByte : list) {
            bytes[i]=aByte;
            i++;
        }
        return bytes;
    }

    /**
     * 將byte轉換成二進制的String
     *
     * @param b
     * @return
     */
    public static String byteToBitStr(boolean flag, byte b) {
        /**
         * 目標: 全部保留八位.正數前面就補零, 負數前面補1
         * 為什麼選256呢?  因為我們前面約定好了, 按照8位進行分隔的
         * 256的二進製表示是  1 0000 0000
         * 假設我們現在是 1
         * 計算              1 0000 0000
         *               或  0 0000 0001
         *              ----------------------
         *                   1 0000 0001
         *                   結果截取8位就是 0000 0001
         *
         * 假設我們現在是   -1
         * 轉換成二進制:    1111 1111 1111 1111 1111 1111 1111 1111
         *
         * 計算                            1 0000 0000
         * 或  1111 1111 1111 1111 1111 1111 1111 1111
         *              ----------------------
         *                        1 1111 1111
         *                   結果截取8位就是 1111 1111
         *
         *
         */
        int temp = b;
        if (flag) {
            temp |= 256;
        }
        String str = Integer.toBinaryString(temp);
        if (flag) {
            return str.substring(str.length() - 8);
        } else {
            return str;
        }

    }

二叉排序樹

二叉排序樹, 又叫二叉搜索樹 , BST (Binary Search Tree)

  • 線性存儲和鏈式存儲的優缺點

比如我們有一個數組 [7,3,10,12,5,1,9]

雖然我們可以直接取出下標為幾的元素,但是卻不能直接取出值為幾的元素, 比如,我們如果想取出值為9的元素的話,就得先去遍歷這個數組, 然後挨個看看當前位置的數是不是9 , 就這個例子來說我們得找7次

假設我們手裡的數組已經是一個有序數組了 [1,3,5,7,9,11,12]

我們可以通過二分法快速的查找到想要的元素,但是對它依然是數組,如果想往第一個位置上插入元素還是需要把從第一個位置開始的元素,依次往後挪. 才能空出第一個位置,把新值放進去

假設我們將這一行數轉換成鏈式存儲, 確實添加, 刪除變的異常方便, 但是查找還是慢, 不管是查詢誰, 都得從第一個開始往後遍歷

  • 我們的主角: 二叉搜索樹

二叉排序樹有如下的特點:

  • 對於二叉排序樹中的任意一個非恭弘=叶 恭弘子節點都要求他的左節點小於自己, 右節點大於自己
  • 空樹也是二叉排序樹

將上面的無序的數組轉換成二叉排序樹長成下圖這樣

如果我們按照中序遍歷的話結果是: 1 3 5 7 9 11 12 , 正好是從小到大完成排序

再看他的特徵: 如果我們想查找12 , 很簡單 7-10-12 , 如果我們想插入也很簡單,它有鏈表的特性

java&二叉排序樹

封裝Node和Tree

// tree
public class BinarySortTree {
    Node root;
}

// node
public class Node {
    private int value;
    private Node leftNode;
    private Node rightNode;
}

構建一顆二叉排序樹, 思路是啥呢? 如果沒有根節點的話,直接返回,如果存在根節點, 就調用根節點的方法,將新的node添加到根節點上, 假設我們現在遍歷到的節點是NodeA. 新添加的節點是NodeB, 既然想添加就得比較一下NodeA和NodeB的值的大小, 將如果NodeB的值小於NodeA,就添加在NodeA的右邊, 反之就添加在NodeA的左邊

-----------BinarySortTree.class--------------- 
/**
     * 向二叉排序樹中添加節點
     */
    public void add(Node node) {
        if (root == null) {
            root = node;
        } else {
            root.add(node);
        }
    }

-------------Node.class------------
/**
     * 添加節點
     *
     * @param node
     */
    public void add(Node node) {
        if (node == null)
            return;
        //判斷需要添加的節點的值比傳遞進來的節點的值大還是小
        // 添加的節點小於當前節點的值
        if (node.value < this.value) {
            if (this.leftNode == null) {
                this.leftNode = node;
            } else {
                this.leftNode.add(node);
            }
        } else {
            if (this.rightNode == null) {
                this.rightNode = node;
            } else {
                this.rightNode.add(node);
            }
        }
    }

刪除一個節點

刪除一節點如如下幾種情況, 但是無論是哪種情況,我們都的保存當前節點的父節點, 通過他的父節點對應節點=null實現節點的刪除

情況1: 如圖

這是最好處理的情況, 就是說需要刪除的元素就是單個的子節點

情況2: 如圖

這種情況也不麻煩,我們讓當前比如我們想上刪除上圖中的3號節點, 我們首先保存下node3的父節點 node7, 刪除node3時發現node3有一個子節點,於是我們讓 node7 的 leftNode = node3

情況3: 如圖

比如我們想刪除7, 但是7這個節點還有一個子樹 按照中序遍歷這個樹的順序是 1,3,5,7,9,11,13, 想刪除7的話,其實

  1. 臨時存儲node9
  2. 刪除node9
  3. 用臨時存儲的node9替換node7

如果node9還有右節點怎麼辦呢?

  1. 臨時保存node9
  2. 刪除node9
  3. 讓node9的右節點替換node9
  4. 讓臨時存儲的node9替換node7
/**
     * 刪除一個節點
     *
     * @param value
     * @return
     */
    public void delete(int value) {
        if (root == null) {
            return;
        } else {
            // 找到這個節點
            Node node = midleSearch(value);
            if (node == null)
                return;
            // 找到他的父節點
            Node parentNode = searchParent(value);

            // todo 當前節點是恭弘=叶 恭弘子節點
            if (node.getLeftNode() == null && node.getRightNode() == null) {
                if (parentNode.getLeftNode().getValue() == value) {
                    parentNode.setLeftNode(null);
                } else {
                    parentNode.setRightNode(null);
                }
                // todo 要刪除的節點存在兩個子節點
            } else if (node.getLeftNode() != null && node.getRightNode() != null) {
                // 假設就是刪除7
                //1. 找到右子樹中最小的節點,保存它的值,然後刪除他
                int minValue = deleteMin(node.getRightNode());
                //2.替換被刪除的節點值
                node.setValue(minValue);

            } else { // todo 要刪除的節點有一個左子節點或者是右子節點
                // 左邊有節點
                if (node.getLeftNode() != null) {
                    // 要刪除的節點是父節點的左節點
                    if (parentNode.getLeftNode().getValue() == value) {
                        parentNode.setLeftNode(node.getLeftNode());
                    } else {// 要刪除的節點是父節點的右節點
                        parentNode.setRightNode(node.getLeftNode());
                    }
                } else { // 右邊有節點
                    // 要刪除的節點是父節點的右節點
                    if (parentNode.getLeftNode().getValue() == value) {
                        parentNode.setLeftNode(node.getRightNode());
                    } else {// 要刪除的節點是父節點的右節點
                        parentNode.setRightNode(node.getRightNode());
                    }
                }
            }
        }
    }

 /**
     * 刪除並保存以當前點為根節點的樹的最小值節點
     * @param node
     * @return
     */
    private int deleteMin(Node node) {
        // 情況1: 值最小的節點沒有右節點
        // 情況2: 值最小的節點存在右節點
        // 但是下面我們使用delete,原來考慮到了
        while(node.getLeftNode()!=null){
            node=node.getLeftNode();
        }
        delete(node.getValue());
        return node.getValue();
    }

    /**
     * 搜索父節點
     *
     * @param value
     * @return
     */
    public Node searchParent(int value) {
        if (root == null) {
            return null;
        } else {
            return root.searchParent(value);
        }
    }

缺點

二叉排序樹其實對節點權是有要求的, 比如我們的數組就是[1,2,3,4] 那麼畫成平衡二叉樹的話長下面這樣

它不僅沒有二叉排序樹的優點,而且還不如單鏈表的速度快

AVL樹(平衡二叉樹)

定義: 什麼是平衡二叉樹

平衡二叉樹的出現就是為了 解決上面二叉排序樹[1,2,3,4,5,6]這樣成單條鏈的略勢的情況,它要求,每個樹的左子樹和右子樹的高度之差不超過1, 如果不滿足這種情況了,馬上馬對各個節點進行調整,這樣做保證了二叉排序樹的優勢

如何調整

  • 情況1: 對於node1來說, 它的左邊深度0 , 右邊的深度2 , 於是我們將它調整成右邊的樣子
  • 情況2: 在1234的情況下, 添加node5,導致node2不平衡, 進行如下的調整
  • 情況3: 在12345的基礎上添加node6,導致node4不平衡, 對node4進行調整, 其實就和情況1相同了
  • 情況4: 在1234567的情況下,進行添加8. 打破了node5的平衡, 因此進行旋轉

一個通用的旋轉規律

看這個典型的有旋轉的例子

node4的出現,使用node8的平衡被打破, 因此我們需要進行調整, 按照下面的步驟進行調整

下面說的this是根節點node8, 按照下面的步驟在紙上畫一畫就ok

  1. 創建新node, 使新node.value = this.value
  2. 新節點的rightNode = this.rightNode
  3. 新節點的leftNode = this.leftNode.rightNode
  4. this.value = this.LeftNode.value
  5. this.leftNode = this.leftNode .leftNode
  6. this.leftNode = 新創建的node

需要注意的情況:

新添加6使得node8不再平衡,但是如果你按照上面的步驟進行旋轉的話,會得到右邊的結果, 但是右邊的結果中對於node4還是不平衡的,因此需要預處理一下

再進行右旋轉時,提前進行檢驗一下,當前節點的左子樹是否存在右邊比左邊高的情況, 如果右邊比較高的話,就先將這個子樹往左旋轉, 再以node8為根,整體往右旋轉

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

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

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

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

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

小三通物流營運型態?

※快速運回,大陸空運推薦?

越南市場出售活體老鼠野鳥 蒼蠅到處飛環境髒亂

摘錄自2020年03月16日聯合新聞網報導

自從武漢肺炎(COVID-19)爆發至今已三個月,而疫情的起源被指向中國湖北省武漢的華南海鮮市場,當中販賣的各式野味到處混雜,直至疫情爆發後商家才停止販售,整個市場遭封閉消毒。雖然近期野味交易在中國明令禁止,但在越南市場上仍持續進行,有民眾目擊市場內販售著活體鳥類和老鼠,現場環境髒亂,蒼蠅圍繞著野味打轉。

根據英國「太陽報」報導,越南隆安省一處市場仍在販售活體野味,遭販售的鳥類被關在籠子內,腳被綁成一束,翅膀遭折斷,且眼睛遭縫合,痛苦地在市場喊叫,目擊者表示「賣家縫住牠們的眼睛,用膠帶黏住喙,折斷翅膀,拔出羽毛,並使用一個微型儲氣瓶快速地將牠們煮熟」,而他也指出市場內還販售烏龜、蛇、老鼠和水獺等動物,現場環境相當髒亂。

生活環境
國際新聞
越南
野味
活體動物
老鼠

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

【其他文章推薦】

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

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

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

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

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

※試算大陸海運運費!

能源局將在4-6月開展電動汽車充電基礎設施安全專項檢查

4月6日從能源局獲悉,國家能源局綜合司發佈《關於開展電動汽車充電基礎安全專項檢查的通知》,旨在加強電動汽車充電基礎設施安全管理,促進能源互聯網建設和新能源汽車產業發展。

通知指出,加強電動汽車充電基礎設施安全管理,促進能源互聯網建設和新能源汽車產業發展,定於2016年4月-6月在全國範圍內組織開展電動汽車充電基礎設施安全專項檢查。

通知明確檢查物件和內容,重點對電動汽車充電基礎設施建設運營企業以及相關充換電設施進行檢查,包括電動汽車充電基礎設施安全管理、設備設施及監控系統安全運行、建設標準執行等情況。

通知要求,全面加強電動汽車充電基礎設施安全運營管理,建立設備設施定期檢查和運行維護工作制度,確保充電設備、配電設備、線纜及保護裝置、充電監控系統及運行管理平臺的工作狀態正常和可靠運行。落實充電設備、配電等電氣設備及監控系統故障資訊檢測手段,建立充電過程的告警監測、過充保護、故障處理等防控措施及應急聯動機制。依照相關標準對有關消防設施進行檢查,保證設備處於可用狀態。加強設備設施安全管理和運行維護,滿足充換電設施運行要求。

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

【其他文章推薦】

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

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

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

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

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