重學 Java 設計模式:實戰迭代器模式「模擬公司組織架構樹結構關係,深度迭代遍歷人員信息輸出場景」

作者:小傅哥
博客:https://bugstack.cn – 原創系列專題文章

沉澱、分享、成長,讓自己和他人都能有所收穫!

一、前言

相信相信的力量!

從懵懂的少年,到拿起鍵盤,可以寫一個HelloWorld。多數人在這並不會感覺有多難,也不會認為做不出來。因為這樣的例子,有老師的指導、有書本的例子、有前人的經驗。但隨着你的開發時間越來越長,要解決更複雜的問題或者技術創新,因此在網上搜了幾天幾夜都沒有答案,這個時候是否想過放棄,還是一直堅持不斷的嘗試一點點完成自己心裏要的結果。往往這種沒有前車之鑒需要自己解決問題的時候,可能真的會折磨到要崩潰,但你要願意執着、願意倔強,願意選擇相信相信的力量,就一定能解決。哪怕解決不了,也可以在這條路上摸索出其他更多的收穫,為後續前進的道路填充好墊腳石。

時間緊是寫垃圾代碼的理由?

擰螺絲?Ctrl+C、Ctrl+V?貼膏藥一樣寫代碼?沒有辦法,沒有時間,往往真的是借口,胸中沒用筆墨,才只能湊合。難道一定是好好寫代碼就浪費時間,拼湊CRUD就快嗎,根本不可能的。因為不會,沒用實操過,很少架構出全場景的設計,才很難寫出優良的代碼。多增強自身的編碼(武術)修為,在各種編碼場景中讓自己變得老練,才好應對緊急情況下的需求開發和人員安排。就像韓信一樣有謀有略,才能執掌百萬雄兵。

不要只是做個工具人!

因為日常的編寫簡單業務需求,導致自己像個工具人一樣,日久天長的也就很少去深入學習更多技術棧。看見有工具、有組件、有框架,拿來就用用,反正沒什麼體量也不會出什麼問題。但如果你想要更多的收入,哪怕是重複的造輪子,你也要去嘗試造一個,就算不用到生產,自己玩玩總可以吧。有些事情只有自己經歷過,才能有最深的感觸,參与過實踐過,才好總結點評學習。

二、開發環境

  1. JDK 1.8
  2. Idea + Maven
  3. 涉及工程一個,可以通過關注公眾號:bugstack蟲洞棧,回復源碼下載獲取(打開獲取的鏈接,找到序號18)
工程 描述
itstack-demo-design-15-00 開發樹形組織架構關係迭代器

三、迭代器模式介紹

迭代器模式,常見的就是我們日常使用的iterator遍歷。雖然這個設計模式在我們的實際業務開發中的場景並不多,但卻幾乎每天都要使用jdk為我們提供的list集合遍歷。另外增強的for循環雖然是循環輸出數據,但是他不是迭代器模式。迭代器模式的特點是實現Iterable接口,通過next的方式獲取集合元素,同時具備對元素的刪除等操作。而增強的for循環是不可以的。

這種設計模式的優點是可以讓我們以相同的方式,遍歷不同的數據結構元素,這些數據結構包括;數組、鏈表、樹等,而用戶在使用遍歷的時候並不需要去關心每一種數據結構的遍歷處理邏輯,從讓使用變得統一易用。

四、案例場景模擬

在本案例中我們模擬迭代遍歷輸出公司中樹形結構的組織架構關係中僱員列表

大部分公司的組織架構都是金字塔結構,也就這種樹形結構,分為一級、二級、三級等部門,每個組織部門由僱員填充,最終體現出一個整體的樹形組織架構關係。

一般我們常用的遍歷就是jdk默認提供的方法,對list集合遍歷。但是對於這樣的偏業務特性較大的樹形結構,如果需要使用到遍歷,那麼就可以自己來實現。接下來我們會把這個組織層次關係通過樹形數據結構來實現,並完成迭代器功能。

五、迭代器模式遍歷組織結構

在實現迭代器模式之前可以先閱讀下java中list方法關於iterator的實現部分,幾乎所有的迭代器開發都會按照這個模式來實現,這個模式主要分為以下幾塊;

  1. Collection,集合方法部分用於對自定義的數據結構添加通用方法;add、remove、iterator等核心方法。
  2. Iterable,提供獲取迭代器,這個接口類會被Collection繼承。
  3. Iterator,提供了兩個方法的定義;hasNext、next,會在具體的數據結構中寫實現方式。

除了這樣通用的迭代器實現方式外,我們的組織關係結構樹,是由節點和節點間的關係鏈構成,所以會比上述的內容多一些入參。

1. 工程結構

itstack-demo-design-15-02
└── src
    ├── main
    │   └── java
    │       └── org.itstack.demo.design
    │           ├── group
    │           │	├── Employee.java
    │           │	├── GroupStructure.java
    │           │	└── Link.java
    │           └──  lang
    │            	├── Collection.java
    │            	├── Iterable.java
    │            	└── Iterator.java
    └── test
        └── java
            └── org.itstack.demo.design.test
                └── ApiTest.java

迭代器模式模型結構

  • 以上是我們工程類圖的模型結構,左側是對迭代器的定義,右側是在數據結構中實現迭代器功能。
  • 關於左側部分的實現與jdk中的方式是一樣的,所以在學習的過程中可以互相參考,也可以自己擴展學習。
  • 另外這個遍歷方式一個樹形結構的深度遍歷,為了可以更加讓學習的小夥伴容易理解,這裏我實現了一種比較簡單的樹形結構深度遍歷方式。後續讀者也可以把遍歷擴展為橫向遍歷也就是寬度遍歷。

2. 代碼實現

2.1 僱員實體類

/**
 * 僱員
 */
public class Employee {

    private String uId;   // ID
    private String name;  // 姓名
    private String desc;  // 備註
    
    // ...get/set
}
  • 這是一個簡單的僱員類,也就是公司員工的信息類,包括必要的信息;id、姓名、備註。

2.2 樹節點鏈路

/**
 * 樹節點鏈路
 */
public class Link {

    private String fromId; // 僱員ID
    private String toId;   // 僱員ID    
    
    // ...get/set
}
  • 這個類用於描述結構樹中的各個節點之間的關係鏈,也就是A to B、B to C、B to D,以此描述出一套完整的樹組織結構。

2.3 迭代器定義

public interface Iterator<E> {

    boolean hasNext();

    E next();
    
}
  • 這裏的這個類和java的jdk中提供的是一樣的,這樣也方面後續讀者可以對照list的Iterator進行源碼學習。
  • 方法描述;hasNext,判斷是否有下一個元素、next,獲取下一個元素。這個在list的遍歷中是經常用到的。

2.4 可迭代接口定義

public interface Iterable<E> {

    Iterator<E> iterator();

}
  • 這個接口中提供了上面迭代器的實現Iterator的獲取,也就是後續在自己的數據結構中需要實現迭代器的功能並交給Iterable,由此讓外部調用方進行獲取使用。

2.5 集合功能接口定義

public interface Collection<E, L> extends Iterable<E> {

    boolean add(E e);

    boolean remove(E e);

    boolean addLink(String key, L l);

    boolean removeLink(String key);

    Iterator<E> iterator();

}
  • 這裏我們定義集合操作接口;Collection,同時繼承了另外一個接口Iterable的方法iterator()。這樣後續誰來實現這個接口,就需要實現上述定義的一些基本功能;添加元素、刪除元素、遍歷。
  • 同時你可能注意到這裏定義了兩個泛型<E, L>,因為我們的數據結構一個是用於添加元素,另外一個是用於添加樹節點的鏈路關係。

2.6 (核心)迭代器功能實現

public class GroupStructure implements Collection<Employee, Link> {

    private String groupId;                                                 // 組織ID,也是一個組織鏈的頭部ID
    private String groupName;                                               // 組織名稱
    private Map<String, Employee> employeeMap = new ConcurrentHashMap<String, Employee>();  // 僱員列表
    private Map<String, List<Link>> linkMap = new ConcurrentHashMap<String, List<Link>>();  // 組織架構關係;id->list
    private Map<String, String> invertedMap = new ConcurrentHashMap<String, String>();       // 反向關係鏈

    public GroupStructure(String groupId, String groupName) {
        this.groupId = groupId;
        this.groupName = groupName;
    }

    public boolean add(Employee employee) {
        return null != employeeMap.put(employee.getuId(), employee);
    }

