【集合系列】- 深入淺出分析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網頁設計已成為網頁設計推薦首選

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

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

美軍擬造機動微型核反應爐 解偏遠基地電力需求

摘錄自2020年3月10日中央社報導

美國軍方今(9日)表示,正在規畫部署微型核能反應爐,這種移動式發電廠可用貨車載運,且不受時間限制為偏遠地區的基地提供電力。

法新社報導,美國國防部已經為了這種移動式核反應爐跟三間企業簽訂合約,目標是產生1百萬瓦至5百萬瓦(megawatt)的電力。預計在兩年內,其中一間公司將獲選來生產反應爐的原型。國防部聲明表示,這項名為「裴雷」(Pele)的計畫是透過發展安全的、可移動的、先進的微型核子反應爐,以因應國防部多元的任務需求。

然而,美軍規畫部署微型核能發電設備,部分民間核能專家對此抱持懷疑態度。這些專家認為,若遭遇攻擊,反應爐被破壞後恐導致放射性燃料外洩,或是核物料落入敵人手中且被用來製造低階的髒彈(dirty bomb)。

能源議題
國際新聞
美國
核反應爐
移動

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

【其他文章推薦】

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

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

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

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

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

※試算大陸海運運費!

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

摘錄自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網頁設計已成為網頁設計推薦首選

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

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

百度將在美國測試無人駕駛汽車 計畫2018年前推商用車型

日前,百度首席科學家吳恩達在接受國外媒體採訪時透露,百度將很快在美國測試無人駕駛汽車,並希望在2018年前推出一款可商用的車型。

百度無人駕駛車專案於2013年起步,由百度研究院主導研發,其技術核心是“百度汽車大腦”,包括高精度地圖、定位、感知、智慧決策與控制四大模組。2014年7月,百度首次對外證實啟動“百度無人駕駛汽車”研發計畫。

2015年12月10日,百度宣佈,百度無人駕駛車國內首次實現城市、環路及高速道路混合路況下的全自動駕駛。百度公佈的路測路線顯示,百度無人駕駛車從位於北京中關村軟體園的百度大廈附近出發,駛入G7京新高速公路,經五環路,抵達奧林匹克森林公園,並隨後按原路線返回。百度無人駕駛車往返全程均實現自動駕駛,並實現了多次跟車減速、變道、超車、上下匝道、調頭等複雜駕駛動作,完成了進入高速(匯入車流)到駛出高速(離開車流)的不同道路場景的切換。測試時最高速度達到100公里/小時。

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

【其他文章推薦】

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

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

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

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

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

※試算大陸海運運費!

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

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

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

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

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

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

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

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

【其他文章推薦】

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

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

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

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

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

flink 流式處理中如何集成mybatis框架

flink 中自身雖然實現了大量的connectors,如下圖所示,也實現了jdbc的connector,可以通過jdbc 去操作數據庫,但是flink-jdbc包中對數據庫的操作是以ROW來操作並且對數據庫事務的控制比較死板,有時候操作關係型數據庫我們會非常懷念在java web應用開發中的非常優秀的mybatis框架,那麼其實flink中是可以自己集成mybatis進來的。 我們這裏以flink 1.9版本為例來進行集成。

如下圖為flink內部自帶的flink-jdbc:

 

創建一個flink的流式處理項目,引入flink的maven依賴和mybatis依賴(注意這裏引入的是非spring版本,也就是mybatis的單機版):

<properties>

<flink.version>1.9.0</flink.version>
</properties>
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.2</version>
</dependency>
<!-- flink java 包 -->
<dependency>
    <groupId>org.apache.flink</groupId>
    <artifactId>flink-streaming-java_2.11</artifactId>
    <version>${flink.version}</version>
</dependency>

maven依賴引入以後,那麼需要在resources下面定義mybatis-config.xml 配置:

mybatis-config.xml 需要定義如下配置:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <typeAliases>
        <typeAlias alias="BankBillPublic" type="xxxx.xx.xx.BankBillPublic" />
    </typeAliases>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver" />
                <property name="url" value="jdbc:mysql://xx.xx.xx.xx:3306/hue?characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&autoReconnect=true" />
                <property name="username" value="xxxx" />
                <property name="password" value="xxxx*123%" />
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mapper/xxxxxMapper.xml" />
    </mappers>
</configuration>

