標籤: 南投搬家公司費用

  • 通過與C++程序對比,徹底搞清楚JAVA的對象拷貝

    通過與C++程序對比,徹底搞清楚JAVA的對象拷貝

    目錄

    • 一、背景
    • 二、JAVA對象拷貝的實現
      • 2.1 淺拷貝
      • 2.2 深拷貝的實現方法一
      • 2.3 深拷貝的實現方法二
        • 2.3.1 C++拷貝構造函數
        • 2.3.2 C++源碼
        • 2.3.3 JAVA通過拷貝構造方法實現深拷貝
    • 四、總結

    一、背景

    JAVA編程中的對象一般都是通過new進行創建的,新創建的對象通常是初始化的狀態,但當這個對象某些屬性產生變更,且要求用一個對象副本來保存當前對象的“狀態”,這時候就需要用到對象拷貝的功能,以便封裝對象之間的快速克隆。

    二、JAVA對象拷貝的實現

    2.1 淺拷貝
    • 被複制的類需要實現Clonenable接口;
    • 覆蓋clone()方法,調用super.clone()方法得到需要的複製對象;
    • 淺拷貝對基本類型(boolean,char,byte,short,float,double.long)能完成自身的複製,但對於引用類型只對引用地址進行拷貝。
      — 下面我們用一個實例進行驗證:
    /**
     * 單隻牌
     *
     * @author zhuhuix
     * @date 2020-06-10
     */
    public class Card implements Comparable, Serializable,Cloneable {
    
        // 花色
        private String color = "";
        //数字
        private String number = "";
    
        public Card() {
        }
    
        public Card(String color, String number) {
            this.color = color;
            this.number = number;
        }
    
        public String getColor() {
            return this.color;
        }
    
        public void setColor(String color) {
            this.color = color;
        }
    
        public String getNumber() {
            return this.number;
        }
    
        public void setNumber(String number) {
            this.number = number;
        }
    
        @Override
        public String toString() {
            return this.color + this.number;
        }
    
        @Override
        public int compareTo(Object o) {
            if (o instanceof Card) {
                int thisColorIndex = Constant.COLORS.indexOf(this.getColor());
                int anotherColorIndex = Constant.COLORS.indexOf(((Card) o).getColor());
                int thisNumberIndex = Constant.NUMBERS.indexOf(this.getNumber());
                int anotherNumberIndex = Constant.NUMBERS.indexOf(((Card) o).getNumber());
    
                // 大小王之間相互比較: 大王大於小王
                if ("JOKER".equals(this.color) && "JOKER".equals(((Card) o).getColor())) {
                        return thisColorIndex > anotherColorIndex ? 1 : -1;
                }
    
                // 大小王與数字牌之間相互比較:大小王大於数字牌
                if ("JOKER".equals(this.color) && !"JOKER".equals(((Card) o).getColor())) {
                    return 1;
                }
                if (!"JOKER".equals(this.color) && "JOKER".equals(((Card) o).getColor())) {
                    return -1;
                }
    
                // 数字牌之間相互比較: 数字不相等,数字大則牌面大;数字相等 ,花色大則牌面大
                if (thisNumberIndex == anotherNumberIndex) {
                    return thisColorIndex > anotherColorIndex ? 1 : -1;
                } else {
                    return thisNumberIndex > anotherNumberIndex ? 1 : -1;
                }
    
            } else {
                return -1;
            }
        }
    
        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    }
    
    /**
     * 撲克牌常量定義
     *
     * @author zhuhuix
     * @date 2020-06-10
     */
    public class Constant {
    
        // 紙牌花色:黑桃,紅心,梅花,方塊
        final static List<String> COLORS = new ArrayList<>(
                Arrays.asList(new String[]{"", "", "", ""}));
        // 紙牌数字
        final static List<String> NUMBERS = new ArrayList<>(
                Arrays.asList(new String[]{"3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A", "2"}));
        // 大王小王
        final static List<String> JOKER = new ArrayList<>(
                Arrays.asList(new String[]{"小王","大王"}));
    }
    
    /**
     * 整副副撲克牌
     *
     * @author zhuhuix
     * @date 2020-06-10
     */
    public class Poker implements Cloneable, Serializable {
    
        private List<Card> cards;
    
        public Poker() {
            List<Card> cardList = new ArrayList<>();
            // 按花色與数字組合生成52張撲克牌
            for (int i = 0; i < Constant.COLORS.size(); i++) {
                for (int j = 0; j < Constant.NUMBERS.size(); j++) {
                    cardList.add(new Card(Constant.COLORS.get(i), Constant.NUMBERS.get(j)));
                }
            }
            // 生成大小王
            for (int i = 0; i < Constant.JOKER.size(); i++) {
                cardList.add(new Card("JOKER", Constant.JOKER.get(i)));
            }
    
            this.cards = cardList;
        }
    
       
        // 從整副撲克牌中抽走大小王
        public void removeJoker() {
            Iterator<Card> iterator = this.cards.iterator();
            while (iterator.hasNext()) {
                Card cardJoker = iterator.next();
                if (cardJoker.getColor() == "JOKER") {
                    iterator.remove();
                }
            }
        }
    
        public List<Card> getCards() {
            return cards;
        }
    
        public void setCards(List<Card> cards) {
            this.cards = cards;
        }
    
        public Integer getCardCount() {
            return this.cards.size();
        }
    
        @Override
        public String toString() {
            StringBuilder poker = new StringBuilder("[");
            Iterator<Card> iterator = this.cards.iterator();
            while (iterator.hasNext()) {
                poker.append(iterator.next().toString() + ",");
            }
            poker.setCharAt(poker.length() - 1, ']');
            return poker.toString();
        }
    
        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    }
    
    
    /**
     * 測試程序
     *
     * @author zhuhuix
     * @date 2020-6-10
     */
    public class PlayDemo {
    
        public static void main(String[] args) throws CloneNotSupportedException {
    
            // 生成一副撲克牌並洗好牌
            Poker poker1 = new Poker();
            System.out.println("新建:第一副牌共 "+poker1.getCardCount()+" 張:"+poker1.toString());
    
            Poker poker2= (Poker) poker1.clone();
            System.out.println("第一副牌拷頁生成第二副牌,共 "+poker2.getCardCount()+" 張:"+poker2.toString());
    
            poker1.removeJoker();
    
            System.out.println("====第一副牌抽走大小王后====");
            System.out.println("第一副牌還有 "+poker1.getCardCount()+" 張:"+poker1.toString());
            System.out.println("第二副牌還有 "+poker2.getCardCount()+" 張:"+poker2.toString());
    
        }
    
    }
    
    • 運行結果:
      在第一副的對象中抽走了“大小王”,克隆的第二副的對象的“大小王”竟然也被“抽走了”
    2.2 深拷貝的實現方法一
    • 被複制的類需要實現Clonenable接口;
    • 覆蓋clone()方法,自主實現引用類型成員的拷貝複製。
      — 我們只要改寫一下Poker類中的clone方法,讓引用類型成員實現複製:
    /**
     * 整副副撲克牌--自主實現引用變量的複製
     *
     * @author zhuhuix
     * @date 2020-06-10
     */
    public class Poker implements Cloneable, Serializable {
    
        private List<Card> cards;
    
        public Poker() {
            List<Card> cardList = new ArrayList<>();
            // 按花色與数字組合生成52張撲克牌
            for (int i = 0; i < Constant.COLORS.size(); i++) {
                for (int j = 0; j < Constant.NUMBERS.size(); j++) {
                    cardList.add(new Card(Constant.COLORS.get(i), Constant.NUMBERS.get(j)));
                }
            }
            // 生成大小王
            for (int i = 0; i < Constant.JOKER.size(); i++) {
                cardList.add(new Card("JOKER", Constant.JOKER.get(i)));
            }
    
            this.cards = cardList;
        }
    
        // 從整副撲克牌中抽走大小王
        public void removeJoker() {
            Iterator<Card> iterator = this.cards.iterator();
            while (iterator.hasNext()) {
                Card cardJoker = iterator.next();
                if (cardJoker.getColor() == "JOKER") {
                    iterator.remove();
                }
            }
        }
    
        public List<Card> getCards() {
            return cards;
        }
    
        public void setCards(List<Card> cards) {
            this.cards = cards;
        }
    
        public Integer getCardCount() {
            return this.cards.size();
        }
    
        @Override
        public String toString() {
            StringBuilder poker = new StringBuilder("[");
            Iterator<Card> iterator = this.cards.iterator();
            while (iterator.hasNext()) {
                poker.append(iterator.next().toString() + ",");
            }
            poker.setCharAt(poker.length() - 1, ']');
            return poker.toString();
        }
    
    	// 遍歷原始對象的集合,對生成的對象進行集合複製
        @Override
        protected Object clone() throws CloneNotSupportedException {
            Poker newPoker = (Poker)super.clone();
            newPoker.cards = new ArrayList<>();
            newPoker.cards.addAll(this.cards);
            return newPoker;
        }
    }
    
    
    • 輸出結果:
      — 通過自主實現引用類型的複製,原對象與對象的拷貝的引用類型成員地址不再關聯
    2.3 深拷貝的實現方法二
    • 在用第二種方式實現JAVA深拷貝之前,我們首先對C++程序的對象拷貝做個了解:
    2.3.1 C++拷貝構造函數

    C++拷貝構造函數,它只有一個參數,參數類型是本類的引用,且一般用const修飾

    2.3.2 C++源碼
    // 單隻牌的類定義
    // Created by Administrator on 2020-06-10.
    //
    
    #ifndef _CARD_H
    #define _CARD_H
    
    #include <string>
    
    using namespace std;
    
    class Card {
    private :
        string color;
        string number;
    public:
        Card();
    
        Card(const string &color, const string &number);
    
        const string &getColor() const;
    
        void setColor(const string &color);
    
        const string &getNumber() const;
    
        void setNumber(const string &number);
    
        string toString();
    
    };
    
    
    #endif //_CARD_H
    
    // 單隻牌類的實現
    // Created by Administrator on 2020-06-10.
    //
    
    #include "card.h"
    
    Card::Card(){}
    
    Card::Card(const string &color, const string &number) : color(color), number(number) {}
    
    const string &Card::getColor() const {
        return color;
    }
    
    void Card::setColor(const string &color) {
        Card::color = color;
    }
    
    const string &Card::getNumber() const {
        return number;
    }
    
    void Card::setNumber(const string &number) {
        Card::number = number;
    }
    
    
    string Card::toString() {
        return getColor()+getNumber();
    }
    
    
    
    
    
    // 撲克牌類的定義
    // Created by Administrator on 2020-06-10.
    //
    
    #ifndef _POKER_H
    #define _POKER_H
    
    #include <vector>
    #include "card.h"
    
    using namespace std;
    
    const int COLOR_COUNT=4;
    const int NUMBER_COUNT=13;
    const int JOKER_COUNT=2;
    
    const string COLORS[COLOR_COUNT] = {"", "", "", ""};
    const string NUMBERS[NUMBER_COUNT]={"3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A", "2"};
    const string JOKER[JOKER_COUNT] ={"小王","大王"};
    
    class Poker {
    private:
        vector<Card> cards;
    public:
        Poker();
    
        Poker(const Poker &poker);
    
        const vector<Card> &getCards() const;
    
        void setCards(const vector<Card> &cards);
    
        int getCardCount();
    
        void toString();
    
        void clear();
    };
    
    
    #endif //_POKER_H
    
    // 撲克牌類的實現
    // Created by zhuhuix on 2020-06-10.
    //
    
    #include "Poker.h"
    #include <iostream>
    
    const vector<Card> &Poker::getCards() const {
        return this->cards;
    }
    
    void Poker::setCards(const vector<Card> &cards) {
        Poker::cards = cards;
    }
    
    // 構造函數
    Poker::Poker() {
        for (int i = 0; i < NUMBER_COUNT; i++) {
            for (int j = 0; j < COLOR_COUNT; j++) {
                this->cards.emplace_back(COLORS[j], NUMBERS[i]);
            }
        }
        for (int i = 0; i < JOKER_COUNT; i++) {
            this->cards.emplace_back("JOKER", JOKER[i]);
        }
    }
    
    // 拷貝構造函數
    Poker::Poker(const Poker &poker) {
        for (int i = 0; i < poker.getCards().size(); i++) {
            this->cards.emplace_back(poker.cards[i].getColor(), poker.cards[i].getNumber());
        }
    }
    
    int Poker::getCardCount() {
        return this->cards.size();
    }
    
    void Poker::toString() {
        cout << "共" << getCardCount() << "張牌:";
        cout << "[";
        for (int i = 0; i < this->cards.size(); i++) {
            cout << this->cards[i].toString();
            if (i != getCardCount() - 1) {
                cout << ",";
            }
        }
        cout << "]" << endl;
    
    }
    
    void Poker::clear() {
        this->cards.clear();
    }
    
    
    // 主測試程序
    // Created by Administrator on 2020-06-10.
    //
    
    #include "Poker.h"
    #include <iostream>
    
    using namespace std;
    
    int main() {
        Poker poker1;
        cout << "第一副牌:";
        poker1.toString();
        // 通過拷貝構造函數生成第二副牌
        Poker poker2(poker1);
        cout << "第二副牌:";
        poker2.toString();
        // 清除撲克牌1
        poker1.clear();
        cout << "清空后,第一副牌:";
        poker1.toString();
        cout << "第二副牌:";
        poker2.toString();
        return 0;
    }
    
    • 輸出:
    2.3.3 JAVA通過拷貝構造方法實現深拷貝
    • JAVA拷貝構造方法與C++的拷貝構造函數相同,被複制對象的類需要實現拷貝構造方法:
      首先需要聲明帶有和本類相同類型的參數構造方法
      其次拷貝構造方法可以通過序列化實現快速複製
    • 拷貝對象通過調用拷貝構造方法進行創建。
      — 我們再改寫一下Poker類,實現拷貝構造方法:
    /**
     * 整副副撲克牌--實現拷貝構造方法
     *
     * @author zhuhuix
     * @date 2020-06-10
     */
    public class Poker implements Serializable {
    
        private List<Card> cards;
    
        public Poker() {
            List<Card> cardList = new ArrayList<>();
            // 按花色與数字組合生成52張撲克牌
            for (int i = 0; i < Constant.COLORS.size(); i++) {
                for (int j = 0; j < Constant.NUMBERS.size(); j++) {
                    cardList.add(new Card(Constant.COLORS.get(i), Constant.NUMBERS.get(j)));
                }
            }
            // 生成大小王
            for (int i = 0; i < Constant.JOKER.size(); i++) {
                cardList.add(new Card("JOKER", Constant.JOKER.get(i)));
            }
    
            this.cards = cardList;
        }
    
        // 拷貝構造方法:利用序列化實現深拷貝
        public Poker(Poker poker) {
    
            try {
    
                ByteArrayOutputStream os = new ByteArrayOutputStream();
                ObjectOutputStream oos = new ObjectOutputStream(os);
                oos.writeObject(poker);
    
                ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
                ObjectInputStream ois = new ObjectInputStream(is);
                this.cards = ((Poker) ois.readObject()).getCards();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
    
        }
    
        // 從整副撲克牌中抽走大小王
        public void removeJoker() {
            Iterator<Card> iterator = this.cards.iterator();
            while (iterator.hasNext()) {
                Card cardJoker = iterator.next();
                if (cardJoker.getColor() == "JOKER") {
                    iterator.remove();
                }
            }
        }
    
        public List<Card> getCards() {
            return cards;
        }
    
        public void setCards(List<Card> cards) {
            this.cards = cards;
        }
    
        public Integer getCardCount() {
            return this.cards.size();
        }
    
        @Override
        public String toString() {
            StringBuilder poker = new StringBuilder("[");
            Iterator<Card> iterator = this.cards.iterator();
            while (iterator.hasNext()) {
                poker.append(iterator.next().toString() + ",");
            }
            poker.setCharAt(poker.length() - 1, ']');
            return poker.toString();
        }
    }
    
    
    • 對測試主程序進行修改:
    /**
     * 測試程序
     *
     * @author zhuhuix
     * @date 2020-6-10
     */
    public class PlayDemo {
    
        public static void main(String[] args) throws CloneNotSupportedException {
    
            // 生成一副撲克牌並洗好牌
            Poker poker1 = new Poker();
            System.out.println("新建:第一副牌共 "+poker1.getCardCount()+" 張:"+poker1.toString());
    
            Poker poker2 = new Poker(poker1);
            System.out.println("第一副牌拷頁生成第二副牌,共 "+poker2.getCardCount()+" 張:"+poker2.toString());
    
            poker1.removeJoker();
    
            System.out.println("====第一副牌抽走大小王后====");
            System.out.println("第一副牌還有 "+poker1.getCardCount()+" 張:"+poker1.toString());
            System.out.println("第二副牌還有 "+poker2.getCardCount()+" 張:"+poker2.toString());
    
    
            Poker poker3 = new Poker(poker1);
            System.out.println("第三副牌還有 "+poker3.getCardCount()+" 張:"+poker3.toString());
        }
    
    }
    
    • 輸出結果:
      –通過序列化的有手段,同樣也能實現對象的深拷貝

    四、總結

    • java程序進行對象拷貝時,如果對象的類中存在引用類型時,需進行深拷貝
    • 對象拷貝可以通過實現Cloneable接口完成
    • java編程也可仿照 C++程序的拷貝構造函數,實現拷貝構造方法進行對象的複製
    • 通過序列化與反序化手段可實現對象的深拷貝

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

    【其他文章推薦】

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

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

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

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

    ※回頭車貨運收費標準

  • Uber基於Apache Hudi構建PB級數據湖實踐

    Uber基於Apache Hudi構建PB級數據湖實踐

    1. 引言

    從確保準確預計到達時間到預測最佳交通路線,在Uber平台上提供安全、無縫的運輸和交付體驗需要可靠、高性能的大規模數據存儲和分析。2016年,Uber開發了增量處理框架Apache Hudi,以低延遲和高效率為關鍵業務數據管道賦能。一年後,我們開源了該解決方案,以使得其他有需要的組織也可以利用Hudi的優勢。接着在2019年,我們履行承諾,進一步將其捐贈給了Apache Software Foundation,差不多一年半之後,Apache Hudi畢業成為Apache Software Foundation頂級項目。為紀念這一里程碑,我們想分享Apache Hudi的構建、發布、優化和畢業之旅,以使更大的大數據社區受益。

    2. 什麼是Apache Hudi

    Apache Hudi是一個存儲抽象框架,可幫助組織構建和管理PB級數據湖,通過使用upsert增量拉取等原語,Hudi將流式處理帶到了類似批處理的大數據中。這些功能通過統一的服務層(幾分鐘左右即可實現數據延遲),幫助我們更快,更新鮮地獲取服務數據,從而避免了維護多個系統的額外開銷。更靈活地,Apache Hudi還可以在Hadoop分佈式文件系統(HDFS)或雲存儲上運行。

    Hudi在數據湖上啟用原子性、一致性、隔離性和持久性(ACID)語義。 Hudi的兩個最廣泛使用的功能是upserts增量拉取,它使用戶能夠捕獲變更數據並將其應用於數據湖,為了實現這一點,Hudi提供了可插拔索引機制,以及自定義索引實現。Hudi具有控制和管理數據湖中文件布局的能力,這不僅能克服HDFS NameNode節點和其他雲存儲限制,而且對於通過提高可靠性和查詢性能來維護健康的數據生態系統也非常重要。另外Hudi支持多種查詢引擎,例如Presto,Apache Hive,Apache Spark和Apache Impala。

    圖1. Apache Hudi通過在表上提供不同的視圖來攝取變更日誌、事件和增量流,以服務於不同的應用場景

    從總體上講,Hudi在概念上分為3個主要組成部分:需要存儲的原始數據;用於提供upsert功能的索引數據以及用於管理數據集的元數據。內核方面,Hudi維護在不同時間點在表上執行的所有動作的時間軸,在Hudi中稱為即時,這提供了表格的即時視圖,同時還有效地支持了按序到達的數據檢索,Hudi保證時間軸上的操作是原子性的,並且基於即時時間,與數據庫中進行更改的時間是一致的。利用這些信息,Hudi提供了同一Hudi表的不同視圖,包括用於快速列式文件性能的讀優化視圖,用於快速數據攝取的實時視圖以及用於將Hudi表作為變更日誌流讀取的增量視圖,如上圖1所示。

    Hudi將數據表組織到分佈式文件系統上基本路徑(basepath)下的目錄結構中。 表分為多個分區,在每個分區內,文件被組織成文件組,由文件ID唯一標識。 每個文件組包含幾個文件切片,其中每個切片包含在某個特定提交/壓縮(commit/compaction)瞬間生成的基本數據文件(*.parquet),以及包含對基本數據文件進行插入/更新的一組日誌文件(*.log)。Hudi採用了Multiversion Concurrency Control(MVCC),其中壓縮操作將日誌和基本文件合併以生成新的文件片,而清理操作則將未使用的/較舊的文件片去除,以回收文件系統上的空間。

    Hudi支持兩種表類型:寫時複製和讀時合併。 寫時複製表類型僅使用列文件格式(例如,Apache Parquet)存儲數據。通過寫時複製,可以通過在寫過程中執行同步合併來簡單地更新版本並重寫文件。

    讀時合併表類型使用列式(例如Apache Parquet)和基於行(例如Apache Avro)文件格式的組合來存儲數據。 更新記錄到增量文件中,然後以同步或異步壓縮方式生成列文件的新版本。

    Hudi還支持兩種查詢類型:快照查詢和增量查詢。 快照查詢是從給定的提交或壓縮操作開始對錶進行”快照”的請求。利用快照查詢時,寫時複製表類型僅暴露最新文件片中的基本/列文件,並且與非Hudi表相比,可保證相同的列查詢性能。寫入時複製提供了現有Parquet表的替代品,同時提供了upsert/delete和其他功能。對於讀時合併表,快照查詢通過動態合併最新文件切片的基本文件和增量文件來提供近乎實時的數據(分鐘級)。對於寫時複製表,自給定提交或壓縮以來,增量查詢將提供寫入表的新數據,並提供更改流以啟用增量數據管道。

    3. Apache Hudi在Uber的使用

    在Uber,我們在各種場景中都使用到了Hudi,從在Uber平台上提供有關行程的快速、準確的數據,從檢測欺詐到在我們的UberEats平台上提供餐廳和美食推薦。為了演示Hudi的工作原理,讓我們逐步了解如何確保Uber Marketplace中的行程數據在數據湖上是最新的,從而改善Uber平台上的騎手和駕駛員的用戶體驗。行程的典型生命周期始於騎手提出的行程,然後隨着行程的進行而繼續,直到行程結束且騎手到達最終目的地時才結束。 Uber的核心行程數據以表格形式存儲在Uber的可擴展數據存儲Schemaless中。行程表中的單個行程條目在行程的生命周期中可能會經歷許多更新。在Uber使用Hudi之前,大型Apache Spark作業會定期將整個數據集重新寫入HDFS,以獲取上游在線表的插入、更新和刪除,從而反映出行程狀態的變化。就背景而言,在2016年初(在構建Hudi之前),一些最大的任務是使用1000個executors並處理超過20TB的數據,此過程不僅效率低下,而且難以擴展。公司的各個團隊都依靠快速、準確的數據分析來提供高質量的用戶體驗,為滿足這些要求,我們當前的解決方案無法擴展進行數據湖上的增量處理。使用快照和重新加載解決方案將數據移至HDFS時,這些低效率的處理正在寫到到所有數據管道,包括使用此原始數據的下游ETL,我們可以看到這些問題只會隨着規模的擴大而加劇。

    在沒有其他可行的開源解決方案可供使用的情況下,我們於2016年末為Uber構建並啟動了Hudi,以構建可促進大規模快速,可靠數據更新的事務性數據湖。Uber的第一代Hudi利用了寫時複製表類型,該表類型每30分鐘將作業處理速度提高到20GB,I/O和寫入放大減少了100倍。到2017年底,Uber的所有原始數據表都採用了Hudi格式,運行着地球上最大的事務數據湖之一。

    圖2. Hudi的寫時複製功能使我們能夠執行文件級更新,從而大大提高數據的新鮮度

    4. 改進Apache Hudi

    隨着Uber數據處理和存儲需求的增長,我們開始遇到Hudi的寫時複製功能的局限性,主要是需要繼續提高數據的處理速度和新鮮度,即使使用Hudi”寫時複製”功能,我們的某些表收到的更新也分散在90%的文件中,從而導致需要重寫數據湖中任何給定的大型表的數據,重寫數據量大約為100TB。由於寫時複製甚至為單個修改的記錄重寫整個文件,因此寫複製功能導致較高的寫放大和損害的新鮮度,從而導致HDFS群集上不必要的I/O以及更快地消耗磁盤空間,此外,更多的數據表更新意味着更多的文件版本,以及HDFS文件數量激增,反過來,這些需求導致HDFS Namenode節點不穩定和較高的計算成本。

    為了解決這些日益增長的擔憂,我們實現了第二種表類型,即”讀時合併”。由於讀時合併通過動態合併數據來使用近實時的數據,為避免查詢端的計算成本,我們需要合理使用此模式。”讀時合併”部署模型包括三個獨立的作業,其中包括一個攝取作業,包括由插入、更新和刪除組成的新數據,一個次要的壓縮作業,以異步方式主動地壓縮少量最新分區的更新/刪除內容,以及一個主要的壓縮作業,該作業會緩慢穩定地壓縮大量舊分區中的更新/刪除。這些作業中的每一個作業都以不同的頻率運行,次要作業和提取作業的運行頻率比主要作業要高,以確保其最新分區中的數據以列格式快速可用。通過這樣的部署模型,我們能夠以列式為數千個查詢提供新鮮數據,並將我們的查詢側合併成本限制在最近的分區上。使用讀時合併,我們能夠解決上面提到的所有三個問題,並且Hudi表幾乎不受任何對數據湖的更新或刪除的影響。現在,在Uber,我們會根據不同場景同時使用Apache Hudi的寫時複製和讀時合併功能。

    圖3. Uber的Apache Hudi團隊開發了一種數據壓縮策略,用於讀時合併表,以便頻繁將最近的分區轉化為列式存儲,從而減少了查詢端的計算成本

    有了Hudi,Uber每天向超過150PB數據湖中插入超過5,000億條記錄,每天使用30,000多個core,超過10,000多個表和數千個數據管道,Hudi每周在我們的各種服務中提供超過100萬個查詢。

    5. Apache Hudi經驗總結

    Uber在2017年開源了Hudi,為其他人帶來了該解決方案的好處,該解決方案可大規模提取和管理數據存儲,從而將流處理引入大數據。當Hudi畢業於Apache軟件基金會下的頂級項目時,Uber的大數據團隊總結了促使我們構建Hudi的各種考慮因素,包括:

    • 如何提高數據存儲和處理效率?
    • 如何確保數據湖包含高質量的表?
    • 隨着業務的增長,如何繼續大規模有效地提供低延遲的數據?
    • 在分鐘級別的場景中,我們如何統一服務層?

    如果沒有良好的標準化和原語,數據湖將很快成為無法使用的”數據沼澤”。這樣的沼澤不僅需要花費大量時間和資源來協調、清理和修復表,而且還迫使各個服務所有者構建複雜的算法來進行調整、改組和交易,從而給技術棧帶來不必要的複雜性。

    如上所述,Hudi通過無縫地攝取和管理分佈式文件系統上的大型分析數據集來幫助用戶控制其數據湖,從而彌補了這些差距。建立數據湖是一個多方面的問題,需要在數據標準化、存儲技術、文件管理實踐,數據攝取與數據查詢之間折衷性能等方面進行取捨。在我們建立Hudi時與大數據社區的其他成員交談時,我們了解到這些問題在許多工程組織中普遍存在。我們希望在過去的幾年中,開源和與Apache社區的合作,在Hudi基礎上發展可以使其他人在不同行業對大數據運營有更深入的了解。 在Uber之外,Apache Hudi已在多家公司用於生產,其中包括阿里雲,騰訊雲,AWS、Udemy等。

    6. 未來計劃

    圖4. Apache Hudi場景包括數據分析和基礎架構運行狀況監視

    Hudi通過對數據集強制schema,幫助用戶構建更強大、更新鮮的數據湖,從而提供高質量的見解。

    在Uber,擁有全球最大的事務數據湖之一為我們提供了各種Apache Hudi用例場景的機會,由於以這種規模解決問題並提高效率可能會產生重大影響,因此有直接的動機促使我們更加深入。在Uber,我們已經使用了先進的Hudi原語,如增量拉取來幫助建立鏈式增量流水線,從而減少了作業的計算空間,而這些作業本來會執行大型掃描和寫入。我們根據特定的用例場景和要求調整讀時合併表的壓縮策略。 自從我們將Hudi捐贈給Apache基金會以來,最近幾個月,Uber貢獻了一些功能,例如嵌入式時間軸服務以實現高效的文件系統訪問,刪除重命名以支持雲友好的部署並提高增量拉取性能。

    在接下來的幾個月中,Uber計劃為Apache Hudi社區貢獻更多新功能。其中一些功能可通過優化計算使用量以及改善數據應用程序的性能來幫助降低成本,我們還將更深入地研究如何根據訪問模式和數據應用程序需求來改善存儲管理和查詢性能。

    有關我們如何計劃實現這些目標的更多信息,您可以閱讀一些RFC,包括支持列索引和O(1)查詢計劃的智能元數據,將Parquet表高效引導到Hudi,記錄級別索引支持更快速插入,這些RFC由Uber的Hudi團隊向Apache社區提出。

    隨着Apache Hudi畢業成為Apache頂級項目,我們很高興為該項目雄心勃勃的路線圖做出貢獻。Hudi使Uber和其他公司可以使用開放源文件格式,在未來證明其數據湖的速度,可靠性和交易能力,從而消除了許多大數據挑戰,並構建了豐富而可移植的數據應用程序。

    Apache Hudi是一個成長中的社區,具有令人興奮且不斷髮展的發展路線圖。 如果您有興趣為這個項目做貢獻,可點擊這裏。

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

    【其他文章推薦】

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

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

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

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

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

  • 程序員過關斬將–作為一個架構師,我是不是應該有很多職責?

    每一個程序員都有一個架構夢。

    上面其實本質上是一句富有事實哲理的廢話,要不然也不會有這麼多人關注你的公眾號。這些年隨着“企業数字化”轉型的口號,一大批企業奔跑在轉型的路上,希望領先一步對手將企業IT部門從單純的成本中心轉變為業務驅動者,而這個過程中,企業的架構師起着舉足輕重的作用。架構師的工作在很多擼碼的開發者眼中是很一項很神聖的工作,而且富有挑戰性。

    但是事物都有兩面性,很多管理者和技術人員都認為架構師的薪酬不符合實際,有很多架構師確實只會用PPT和大幅海報來應付了事,而且會依仗着在公司地位把自己的一些想法強加給公司其他同事,有的架構師甚至會追求一些無關緊要的概念,在高層和底層灌輸一些錯誤的思想,從而導致做出一些不可逆轉的糟糕決策,使公司陷入危險逆境。

    很多時候,公司給予架構師這個角色太多的責任,管理者希望他們能在突發性能問題時能快速解決問題,還能推動企業快速轉型,甚至能幫助企業文化的快速建立,作為一個架構師是不是要抗下這些職責呢?

    我不是項目經理

    架構師的日常工作經常會面臨并行處理多個不同維度的問題,這些問題可能是不同的主題,甚至在做決策的時候也需要考慮人員的分配,項目時間表的排期,需要用的核心技術以及組件等。有很多高層領導喜歡直接在架構師這裏獲取項目的詳細信息以及技術方案,雖然架構師角色涉及這些信息並且很了解這些信息,但是這並不是架構師的職責所在,甚至很多情況下令架構師處於項目經理的尷尬角色。

    我不是開發人員

    我想很多人看過那篇文章:作為架構師該不該寫代碼?很多架構師是出身於開發人員,這也難怪會出現這樣的疑問。但是,架構師其實和資深開發是兩條不同的職業路線,我認為兩者沒有高低之分。出色的開發人員需要很深的開發功力,需要最終交付出可運行的軟件。而架構師則需要更廣闊的知識面,更好的組織戰略思想,更好的溝通能力。在一個產品的開發流水線上,架構師可能會負責一部分核心代碼的編寫,但是最主要的工作還是保證這條流水線的正常運轉。

    我不是救火員

    由於架構師這個角色在公司的地位,很多管理者認為架構師要隨時隨地的能分析並解決任何突發的問題,不瞞各位,這種現象在很多大廠依然存在,包括我司(雖然只是一個四線小廠)。如果一個架構師每天都忙着“救火”這種工作,根本沒有時間去做真正的架構工作,真正的架構設計需要思考,是不可能在短短時間內完成的。但是架構師必須接受出現的產品問題,因為這些問題的產生有可能和架構有着直接關係,在很大程度上能反應架構的缺陷或者問題。

    寫在最後

    架構師作為企業中很重要的一環,在很多重大技術問題中都作為決策者而存在。很難用代碼的多少或者質量來衡量一個架構師的好壞,如果一個系統在正常運行5年後依然能良好運行並且可以承受一定的變更能力,說明這個系統的架構師的工作是很出色的。如果非要給架構師定義一個KPI標準的話,以下這些工作也許能成為一個參考

    1. 定義IT戰略。小到一個系統的組件列表可行性的確定,大到公司技術的發展方向,乃至未來10年公司技術的預測與大膽嘗試。這些技術戰略都需要架構師根據自身經驗來制定。

    2. 落實對IT藍圖的管控,以實現協調一致,降低複雜度,保證公司所有系統有條不紊的正常工作,架構師的工作之一就是要把複雜度降低,化繁為簡,這需要架構師很強的抽象能力。

    3. 關注項目的實際落地情況,並根據項目實施中反饋的問題進行戰略的適當調整。一個合格的架構師從來不會忽略來自實際項目中的問題反饋。

    架構師一定要避免和消除那些系統設計中不可逆轉的錯誤決策

    來源參考:架構師應該知道的37件事

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

    【其他文章推薦】

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

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

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

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

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

  • 比埃爾法還大的MPV! 20萬就能買你敢信?

    比埃爾法還大的MPV! 20萬就能買你敢信?

    0T發動機,賬面數據203馬力,峰值扭矩300牛米。與之匹配的則是5速手動變速箱。這個時候你甚至可以直接二擋。雖然開着一台這麼大的手動擋MpV感覺很爽,但是開着MpV 7200干他的人肯定不多,據聞明年將會匹配一款6AT變速箱。而邁特威這邊則是EA888發動機,204馬力,峰值扭矩350牛·米。

    早些年的時候看到MpV總覺得和麵包車差不多,但是現在的MPV越來越豪華,空間上又有轎車無可比擬的優勢,逐漸成為很多公司和家庭的多用途車首選。

    今天我們就為大家帶來福特途睿歐與大眾邁特威的對比評測。

    空間

    既然是MpV,那麼空間肯定是很重要的部分,你說轎車有多長軸距,在MpV面前全部都是渣渣 。此次試駕的兩台車均為7座車型。坐姿足夠舒服,坐墊支撐到位,腿部空間?自己看!

    再到後面,福特途睿歐的第三排座椅擁有三個可獨立調整的靠背,而邁特威第三排的中間座椅就稍微吃虧了,但是可以抽出一個扶手,兩個人乘坐的時候比較舒適。

    不過邁特威最爽的用法應該是這樣。

    儲物空間

    在以往轎車的評測中,我們會到處找放水杯的地方,但是在MPV上真的就是多擔心了,兩台車的空間都是可以用海量來形容,圖中皆為2L大水瓶!!!

    福特途睿歐的杯架

    邁特威的杯架

    除此之外邁特威座椅下方空間還有神奇的儲物空間。途睿歐取而代之的則是獨立的空調出風口。

    一般7座SUV如果使用第三排座椅時,後備箱基本上沒有什麼空間,來到MpV上。情況則有所不同。如有需要福特途睿歐還可以將第三排座椅翻起,除了可以開去買菜,心情好還可以開去賣菜。

    邁特威的後備箱空間也不俗,不過座椅只能向前移,不能繼續將座椅翻起來。

    動力

    雖然一台MpV你不指望它能開多快,但是動力就像存款一樣,可以不用,但不能沒有。途睿歐搭載的是福特ECOboost的2.0T發動機,賬面數據203馬力,峰值扭矩300牛米。與之匹配的則是5速手動變速箱。這個時候你甚至可以直接二擋。

    雖然開着一台這麼大的手動擋MpV感覺很爽,但是開着MpV 7200干他的人肯定不多,據聞明年將會匹配一款6AT變速箱。

    而邁特威這邊則是EA888發動機,204馬力,峰值扭矩350牛·米。加上DQ500的7速雙離合變速箱,動力是足,但是感覺變速箱是刻意放慢了動作以尋求一個平順的效果,大腳油門並不會直接降檔,而是會選擇拉高發動機轉速,並且傳遞到車廂的發動機聲音較大。

    配置對比

    福特途睿歐配上了這個級別少有的後排天窗。

    邁特威則是側滑的玻璃窗。

    邁特威兩側都用上了電動門,這一點福特途睿歐上略有遺憾。

    舒適性對於MpV同樣重要,福特途睿歐用上了空氣懸挂,這也是福特專門為中國研發的,對於細碎過濾相當到位,國內的這套空氣懸挂是標配,而在英國也只有改款后的頂配車型才配備。

    邁特威則是自家的DCC動態懸挂,兩種懸挂相比各有特色。不過有一個細節方面則是搞不懂大眾,這個最常用的扶手,調節起高低真的有點反人類。

    總結

    邁特威和福特途睿歐相比各有優勢,但是從價格看來,一台邁特威的價格能夠買兩台福特途睿歐了,邁特威(41.88-54.98萬);福特途睿歐(17.69-20.39萬)。質感方面邁特威會更好一些,但是福特途睿歐在如此價位還能取得如此不俗的成績實屬驚人。大家會怎麼選呢?

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

    【其他文章推薦】

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

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

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

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

    ※回頭車貨運收費標準

  • 為什麼我心目中的年度車型一定會有他?

    為什麼我心目中的年度車型一定會有他?

    在材料與結構上的極致輕量化,不僅帶來更低的能耗,關鍵在於輕量化與高剛性的車身是整車高性能的基礎。與車身科技一樣, ESS II智能安全、安吉星4G LTE移動互聯等產品優勢都是CT6家族全系共享的。此外,三款車在技術與調教上有相同的底蘊,也造就了共通的調性,豪華舒適但不失操控的樂趣。

    轉眼又到車市年終盤點的時候了,循例會收到很多平台年度車型的評選邀請,凱迪拉克CT6在我的榜單中從來不會缺席。之所以會選CT6,不是因為其中一款車型,而是在這個級別中, CT6家族中每一款車的產品力在其所在細分市場都處於領先地位。今日,CT6插電式混合動力(plug-in)正式上市,售價55.88-65.88萬元。隨着這款pHEV的加入,凱迪拉克CT6家族變得更加完善,我也覺得是時候與大家聊一下這款我心目中的年度車型。

    凱迪拉克CT6 plug-in雖然是CT6家族中的首個新能源車型,但這個級別中本身也有不少插電式混動的車型,為何偏偏要選TA? 其實仔細分析一下豪華插電混動細分市場的車型就很容易明白了,特別是德系选手,這裏就不點名批評了,在汽油車型的基礎上加個電機和電池就叫pHEV了,電機夾在變速箱和發動機之間的串聯式結構,整套傳動系統都是來自供應商,簡單粗暴完全沒有技術含量,無論是發動機還是電機驅動的效率都很低,對能源的利用效率甚至不如普通汽油車型,充其量只能說是大環境下逼出來的產物。

    舉例說明,同樣是pHEV的車型,CT6 plug-in與寶馬的iperformance的理念則完全不一樣,凱迪拉克的performance Hybrid對新能源的理解屬於性能趨向,直接打造出一款技術含量最高,專為插電式混動打造的解決方案,其power Split動力分流技術可以說是目前最先進的電驅動科技,CT6 plug-in也是細分市場內的最強單品。而反觀寶馬iperformance,自成都車展大規模發布新車后,收效甚微,因為其多款產品都是由燃油車加一個電機,技術含量相對較低。我們可以理解iperformance更多是為了迎合排放政策,博眼球的戰術,而非精心打造的產品,其戰略意義更明顯。但CT6 plug-in對細分市場向高性能發展的長期引領作用,未來將會越來越明顯。很顯然,新能源行業是得技術者得天下。

    相比之下,CT6 plug-in這套系統在結構上具有絕對的領先優勢,其搭載的DHT變速箱其實與本家的HEV有異曲同工之妙,兩種動力耦合的關鍵在於行星齒輪組,從而實現power Split動力分流技術,3排行星齒輪與5組離合器不只是簡單的耦合動力,配合智能電控系統更可以實現四種智能能量輸出狀態(純電力驅動、發動機驅動、混合動力驅動以及智能能量回收),在各種複雜的工況下都有最合適的動力組合去適應,通用是混動領域內首次將這個概念進行量產的廠商。

    此外,CT6 plug-in的整套動力系統是圍繞performance Hybrid(性能駕享)豪華新能源研發策略開發的,得益於三組行星齒輪組的動力耦合結構,兩台電機與2.0T SIDI 渦輪增壓直噴發動機可以共同發力驅動車輛,最大扭矩達586Nm,靠着電機瞬間爆發的扭矩,百公里加速只需5.4s,在同級中性能無出其右。對於追求駕控樂趣的人而言,例如我,對如此高性能自然是無抗拒之力的。

    除了DHT變速箱先進的結構,還需要強大的控制系統,CT6 plug-in的智能電控系統通過TpIM驅動能量轉換模塊協同電驅單元與發動機的高效工作,帶來更加智能的動力組合路徑,也能實現比傳統燃油車更平順的駕駛體驗。此外,CT6 plug-in還擁有巡航、運動、電量保持三種駕駛模式,讓駕駛者在任何工況下都能享受更多駕駛樂趣。

    同樣是源自performance Hybrid這個理念, 強於一般混動車型的還有電池,CT6 plug-in搭載18.4kWh大容量鋰離子電池,能量密度遠超其他混動車型搭載的鎳氫電池,可以提供80Km的純電里程,綜合續航里程能夠達到935km。雖與特斯拉一樣是鋰離子電池,但其BMS電池熱管理及液冷技術,可通過3組冷液循環系統進行散熱,這在全球都是處於領先地位的。其實電池技術和汽油機也有共通之處,越高的性能相對就需要越強的冷卻效果,要知道布加迪的W12發動機可是配了11個散熱器,這套熱管理系統能更好的配合高密度的鋰離子電池,適應高性能的輸出。

    誠然,憑藉著這套業界領先的高性能混動系統,CT6 plug-in就足以讓我對其傾心,但CT6的能力卻不止於此。輕量化一直是近幾年汽車工業的一大主題,車身技術一直是我非常看重的技術點,在其他條件相仿的情況下,輕量化做的越好,車子的動態響應自然就會更好。

    因此CT6全系都擁有宇航級輕量化車身,這在我的選車之道中無疑是關鍵的加分點。鋁材用量超過57%,11種混合材質輕量化車身,通過獨特的專利技術,首次實現了鋼、鋁兩種不同材質的焊接。在材料與結構上的極致輕量化,不僅帶來更低的能耗,關鍵在於輕量化與高剛性的車身是整車高性能的基礎。

    與車身科技一樣, ESS II智能安全、安吉星4G LTE移動互聯等產品優勢都是CT6家族全系共享的。此外,三款車在技術與調教上有相同的底蘊,也造就了共通的調性,豪華舒適但不失操控的樂趣。而我之所以視CT6為年度車型,是因在此基礎上,家族各成員又都有不同的亮點。

    其中凱迪拉克CT6 40T作為凱迪拉克的,乃至整個通用車系的旗艦車型,集成了通用的所有科技技術,在同級中無出其右,追求的是技術的巔峰。搭載3.0T雙渦輪增壓發動機不僅擁有405ps和543N·m的傲人數據,配合閉缸技術與自動啟停,兼顧性能與油耗。除了車身材質與結構的高度輕量化, ARS主動式後輪轉向系統和AWD全時四驅系統的搭載,讓身長5米多的大車開起來依然保持靈巧,彎道表現穩健,車身響應积極,讓“大車也能玩操控”變成了現實。360°全方位降噪,MRC主動電磁感應懸挂等技術的搭載則保證了其安靜舒適的豪華本色。

    不難發現在CT6 40T上所體驗到的一切都來自於科技,其實每個車型都有自己的側重點,特別是豪華車型都有其獨有的底蘊與內涵。你們知道日本的匠心,德國的精工,但這個世界上最好的手機,最好的電腦,最先進的產品都來自美國。CT6作為新美式豪華的代表,帶給消費者科技與豪華結合的完美體驗。

    作為CT6家族中最走量的一款車型,想必關注CT6 28T的人應該是最多的。確實如此,三款車各自扮演的角色不同,28T的品質很大程度上能決定整個家族能走多遠。因此,我們可以看見28T傳承了40T上的諸多產品優勢,標配20多項同級獨有的配置,你完全不用懷疑其豪華程度。另一方面,受到大環境的改變影響,2.0T的大型豪華車正在逐步為大家所接受,特別是生活在擁堵的城市環境中, 想兼顧節能與樂趣卻着實不易。CT6 28T搭載的是為數不多,在大型車上動力表現還能讓我滿意的2.0T發動機。沃德十佳的2.0T SIDI 渦輪增壓直噴發動機可以輸出276ps與400N·m的動力,其表現已經完全超越了3.0L排量的車型。加之與40T共享的輕量化車身與底盤技術,輕快的動力響應,精準的轉向手感,紮實的底盤質感加上更經濟的油耗,配合上終端合理的優惠,CT6 28T更像是一輛能取悅你的車。

    現如今路上豪車觸目皆是,汽車對於國人而言早已不只是代步工具,無論是對面子還是品質都有更高的追求。寶馬5系月銷輕鬆過萬,奔馳新E級一上來也是呼風喚雨,其中大家對BBA豪華老字號的追捧起了關鍵性的作用。在我看來,凱迪拉克CT6家族產品力完全不輸5系E級等對手,甚至直逼7系與A8L等車型,這樣的錯位競爭無疑讓CT6更具入手的價值。而就我個人而言,CT6最打動我的是在其豪華配置之下還為駕駛者提供了這個級別罕有的駕駛樂趣,這是非常難能可貴的。同時你不難發現,plug-in創新高的性能駕趣,40T造就的技術巔峰,28T迎合了城市通勤,不同的需求不同的價位你都能找到合適的CT6,這就是為何我的年度車型榜單中必備凱迪拉克CT6的原因。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

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

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

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

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

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

  • Linux系統如何設置開機自動運行腳本?

    Linux系統如何設置開機自動運行腳本?

    大家好,我是良許。

    在工作中,我們經常有個需求,那就是在系統啟動之後,自動啟動某個腳本或服務。在 Windows 下,我們有很多方法可以設置開機啟動,但在 Linux 系統下我們需要如何操作呢?

    Linux 下同樣可以設置開機啟動,但可能需要我們敲一些命令(可能也有 UI 界面的設置方法,但我不熟,我更多是玩命令)。下面我們就介紹三種簡單但可行的開機啟動設置方法。

    方法一:修改 /etc/rc.d/rc.local 文件

    /etc/rc.d/rc.local 文件會在 Linux 系統各項服務都啟動完畢之後再被運行。所以你想要自己的腳本在開機后被運行的話,可以將自己腳本路徑加到該文件里。

    但是,首先需要確認你有運行這個文件的權限。

    $ chmod +x /etc/rc.d/rc.local
    

    為了演示,我們創建了一個腳本,當它被執行之後,將在家目錄下寫入有特定信息的文件。

    $ vim auto_run_script.sh
    
    #!/bin/bash
    date >> /home/alvin/output.txt
    hostname >> /home/alvin/output.txt
    

    保存退出后,再給它賦予可執行權限:

    $ chmod +x auto_run_script.sh
    

    然後,我們再將腳本添加到 /etc/rc.d/rc.local 文件最後一行:

    $ vim /etc/rc.d/rc.local
    
    /home/alvin/auto_run_script.sh
    

    接下來,我們就可以試試效果了。直接重啟系統就可以了:

    $ sudo reboot
    

    重啟之後,就會在家目錄下看到腳本執行的結果了。

    方法二:使用 crontab

    大家知道,crontab 是 Linux 下的計劃任務,當時間達到我們設定的時間時,可以自動觸發某些腳本的運行。

    我們可以自己設置計劃任務時間,然後編寫對應的腳本。但是,有個特殊的任務,叫作 @reboot ,我們其實也可以直接從它的字面意義看出來,這個任務就是在系統重啟之後自動運行某個腳本。

    那它將運行的是什麼腳本呢?我們如何去設置這個腳本呢?我們可以通過 crontab -e 來設置。

    $ crontab -e
    
    @reboot /home/alvin/auto_run_script.sh
    

    然後,直接重啟即可。運行的效果跟上面類似。

    方法三:使用 systemd 服務

    以上介紹的兩種方法,在任何 Linux 系統上都可以使用。但本方法僅適用於 systemd 系統。如何區分是不是 systemd 系統?很簡單,只需運行 ps aux 命令,查看 pid 為 1 的進程是不是 systemd 。

    為了實現目的,我們需要創建一個 systemd 啟動服務,並把它放置在 /etc/systemd/system/ 目錄下。

    我們創建的 systemd 啟動服務如下。請注意,這時後綴是 .service ,而不是 .sh

    $ vim auto_run_script.service
    
    [Unit]
    Description=Run a Custom Script at Startup
    After=default.target
    
    [Service]
    ExecStart=/home/alvin/auto_run_script.sh
    
    [Install]
    WantedBy=default.target
    

    從服務的內容可以看出來,我們最終還是會調用 /home/alvin/auto_run_script.sh 這個腳本。

    然後,我們再把這個腳本放置在 /etc/systemd/systerm/ 目錄下,之後我們再運行下面兩條命令來更新 systemd 配置文件,並啟動服務。

    $ systemctl daemon-reload
    $ systemctl enable auto_run_script.service
    

    萬事俱備之後,我們就可以重啟系統啦。

    $ reboot
    

    公眾號:良許Linux

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

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

    【其他文章推薦】

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

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

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

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

    ※回頭車貨運收費標準

  • 深度學習-神經網絡

    深度學習-神經網絡

    目錄

    • 深度學習-神經網絡
      • 摘要
      • 神經網絡
        • 淺層神經網絡
        • 深層神經網絡
        • 激活函數
        • 反向傳播
          • 損失(loss)
      • 卷積神經網絡
        • 局部感受野
        • 卷積核
        • 共享權值
        • 池化
      • 遷移學習
        • 對抗網絡

    深度學習-神經網絡

    摘要

    機器學習人工智能的核心,而深度學習又是機器學習的核心。三者關係可用如下圖來表示。

    人工神經網絡(Artificial Neural Networks,簡寫為ANNs)也簡稱為神經網絡(NNs)或稱作連接模型(Connection Model),它是一種模仿動物神經網絡行為特徵,進行分佈式并行信息處理的算法數學模型。

    首先認識一下人腦的神經元之間的聯繫

    神經網絡仿照人腦的神經元結構之間的聯繫,當某個神經元的軸突電信號強度達到一定程度時,就會觸發將信號傳遞到下一個神經元。在傳遞的過程中加上一些對數據處理的操作,從而達到處理傳遞信息的目的。上面的信號其實就是數值或者多維矩陣。

    神經網絡

    神經網絡又分為兩種,一種是淺層神經網絡,另一種是深層神經網絡,顧名思義可知,深層神經網絡要比淺層神經網絡複雜。

    淺層神經網絡

    深層神經網絡

    上面兩圖中的圓圈則代表神經元,連線則代表上一層神經元對下一層神經元的信號傳遞

    淺層神經網絡相比,深層神經網絡具有更複雜的模型結構,並且層次結構較多,神經網絡採用全連接的方式將神經元之間的信號進行傳遞,淺層神經網絡則是非全連接,或者單層全連接。

    • 全連接:是上一層的每個神經元都連接到下一層所有的神經元
    • 非全連接:一個神經元連接一個或者下一層的其中幾個神經元

    每個神經元鏈接下一層多個神經元,由於不同神經元對該神經元的信號的Value不同,所以通過設置權重(Weight)的方式來降低或提高該神經元傳遞過來的信號。

    其關係滿足 y = Wx+ B

    • w:weight權重,對該神經元的重視(需求)程度,也就是上面所說的數據處理階段,一般總weight(w1+w2+w3)為1
    • x:傳遞過來的信號量的值
    • b:為偏移量,對於在線性分類問題,偏移量是不可或缺的。

    上圖如果沒有偏移量的話,該直線就會過原點。

    顯然分類效果就不會好,通過加上一個大於0的偏移量b使得分類器往左平移。

    • y:是加權后的值,激活函數的參數x就是所有y的和

    激活函數

    激活函數是神經網絡模型中上一層神經元到下一層神經元數值的處理,上一層神經元通過線性函數(加權求和)得到的數值y,所有的y也是具有線性函數特性的,激活函數就是將這些數值非線性化,把y當作x帶入到激活函數中。

    • 線性函數關係為 y = Wx + B
    • 非線性函數即不是一條直線的函數,例如冪函數,指數函數,對數函數等

    激活函數又類似於高等數學中的符號函數(sgn),(sgn不是激活函數)

    • x>0,則輸出f(x)=1,傳給下一個神經元
    • x<0,則輸出f(x)=-1,傳給下一個神經元
    • x=0,則輸出f(x)=0,傳給下一個神經元

    經典的激活函數是Sigmoid 函數,其函數關係為f(x) = 1/(1-e^x)

    • x較大,則輸出f(x)=1,傳給下一個神經元
    • x較小,則輸出f(x)=0,傳給下一個神經元
    • 所有的值均壓縮在-1~1之間

    反向傳播

    反向傳播的基本思想就是在執行完所有層后,通過計算輸出層期望值之間的誤差來逆向調整權重參數,從而使損失減小,誤差變小。

    損失(loss)

    在程序運行過程中,模型生成好之後,會進行模型準確性評估,計算它的損失值,損失值越小,模型就越好。損失函數是計算損失函數的方法,也有一下幾種。

    • 0-1損失函數(0-1 lossfunction):
    • 平方損失函數(quadraticloss function)
    • 絕對損失函數(absoluteloss function)
    • 對數損失函數(logarithmicloss function)或對數似然損失函數(log-likelihood loss function)

    卷積神經網絡

    卷積神經網絡(Convolutional Neural Network,CNN)是一種前饋型的神經網絡,其在大型圖像處理方面有出色的表現,目前已經被大範圍使用到圖像分類、定位等領域中。相比於其他神經網絡結構,卷積神經網絡需要的參數相對較少,使的其能夠廣泛應用。

    首先看下卷積網絡結構流程圖

    回顧深層神經網絡,因為每個層都是全連接的,假設有一個64×64×3的圖像(64×64是二維平面上的像素值,3為第三維RGB的值),每個節點的權重是不同的,則下一層的每個神經元將會計算64×64×3個節點,若下一層有n個節點的神經元則計算量還需乘n,訓練起來較為複雜。

    卷積神經網絡主要有三個特點

    • 局部感受野(Local Receptive Fields)
    • 共享權值(Shared Weights)
    • 池化(Pooling)

    局部感受野

    在動物器官中,大腦皮層有不同的感受區域,如聽覺區,視覺區等,每塊區域都有自己特殊的神經元,當有聽覺信號時會傳到大腦皮層的聽覺區的神經元,因此每個神經元對應的感受區域叫感受野.
    那麼在神經網絡中,不同的卷積核會對應不同的感受區域,在卷積時彼此之間無聯繫。

    卷積神經網絡的卷積層每個節點與上一層某個區域通過卷積核連接,而與這塊區域以外的區域無連接。

    從右邊圖可見,每個圓圈代表一個卷積核,每個卷積核只需要關注自己的局部感受區域。

    卷積核

    卷積核到底是什麼?那就來觀察一下每個卷積核局部作用域。

    上圖是取一個卷積和其對應的局部感受野,中間的那一層就是卷積核,3×3的卷積核對源數據中左上角的3×3的矩陣進行點乘,就得到了一個數值,叫做該區域的卷積層的值。然而,這隻是源數據中的部分區域,源數據是7×7的矩陣,所以卷積核再通過移動映射的方式多次卷積,如下圖。
    該圖中在卷積層的值與源數據之間還應有個3×3的卷積核,未畫出。

    通過上圖可以看出,如果一個7×7×1像素的圖片通過一個3×3×1的卷積核卷積,則被卷積后的值為5×5×1 !這裏注意一點,還是以圖片為例,若是圖片像素是三維的(彩色圖片),則像素為7×7×3的圖片必須使用三維的卷積核,並且第三維度也是3。

    • stride:步長
    • padding:擴展像素,填充像素
    • in:輸入的模式數,
    • out:輸出的模式數
    • W H:卷積核寬度長度

    共享權值

    每個卷積核連接8×8個節點也就64個權重值,然而還有8×8×2個卷積核呢!這裏出現了共享權值,共享權值就是每個卷積核對自己區域的權重比值都是相同的,所以僅需要給出64個權重比值即可,極大的減少了計算量。

    池化

    池化層也叫採樣層,作用是降低數據維度,主要有兩個池化方式

    • 最大池化
    • 平均池化

    以最大池化為例,有4個2×2的矩陣,則取每個矩陣中的最大值作為該矩陣的特徵。

    另一個就是平均池化,就是取矩陣中的平均值來作為該矩陣的特徵

    遷移學習

    對抗網絡

    G生成的圖像被D識別為真圖像loss越小,D對於G生成的圖像的判別正確率越高越好

    繼續更新,點個關注哦

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

    【其他文章推薦】

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

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

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

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

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

  • Java併發相關知識點梳理和研究

    Java併發相關知識點梳理和研究

    1. 知識點思維導圖

    (圖比較大,可以右鍵在新窗口打開)

    2. 經典的wait()/notify()/notifyAll()實現生產者/消費者編程範式深入分析 & synchronized

    注:本節代碼和部分分析參考了你真的懂wait、notify和notifyAll嗎。

    看下面一段典型的wait()/notify()/notifyAll()代碼,對於值得注意的細節,用註釋標出。

    import java.util.ArrayList;
    import java.util.List;
    
    public class Something {
        private Buffer mBuf = new Buffer(); // 共享的池子
    
        public void produce() {
            synchronized (this) { // 注1、注2
                while (mBuf.isFull()) { // 注3
                    try {
                        wait(); // 注4
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                mBuf.add();
                notifyAll();  // 注5、注6
            }
        }
    
        public void consume() {
            synchronized (this) { // 見注1、注2
                while (mBuf.isEmpty()) { // 注3
                    try {
                        wait(); // 注4
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                mBuf.remove();
                notifyAll(); // 注5、注6
            }
        }
    
        private class Buffer {
            private static final int MAX_CAPACITY = 1;
            private List innerList = new ArrayList<>(MAX_CAPACITY);
    
            void add() {
                if (isFull()) {
                    throw new IndexOutOfBoundsException();
                } else {
                    innerList.add(new Object());
                }
                System.out.println(Thread.currentThread().toString() + " add");
    
            }
    
            void remove() {
                if (isEmpty()) {
                    throw new IndexOutOfBoundsException();
                } else {
                    innerList.remove(MAX_CAPACITY - 1);
                }
                System.out.println(Thread.currentThread().toString() + " remove");
            }
    
            boolean isEmpty() {
                return innerList.isEmpty();
            }
    
            boolean isFull() {
                return innerList.size() == MAX_CAPACITY;
            }
        }
    
        public static void main(String[] args) {
            Something sth = new Something();
            Runnable runProduce = new Runnable() {
                int count = 4;
    
                @Override
                public void run() {
                    while (count-- > 0) {
                        sth.produce();
                    }
                }
            };
            Runnable runConsume = new Runnable() {
                int count = 4;
    
                @Override
                public void run() {
                    while (count-- > 0) {
                        sth.consume();
                    }
                }
            };
            for (int i = 0; i < 2; i++) {
                new Thread(runConsume).start();
            }
            for (int i = 0; i < 2; i++) {
                new Thread(runProduce).start();
            }
        }
    }
    
    • 注1:wait()/notify()/notifyAll()必須在synchronized塊中使用
    • 注2:使用synchronized(this)的原因是,這段代碼的main(),是通過實例化Something的對象,並使用它的方法來進行生產/消費的,因此是一個指向this的對象鎖。不同的場景,需要注意同步的對象的選擇。
    • 注3:必須使用while循環來包裹wait()。設想一種場景:存在多個生產者或多個消費者消費者,以多個生成者為例,在緩衝區滿的情況下,如果生產者通過notify()喚醒的線程仍是生產者,如果不使用while,那麼獲取鎖的線程無法重新進入睡眠,鎖也不能釋放,造成死鎖。
    • 注4:wait()會釋放鎖
    • 注5:notfiy()、notifyAll()會通知其他在wait的線程來獲取鎖,但是獲取鎖的真正時機是鎖的原先持有者退出synchronized塊的時候。
    • 注6:使用notifyAll()而不是notfiy()的原因是,仍考慮注3的場景,假如生產者喚醒的也是生產者,後者發現緩衝區滿重新進入阻塞,此時沒有辦法再喚醒在等待的消費者線程了,也會造成死鎖。

    擴展知識點1:synchronized塊的兩個隊列

    synchronized入口是將線程放入同步隊列,wait()是將線程放入阻塞隊列。notify()/notifyAll()實際上是把線程從阻塞隊列放入同步隊列。wait/notify/notifyAll方法需不需要被包含在synchronized塊中,為什麼?

    擴展知識點2:synchronized重入原理

    synchronized是可重入的,原理是它內部包含了一個計數器,進入時+1,退出時-1。 Java多線程:synchronized的可重入性

    擴展知識點3:作用範圍

    synchronized支持三種用法:修飾靜態方法、修飾實例方法、修飾代碼塊,前兩種分別鎖類對象、鎖對象實例,最後一種根據傳入的值來決定鎖什麼。
    synchronized是基於java的對象頭實現的,從字節碼可以看出包括了一對進入&退出的監視器。
    深入理解Java併發之synchronized實現原理

    擴展知識點4:分佈式環境synchronized的意義

    單看應用所運行的的單個宿主機,仍然可能有多線程的處理模式,在這個前提下使用併發相關技術是必須的。

    擴展知識點5:哪些方法釋放資源,釋放鎖

    所謂資源,指的是系統資源。

    wait(): 線程進入阻塞狀態,釋放資源,釋放鎖,Object類final方法(notify/notifyAll一樣,不可改寫)。
    sleep(): 線程進入阻塞態,釋放資源,(如果在synchronized中)不釋放鎖,進入阻塞狀態,喚醒隨機線程,Thread類靜態native方法。
    yield(): 線程進入就緒態,釋放資源,(如果在synchronized中)不釋放鎖,進入可執行狀態,選擇優先級高的線程執行,Thread類靜態native方法。
    如果線程產生的異常沒有被捕獲,會釋放鎖。
    sleep和yield的比較

    可以進一步地將阻塞劃分為同步阻塞——進入synchronized時沒獲取到鎖、等待阻塞——wait()、其他阻塞——sleep()/join(),可以參考線程的狀態及sleep、wait等方法的區別

    再進一步地,Java線程狀態轉移可以用下圖表示(圖源《Java 併發編程藝術》4.1.4 節)

    WAITING狀態的線程是不會消耗CPU資源的。

    3. 線程數調優

    理論篇

    本節參考了《Java併發編程實戰》8.2節,也可以結合面試問我,創建多少個線程合適?我該怎麼說幫助理解,其中的計算題比較有價值。

    前置知識

    I/O密集型任務:I/O任務執行時CPU空閑。
    CPU密集型任務:進行計算
    有的任務是二者兼備的。為了便於分析,不考慮。

    定性分析

    場景:單核單線程/單核多線程/多核多線程。單核多線程+CPU密集型不能提升執行效率,多核+CPU密集型任務可以;單核多線程+I/O密集型可以提升執行效率。
    因此,I/O耗時越多,線程也傾向於變多來充分利用IO等待時間。

    定量分析

    對於CPU密集型,線程數量=CPU 核數(邏輯)即可。特別的,為了防止線程在程序運行異常時不空轉,額外多設一個線程線程數量 = CPU 核數(邏輯)+ 1
    對於I/O密集型,最佳線程數 = CPU核數 * (1/CPU利用率) = CPU核數 * (1 + I/O耗時/CPU耗時)
    為什麼CPU利用率=1/(1+ I/O耗時/CPU耗時)?簡單推導一下:

    1/(1+ I/O耗時/CPU耗時) = 1/((CPU耗時+I/O耗時)/ CPU耗時) = CPU耗時/總耗時 = CPU利用率

    如何獲取參數——CPU利用率?

    因為利用率不是一成不變的,需要通過全面的系統監控工具(如SkyWalking、CAT、zipkin),並長期進行調整觀測。
    可以先取2N即2倍核數,此時即假設I/O耗時/CPU耗時=1:1,再進行調優。

    阿姆達爾定律

    CPU併發處理時性能提升上限。
    S=1/(1-a+a/n)
    其中,a為并行計算部分所佔比例,n為并行處理結點個數。
    簡單粗暴理解【阿姆達爾定律】

    Java線程池篇

    基本屬性

    /**
     * 使用給定的初始參數和默認線程工廠創建一個新的ThreadPoolExecutor ,並拒絕執行處理程序。 使用Executors工廠方法之一可能更方便,而不是這種通用構造函數。
    參數
     *  corePoolSize - 即使空閑時仍保留在池中的線程數,除非設置 allowCoreThreadTimeOut
     *  maximumPoolSize - 池中允許的最大線程數
     *  keepAliveTime - 當線程數大於核心時,這是多餘的空閑線程在終止之前等待新任務的最大時間。
     *  unit - keepAliveTime參數的時間單位
     *  workQueue - 在執行任務之前用於保存任務的隊列。 該隊列將僅保存execute方法提交的Runnable任務。
     * threadFactory - 執行程序創建新線程時使用的工廠
     */
    public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory)
    
    

    常見線程池

    由java.util.concurrent.Executors創建的線程池比較常用,而不是使用ThreadPoolExecutor的構造方法。

    名稱 特性
    newFixedThreadPool 線程池大小為固定值
    newSingleThreadExecutor 線程池大小固定為1
    newCachedThreadPool 線程池大小初始為0,默認最大值為MAX INTEGER
    newScheduledExecutor 延遲執行任務或按周期重複執行任務

    線程工廠的作用

    用來創建線程,統一在創建線程時設置一些參數,如是否守護線程。線程一些特性等,如優先級。
    可參考004-多線程-JUC線程池-ThreadFactory線程工廠

    4. 併發容器相關

    併發容器可以說是一個面試時的高頻問題了,網絡上也有很多介紹,這裏就不重複解讀,將相關的知識整理一下,邊看源碼邊讀文章效果會很好。
    先提一句,Vector是線程安全的,為啥現在不推薦用呢?看源碼可以知道,它將大部分方法都加了synchronized,犧牲了性能換取線程安全,是不可取的。如果真的有需要線程安全的容器,可以用Collections.synchronizedList()來手動給list加synchronized。
    再補充一句,其實Vector和Collections.synchronizedList()使用複合操作或迭代器Iterator時也不是線程安全的,具體解釋會在下一篇博客Java容器中介紹。

    ConcurrentHashMap

    先重點介紹Map的兩個實現類HashMap和ConcurrentHashMap

    • HashMap和ConcurrentHashMap HashMap?ConcurrentHashMap?相信看完這篇沒人能難住你!
    • HashMap擴容原理:HashMap的擴容機制—resize()
    • 多線程下HashMap擴容resize可能導致鏈表循環
    • 這兩個數據結構在JDK1.7到1.8時,當數目達到一個閾值時,都從鏈表改用了紅黑樹
    • HashMap的node重寫了equals方法來比較節點。Objects.equals會調用Object的equals,對於Object實現類則是實現類自己的equals。
     public final boolean equals(Object o) {
         if (o == this)
             return true;
         if (o instanceof Map.Entry) {
             Map.Entry<?,?> e = (Map.Entry<?,?>)o;
             if (Objects.equals(key, e.getKey()) &&
                 Objects.equals(value, e.getValue()))
                 return true;
         }
         return false;
     }
    

    ConcurrentLinkedQueue

    ConcurrentLinkedQueue使用CAS無鎖操作,保證入隊出隊的線程安全,但不保證遍歷時的線程安全。遍歷要想線程安全需要單獨加鎖。
    由於算法的特性,這個容器的尾結點是有延遲的,tail不一定是尾節點,但p.next == null的節點一定是尾結點。
    入隊出隊操作很抽象,需要畫圖幫助理解源碼,對應的源碼分析可參考併發容器-ConcurrentLinkedQueue詳解。

    5. AQS解讀

    抽象隊列同步器AbstractQueuedSynchronizer(AQS)是JUC中很多併發工具類的基礎,用來抽象各種併發控制行為,如ReentranLock、Semaphore。
    之前試着直接讀源碼,效果不太好,還是建議結合質量較高的文章來讀,這裏推薦一篇:Java併發之AQS詳解,並且作者還在不斷更新。
    這裏簡單記錄一下總結的點。

    結構特點

    • volatile int state標記位,標識當前的同步狀態。具體的用法和使用AQS的工具類有關。同時,在做CAS的時候,state的狀態變更是通過計算該變量在對象的偏移量來設置的。
    • CLH隊列。CLH鎖(Craig,Landin andHagersten)是一種在SMP(Symmetric Multi-Processor對稱多處理器)架構下基於單鏈表的高性能的自旋鎖,隊列中每個節點代表一個自旋的線程,每個線程只需在代表前一個線程的節點上的布爾值locked自旋即可,如圖

      圖源和CLH的詳解見算法:CLH鎖的原理及實現

    • exclusiveOwnerThread獨佔模式的擁有者,記錄現在是哪個線程佔用這個AQS。

    操作特點

    • 對state使用>0和<0的判斷,初看代碼很難看懂,這麼寫的原因是負值表示結點處於有效等待狀態,而正值表示結點已被取消
    • 大量的CAS:無論是獲取鎖、入隊、獲取鎖失敗后的自旋,全部是依賴CAS實現的。
    • 沒有使用synchronized:不難理解,如果使用了同步塊,那麼其實現ReentranLock就沒有和synchronized比較的價值了。不過這一點很少有文章專門提到。
    • LockSupport類的unpark()/park()方法的使用:回憶上文提到的線程狀態,如果線程獲取不到AQS控制的資源,需要將線程置於waiting,對應可選的方法是wait()/join()/park()。在AQS這個場景下,顯然一沒有synchronized,二沒有顯式的在同一個代碼塊中用join處理多線程(藉助隊列來處理線程,線程相互之間不感知),那麼只有park()才能達到目的。

    處理流程

    獲取資源acquire(int)

    1. 嘗試獲取資源(改寫state),成功則返回
    2. CAS(失敗則自旋)加入等待隊列隊尾
    3. 在隊列中自旋,嘗試獲取一次資源(前提:隊頭+ tryAcquire()成功),每次失敗都會更改線程狀態為waiting。自旋時會看看前驅有沒有失效的節點(即不再請求資源的),如果有就插隊到最前面並把前面無效節點清理掉便於gc
    4. waiting狀態中不響應中斷,獲取資源后才會補一個自我中斷selfInterrupt (調用Thread.currentThread().interrupt())

    釋放資源release(int)

    1. 嘗試釋放,成功則處理後續動作,失敗直接返回false
    2. 喚醒(unpark)等待隊列的下一個線程。如果當前節點沒找到後繼,則從隊尾tail從后往前找。

    共享模式獲取資源acquireShared(int)

    除了抽象方法tryAcquireShared()以外,基本和acquire(int)一致。
    在等待隊列中獲取資源后,會調用獨有的setHeadAndPropagate()方法,將這個節點設為頭結點的同時,檢查後續節點是否可以獲取資源。

    共享模式釋放資源releaseShared()

    和release(int)區別在於,喚醒後繼時,不要求當前線程節點狀態為0。舉例:當前線程A原先擁有5個資源,釋放1個,後繼的等待線程B剛好需要1個,那麼此時A、B就可以并行了。

    未實現的方法

    為了便於使用AQS的類更加個性化,AQS有一下方法直接拋UnsupportedOperationException。

    • isHeldExclusively()
    • tryAcquire()
    • tryRelease()
    • tryAcquireShared()
    • tryReleaseShared()
      不寫成abstract方法的原因是,避免強迫不需要對應方法的類實現這些方法。比如要寫一個獨佔的鎖,那麼就不需要實現共享模式的方法。

    AQS小結

    讀完源碼總結一下,AQS是一個維護資源和請求資源的線程之間的關係的隊列。對於資源(有序或無序的)獲取和釋放已經提取成了線程的出入隊方法,這個隊列同時維護上線程的自旋狀態和管理線程間的睡眠喚醒。

    應用

    本節可以看作為《JAVA併發變成實戰》14.6的引申。

    ReentrantLock

    用內部類Sync實現AQS,Sync實現ReentrantLock的行為。Sync又有FairSync和UnfairSync兩種實現。FairSync,lock對應aquire(1);UnfairSync,lock先CAS試着獲取一次,不行再aquire(1)。
    實際上,ReentrantLock的公平/非公平鎖只在首次lock時有區別,入隊后喚醒仍是按順序的。可以參考reentrantLock公平鎖和非公平鎖源碼解析
    Sync只實現了獨佔模式。

    注意:CyclicBarrier直接用了ReentrantLock,沒有直接用AQS。

    Semaphore

    和ReentrantLock類似,Semaphore也有一個內部類Sync,但相反的是這個Sync只實現了共享模式的acquire()/release()。
    Semaphore在acquire()/release()時會計算資源余量並設置,其中unfair模式下的acquire會無條件自旋CAS,fair模式下只有在AQS里不存在排隊中的後繼的情況下才會CAS,否則自旋。

    CountDownLatch

    同樣有一個內部類Sync,但是不再區分fair/unfair,並且是共享模式的。
    await()調用的是acquireSharedInterruptibly(),自然也存在自旋的可能,只是編程時一般不這麼用。countDown()時釋放一個資源繼續在releaseShared()里自旋直到全部釋放。

    FutureTask

    新版的FutureTask已經重寫,不再使用AQS,這裏就不再提了。

    ReentrantReadWriteLock

    可重入讀寫鎖,涉及到鎖升級,這裏沒有研究的很透徹,有興趣可以自行了解。
    注意到讀鎖和寫鎖是共用同一個Sync的。

    6 JMM到底是個啥?

    The Java memory model specifies how the Java virtual machine works with the computer’s memory (RAM)。
    —— Java Memory Model
    雖然被冠以”模型“,JMM實際上是定義JVM如何與計算機內存協同工作的規範,也可以理解為__指令__與其操作的__數據__的行為。這樣,自然而然地引入了指令重排序、變量更改的可見性的探討。
    JMM定義了一個偏序關係,稱之為happens-before。不滿足happens-before的兩個操作可以由JVM進行重排序。

    6.1 什麼是偏序關係

    假設 R 是集合 A 上的關係,如果R是自反的、反對稱的和傳遞的,則稱 R 是 A 上的一個偏序。偏序關係
    那麼,自反的、反對稱的和傳遞的,又是什麼?下面粘貼了百度百科相關詞條:

    • 自反關係:設 R是 A上的一個二元關係,若對於 A中的每一個元素 a, (a,a)都屬於 R,則稱 R為自反關係。
    • 反對稱關係:集合 A 上的二元關係 R 是反對稱的,當且僅當對於X里的任意元素a, b,若a R-關係於 b 且 b R-關係於 a,則a=b。
    • 傳遞關係:令R是A上的二元關係,對於A中任意的 ,若 ,且 ,則 ,則稱R具有傳遞性(或稱R是傳遞關係)。

    上面的反對稱關係稍微不好理解,轉換成逆否命題就好理解了:若a!=b,那麼R中不能同存在aRb和bRa。

    6.2 偏序關係和JMM

    將R作為兩個操作間的關係,集合A是所有操作的集合,那麼就可以理解JMM為什麼實際上是一套偏序關係了。

    6.3 happens-before規則

    這部分的說明很多文章都是有差異,比如鎖原則,JLS(Java Language Specification,Java語言規範)特指的是監視器鎖,只不過顯式鎖和內置鎖有相同的內存語義而已。這裏直接摘錄原文並配上說明。原文見Chapter 17. Threads and Locks

    If we have two actions x and y, we write hb(x, y) to indicate that x happens-before y.

    If x and y are actions of the same thread and x comes before y in program order, then hb(x, y).

    There is a happens-before edge from the end of a constructor of an object to the start of a finalizer (§12.6) for that object.

    If an action x synchronizes-with a following action y, then we also have hb(x, y).

    If hb(x, y) and hb(y, z), then hb(x, z).

    The wait methods of class Object (§17.2.1) have lock and unlock actions associated with them; their happens-before relationships are defined by these associated actions.

    It should be noted that the presence of a happens-before relationship between two actions does not necessarily imply that they have to take place in that order in an implementation. If the reordering produces results consistent with a legal execution, it is not illegal.

    For example, the write of a default value to every field of an object constructed by a thread need not happen before the beginning of that thread, as long as no read ever observes that fact.

    More specifically, if two actions share a happens-before relationship, they do not necessarily have to appear to have happened in that order to any code with which they do not share a happens-before relationship. Writes in one thread that are in a data race with reads in another thread may, for example, appear to occur out of order to those reads.

    The happens-before relation defines when data races take place.

    A set of synchronization edges, S, is sufficient if it is the minimal set such that the transitive closure of S with the program order determines all of the happens-before edges in the execution. This set is unique.

    It follows from the above definitions that:

    An unlock on a monitor happens-before every subsequent lock on that monitor.

    A write to a volatile field (§8.3.1.4) happens-before every subsequent read of that field.

    A call to start() on a thread happens-before any actions in the started thread.

    All actions in a thread happen-before any other thread successfully returns from a join() on that thread.

    The default initialization of any object happens-before any other actions (other than default-writes) of a program.

    試着翻譯一下各項規則:
    先定義hb(x, y)表示操作x和操作y的happens-before關係。

    1. 同一個線程的操作x, y,代碼中順序為x, y,那麼hb(x, y)
    2. 對象構造方法要早於終結方法完成
    3. 如果x synchronizes-with y那麼hb(x,y)
    4. 傳遞性,hb(x, y) 且hb(y,z)則hb(x,z)
    5. 同一個監視器鎖解鎖需要hb所有加鎖(注:該規則擴展到顯式鎖)
    6. volatile的讀hb所有寫(該規則擴展到原子操作)
    7. 線程start() hb所有它的啟動后的任何動作
    8. 線程中所有操作hb 對它的join()
    9. 對象默認構造器hb對它的讀寫

    synchronizes-with又是啥?查閱了一下,表示”這個關係表示一個行為在發生時,它首先把要操作的那些對象同主存同步完畢之後才繼續執行“。參考JMM(Java內存模型)中的核心概念。
    JLS上對happens-before的解釋翻譯過來還是不太好理解,《Java併發編程實戰》的解釋和Happens-beofre 先行發生原則(JVM 規範)一樣,可以參考下。

    最後可以發現,JMM只是一套規則,並沒有提到具體的實現,程序員知道Java有這一重保證即可。

    7. 短篇話題整理總結

    7.1 ThreadLocal的用法總結

    應用場景:在多線程下替代類的靜態變量(static),在多線程環境進行單個 的數據隔離。

    為什麼推薦使用static修飾ThreadLocal?

    這時才能保證”一個線程,一個ThreadLocal”,否則便成了“一個線程,(多個對象實例時)多個ThreadLocal”。
    可能會有內存泄漏:ThreadLocalMap的key(Thread對象)是弱引用,但value不是,如果key被回收,value還在。解法是手動remove掉。
    (本節參考了《Java併發編程實戰》)

    7.2 CountDownLatch和CyclicBarrier區別

    https://blog.csdn.net/tolcf/article/details/50925145
    CountDownLatch的子任務調用countDown後會繼續執行直至該線程結束。
    CyclicBarrier的子任務await時會暫停執行;可重複使用,即await的數目達到設置的值時,喚醒所有await的線程進行下一輪。

    7.3 ReentrantLock用了CAS但為什麼不是樂觀鎖?

    https://blog.csdn.net/qq_35688140/article/details/101223701
    我的看法:因為仍有可能造成阻塞,而樂觀鎖更新失敗則會直接返回(CAS允許自旋)。
    換一個角度,悲觀鎖是預先做最壞的設想——一定會有其他任務併發,那麼就先佔好坑再更新;樂觀鎖則是認為不一定有併發,更新時判斷再是否有問題。這樣看來ReentrantLock從使用方式上來說是悲觀鎖。

    7.4 雙重檢查加鎖

    public classDoubleCheckedLocking{ //1
          private static Instance instance; //2
          public staticI nstance getInstance(){ //3
                if(instance==null){ //4:第一次檢查
                      synchronized(DoubleCheckedLocking.class){ //5:加鎖
                            if(instance==null) //6:第二次檢查
                                  instance=newInstance(); //7:問題的根源出在這裏
                      } //8
                }//9
                return instance;
          }
    }
    

    問題

    一個線程看到另一個線程初始化該類的部分構造的對象,即以上代碼註釋第4處這裏讀到非null但未完全初始化

    原因

    註釋第7處,創建對象實例的三步指令1.分配內存空間2.初始化3.引用指向分配的地址,2和3可能重排序

    解決

    方案1,給instance加violatile
    方案2,使用佔位類,在類初始化時初始化對象,如下

    public class InstanceFactory {
          private static class InstanceHolder{
                public static Instance instance= newInstance();
          }
          public static Instance getInstance() {
                return InstanceHolder.instance;  //這裏將導致InstanceHolder類被初始化
          }
    }
    

    7.5 FutureTask

    FutureTask是Future的實現類,可以使用Future來接收線程池的submit()方法,也可以直接用FutureTask封裝任務,作為submit()的參數。具體的用法可以參考Java併發編程:Callable、Future和FutureTask 。
    新版的FutureTask不再使用AQS。
    FutureTask設置了當前工作線程,對於其任務維護了一個內部狀態轉換狀態機,通過CAS做狀態判斷和轉換。
    當其他線程來get()時,如果任務未完成則放入等待隊列,自旋直到取到結果(for循環+LockSupport.park()),否則直接取結果。
    具體實現原理可以參考《線程池系列一》-FutureTask原理講解與源碼剖析。

    7.6 JDK1.6鎖優化之輕量級鎖和偏向鎖

    實際上二者是有聯繫的,都是基於mark word實現。這個轉換關係可以用《深入理解Java虛擬機》第十三章的插圖表現

    但是這個圖沒有體現輕量級鎖釋放后,仍可恢復為可偏向的。

    7.7 問題排查三板斧

    1. top查看內存佔用率,-H可以看線程(不會完整展示),-p [pid]看指定進程的線程
      注意:linux線程和進程id都是在pid這一列展示的。
    2. pstack跟蹤進程棧,strace查看進程的系統操作。多次執行pstack來觀察進程是不是總是處於某種上下文中。
    3. jps直接獲取java進程id,jstat看java進程情況。jstate可用不同的參數來查看不同緯度的信息:類加載情況、gc統計、堆內存統計、新生代/老年代內存統計等,具體可以參考【JVM】jstat命令詳解—JVM的統計監測工具
    4. jstack打印java線程堆棧,和pstack展示方式很像,是java緯度的
    5. jmap打印java內存情況,-dump可以生成dump文件
    6. 分析dump文件,如MAT

    8. LeetCode多線程習題

    原題目和詳解參考Concurrency – 力扣

    1114.按序打印

    按照指定次序完成一系列動作,可以看做是buffer為1的1對1生產者消費者模型。

    1115.交替打印FooBar

    交替執行(不完全是生產者-消費者模型)某些動作。
    可用的解法:

    • synchronized
    • Semaphore
    • CountDownLatch
    • CyclicBarrier
    • Lock

    1116.打印零與奇偶數:0102…

    和1114類似

    1188. 設計有限阻塞隊列

    注意: 使用synchronize解法時,wait()應置於while中循環判斷.
    如果只用if,喚醒后不再次判斷dequeue可能NPE
    本題可以加深理解為什麼要用while

    1195. 交替打印字符串

    根據AC的解法推斷, 每個線程只調用對應方法一次,因此需要在方法內部循環
    不推薦只用synchronized,四個線程按順序打印, 如果使用單一的鎖很容易飢餓導致超時

    推薦解法:
    AtomicInteger無鎖解法
    CylicBarrier高效解法
    Semaphore加鎖

    1279. 紅綠燈路口

    題目難懂,暗含條件:車來時紅綠燈不是綠的,則強制變綠通過。紅綠燈本身的時間沒有嚴格控制

    延伸閱讀

    什麼是分佈式鎖
    一文了解分佈式鎖

    9. 未展開的話題

    併發研究之CPU緩存一致性協議(MESI)
    線程池原理(四):ScheduledThreadPoolExecutor
    一半是天使一半是魔鬼的Unsafe類詳解 —— unsafe類都有什麼?用偏移量直接訪問、線程操作、內存管理和內存屏障、CAS

    10. 其他參考

    Java併發高頻面試題

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

    【其他文章推薦】

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

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

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

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

    ※回頭車貨運收費標準

  • Accord.NET重啟4.0 開發

    Accord.NET重啟4.0 開發

    Accord.NET Framework是在AForge.NET基礎上封裝和進一步開發來的。功能也很強大,因為AForge.NET更注重與一些底層和廣度,而Accord.NET Framework更注重與機器學習這個專業,在其基礎上提供了更多統計分析和處理函數,包括圖像處理和計算機視覺算法,所以側重點不同,但都非常有用。 官方網站:http://accord-framework.net/

    在項目中斷2年時間之後,作者cesarsouza 在2020年5月1日更新了項目狀態, 他在歐洲完成博士,雖然他的工作中主要使用Python完成他的工作,但是他喜歡C#/.NET,一直在考慮Accprd.NET的發展問題,5月15日重新設定了4.0 版本的路線圖https://github.com/accord-net/framework/issues/2123,  其中他寫道:“我看到這個項目仍然被認為對許多人有用,我不認為讓項目消亡符合任何人的利益。我最初認為這個項目將由ML.NET取代,但事實並非如此。我們可以轉換框架,轉而與它合作。”

    我們在ML.NET的最初宣布文章中有Accord.NET的影子:

    CNTK 已經死了,目前只有 Tensoflow.NET在蓬勃發展,發展的情況很不錯,隨着Accord.NET的加入,這個生態又重新激活,期待大家一起加入,推動.NET機器學習生態的發展。

    (一)框架的三大功能模塊

    Accord.NET框架主要有三個大的功能性模塊。

    • 分別為科學技術,
    • 信號與圖像處理,
    • 支持組件。

    下面將對3個模型的命名空間和功能進行簡單介紹。可以讓大家更快的接觸和了解其功能是否是自己想要的,下面是主要的命名空間介紹。

    (二) 科學計算

    Accord.Math:包括矩陣擴展程序,以及一組矩陣數值計算和分解的方法,也包括一些約束和非約束問題的數值優化算法,還有一些特殊函數以及其他一些輔助工具。

    Accord.Statistics:包含概率分佈、假設檢驗、線性和邏輯回歸等統計模型和方法,隱馬爾科夫模型,(隱藏)條件隨機域、主成分分析、偏最小二乘判別分析、內核方法和許多其他相關的技術。

    Accord.MachineLearning: 為機器學習應用程序提供包括支持向量機,決策樹,樸素貝恭弘=叶 恭弘斯模型,k-means聚類算法,高斯混合模型和通用算法如Ransac,交叉驗證和網格搜索等算法。

    Accord.Neuro:包括大量的神經網絡學習算法,如Levenberg-Marquardt,Parallel Resilient Backpropagation,Nguyen-Widrow初始化算法,深層的信念網絡和許多其他神經網絡相關的算法。具體看參考幫助文檔。

    (三)信號與圖像處理

    Accord.Imaging:包含特徵點探測器(如Harris, SURF, FAST and  FREAK),圖像過濾器、圖像匹配和圖像拼接方法,還有一些特徵提取器。

    Accord.Audio:包含一些機器學習和統計應用程序說需要的處理、轉換過濾器以及處理音頻信號的方法。

    Accord.Vision:實時人臉檢測和跟蹤,以及對人流圖像中的一般的檢測、跟蹤和轉換方法,還有動態模板匹配追蹤器。

    (四) 支持組件

    主要是為上述一些組件提供數據显示,繪圖的控件,分為以下幾個命名空間:

    Accord.Controls:包括科學計算應用程序常見的柱狀圖、散點圖和表格數據瀏覽。

    Accord.Controls.Imaging:包括用來显示和處理的圖像的WinForm控件,包含一個方便快速显示圖像的對話框。

    Accord.Controls.Audio:显示波形和音頻相關性信息的WinForm控件。

    Accord.Controls.Vision:包括跟蹤頭部,臉部和手部運動以及其他計算機視覺相關的任務WinForm控件。

    (五) 支持的算法介紹

    下面將Accord.NET框架包括的主要功能算法按照類別進行介紹。來源主要是官網介紹,進行了簡單的翻譯和整理。

    1、分類(Classification)

    SVM(支持向量機,類SupportVectorMachine、類KernelSupportVectorMachine、類SequentialMinimalOptimization—序列最小優化算法)、

    K-NN鄰近算法(類KNearestNeighbors);

    Logistic Regression(邏輯回歸)、

    Decision Trees(決策樹,類DecisionTree、ID3Learning、C45Learning)、

    Neural Networks(神經網絡)、

    Deep Learning(深度學習)

    (Deep Neural Networks深層神經網絡)、

    Levenberg-Marquardt with Bayesian Regularization、

    Restricted Boltzmann Machines(限制玻耳茲曼機)、

    Sequence classification (序列分類),

    Hidden Markov Classifiers and Hidden Conditional Random Fields(隱馬爾科夫分類器和隱藏條件隨機域)。

    2、回歸(Regression)

    Multiple linear regression(多元線性回歸-單因變量多自變量)、

    SimpleLinearRegression(線性回歸,類SimpleLinearRegression)、

    Multivariate linear regression(多元線性回歸-多因變量多自變量)、polynomial regression (多項式回歸)、logarithmic regression(對數回歸)、Logistic regression(邏輯回歸)、multinomial logistic regression(多項式邏輯回歸)(softmax) and generalized linear models(廣義線性模型)、L2-regularized L2-loss logistic regression , L2-regularized logistic regression , L1-regularized logistic regression , L2-regularized logistic regression in the dual form and regression support vector machines。

    3、聚類(Clustering)

    K-Means、K-Modes、Mean-Shift(均值漂移)、Gaussian Mixture Models(高斯混合模型)、Binary Split(二元分裂)、Deep Belief Networks(深層的信念網絡)、 Restricted Boltzmann Machines(限制玻耳茲曼機)。聚類算法可以應用於任意數據,包括圖像、數據表、視頻和音頻。

    4、概率分佈(Distributions)

    包括40多個分佈的參數和非參數估計。包括一些常見的分佈如正態分佈、柯西分佈、超幾何分佈、泊松分佈、伯努利;也包括一些特殊的分佈如Kolmogorov-Smirnov , Nakagami、Weibull、and Von-Mises distributions。也包括多元分佈如多元正態分佈、Multinomial 、Independent 、Joint and Mixture distributions。

    5、假設檢驗(Hypothesis Tests)

    超過35統計假設測試,包括單向和雙向方差分析測試、非參數測試如Kolmogorov-Smirnov測試和媒體中的信號測試。contingency table tests such as the Kappa test,with variations for multiple tables , as well as the Bhapkar and Bowker tests; and the more traditional Chi-Square , Z , F , T and Wald tests .

    6、核方法(Kernel Methods)

    內核支持向量機,多類和多標籤向量機、序列最小優化、最小二乘學習、概率學習。Including special methods for linear machines such as LIBLINEAR’s methods for Linear Coordinate Descent , Linear Newton Method , Probabilistic Coordinate Descent , Probabilistic Coordinate Descent in the Dual , Probabilistic Newton Method for L1 and L2 machines in both the dual and primal formulations .

    7、圖像(Imaging)

    興趣和特徵點探測器如Harris,FREAK,SURF,FAST。灰度共生矩陣,Border following,Bag-of-Visual-Words (BoW),RANSAC-based homography estimation , integral images , haralick textural feature extraction , and dense descriptors such as histogram of oriented gradients (HOG) and Local Binary Pattern (LBP).Several image filters for image processing applications such as difference of Gaussians , Gabor , Niblack and Sauvola thresholding。還有幾個圖像處理中經常用到的圖像過濾器。

    8、音頻信號(Audio and Signal)

    音頻信號的加載、解析、保存、過濾和轉換,如在空間域和頻域應用音頻過濾器。WAV文件、音頻捕捉、時域濾波器,高通,低通,波整流過濾器。Frequency-domain operators such as differential rectification filter and comb filter with Dirac’s delta functions . Signal generators for Cosine , Impulse , Square signals.

    9、視覺(Vision)

    實時人臉檢測和跟蹤,以及圖像流中檢測、跟蹤、轉換的一般的檢測方法。Contains cascade definitions , Camshift and Dynamic Template Matching trackers . Includes pre-created classifiers for human faces and some facial features such as noses。

    10、降維技術

    SVD奇異值分解(OctaveEnvironment.svd方法);

    PCA主成分分析(類PrincipalComponent);

    ICA獨立成份分析(類IndependentComponetAnalysis)

    11、算法精度測算

    混淆矩陣(類ConfusionMatrix);

    ROC曲線評估(類ReceiverOperatingCharacteristic);

    Bootstrap算法(自助算法;類(Bootstrap));

    CrossValidation算法(交叉檢驗;類(CrossValidation));

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

    【其他文章推薦】

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

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

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

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

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

  • 車主們說這款有着跑車外觀的車 油耗出奇地低

    車主們說這款有着跑車外觀的車 油耗出奇地低

    只是高速時的風噪略大、儲物空間比較少,應該在四個門板多做一些儲物格。關於空間,我的身高176cm,把駕駛位置調好后,我在後排還有一拳多的腿部空間。而我的車型是2。0L的平均百公里油耗是7。8L。車主:Bestss購買車型:三廂 1。

    10多萬的合資A級車選擇非常多,要論最個性、最具動感外觀的車型,馬自達3昂克賽拉絕對是最具實力的車型之一。下面我們就來看看這款車的車主們都對它有哪些評價。

    長安馬自達-馬自達3 Axela昂克賽拉

    指導價:11.49-15.99萬

    車主:人稱啊明

    購買車型:三廂 1.5L自動豪華型

    裸車價格:12.89萬

    每一個男人都有一個跑車夢,昂克賽拉的外觀很像跑車,還有聰明的變速箱、讓人滿意的低油耗、起步快等優點,所以我最終選擇了這款車。

    它的操控真的不錯,給人的感覺是穩、實、准,轉向手感不錯,採用了四輪獨立懸架,支撐性好,在同級車型中性價比很高。

    目前我的車行駛了快8000公里了,平均百公里油耗只有7.1L!創馳藍天技術真不是蓋的。

    車主:佛山小偉

    購買車型:三廂 2.0L自動旗艦型

    裸車價格:14.99萬

    我對“魂動”的設計外觀和駕駛體驗最滿意,它指向精準、換擋果斷、動力也充沛。只是高速時的風噪略大、儲物空間比較少,應該在四個門板多做一些儲物格。

    關於空間,我的身高176cm,把駕駛位置調好后,我在後排還有一拳多的腿部空間。而我的車型是2.0L的平均百公里油耗是7.8L。

    車主:Bestss

    購買車型:三廂 1.5L手動豪華型

    裸車價格:12.29萬

    外觀和內飾就不用我多說了,大多數買昂克賽拉的人都是奔着漂亮的外觀去的!它的1.5L缸內直噴發動機動力夠用,2000轉以後的動力有比較大的爆發。

    行駛起來胎噪有些大,可能是輪胎側重抓地力的原因,還有就是車漆有些薄。

    目前我的車行駛了15000公里了,我對它比較滿意,目前的平均油耗是7.2L,油耗不算高!

    編者點評:

    昂克賽拉是一款性格鮮明的車型,它堅持採用四輪獨立懸挂、偏向性能的輪胎、AT變速箱等,讓它的操控性出色,如果你喜歡駕駛感受好的A級車,昂克賽拉絕對是一個不錯的選擇。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

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

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

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

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

    ※回頭車貨運收費標準