    public boolean remove(Employee o) {
        return null != employeeMap.remove(o.getuId());
    }

    public boolean addLink(String key, Link link) {
        invertedMap.put(link.getToId(), link.getFromId());
        if (linkMap.containsKey(key)) {
            return linkMap.get(key).add(link);
        } else {
            List<Link> links = new LinkedList<Link>();
            links.add(link);
            linkMap.put(key, links);
            return true;
        }
    }

    public boolean removeLink(String key) {
        return null != linkMap.remove(key);
    }

    public Iterator<Employee> iterator() {

        return new Iterator<Employee>() {

            HashMap<String, Integer> keyMap = new HashMap<String, Integer>();

            int totalIdx = 0;
            private String fromId = groupId;  // 僱員ID,From
            private String toId = groupId;   // 僱員ID,To

            public boolean hasNext() {
                return totalIdx < employeeMap.size();
            }

            public Employee next() {
                List<Link> links = linkMap.get(toId);
                int cursorIdx = getCursorIdx(toId);

                // 同級節點掃描
                if (null == links) {
                    cursorIdx = getCursorIdx(fromId);
                    links = linkMap.get(fromId);
                }

                // 上級節點掃描
                while (cursorIdx > links.size() - 1) {
                    fromId = invertedMap.get(fromId);
                    cursorIdx = getCursorIdx(fromId);
                    links = linkMap.get(fromId);
                }

                // 獲取節點
                Link link = links.get(cursorIdx);
                toId = link.getToId();
                fromId = link.getFromId();
                totalIdx++;

                // 返回結果
                return employeeMap.get(link.getToId());
            }
             
            // 給每個層級定義寬度遍歷進度
            public int getCursorIdx(String key) {
                int idx = 0;
                if (keyMap.containsKey(key)) {
                    idx = keyMap.get(key);
                    keyMap.put(key, ++idx);
                } else {
                    keyMap.put(key, idx);
                }
                return idx;
            }
        };
    }

}
  • 以上的這部分代碼稍微有點長,主要包括了對元素的添加和刪除。另外最重要的是對遍歷的實現 new Iterator<Employee>。
  • 添加和刪除元素相對來說比較簡單,使用了兩個map數組結構進行定義;僱員列表、組織架構關係;id->list。當元素添加元素的時候,會分別在不同的方法中向map結構中進行填充指向關係(A->B),也就構建出了我們的樹形組織關係。

迭代器實現思路

  1. 這裏的樹形結構我們需要做的是深度遍歷,也就是左側的一直遍歷到最深節點。
  2. 當遍歷到最深節點后,開始遍歷最深節點的橫向節點。
  3. 當橫向節點遍歷完成后則向上尋找橫向節點,直至樹結構全部遍歷完成。

3. 測試驗證

3.1 編寫測試類

@Test
public void test_iterator() { 
    // 數據填充
    GroupStructure groupStructure = new GroupStructure("1", "小傅哥");  
    
    // 僱員信息
    groupStructure.add(new Employee("2", "花花", "二級部門"));
    groupStructure.add(new Employee("3", "豆包", "二級部門"));
    groupStructure.add(new Employee("4", "蹦蹦", "三級部門"));
    groupStructure.add(new Employee("5", "大燒", "三級部門"));
    groupStructure.add(new Employee("6", "虎哥", "四級部門"));
    groupStructure.add(new Employee("7", "玲姐", "四級部門"));
    groupStructure.add(new Employee("8", "秋雅", "四級部門"));   
    
    // 節點關係 1->(1,2) 2->(4,5)
    groupStructure.addLink("1", new Link("1", "2"));
    groupStructure.addLink("1", new Link("1", "3"));
    groupStructure.addLink("2", new Link("2", "4"));
    groupStructure.addLink("2", new Link("2", "5"));
    groupStructure.addLink("5", new Link("5", "6"));
    groupStructure.addLink("5", new Link("5", "7"));
    groupStructure.addLink("5", new Link("5", "8"));       

    Iterator<Employee> iterator = groupStructure.iterator();
    while (iterator.hasNext()) {
        Employee employee = iterator.next();
        logger.info("{},僱員 Id:{} Name:{}", employee.getDesc(), employee.getuId(), employee.getName());
    }
}

3.2 測試結果

22:23:37.166 [main] INFO  org.itstack.demo.design.test.ApiTest - 二級部門,僱員 Id:2 Name:花花
22:23:37.168 [main] INFO  org.itstack.demo.design.test.ApiTest - 三級部門,僱員 Id:4 Name:蹦蹦
22:23:37.169 [main] INFO  org.itstack.demo.design.test.ApiTest - 三級部門,僱員 Id:5 Name:大燒
22:23:37.169 [main] INFO  org.itstack.demo.design.test.ApiTest - 四級部門,僱員 Id:6 Name:虎哥
22:23:37.169 [main] INFO  org.itstack.demo.design.test.ApiTest - 四級部門,僱員 Id:7 Name:玲姐
22:23:37.169 [main] INFO  org.itstack.demo.design.test.ApiTest - 四級部門,僱員 Id:8 Name:秋雅
22:23:37.169 [main] INFO  org.itstack.demo.design.test.ApiTest - 二級部門,僱員 Id:3 Name:豆包

Process finished with exit code 0
  • 從遍歷的結果可以看到,我們是順着樹形結構的深度開始遍歷,一直到右側的節點3;僱員 Id:2、僱員 Id:4...僱員 Id:3

六、總結

  • 迭代器的設計模式從以上的功能實現可以看到,滿足了單一職責和開閉原則,外界的調用方也不需要知道任何一個不同的數據結構在使用上的遍歷差異。可以非常方便的擴展,也讓整個遍歷變得更加乾淨整潔。
  • 但從結構的實現上可以看到,迭代器模式的實現過程相對來說是比較負責的,類的實現上也擴增了需要外部定義的類,使得遍歷與原數據結構分開。雖然這是比較麻煩的,但可以看到在使用java的jdk時候,迭代器的模式還是很好用的,可以非常方便擴展和升級。
  • 以上的設計模式場景實現過程可能對新人有一些不好理解點,包括;迭代器三個和接口的定義、樹形結構的數據關係、樹結構深度遍歷思路。這些都需要反覆實現練習才能深入的理解,事必躬親,親歷親為,才能讓自己掌握這些知識。

七、推薦閱讀

  • 1. 重學 Java 設計模式:實戰工廠方法模式「多種類型商品不同接口,統一發獎服務搭建場景」
  • 2. 重學 Java 設計模式:實戰原型模式「上機考試多套試,每人題目和答案亂序排列場景」
  • 3. 重學 Java 設計模式:實戰橋接模式「多支付渠道(微信、支付寶)與多支付模式(刷臉、指紋)場景」
  • 4. 重學 Java 設計模式:實戰組合模式「營銷差異化人群發券,決策樹引擎搭建場景」
  • 5. 重學 Java 設計模式:實戰外觀模式「基於SpringBoot開發門面模式中間件,統一控制接口白名單場景」
  • 6. 重學 Java 設計模式:實戰享元模式「基於Redis秒殺,提供活動與庫存信息查詢場景」

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

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

※台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

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

※回頭車貨運收費標準

什麼是技術債,為什麼要還技術債?

先說我的結論就是:技術債要還,還不還技術債,決定你所在的公司是不是尊重科學尊重技術,觀點主要有一下三個:

  • 技術債是什麼,對產品和項目有什麼影響
  • 技術債對開發環境和技術氛圍的影響
  • 技術債和技術價值觀

技術棧是什麼,對產品和項目有什麼影響