typeAlias 標籤中為自定義的數據類型,然後在xxxxxMapper.xml 中parameterType或者resultType就可以直接用這種定義的數據類型。

dataSource type=”POOLED” 我們使用的是mybatis中的POOLED 類型,也就是連接池的方式去使用。默認支持如下這三種類型。

 我們也可以使用阿里巴巴開源的druid連接池,那麼就需要引入對應的maven依賴,如下所示:

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.0.14</version>
        </dependency>  

 然後定義一個對應的druid的DataSource,如下所示:

import java.sql.SQLException;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.ibatis.datasource.DataSourceFactory;
import com.alibaba.druid.pool.DruidDataSource;

public class DruidDataSourceFactory implements DataSourceFactory {
    private Properties props;

    @Override
    public DataSource getDataSource() {
        DruidDataSource dds = new DruidDataSource();
        dds.setDriverClassName(this.props.getProperty("driver"));
        dds.setUrl(this.props.getProperty("url"));
        dds.setUsername(this.props.getProperty("username"));
        dds.setPassword(this.props.getProperty("password"));
        // 其他配置可以根據MyBatis主配置文件進行配置
        try {
            dds.init();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return dds;
    }

    @Override
    public void setProperties(Properties props) {
        this.props = props;
    }
}

之後就可以mybatis的配置中使用了,如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <typeAliases>
        <typeAlias alias="BankBillPublic" type="xxxx.xx.xx.BankBillPublic" />
        <typeAlias alias="DRUID" 
 type="com.xx.mybatis.druid.utils.DruidDataSourceFactory" />
    </typeAliases>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC" />
            <dataSource type="DRUID">
                <property name="driver" value="com.mysql.jdbc.Driver" />
                <property name="url" value="jdbc:mysql://xx.xx.xx.xx:3306/hue?characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&autoReconnect=true" />
                <property name="username" value="xxxx" />
                <property name="password" value="xxxx*123%" />
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mapper/xxxxxMapper.xml" />
    </mappers>
</configuration>

<mappers> 下面為定義的mybatis 的xxxxxMapper文件。裏面放置的都是sql語句。

本文作者張永清,轉載請註明出處:

xxxxxMapper.xml 中的sql示例:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="xx.xx.bigdata.flink.xx.xx.mapper.UserRelaInfoMapper">
    <!--查詢關鍵字匹配 -->
    <select id="queryUserRelaInfo" parameterType="String" resultType="UserRelaInfo">
        SELECT id AS id,
        USER_NAME AS userName,
        APPL_IDCARD AS applIdCard,
        PEER_USER AS peerUser,
        RELA_TYPE AS relaType,
        CREATE_USER AS createUser,
        CREATE_TIME AS createTime
        FROM USER_RELA_INFO
        <where>
            <if test="applIdCard != null">
                APPL_IDCARD=#{applIdCard}
            </if>
            <if test="peerUser != null">
            AND PEER_USER=#{peerUser}
            </if>
        </where>
    </select>
</mapper>

 定義Mapper,一般可以定義一個interface ,和xxxxxMapper.xml中的namespace保持一致

注意傳入的參數一般加上@Param 註解,傳入的參數和xxxxxMapper.xml中需要的參數保持一致

public interface UserRelaInfoMapper {
    List<UserRelaInfo> queryUserRelaInfo(@Param("applIdCard")String applIdCard,@Param("peerUser") String peerUser);
}

定義SessionFactory工廠(單例模式):

/**
 *
 *  sqlsession factory 單例  事務設置為手動提交
 */
public class MybatisSessionFactory {
    private static final Logger LOG = LoggerFactory.getLogger(MybatisSessionFactory.class);
    private static SqlSessionFactory sqlSessionFactory;
    private MybatisSessionFactory(){
        super();
    }
    public synchronized static SqlSessionFactory getSqlSessionFactory(){
        if(null==sqlSessionFactory){
            InputStream inputStream=null;
            try{
                inputStream = MybatisSessionFactory.class.getClassLoader().getResourceAsStream("mybatis-config.xml");
                sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            }
            catch (Exception e){
                LOG.error("create MybatisSessionFactory read mybatis-config.xml cause Exception",e);
            }
            if(null!=sqlSessionFactory){
                LOG.info("get Mybatis sqlsession sucessed....");
            }
            else {
                LOG.info("get Mybatis sqlsession failed....");
            }
        }
        return sqlSessionFactory;
    }
}

  

使用mybatis 對數據庫進行操作:

        SqlSession sqlSession = MybatisSessionFactory.getSqlSessionFactory().openSession();
        UserRelaInfoMapper  userRelaInfoMapper  = sqlSession.getMapper(UserRelaInfoMapper .class);
		//調用對應的方法
		userRelaInfoMapper.xxxx();
		//提交事務
		sqlSession.commit();
		//回滾事務,一般可以捕獲異常,在發生Exception的時候,事務進行回滾
		sqlSession.rollback();
		
		
		

這裏以mysql為示例,寫一個flink下mysql的sink示例,可以自己來靈活控制事務的提交:

public class MysqlSinkFunction<IN> extends RichSinkFunction {
    private static final Logger LOG = LoggerFactory.getLogger(MysqlSinkFunction.class);
    @Override
    public void invoke(Object value, Context context) throws Exception{
        SqlSession sqlSession = MybatisSessionFactory.getSqlSessionFactory().openSession();
        try{
                            //插入
                            LOG.info("MysqlSinkFunction start to do insert data...");
                            xxx.xxx();
							//更新
                            LOG.info("MysqlSinkFunction start to do update data...");
							xxx.xxx();
                            //刪除
                            LOG.info("MysqlSinkFunction start to do delete data...");
							xxx.xxx();

                    
                
                sqlSession.commit();
                LOG.info("MysqlSinkFunction commit transaction success...");
        }
        catch (Throwable e){
            sqlSession.rollback();
            LOG.error("MysqlSinkFunction cause Exception,sqlSession transaction rollback...",e);
        }
    }
}  
相信您如果以前在spring中用過mybatis的話,對上面的這些操作一定不會陌生。由此你也可以發現,在大數據中可以完美的集成mybatis,這樣可以發揮mybatis框架對數據庫操作的優勢,使用起來也非常簡單方便。
一旦集成了mybaitis后,在flink中就可以方便的對各種各樣的關係型數據庫進行操作了。

本文作者張永清,轉載請註明出處:

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

【其他文章推薦】

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

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

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

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

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

※試算大陸海運運費!

整數的表示與編碼

補一下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網頁設計已成為網頁設計推薦首選

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

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

現代轉攻新能源汽車 計畫2020年之前推26款新車

日前,據海外媒體報導,現代汽車集團將在2020年之前推出26款新能源汽車,其中包括旗下現代和起亞兩大品牌。現代汽車集團此舉意在搶佔新能源汽車市場的領先地位,爭取在2020年達到僅次於豐田的行業第二水準。

現代汽車集團計畫推出的26款新車型包括12款混動車型、6款插電式混動車型、2款純電動車型以及2款氫燃料電池車型等。其中,起亞將推出包括Niro混動版、凱尊(參配、圖片、詢價) 混動版、SoulEV在內的11款新車,而現代則會推出插電式混動版、純電動版IONIQ與豐田普銳斯一決高下。據現代汽車集團預估,到2020年現代汽車集團在新能源汽車市場的年銷量有望達到30萬台。

為了節省新能源汽車的開發成本,現代汽車集團相關負責人透露,旗下新能源車型將採用共用零部件策略。雖然每款車的動力系統在動力輸出上可能有所不同,但電動機直徑、核心部件等將會遵循統一化標準。

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

【其他文章推薦】

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

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

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

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

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

※試算大陸海運運費!

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

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

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

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

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

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

【其他文章推薦】

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

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

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

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

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

【從今天開始好好學數據結構04】程序員你心中就沒點“樹”嗎?

目錄

前面我們講的都是線性表結構,棧、隊列等等。今天我們講一種非線性表結構,樹。樹這種數據結構比線性表的數據結構要複雜得多,內容也比較多,首先我們先從樹(Tree)開始講起。
@

樹(Tree)

樹型結構是一種非線性結構,它的數據元素之間呈現分支、分層的特點。

1.樹的定義

樹(Tree)是由n(n≥0)個結點構成的有限集合T,當n=0時T稱為空樹;否則,在任一非空樹T中:
(1)有且僅有一個特定的結點,它沒有前驅結點,稱其為根(Root)結點;
(2)剩下的結點可分為m(m≥0)個互不相交的子集T1,T2,…,Tm,其中每個子集本身又是一棵樹,並稱其為根的子樹(Subtree)。

注意:樹的定義具有遞歸性,即“樹中還有樹”。樹的遞歸定義揭示出了樹的固有特性

2.什麼是樹結構

什麼是“樹”?再好的定義,都沒有圖解來的直觀。所以我在圖中畫了幾棵“樹”。你來看看,這些“樹”都有什麼特徵?

你有沒有發現,“樹”這種數據結構真的很像我們現實生活中的“樹”

3.為什麼使用樹結構

在有序數組中,可以快速找到特定的值,但是想在有序數組中插入一個新的數據項,就必須首先找出新數據項插入的位置,然後將比新數據項大的數據項向後移動一位,來給新的數據項騰出空間,刪除同理,這樣移動很費時。顯而易見,如果要做很多的插入和刪除操作和刪除操作,就不該選用有序數組。另一方面,鏈表中可以快速添加和刪除某個數據項,但是在鏈表中查找數據項可不容易,必須從頭開始訪問鏈表的每一個數據項,直到找到該數據項為止,這個過程很慢。 樹這種數據結構,既能像鏈表那樣快速的插入和刪除,又能想有序數組那樣快速查找

4.樹的常用術語

結點——包含一個數據元素和若干指向其子樹的分支
度——結點擁有的子樹個數
樹的度——該樹中結點的最大度數
恭弘=叶 恭弘子——度為零的結點
分支結點(非終端結點)——度不為零的結點
孩子和雙親——結點的子樹的根稱為該結點的孩子,相應地,該結點稱為孩子的雙親
兄弟——同一個雙親的孩子
祖先和子孫——從根到該結點所經分支上的所有結點。相應地,以某一結點為根的子樹中的任一結點稱為該結點的子孫。
結點的層次——結點的層次從根開始定義,根結點的層次為1,其孩子結點的層次為2,……
堂兄弟——雙親在同一層的結點
樹的深度——樹中結點的最大層次
有序樹和無序樹——如果將樹中每個結點的各子樹看成是從左到右有次序的(即位置不能互換),則稱該樹為有序樹;否則稱為無序樹。
森林——m(m≥0)棵互不相交的樹的有限集合

到這裏,樹就講的差不多了,接下來講講二叉樹(Binary Tree)

二叉樹(Binary Tree)

樹結構多種多樣,不過我們最常用還是二叉樹,我們平時最常用的樹就是二叉樹。二叉樹的每個節點最多有兩個子節點,分別是左子節點和右子節點。二叉樹中,有兩種比較特殊的樹,分別是滿二叉樹和完全二叉樹。滿二叉樹又是完全二叉樹的一種特殊情況。

1.二叉樹的定義和特點

二叉樹的定義:
二叉樹(Binary Tree)是n(n≥0)個結點的有限集合BT,它或者是空集,或者由一個根結點和兩棵分別稱為左子樹和右子樹的互不相交的二叉樹組成 。
————————————
二叉樹的特點:
每個結點至多有二棵子樹(即不存在度大於2的結點);二叉樹的子樹有左、右之分,且其次序不能任意顛倒。

2.幾種特殊形式的二叉樹

1、滿二叉樹
定義:深度為k且有2k-1個結點的二叉樹,稱為滿二叉樹。
特點:每一層上的結點數都是最大結點數
2、完全二叉樹
定義:
深度為k,有n個結點的二叉樹當且僅當其每一個結點都與深度為k的滿二叉樹中編號從1至n的結點一一對應時,稱為完全二叉樹
特點:
特點一 : 恭弘=叶 恭弘子結點只可能在層次最大的兩層上出現;
特點二 : 對任一結點,若其右分支下子孫的最大層次為l,則其左分支下子孫的最大層次必為l 或l+1

建議看圖對應文字綜合理解

代碼創建二叉樹

首先,創建一個節點Node類

package demo5;
/*
 * 節(結)點類 
 */
public class Node {
    //節點的權
    int value;
    //左兒子(左節點)
    Node leftNode;
    //右兒子(右節點)
    Node rightNode;
    //構造函數,初始化的時候就給二叉樹賦上權值
    public Node(int value) {
        this.value=value;
    }
    