既然叫技術債,那麼他本質是一種“債”,所以我們先脫離所謂的技術,單獨聊聊什麼是債?債是一個金融上的術語,代表你的負資產,說人話就是代表你欠了別人的錢,在著名美劇《冰與火之歌》裏面的蘭尼斯特家族有一句名言就是:有債必嘗

  1. 那麼生活中有哪些跟債相關的事情呢?我們日常接觸的債有哪些? 花唄,信用卡,透支下個月工資,貸款,高利貸 等等

  2. 債有什麼特點?債本身是一種透支行為,是你犧牲未來滿足自己當下的某種需求,而且所有的債都有一個共同的特點,就是利息,而且跟隨時間~利滾利

  3. 那麼債是怎麼產生的?大家可以想想你什麼時候會用信用卡,用花唄去購物,或者去借高利貸?當你渴望得到某一個東西,但是你本身還不具備購買能力的時候,你會去借債對吧,例如在你財務狀況還很差的情況下,你想買名牌包包,你想買最新款性能最好的蘋果電腦,你想買豪華轎車等等,通過透支未來,來滿足當下的需求,技術債為什麼叫債,就是通過借債,透支系統的擴展性,安全性,來達到快速上線功能目的,借債很容易上癮,為什麼?因為它可以讓你快速滿足慾望(物質,快速變現),嘗到甜頭

  4. 要麼有債要不要還?還債重不重要?:我覺得這其實是看你個人的選擇而已,你借錢也可以不還,可以賴賬,可以能拖一年是一年,甚至也可以忘記你借過的債或者否認它,這可以讓你獲得一些短期利益,讓你嘗到一些甜頭,例如技術上你也可以通過欠債,來快速的實現功能,但是不知道大家是否在意自己的信用和口碑,但在如今的文明社會正在構建就是個人的信用體系,國家徵信中心,支付寶的芝麻信用,微信的支付積分,都是在評價你的個人信用,你的還債的及時和履行契約的能力,最終都是體現在你的個人信用積分上,所以說有債不還也是可以的,這取決與你是否在意你的個人信用和口碑,但如果是一個信用不好的人那麼在一個信用體系如此完善的現代社會裡面是很艱難的,別人不敢跟你做生意,你做什麼時候都必須要先交押金,出行乘坐交通工作,信用好的可以走安全通道,你就必須過安檢和全身掃描,所以在不在債務,還不還債務,其實取決於你想不想做一個講信用的人,做一個用誠信為本去安身立命的人,如果你想做一個誠信為本的人,那麼就要放棄短期利益,把目光放的更加長遠一些,記得我曾經看過一個報道,是講京東創業的故事,京東的企業家劉強東對記者說,如果我們想要賺錢,那麼很簡單,我們有很多捷徑可以走,例如我們不給員工買交社保和五險一金,把大量人員全部轉去外包公司,那麼我們每年營業額馬上就會多十幾個億,可以馬上賺很多的錢,但是他沒有選擇這樣做,這樣通過透支的做事方式確實短期可以獲得一些利益,但是長期來看,你失去的人心,失去了企業的誠信

技術債對開發環境和技術氛圍的影響

產品的迭代就像一個運動員在跑步,汽車在前進,技術債就像運動員消耗的體力,汽車在運行當中所出現的各種問題,沒油,爆胎,熄火等等,還債就是給運動員補水,給汽車加油一樣,是為了可以讓運動員和汽車跑的更遠,不至於因為累積技術債而掛掉,為什麼要重視技術債和細節?因為魔鬼藏在細節當中,再舉幾個和生活息息相關的:

  1. 為什麼我們大樓每天檢修消防和安全設備,為什麼消防要經常做演習?在這些沒有真正產出的事情上耗費精力,難道不能等到真正發生火災發生後去撲滅和搶救嗎 ?
  2. 飛機是在起飛前,為什麼需要做那麼多的安全和檢查措施?確保沒有風險后,然後再執行起飛,難道不能先讓飛機起飛,等到出現問題后再去補救和修復嗎 ?
  3. 為什麼我們提倡每天鍛煉身體,健康飲食?為什麼每年要去醫院體檢?難道不應該等到你的身體已經出現問題,或者發出警報后,你再去看醫生嗎?

說到這裏,技術債的重要性毋庸置疑,重視技術債,就是重視於未然,已最低的成本或者零成本,防止未來的災難發生,還不還技術債很多時候是一種選擇,這些選擇決定了你有沒有預先判斷和解決問題的能力,那麼什麼樣的產品不用還技術債?一次性產品,例如一次性杯子,一次性手套用完就扔掉,所以如果產品長期的可持續的發展,那麼技術債的重要性是毋庸置疑的,對方辯友可能會說我們不是不還技術債,我們只是等做完緊急需求等到空閑時間再還技術債,但是經常做項目的同事應該了解,哪有什麼空閑時間?我們在項目衝刺的時候怎麼可能還會有空閑時間,大部分時間所謂的稍後處理,其實就是不處理,屬於掩耳盜鈴,當技術債被遺忘后就成為項目的定時炸彈埋在那裡了,而且技術債的特性前面也說了,所謂的稍後處理,就是讓它利滾利,拖延時間越長,還債的成本越高,而且人們還債的意願就越低,誰也不敢去碰它,例如,你身體出現問題,你不去看醫院檢查和修復問題,而是一直繼續使用和消耗你的身體,拖到最後實在不能動的,你沒辦法去醫院一查,癌症晚期,那時候神仙也沒救了, 而且技術債不單單是技術債,它就像一個垃圾堆一樣,久而久之不處理,慢慢周圍就會產生更多的垃圾,因此產生的“破窗效應”更加是會對未來的項目環境造成很大的影響,大家也會逐漸喪失維護環境的信心,所以我們在討論技術債的時候不僅僅是討論技術債本身,技術債對團隊追求質量的信心,對大家維護環境整潔的积極性都會造成很大的影響,所以我方觀點是,技術債,有債必嘗,越拖成本越高,最好是在發現的時候馬上處理它,不要讓乾淨的房間出現垃圾堆,只有在乾淨的環境下大家才能持續的高效的去創造,一個需求捏着鼻子做,兩個需求捏着鼻子做,久而久之代碼中就散發出臭味,對於大家的工作體驗和項目質量都會產生巨大的影響,如果連工作都不開心,那還談什麼夢想?沒有良好的技術環境企業就無法吸收和留住高質量的技術人才,人才是現代企業的核心競爭力,沒有人才的企業在瞬息萬變的市場上是難以做出快速反應的

技術債和技術價值觀

不重視技術債就是不重視技術,不尊重科學發展,不能客觀的認識和理解技術的複雜性和軟件工程帶來的價值和意義,我們國家近幾年就因為不重視技術吃了不少虧,比如去年的中興通訊公司被制裁,因為沒有自己的技術,芯片被斷供製裁后卻毫無還手之力,國產目前的大多手機廠商看似繁榮,但手機行業的 8,9 成利潤被都被掌握技術的蘋果公司賺走,打開現在的智能手機裏面你會看到,美國的芯片和谷歌的安卓操作系統,日本的鏡頭和相機模組,三星的屏幕,還要在微薄的利潤上繳納高通的芯片稅,實際上國內大多廠商做的都是代加工和組裝的臟活累活,沒有技術的公司,就會受制於人,不僅賺不到錢,而且公司的命運都是由掌握核心技術的公司決定,再比如一個近期的新聞,哈工大的建模軟件被斷供等等例子,不勝枚舉,那麼技術有多重要?我們就用華為來舉例,華為為什麼是一家值得尊重的科技公司,因為他打破了中國自從第二次工業革命以來,但是因為長期技術落後長期受制於人的客觀事實,中國以前的代號叫做世界工廠,只適合做一些勞動密集型產業,但華為讓中國企業在先進的技術領域,同樣是被美國制裁,為什麼華為活的比中興好很多?因為華為重視技術,從海思芯片到5G 再到操作系統,自己擁有產業供應鏈,有自己的的核心技術,才能掌握自己的命運,而且在取得商業上的成功后,也得到的大家的尊重,相同還有最近處於風口的台灣的芯片製造廠商台積電公司,全球唯二掌握 7納米芯片製造技術的芯片公司,因為自主的核心技術在擁有可以在國際上和英特爾平起平坐資本,綜上所述,不重視技術雖然也可以生存,但是重視技術,掌握核心技術,才能走的更遠,我們都知道技術的目的是要體現商業價值,但前提是要擁有核心技術才配擁有商業價值,沒有技術壁壘的企業和人隨時都可能被人替換,而且幾乎沒有什麼成本,重視技術公司才能發展的更遠,才不會受制於人,才能成為頭部玩家,收割行業90%的利潤,才有可能成為一家偉大並且受人尊重的公司,不然你去想想蘋果公司為什麼不放棄技術,微軟和谷歌為什麼不放棄技術,英特爾和高通為什麼不放棄技術,技術很重要,可以讓個人和企業提升競爭力,不容易被淘汰,對於國家和社會,二次工業革命以來,技術改變了我們的生產效率,從而改變我們社會的運行方式,技術幫助解決了困擾我們幾千年的《馬爾薩斯陷阱》,我們國家經歷過近代史的幾百年技術落後的屈辱后,更加的尤為重視技術,我們在1960 年代大家都吃不飽的情況下我們就研發出自己的原子彈,我們國家級的戰略目標《中國製造 2025》就包含的“芯片,人工智能,區塊鏈,機器人,新能源”等等高精尖產業,目的就是讓我們脫離低端製造業,脫離勞動密集型產業,因為沒有技術含量的重複性的勞動工作未來都將被機器和 人工智能 取代,在未來很難被取代就是人類特有的豐富的想象力和創造力。

最後我想再引用 一個真實的故事,是來源於 NASA 的著名文章《為什麼要探索太空?》,文章的背景是來源於 1970年,贊比亞修女 Mary Jucunda 給 NASA 科學家 Ernst Stuhlinger 博士寫了一封信,信中,Mary Jucunda 修女問道:目前地球上還有這麼多小孩子吃不上飯,他怎麼能捨得為遠在火星的項目花費數十億美元。Ernst Stuhlinger 在回信中寫到一個真實的故事如下:

那是在400年前,德國某小鎮里有一位伯爵。他是個心地善良的人,他將自己收入的一大部分捐給了鎮子上的窮人。這十分令人欽佩,因為中世紀時窮人很多,而且那時經常爆發席捲全國的瘟疫。一天,伯爵碰到了一個奇怪的人,他家中有一個工作台和一個小實驗室,他白天賣力工作,每天晚上的幾小時的時間專心進行研究。他把小玻璃片研磨成鏡片,然後把研磨好的鏡片裝到鏡筒里,用此來觀察細小的物件。伯爵被這個前所未見的可以把東西放大觀察的小發明迷住了。他邀請這個怪人住到了他的城堡里,作為伯爵的門客,此後他可以專心投入所有的時間來研究這些光學器件。然而,鎮子上的人得知伯爵在這麼一個怪人和他那些無用的玩意兒上花費金錢之後,都很生氣。“我們還在受瘟疫的苦,”他們抱怨道,“而他卻為那個閑人和他沒用的愛好亂花錢!”伯爵聽到后不為所動。“我會盡可能地接濟大家,”他表示,“但我會繼續資助這個人和他的工作,我確信終有一天會有回報。”果不其然,他的工作(以及同時期其他人的努力)贏來了豐厚的回報:顯微鏡。顯微鏡的發明給醫學帶來了前所未有的發展,由此展開的研究及其成果,消除了世界上大部分地區肆虐的瘟疫和其他一些傳染性疾病。伯爵為支持這項研究發明所花費的金錢,其最終結果大大減輕了人類所遭受的苦難,這回報遠遠超過單純將這些錢用來救濟那些遭受瘟疫的人。

綜上所述,重視技術債就是重視技術,重視技術就是重視細節和未來,魔鬼存在細節當中,細節決定成敗

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

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

程序員過關斬將–分佈式系統消息異常該何去何從

異步處理模型

一旦談到分佈式,微服務等這些具有很高逼格的代名詞,總能讓你在面試中脫穎而出,不是因為這些詞的英文翻譯的好,而是現代互聯網乃至企業級開發確實在分佈式,微服務等模式下取得了良好的架構效果。無論是微服務,還是之前的SOA,總是離不開異步處理模型,小到程序中IO的處理,大到系統間的消息交互,處處都有異步的身影。

談到系統之間的消息異步處理,就不能不談消息隊列(MQ),目前業界比較流行的MQ類型請自行百度腦補。但是需要提醒一下,MQ只是實現數據異步處理的一種解決方案,沒有MQ能不能實現異步處理呢?當然能,最簡單粗暴的莫過於採用數據庫方式,消息生產者直接把數據插入數據庫,消費者採用讀取數據庫的方式來獲取數據,所以MQ並不等於異步處理哦。

異步消息處理最大程度上解耦了各個系統,為每個系統獨立擴展提供了更大空間,但是異步消息處理也同時面臨着一些挑戰,比如:消息管道性能,消息管道的高可用等,其中最貼近業務層的可能非“數據異常處理”莫屬,基本上可認為這是數據處理模型的最末端,數據流向的尾部,但往往卻是業務中比較重要的部分。

如果一條異步消息作為一個分佈式事務中的一環,那還設計到消息處理結果的反饋,分佈式協調器會根據消息結果的成功與否來決定的事務的結果。

單就異步消息消費端來講,根據不同的業務場景就有以下幾種異常處理方案

直接忽略

這是所有異常數據處理方案中最粗暴,同時也是最簡單的一種:發生異常的時候,直接忽略,什麼都不做。

面對異常不採取任何措施,乍一聽這可能是個很糟糕的方案,但是在實際業務中,這可能是完全可以接受的。如果因為錯誤導致的損失很小,甚至可以忽略,但是建立一套錯誤糾正機制的成本遠遠高於忽略異常,這種場景下選擇直接忽略往往是一種更優的方案。而且當錯誤糾正機制設計到需要人工介入操作的時候,代價會更高,而且還會引入影響其他業務的可能,更為可怕的是如果錯誤糾正機制本身出現問題,那代價更是…..

舉個很簡單的栗子:像一些登錄日誌的統計操作,如果處理某個人登錄的數據出現異常,往往會選擇直接忽略。因為統計這種業務本身就帶有數據的容錯機制,100000和100001在統計需求看來沒有什麼區別。

重試

當直接忽略的方案不可行的時候,你可能需要重試的操作。如果在重試的情況下有足夠高的成功率,那重試就是合理的選擇。重試雖然可以改正間接性的錯誤,但是它對那些違反業務規則,違反數據模型的數據無能為力。

在最理想的情況下,如果重試操作是冪等性的,什麼叫冪等性能(自己去百度吧)?事情就會簡單很多,重試操作可以放心大膽的去實施。但是在重試操作經過一段時間或者一定次數之後還未成功的話,多數情況下可能需要有一定的後續策略,比如:重試10次之後如果還是失敗,則放棄。

補償

這個策略是分佈式事務中經常用到的,與其說是補償,不如說是回滾操作。特別是在程序接收到數據會有一系列的操作的情景下,補償操作類似於事務回滾的概念,讓系統回到發生這一系列操作之前的狀態。這種補償的機制非常適合於那種有“事務”需求的場景。

你的業務中有哪些“事務”的需求場景呢?歡迎在留言中體現,另外再稍微提一下,每周送架構書籍的活動仍然在進行哦,歡迎關注

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

【其他文章推薦】

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

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

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

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

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

恕我直言你可能真的不會java第6篇:Stream性能差?不要人云亦云

一、粉絲的反饋

問:stream比for循環慢5倍,用這個是為了啥?
答:互聯網是一個新聞泛濫的時代,三人成虎,以假亂真的事情時候發生。作為一個技術開發者,要自己去動手去做,不要人云亦云。

的確,這位粉絲說的這篇文章我也看過,我就不貼地址了,也沒必要給他帶流量。怎麼說呢?就是一個不懂得測試的、不入流開發工程師做的性能測試,給出了一個危言聳聽的結論。

二、所有性能測試結論都是片面的

性能測試是必要的,但針對性能測試的結果,永遠要持懷疑態度。為什麼這麼說?

  • 性能測試脫離業務場景就是片面的性能測試。你能覆蓋所有的業務場景么?
  • 性能測試脫離硬件環境就是片面的性能測試。你能覆蓋所有的硬件環境么?
  • 性能測試脫離開發人員的知識面就是片面的性能測試。你能覆蓋各種開發人員奇奇怪怪的代碼么?

所以,我從來不相信網上的任何性能測試的文章。凡是我自己的從事的業務場景,我都要在接近生產環境的機器上自己測試一遍。 所有性能測試結論都是片面的,只有你生產環境下的運行結果才是真的。

三、動手測試Stream的性能

3.1.環境

windows10 、16G內存、i7-7700HQ 2.8HZ 、64位操作系統、JDK 1.8.0_171

3.2.測試用例與測試結論

我們在上一節,已經講過:

  • 針對不同的數據結構,Stream流的執行效率是不一樣的
  • 針對不同的數據源,Stream流的執行效率也是不一樣的

所以記住筆者的話:所有性能測試結論都是片面的,你要自己動手做,相信你自己的代碼和你的環境下的測試!我的測試結果僅僅代表我自己的測試用例和測試數據結構!

3.2.1.測試用例一