    //設置左兒子(左節點)
    public void setLeftNode(Node leftNode) {
        this.leftNode = leftNode;
    }
    //設置右兒子(右節點)
    public void setRightNode(Node rightNode) {
        this.rightNode = rightNode;
    }

接着創建一個二叉樹BinaryTree 類

package demo5;
/*
 * 二叉樹Class
 */
public class BinaryTree {
    //根節點root
    Node root;
    
    //設置根節點
    public void setRoot(Node root) {
        this.root = root;
    }
    
    //獲取根節點
    public Node getRoot() {
        return root;
    }
}

最後創建TestBinaryTree 類(該類主要是main方法用來測試)來創建一個二叉樹

package demo5;
public class TestBinaryTree {

    public static void main(String[] args) {
        //創建一顆樹
        BinaryTree binTree = new BinaryTree();
        //創建一個根節點
        Node root = new Node(1);
        //把根節點賦給樹
        binTree.setRoot(root);
        //創建一個左節點
        Node rootL = new Node(2);
        //把新創建的節點設置為根節點的子節點
        root.setLeftNode(rootL);
        //創建一個右節點
        Node rootR = new Node(3);
        //把新創建的節點設置為根節點的子節點
        root.setRightNode(rootR);
        //為第二層的左節點創建兩個子節點
        rootL.setLeftNode(new Node(4));
        rootL.setRightNode(new Node(5));
        //為第二層的右節點創建兩個子節點
        rootR.setLeftNode(new Node(6));
        rootR.setRightNode(new Node(7));
    }

}

下面將會講的遍歷、查找節點、刪除節點都將圍繞這三個類開展

不難看出創建好的二叉樹如下(畫的不好,還望各位見諒):

3.二叉樹的兩種存儲方式

二叉樹既可以用鏈式存儲,也可以用數組順序存儲。數組順序存儲的方式比較適合完全二叉樹,其他類型的二叉樹用數組存儲會比較浪費存儲空間,所以鏈式存儲更合適。

我們先來看比較簡單、直觀的鏈式存儲法

接着是基於數組的順序存儲法(該例子是一棵完全二叉樹)

上面例子是一棵完全二叉樹,所以僅僅“浪費”了一個下標為0的存儲位置。如果是非完全二叉樹,則會浪費比較多的數組存儲空間,如下。

還記得堆和堆排序嗎,堆其實就是一種完全二叉樹,最常用的存儲方式就是數組。

4.二叉樹的遍歷

前面我講了二叉樹的基本定義和存儲方法,現在我們來看二叉樹中非常重要的操作,二叉樹的遍歷。這也是非常常見的面試題。

經典遍歷的方法有三種,前序遍歷中序遍歷後序遍歷

前序遍歷是指,對於樹中的任意節點來說,先打印這個節點,然後再打印它的左子樹,最後打印它的右子樹。

中序遍歷是指,對於樹中的任意節點來說,先打印它的左子樹,然後再打印它本身,最後打印它的右子樹。

後序遍歷是指,對於樹中的任意節點來說,先打印它的左子樹,然後再打印它的右子樹,最後打印這個節點本身。

我想,睿智的你已經想到了二叉樹的前、中、後序遍歷就是一個遞歸的過程。比如,前序遍歷,其實就是先打印根節點,然後再遞歸地打印左子樹,最後遞歸地打印右子樹。

在之前創建好的二叉樹代碼之上,我們來使用這三種方法遍歷一下~

依舊是在Node節點類上添加方法:可以看出遍歷方法都是用的遞歸思想

package demo5;
/*
 * 節(結)點類 
 */
public class Node {
//===================================開始 遍歷========================================
    //前序遍歷
    public void frontShow() {
        //先遍歷當前節點的內容
        System.out.println(value);
        //左節點
        if(leftNode!=null) {
            leftNode.frontShow();
        }
        //右節點
        if(rightNode!=null) {
            rightNode.frontShow();
        }
    }