測試用例:5億個int隨機數,求最小值
測試結論(測試代碼見後文):

  • 使用普通for循環,執行效率是Stream串行流的2倍。也就是說普通for循環性能更好。
  • Stream并行流計算是普通for循環執行效率的4-5倍。
  • Stream并行流計算 > 普通for循環 > Stream串行流計算

3.2.測試用例二

測試用例:長度為10的1000000隨機字符串,求最小值
測試結論(測試代碼見後文):

  • 普通for循環執行效率與Stream串行流不相上下
  • Stream并行流的執行效率遠高於普通for循環
  • Stream并行流計算 > 普通for循環 = Stream串行流計算

3.3.測試用例三

測試用例:10個用戶,每人200個訂單。按用戶統計訂單的總價。
測試結論(測試代碼見後文):

  • Stream并行流的執行效率遠高於普通for循環
  • Stream串行流的執行效率大於等於普通for循環
  • Stream并行流計算 > Stream串行流計算 >= 普通for循環

四、最終測試結論

  • 對於簡單的数字(list-Int)遍歷,普通for循環效率的確比Stream串行流執行效率高(1.5-2.5倍)。但是Stream流可以利用并行執行的方式發揮CPU的多核優勢,因此并行流計算執行效率高於for循環。
  • 對於list-Object類型的數據遍歷,普通for循環和Stream串行流比也沒有任何優勢可言,更不用提Stream并行流計算。

雖然在不同的場景、不同的數據結構、不同的硬件環境下。Stream流與for循環性能測試結果差異較大,甚至發生逆轉。但是總體上而言:

  • Stream并行流計算 >> 普通for循環 ~= Stream串行流計算 (之所以用兩個大於號,你細品)
  • 數據容量越大,Stream流的執行效率越高。
  • Stream并行流計算通常能夠比較好的利用CPU的多核優勢。CPU核心越多,Stream并行流計算效率越高。

stream比for循環慢5倍?也許吧,單核CPU、串行Stream的int類型數據遍歷?我沒試過這種場景,但是我知道這不是應用系統的核心場景。看了十幾篇測試博文,和我的測試結果。我的結論是: 在大多數的核心業務場景下及常用數據結構下,Stream的執行效率比for循環更高。 畢竟我們的業務中通常是實實在在的實體對象,沒事誰總對List<Int>類型進行遍歷?誰的生產服務器是單核?。

五、測試代碼

<dependency>
    <groupId>com.github.houbb</groupId>
    <artifactId>junitperf</artifactId>
    <version>2.0.0</version>
</dependency>

測試用例一:

import com.github.houbb.junitperf.core.annotation.JunitPerfConfig;
import com.github.houbb.junitperf.core.report.impl.HtmlReporter;
import org.junit.jupiter.api.BeforeAll;

import java.util.Arrays;
import java.util.Random;

public class StreamIntTest {

    public static int[] arr;

    @BeforeAll
    public static void init() {
        arr = new int[500000000];  //5億個隨機Int
        randomInt(arr);
    }

    @JunitPerfConfig( warmUp = 1000, reporter = {HtmlReporter.class})
    public void testIntFor() {
        minIntFor(arr);
    }

    @JunitPerfConfig( warmUp = 1000, reporter = {HtmlReporter.class})
    public void testIntParallelStream() {
        minIntParallelStream(arr);
    }

    @JunitPerfConfig( warmUp = 1000, reporter = {HtmlReporter.class})
    public void testIntStream() {
        minIntStream(arr);
    }

    private int minIntStream(int[] arr) {
        return Arrays.stream(arr).min().getAsInt();
    }

    private int minIntParallelStream(int[] arr) {
        return Arrays.stream(arr).parallel().min().getAsInt();
    }

    private int minIntFor(int[] arr) {
        int min = Integer.MAX_VALUE;
        for (int anArr : arr) {
            if (anArr < min) {
                min = anArr;
            }
        }
        return min;
    }

    private static void randomInt(int[] arr) {
        Random r = new Random();
        for (int i = 0; i < arr.length; i++) {
            arr[i] = r.nextInt();
        }
    }
}

測試用例二:

import com.github.houbb.junitperf.core.annotation.JunitPerfConfig;
import com.github.houbb.junitperf.core.report.impl.HtmlReporter;
import org.junit.jupiter.api.BeforeAll;

import java.util.ArrayList;
import java.util.Random;

public class StreamStringTest {

    public static ArrayList<String> list;

    @BeforeAll
    public static void init() {
        list = randomStringList(1000000);
    }

    @JunitPerfConfig(duration = 10000, warmUp = 1000, reporter = {HtmlReporter.class})
    public void testMinStringForLoop(){
        String minStr = null;
        boolean first = true;
        for(String str : list){
            if(first){
                first = false;
                minStr = str;
            }
            if(minStr.compareTo(str)>0){
                minStr = str;
            }
        }
    }

    @JunitPerfConfig(duration = 10000, warmUp = 1000, reporter = {HtmlReporter.class})
    public void textMinStringStream(){
        list.stream().min(String::compareTo).get();
    }

    @JunitPerfConfig(duration = 10000, warmUp = 1000, reporter = {HtmlReporter.class})
    public void testMinStringParallelStream(){
        list.stream().parallel().min(String::compareTo).get();
    }

    private static ArrayList<String> randomStringList(int listLength){
        ArrayList<String> list = new ArrayList<>(listLength);
        Random rand = new Random();
        int strLength = 10;
        StringBuilder buf = new StringBuilder(strLength);
        for(int i=0; i<listLength; i++){
            buf.delete(0, buf.length());
            for(int j=0; j<strLength; j++){
                buf.append((char)('a'+ rand.nextInt(26)));
            }
            list.add(buf.toString());
        }
        return list;
    }
}

測試用例三:

import com.github.houbb.junitperf.core.annotation.JunitPerfConfig;
import com.github.houbb.junitperf.core.report.impl.HtmlReporter;
import org.junit.jupiter.api.BeforeAll;

import java.util.*;
import java.util.stream.Collectors;

public class StreamObjectTest {

    public static List<Order> orders;

    @BeforeAll
    public static void init() {
        orders = Order.genOrders(10);
    }

    @JunitPerfConfig(duration = 10000, warmUp = 1000, reporter = {HtmlReporter.class})
    public void testSumOrderForLoop(){
        Map<String, Double> map = new HashMap<>();
        for(Order od : orders){
            String userName = od.getUserName();
            Double v; 
            if((v=map.get(userName)) != null){
                map.put(userName, v+od.getPrice());
            }else{
                map.put(userName, od.getPrice());
            }
        }

    }

    @JunitPerfConfig(duration = 10000, warmUp = 1000, reporter = {HtmlReporter.class})
    public void testSumOrderStream(){
        orders.stream().collect(
                Collectors.groupingBy(Order::getUserName, 
                        Collectors.summingDouble(Order::getPrice)));
    }

    @JunitPerfConfig(duration = 10000, warmUp = 1000, reporter = {HtmlReporter.class})
    public void testSumOrderParallelStream(){
        orders.parallelStream().collect(
                Collectors.groupingBy(Order::getUserName, 
                        Collectors.summingDouble(Order::getPrice)));
    }
}


class Order{
    private String userName;
    private double price;
    private long timestamp;
    public Order(String userName, double price, long timestamp) {
        this.userName = userName;
        this.price = price;
        this.timestamp = timestamp;
    }
    public String getUserName() {
        return userName;
    }
    public double getPrice() {
        return price;
    }
    public long getTimestamp() {
        return timestamp;
    }

    public static List<Order> genOrders(int listLength){
        ArrayList<Order> list = new ArrayList<>(listLength);
        Random rand = new Random();
        int users = listLength/200;// 200 orders per user
        users = users==0 ? listLength : users;
        ArrayList<String> userNames = new ArrayList<>(users);
        for(int i=0; i<users; i++){
            userNames.add(UUID.randomUUID().toString());
        }
        for(int i=0; i<listLength; i++){
            double price = rand.nextInt(1000);
            String userName = userNames.get(rand.nextInt(users));
            list.add(new Order(userName, price, System.nanoTime()));
        }
        return list;
    }
    @Override
    public String toString(){
        return userName + "::" + price;
    }
}

歡迎關注我的博客,裏面有很多精品合集

  • 本文轉載註明出處(必須帶連接,不能只轉文字):字母哥博客。