    //中序遍歷
    public void midShow() {
        //左子節點
        if(leftNode!=null) {
            leftNode.midShow();
        }
        //當前節點
        System.out.println(value);
        //右子節點
        if(rightNode!=null) {
            rightNode.midShow();
        }
    }

    //後序遍歷
    public void afterShow() {
        //左子節點
        if(leftNode!=null) {
            leftNode.afterShow();
        }
        //右子節點
        if(rightNode!=null) {
            rightNode.afterShow();
        }
        //當前節點
        System.out.println(value);
    }

}

然後依舊是在二叉樹BinaryTree 類上添加方法,並且添加的方法調用Node類中的遍歷方法

package demo5;
/*
 * 二叉樹Class
 */
public class BinaryTree {

    public void frontShow() {
        if(root!=null) {
            //調用節點類Node中的前序遍歷frontShow()方法
            root.frontShow();
        }
    }

    public void midShow() {
        if(root!=null) {
            //調用節點類Node中的中序遍歷midShow()方法
            root.midShow();
        }
    }

    public void afterShow() {
        if(root!=null) {
            //調用節點類Node中的後序遍歷afterShow()方法
            root.afterShow();
        }
    }

}

依舊是在TestBinaryTree類中測試

package demo5;

public class TestBinaryTree {