覺得對您有幫助的話,幫我點贊、分享!您的支持是我不竭的創作動力! 。另外,筆者最近一段時間輸出了如下的精品內容,期待您的關注。

  • 《手摸手教你學Spring Boot2.0》
  • 《Spring Security-JWT-OAuth2一本通》
  • 《實戰前後端分離RBAC權限管理系統》
  • 《實戰SpringCloud微服務從青銅到王者》
  • 《VUE深入淺出系列》

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

【其他文章推薦】

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

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

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

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

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

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

蒲公英 · JELLY技術周刊 Vol.12 尤雨溪新作 Vite, 你會支持么?

「蒲公英」期刊,每周更新,我們專註於挖掘「基礎技術、工程化、跨端框架技術、圖形編程、服務端開發、桌面開發、人工智能」等多個大方向的業界熱點,並加以專業的解讀;不僅如此,我們還精選凹凸技術文章,向大家呈現團隊內的研究技術方向。

抬頭仰望,蒲公英的種子會生根發芽,如夏花絢爛;格物致知,我們登高遠眺、滄海拾遺,以求積硅步而至千里。

登高遠眺

天高地迥,覺宇宙之無窮

前端框架

Vue3 Composition API 提案

Vue3 其中一個重量級的特性就是 Composition API,它能幫助我們更好地組織代碼。本網頁是 Composition API 的草案,詳細介紹了 Composition API 的設計動機、設計細節、具體 API 用法等。文章篇幅較長,推薦找一個悠閑的周末,泡上咖啡,帶上耳機,細細品讀一番。

工具測評: React Hook Form VS Formik

使用 React 構建表單是一件痛苦的事情,官方推薦了 Formik。本文對使用 Formik 和 React Hook Form 構建表單進行了比較,得出 React Hook Form 比 Formik 更易用、更高效的結論。如果你正巧在這方面有困惑,可實踐嘗試體驗。

Quark-h5 — 從零開始的可視化編輯器

想必你一定使用微場景生成工具製作過炫酷的 h5 頁面,除了感嘆其神奇之處有沒有想過其實現方式呢?本文從零開始實現一個 H5 編輯器項目完整設計思路和主要實現步驟,並開源前後端代碼。有需要的小夥伴可以按照該教程從零實現自己的H5編輯器。

圖形編程

初探虛幻引擎5

5月底, 遊戲公司Epic揭開了虛幻5引擎神秘的面紗,此次更新包含 Nanite虛擬微多邊形 和 全新的動態全局光照Lumen 兩大核心技術,然後展示了該引擎運行在PS5上實時渲染效果,其逼真的光照和媲美電影的細節震驚了整個行業。

工程化

Vite — 入門到實戰

Vite 是 Vue 技術生態新推出的開發工具,針對 Vue 應用的無打包開發服務器,開發者無需藉助 webpack 等打包工具,即可直接在瀏覽器中預覽 Vue 項目。Vite 的原理與本技術周刊之前介紹過的 snowpack 有着異曲同工之處,Vite 本身也表示一部分靈感來自 snowpack 項目。本文從 0 開始一步一步實現了一個簡易版本的 Vite 來講解 Vite 的技術原理,讀過本文之後,再去閱讀 Vite 的項目源碼,相信會有不小的收穫。

人工智能

杜克大學出品 AI 黑科技 PULSE 算法,讓你的照片有碼變高清

近日杜克大學開源了新型超分辨率圖像算法 PULSE,可將16×16像素的低分辨率人像放大到1024×1024像素的高分辨率。

工具推介

手把手教你快速搭建專屬的 StoryBook

Storybook是一個輔助UI控件開發的工具。通過story創建獨立的控件,讓每個控件開發都有一個獨立的開發調試環境。 Storybook的運行不依賴於項目,開發人員不用擔心由於開發環境、依賴問題導致不能開發控件。Storybook支持的框架覆蓋主流的框架(React、Vue、Angular)。 由於使用React作為技術棧,本文將介紹使用react的項目如何配置Storybook環境。

滄海拾遺

滄海拾遺,積跬步以至千里

ELF – 靈活可擴展的 HTML5 構建工具

前端工程化的問題由來已久,除了尤老師正在努力的方向,還出現過很多優秀的小工具幫助我們解決各個方面的問題,ELF 就是其中一種解放我們重複勞動的構建工具之一。

用 Git 鈎子進行簡單自動部署

除了這些工具,工程化中也還是有很多小問題,可以用很多方法去解決,自動化部署就是其中之一,如果你還不懂的如何利用 Git Hook 完成自動化部署的方法,趕緊補起這一課吧,未來正在向你招手~

歡迎關注凹凸實驗室博客:aotu.io

或者關注凹凸實驗室公眾號(AOTULabs),不定時推送文章:

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

【其他文章推薦】

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

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

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

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

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

【代碼修鍊系列分享】改掉這些壞習慣,還怕寫不出健壯的代碼?(一)

Code Review 是一場苦澀但有意思的修行。

近期對團隊負責的項目,進行了一次 Code Review,代碼評審過程中遇到的那些編碼壞習慣,笑的合不攏嘴。不過,評審中很多代碼編寫問題,以往都多次提及過,所以還是按奈不住心中怒氣的小火苗。

作為用代碼編寫人生的程序員,能擁有寫一手健壯代碼的本領,那絕對很有必要。因為健壯的代碼能夠把 Bug 扼殺在搖籃里,能夠讓問題止步於上線前。

那麼,怎樣才能練就寫出健壯代碼的本領呢?

本次着重談談那些代碼編寫時的一些壞習慣,改掉這些壞習慣,相信會向健壯代碼邁進一大步。

一、編碼時易忽略性能的壞習慣 

壞習慣一:調用低效的構造器,創建包裝類型的對象。

反例:

正解:

解惑:使用 Long.valueOf(long) 代替 new Long(long),可以提高性能。

如 Long 源碼所示,如果當傳入的值介於 -128~127 時,會優先從緩存中返回緩存的值,而不是進行 new,充分利用空間換取時間,所以當值介於 -128~127 時,採取 Long.valueOf(long) 的效率要比 new Long(long) 快很多。

建議:

  • 凡是涉及到 Long, Integer, Short, Character 以及 Byte 創建對象時,優先採用高效的 valueOf() 方法,而不是直接用低效構造器創建實例。
  • 享元設計模式在這兒用到了,什麼是享元模式?(留個作業)

壞習慣二:使用 keySet 迭代器迭代 Map,獲取對應的 value。

反例:

正解:

解惑:keySet 方式遍歷 Map 的性能不如 entrySet 性能好。

如果採用 keySet 的方式獲取 Map 中 key,然後通過 key 獲取 Map 對應的 value,如上圖 HashMap 源碼所示,每次都需要通過 key 去計算對應的 hash 值,然後再通過 hash 值獲取對應的 value,效率會低不少。

建議:

  • 如果想獲取 Map 對應的 key 和 value,則推薦使用 entrySet。
  • 如果只是單純獲取 Map 對應的 key,則推薦使用 keySet。

壞習慣三:使用 new Date().getTime() 獲取當前時間戳。

反例:

正解:

解惑:如下圖 Date 源碼所示,Date 構造方法中最終還是調用了 System.currentTimeMillis() 方法來獲取時間戳。

建議:

  • 獲取當前毫秒數採用 System.currentTimeMillis(),而不是new Date().getTime(); 
  • 獲取更加精確的納秒級時間值,採用 System.nanoTime;
  • 在 JDK8 中,針對統計時間等場景,建議使用 Instant 類。

壞習慣四:循環中使用 ”+“ 號拼接字符串。

反例:

正解:推薦使用 StringBuilder/StringBuffer 進行字符串拼接。

解惑:「Java 程序該怎麼優化?技巧篇」以前的這篇分享做過試驗,本次不贅述。

二、編碼時易犯的一些小毛病 

毛病一:變量作為 equals() 方法的調用方。

反例:

正解:

解惑:totalCount 應該作為方法  equals() 的調用方,而不是參數 作為調用方,因為參數作為調用方會出現空指針異常。

建議:

  • 字符串的比較,常量建議當做 equals() 方法的調用方;
  • 字符串判斷空,建議用項目中的工具類。

毛病二:對象為 null 的檢查滯后。

反例:

正解:請在使用 data 對象前,做好是否為 null 的判斷。

解惑:後置對象為空的檢查,可能會導致空指針異常的發生。