    public static void main(String[] args) {
        //前序遍歷樹
        binTree.frontShow();
        System.out.println("===============");
        //中序遍歷
        binTree.midShow();
        System.out.println("===============");
        //後序遍歷
        binTree.afterShow();
        System.out.println("===============");
        //前序查找
        Node result = binTree.frontSearch(5);
        System.out.println(result);
        
}

如果遞歸理解的不是很透,我可以分享一個學習的小方法:我建議各位可以這樣斷點調試,一步一步調,思維跟上,仔細推敲每一步的運行相信我,你會重新認識到遞歸!(像下面這樣貼個圖再一步一步斷點思維更加清晰)

貼一下我斷點對遞歸的分析,希望對你有一定的幫助~

二叉樹遍歷的遞歸實現思路自然、簡單,易於理解,但執行效率較低。為了提高程序的執行效率,可以顯式的設置棧,寫出相應的非遞歸遍歷算法。非遞歸的遍歷算法可以根據遞歸算法的執行過程寫出。至於代碼可以嘗試去寫一寫,這也是一種提升!具體的非遞歸算法主要流程圖貼在下面了:

二叉樹遍歷算法分析:

二叉樹遍歷算法中的基本操作是訪問根結點,不論按哪種次序遍歷,都要訪問所有的結點,對含n個結點的二叉樹,其時間複雜度均為O(n)。所需輔助空間為遍歷過程中所需的棧空間,最多等於二叉樹的深度k乘以每個結點所需空間數,最壞情況下樹的深度為結點的個數n,因此,其空間複雜度也為O(n)。

5.二叉樹中節點的查找與刪除

剛才講到二叉樹的三種金典遍歷放法,那麼節點的查找同樣是可以效仿的,分別叫做前序查找、中序查找以及後序查找,下面代碼只以前序查找為例,三者查找方法思路類似~

至於刪除節點,有三種情況:

1、如果刪除的是根節點,那麼二叉樹就完全被刪了
2、如果刪除的是雙親節點,那麼該雙親節點以及他下面的所有子節點所構成的子樹將被刪除
3、如果刪除的是恭弘=叶 恭弘子節點,那麼就直接刪除該恭弘=叶 恭弘子節點

那麼,我把完整的三個類給貼出來(包含創建、遍歷、查找、刪除)

依舊是Node節點類

package demo5;
/*
 * 節(結)點類 
 */
public class Node {
    //節點的權
    int value;
    //左兒子
    Node leftNode;
    //右兒子
    Node rightNode;
    //構造函數,初始化的時候就給二叉樹賦上權值
    public Node(int value) {
        this.value=value;
    }
    
    //設置左兒子
    public void setLeftNode(Node leftNode) {
        this.leftNode = leftNode;
    }
    //設置右兒子
    public void setRightNode(Node rightNode) {
        this.rightNode = rightNode;
    }
    
    //前序遍歷
    public void frontShow() {
        //先遍歷當前節點的內容
        System.out.println(value);
        //左節點
        if(leftNode!=null) {
            leftNode.frontShow();
        }
        //右節點
        if(rightNode!=null) {
            rightNode.frontShow();
        }
    }

    //中序遍歷
    public void midShow() {
        //左子節點
        if(leftNode!=null) {
            leftNode.midShow();
        }
        //當前節點
        System.out.println(value);
        //右子節點
        if(rightNode!=null) {
            rightNode.midShow();
        }
    }

    //後序遍歷
    public void afterShow() {
        //左子節點
        if(leftNode!=null) {
            leftNode.afterShow();
        }
        //右子節點
        if(rightNode!=null) {
            rightNode.afterShow();
        }
        //當前節點
        System.out.println(value);
    }