毛病三:要求傳入非空的方法,傳入空值。

反例:

正解:signInfo 變量的值可能存在為空的情形,導致發生空指針異常。

建議:發生異常的時候,方法該終止就終止;盡量做好防禦性編程,該校驗的參數進行必要的校驗。

三、寄語寫最後 

常在河邊站哪有不濕鞋,再牛逼的碼農,編碼也會有失誤的時候,很有必要藉助一款代碼檢查工具,做最後一道防線。

在這裏,推薦 FindBugs、Checkstyle、SonarQube 三款代碼檢查工具,不過我用的最多的當屬 FindBugs,可以拿去一試,使用門檻幾乎為零。

好了,編碼中易犯的那些臭毛病,本次就談到這裏,不知道有多少條是觸動了你的心弦,希望有則改之。

關注同名公眾號:一猿小講,回復「1024」可以獲取精心為您準備的職場打怪進階資料。

一起聊技術、談業務、噴架構,少走彎路,不踩大坑,會持續輸出原創精彩分享,敬請期待!

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

【其他文章推薦】

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

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

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

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

※超省錢租車方案

【故障公告】阿里雲 RDS 實例 CPU 100% 故障引發全站無法正常訪問

非常抱歉,今天凌晨 3:20~8:30 左右,我們使用的阿里雲 RDS 實例 SQL Server 2016 標準版突然出現 CPU 100% 故障,造成全站無法正常訪問,由此給您帶來巨大的麻煩,請您諒解。

問題很奇怪,故障期間是數據庫服務器負載極低的時間段。從阿里雲 RDS 控制台 CloudDBA 看,故障期間下面的一個 SQL 語句大量執行,並且極其消耗 CPU 。

開始我們以為是這個 SQL 語句引發的故障,但排查下來這個 SQL 語句本身並沒有性能問題,而且已經使用了至少6個月。

最終恢復正常是通過 RDS 的2次主備切換,當發現故障后,我們立即進行主備切換,但切換后 CPU 依然 100% ,然後我們排查 SQL 語句的問題,排查未果,然後又進行一次主備切換,才恢復正常。

事後分析后發現應該是第一次主備切換沒有成功完成,阿里雲 RDS 控制台查看不到主備切換日誌,但2次切換,只有第2次收到郵件通知,由此可以推斷。

您的雲數據庫RDS實例:xxx(名稱:enable or disable task fetching while rds2slb transgfer.)任務觸發切換完畢,請檢查程序連接是否正常,建議設置自動重連機制以避免切換影響。

問題的原因有待進一個分析,再次抱歉由此給您帶來的麻煩。

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

【其他文章推薦】

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

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

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

※超省錢租車方案

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

《T-GCN: A Temporal Graph Convolutional Network for Traffic Prediction》 論文解讀

論文鏈接:https://arxiv.org/abs/1811.05320

最近發現博客好像會被CSDN和一些奇怪的野雞網站爬下來?看見有人跟爬蟲機器人單方面討論問題我也蠻無奈的。總之原作者Missouter,博客鏈接https://www.cnblogs.com/missouter/,歡迎交流。

整理、精鍊了一下這篇論文的思路。

Abstract:

交通預測的難點在於交通拓撲網絡複雜的結構與隨時間動態發生的交通變化;為了提取交通網的空間與時間特徵,文章提出了一種時間性的圖卷積網絡模型,結合了門級循環單元(GRU)與圖卷積網絡(GCN)。其中圖卷積網絡被用於提取複雜拓撲結構中的空間性信息,而門級循環單元則為了提取時間關係,被用於學習交通數據中的動態數據。

Introduction:

交通預測過程:分析交通道路狀況,包含流量、速度、密度;挖掘交通模式,同時對道路上的交通狀況進行預測。

預測結果:交通管理者預測擁堵狀況、限制交通工具的科學基礎、出行者效率選擇出行工具、路線的保障。

難點:

1、 空間關係:交通流的變化被拓撲結構的城市交通網主導,上游的交通狀態通過傳輸作用影響下游的交通狀態,而下游交通狀態通過反饋作用再影響上游交通。

2、 時間關係:交通流隨時間動態變化,主要表現在周期性和趨勢上;現有的交通狀況被前一刻的交通狀況影響。

現存交通預測方法缺陷:一些交通預測方法(ARIMA、Kalman filtering model,etc)只關注了交通狀況的動態變化而忽視了空間關係,導致交通狀態的變化不被道路網約束,同時一些模型嘗試使用卷積神經網絡進行空間性建模,但這些模型一般只使用於歐幾里得類型的數據(規則矩陣、圖像等),無法在拓撲結構的城市交通網絡中運作。

T-GCN貢獻:

1、 結合了GCN與GRU,內容與abstract重複,不做多提。

2、 T-GCN的預測結果展示了一個不同視角下的穩定狀態,表示T-GCN除了預測短周期的變化,還能預測長周期的變化。

3、 我們使用兩個現實的數據集評估模型,相較其他預測模型,減少了1.5%~57.8%的預測錯誤率。

Related work:

交通預測方法分類:模型驅動的方法、數據驅動的方法;

模型驅動的方法:解釋即時、穩定的交通關係如交通流量、速度與密度,需要複雜細緻的系統建模與巨大的算力,且由於諸多因素的影響,現實環境中交通數據的多樣性無法被精確描繪。

數據驅動的方法:基於數據統計所得規律推斷交通狀況的變化。不分析物理屬性與交通系統的動態變化有着較高的複雜度。其中的historical average model不需要假設、計算過程簡單,但精確度不佳。

擁有更高精確度的模型被提出,分為兩種:含參數的與非參數的。

含參模型假定回歸函數,參數在對原始數據的處理過程中確定。傳統含參的模型是在系統模型為靜態的假設基礎上建立的,反映不了交通系統的非線性與不確定性,也克服不了交通事故等突發隨機事件的困難。不含參數的模型只需要足夠的歷史數據就能自動學習靜態規律特徵,但先前形如LSTM、GRU的模型僅僅考慮了時間關係而忽略了空間關係,不能精確預測道路上的交通信息,如何充分利用空間信息成了交通預測的關鍵,而利用CNN進行空間關係提取的模型雖然在交通預測上取得了顯著進步,但無法應用於複雜拓撲結構的城市交通網,隨着GCN研究的深入,拓撲結構空間特徵的提取問題也得到解決。

(這段論文寫的有點長…堆了一堆文字讀的我有點難受)

Methodology:

問題定義:根據歷史數據預測某一特定時間段的交通信息,交通數據通常被定義為速度、密集程度、交通流。使實驗不失普遍性,在實驗章節使用交通速度作為交通數據的代表。(Without loss of generality, we use traffic speed as an example of traffic information in experiment section.這句語感有點僵了)

定義:設定無權圖G=(V, E),V、E分別代表道路點與邊,N為道路的數量,設定鄰接N*N矩陣A表達道路與道路之間的聯繫;設定N*P特徵矩陣X,P代表點屬性特徵的數量,即歷史時間序列的長度,使用N*i矩陣 表示在時刻i,每條道路上的速度,屬性特徵也可以是交通速度、交通流等屬性。

問題轉化:交通空間-時間性預測被轉化為學習以拓撲圖G為前置與特徵矩陣X的映射函數,計算得到在下T時刻的交通信息:

過程概覽:首先以長度為n的歷史時間序列數據作為輸入,使用GCN接收拓撲結構的空間信息,其次將接收到的空間、時間信息輸入GRU當中,獲取各個單元間的動態信息變化,以提取時間性的特徵,最後在全連接層獲得結果。

空間關係建模:利用GCN提取圖結構的數據,通過給定的鄰接矩陣A、特徵矩陣X,GCN在圖上通過提取相鄰結點特徵構建傅里恭弘=叶 恭弘域,使用堆疊的卷積網絡,表示為:

Â為A與單位矩陣I相加所得, D為度矩陣, Hl為第l層的輸出, θl包含該層的參數, σ代表非線性回歸的激活函數。(我懷疑論文這裏的上標打錯了)

兩層GCN模型可被表示為:

其中 ,P*H的 矩陣代表從輸入到隱藏層的權重,P為特徵矩陣的長度,H為隱藏單元的數量;H*T的W1代表從隱藏層到輸出層的權重, f(X,A)∈RN*T代表長度為T的預測輸出,ReLU()作為修正線性單元,作激活層用。