    //前序查找
    public Node frontSearch(int i) {
        Node target=null;
        //對比當前節點的值
        if(this.value==i) {
            return this;
        //當前節點的值不是要查找的節點
        }else {
            //查找左兒子
            if(leftNode!=null) {
                //有可能可以查到,也可以查不到,查不到的話,target還是一個null
                target = leftNode.frontSearch(i);
            }
            //如果不為空,說明在左兒子中已經找到
            if(target!=null) {
                return target;
            }
            //查找右兒子
            if(rightNode!=null) {
                target=rightNode.frontSearch(i);
            }
        }
        return target;
    }
    
    //刪除一個子樹
    public void delete(int i) {
        Node 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.delete(i);
        }
        
        //遞歸檢查並刪除右兒子
        parent=rightNode;
        if(parent!=null) {
            parent.delete(i);
        }
    }

}

依舊是BinaryTree 二叉樹類

package demo5;
/*
 * 二叉樹Class
 */
public class BinaryTree {
    //根節點root
    Node root;
    
    //設置根節點
    public void setRoot(Node root) {
        this.root = root;
    }
    
    //獲取根節點
    public Node getRoot() {
        return root;
    }

    public void frontShow() {
        if(root!=null) {
            //調用節點類Node中的前序遍歷frontShow()方法
            root.frontShow();
        }
    }

    public void midShow() {
        if(root!=null) {
            //調用節點類Node中的中序遍歷midShow()方法
            root.midShow();
        }
    }

    public void afterShow() {
        if(root!=null) {
            //調用節點類Node中的後序遍歷afterShow()方法
            root.afterShow();
        }
    }
    //查找節點i
    public Node frontSearch(int i) {
        return root.frontSearch(i);
    }
    //刪除節點i
    public void delete(int i) {
        if(root.value==i) {
            root=null;
        }else {
            root.delete(i);
        }
    }
    
}

依舊是TestBinaryTree測試類

package demo5;

public class TestBinaryTree {

    public static void main(String[] args) {
        //創建一顆樹
        BinaryTree binTree = new BinaryTree();
        //創建一個根節點
        Node root = new Node(1);
        //把根節點賦給樹
        binTree.setRoot(root);
        //創建一個左節點
        Node rootL = new Node(2);
        //把新創建的節點設置為根節點的子節點
        root.setLeftNode(rootL);
        //創建一個右節點
        Node rootR = new Node(3);
        //把新創建的節點設置為根節點的子節點
        root.setRightNode(rootR);
        //為第二層的左節點創建兩個子節點
        rootL.setLeftNode(new Node(4));
        rootL.setRightNode(new Node(5));
        //為第二層的右節點創建兩個子節點
        rootR.setLeftNode(new Node(6));
        rootR.setRightNode(new Node(7));
        //前序遍歷樹
        binTree.frontShow();
        System.out.println("===============");
        //中序遍歷
        binTree.midShow();
        System.out.println("===============");
        //後序遍歷
        binTree.afterShow();
        System.out.println("===============");
        //前序查找
        Node result = binTree.frontSearch(5);
        System.out.println(result);
        
        System.out.println("===============");
        //刪除一個子樹
        binTree.delete(4);
        binTree.frontShow();
        
    }

}

到這裏,總結一下,我們學了一種非線性表數據結構,樹。關於樹,有幾個比較常用的概念你需要掌握,那就是:根節點、恭弘=叶 恭弘子節點、父節點、子節點、兄弟節點,還有節點的高度、深度、層數,以及樹的高度等。我們平時最常用的樹就是二叉樹。二叉樹的每個節點最多有兩個子節點,分別是左子節點和右子節點。二叉樹中,有兩種比較特殊的樹,分別是滿二叉樹和完全二叉樹。滿二叉樹又是完全二叉樹的一種特殊情況。二叉樹既可以用鏈式存儲,也可以用數組順序存儲。數組順序存儲的方式比較適合完全二叉樹,其他類型的二叉樹用數組存儲會比較浪費存儲空間。除此之外,二叉樹里非常重要的操作就是前、中、後序遍歷操作,遍歷的時間複雜度是O(n),你需要理解並能用遞歸代碼來實現。

如果本文章對你有幫助,哪怕是一點點,請點個讚唄,謝謝~

歡迎各位關注我的公眾號,一起探討技術,嚮往技術,追求技術…說好了來了就是盆友喔…

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

【其他文章推薦】

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

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

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

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

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

※試算大陸海運運費!