時間關係建模:被廣泛應用的循環神經網絡因梯度消失/爆炸的原因,不適用於長周期的預測;LSTM與GRU作為循環神經網絡的變種,克服了上述問題。其共同原理都是利用門級機制儲存盡可能長的周期信息;LSTM因其複雜的結構,GRU結構更加簡單,故計算時間更短。

 

圖中ht-1表示t-1時刻的隱藏狀態, xt表示t時刻的交通信息, rt代表重置門,用於控制先前時刻狀態信息的度量; ut為上傳門,用於控制上傳到下一狀態的信息度量; ct為t時刻時儲存的信息, ht為t時刻的輸出狀態;總的來說,GRU通過獲取t-1時刻的隱藏狀態與當時的交通狀態信息得到t時刻的交通信息。

T-GCN:

 

T-GCN的結構如圖,右側為T-GCN的處理單元: 為t-1時刻的輸出,GC為圖卷積過程,ut 、 rt分別為上傳門與重置門, 為t時刻的輸出。具體計算過程為:

f(A,X)代表前文定義的GCN計算過程,w與b代表訓練過程中的權重與偏移量。(很大部分照搬了GNU的公式)

損失函數:

目標:最小化真實交通速度與預測交通速度的誤差。Y分別代表真實速度與預測速度,損失函數如下:

其中 λLreg用於防止過擬合。

Experiments:

選取數據集:

1、SZ-taxi:數據分為兩部分,156*156的鄰接矩陣表示路與路之間的空間關係,描述每條路上隨時間變化的特徵矩陣。

2、Losloop:由鄰接矩陣與特徵矩陣組成,鄰接矩陣由交通網絡中的傳感器計算;同時作者對數據集中的殘缺部分使用線性填充的方法進行了補全。

輸入的數據全部進行了歸一化處理,80%的數據用於訓練,而20%的數據被用於測試。實驗對接下來15、30、45、60分鐘的交通速度進行預測。

評價指標:

文章列出了五個用於評價T-GCN預測表現的指標:

1、均方根誤差:

2、平均絕對誤差:

3、準確率:

4、 確定係數:

5、可釋方差值:

其中yj’i分別代表第j次時間、第i條路的真實交通信息與預測信息,M為時間樣本的數量,N為路的數量,Y分別代表 與 的集合, 帶上劃線的Y為Y的平均數。

R2與var用於計算相關係數,衡量預測結果表示實際數據的能力,數值越大則預測能力越優越。

選取模型參數:

超參數:包括學習率(0.001)、抓取數量(32)、訓練輪數(5000)、隱藏單元數量(通過多次實驗取最優預測效果)

針對SZ-taxi文章選取[8,16,32,64,100,128],分析預測準確率的變化,得到不同隱藏單元數量下RASE與MAE、Accuracy、R2 、var的值如圖:

 

顯然數量為100時效果是最好的(真有這麼巧的事情嘛…)。隱藏單元超過一定數量性能下降的原因在於數據單元超過一定值後計算複雜度增加,且訓練數據會出現過擬合現象。

實驗結果:

將T-GCN預測效果與baseline對比,包括HA、ARIMA、SVR、GCN、GRU,所得實驗數據如下:

 

高預測精準度:門級循環網絡的預測精度顯著高於其他baseline算法,HA、ARIMA、SVR等算法因難以處理複雜、非固定的時間數據而預測效果不佳,GCN預測效果不佳的原因僅僅在於僅考慮了空間特徵而忽略了交通數據是一個典型的時間序列數據這一事實。

ARIMA比HA檢測效果弱的原因在於ARIMA不適用於長周期的檢測,且計算每個節點誤差的方式導致一些數據中的波動會導致最終的計算錯誤。

時空預測能力:為了檢驗模型是否能夠從交通數據中提取時空特徵,文章將模型與GCN、GNU模型進行了比較:

長周期預測能力:在不同的時間範圍(文章只寫了“horizon”,根據前後文認定為是時間範圍)下,T-GCN都能獲得最好的預測效果且預測結果擁有較小的變化趨勢,證明了T-GCN擁有更好的長周期預測能力;文章以T-GCN在不同時間點的結果作為論證:

 

干擾分析與魯棒性:通過向數據中添加兩種不同的噪聲檢驗T-GCN的魯棒性,高斯分佈、泊松分佈的 值作為自變量被改變,分別添加到兩種數據集上,得到結果:

 

由評估指標的隨噪聲的變化細微可以得出T-GCN具強魯棒性的結論。

模型解釋:

文章將T-GCN所得的預測結果與真實數據可視化得到結論:

 

模型對於峰谷值的預測效果不佳,原因在於T-GCN在傅里恭弘=叶 恭弘域中定義了平滑過濾器,通過不斷移動過濾器提取空間特徵,造成了全局預測中的細微變化,使得曲線中的峰變得平滑。

文章還指出預測與實際之間存在固定誤差,由數據集的特殊性造成,即SZ-taxi數據集表示某時刻出租車的數量可能為0,但實際道路上車輛的數量不一定為0;

對這篇論文的解讀就到這裏結束了,Methodology部分使用GCN與GNU的理論,設計了新的計算單元;Experiment部分很讓我受教,對實驗的條件、前提設定的非常詳盡,在介紹數據集與衡量指標后,對選取實驗參數作了嚴格的實驗論證;對實驗結果進行了詳盡的分析,通過與其他幾個模型的預測結果進行對比,從空間、時間、時間跨度預測、精度、魯棒性等角度對模型的優越性進行對比論證,數據的選取與展示都精確地契合了論證的論點;最後關於傅里恭弘=叶 恭弘與峰谷偏差的模型解釋很精妙。

關於GNU、LSTM的學習解讀會繼續跟進。同時也會對這篇論文git上的開源代碼進行解讀與實驗復現。

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

奧迪看衰純電動車市場 新款車型延後推出

 

隨著寶馬等公司逐漸擴大投入純電動車市場,奧迪並未選擇加速緊追,相反的,奧迪並不看好純電動車市場的發展。

近日歐洲媒體 Worldcarfuns 報導,奧迪公司並不想花費大量的時間和金錢來推出出色的電動車型,來與寶馬、特斯拉等品牌的電動車競爭。

根據奧迪的純電動車產品規劃,其曾計劃推出的 R8 電動版、Q6 電動版等 4 款新純電動車,量產時間都將推遲延後。對此,奧迪董事會成員兼銷售總監 Luca de Meo 表示,奧迪對純電動車的前景並不看好,因此暫時沒有生產純電動車的計劃。

 

(圖片來源:)

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

【其他文章推薦】

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

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

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

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

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

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

澳大學生研發電動車 續航力超越 Tesla

    電動車廠特斯拉遇到了新對手,但這並不是寶馬也不是通用汽車,而是來自澳洲大學的一群學生。這些學生研發 eVe 電動車,單次充電後以每小時 100 公里的時速行駛了 500 公里,這也打破了塵封已久的電動車續航世界紀錄。此前,這項記錄為單次充電後,以 72 公里每小時速度跑滿 500 公里。   一直以來,續航能力是電動車推廣普及的最大難題,而 eVe 打破記錄外也表明,電動車可以在合理高速下行駛數百公里。eVe 由澳洲新南威爾士大學 Sunswift 團隊學生研發,到如今已是第五代版本。Sunswift 團隊早前曾以打造太陽能汽車名聲大噪,其推出的 IVy 太陽能動力車在 2011 年跑出了時速達 88 公里/小時,創造了該領域車輛的最快時速記錄。   eVe 電動車配有傳統電池,可以採用使用常規充電樁進行電力補充,其也可以通過覆蓋車身的太陽能電池板充電,車身重量為 317公斤(700磅),使用重量 59 公斤的松下電池,用常規家用插座可在 8 小時內充滿電,若接入工業用電插口,可在 5 小時內充滿。Sunswift 團隊表示,如果 eVe 停在太陽下 8 個小時,搭載的 800 瓦太陽能電池組可以提供 2 小時行駛里程,且太陽能面板還可在車輛行駛過程中收集能量。   Sunswift 團隊負責人Hayden Smith 表示,eVe 證明太陽能電動車是傳統石化燃料汽車可行性替代方案。該團隊希望以此激勵商業公司進入這一技術領域,並促使 eVe 成為澳洲首個合法上路的太陽能電動車。     (圖片來源:)

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

【其他文章推薦】

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

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

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

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

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

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