設計模式系列之外觀模式(Facade Pattern)——提供統一的入口

說明:設計模式系列文章是讀劉偉所著《設計模式的藝術之道(軟件開發人員內功修鍊之道)》一書的閱讀筆記。個人感覺這本書講的不錯,有興趣推薦讀一讀。詳細內容也可以看看此書作者的博客https://blog.csdn.net/LoveLion/article/details/17517213

模式概述

絕大多數B/S系統都有一個首頁或者導航頁面,大部分C/S系統都提供了菜單或者工具欄,在這裏,首頁和導航頁面就充當了B/S系統的外觀角色,而菜單和工具欄充當了C/S系統的外觀角色,通過它們用戶可以快速訪問子系統,增強了軟件的易用性。

在軟件開發中,有時候為了完成一項較為複雜的功能,一個客戶類需要和多個業務類交互,而這些需要交互的業務類經常會作為一個整體出現,由於涉及到的類比較多,導致使用時代碼較為複雜,此時,特別需要一個類似服務員一樣的角色,由它來負責和多個業務類進行交互,而客戶類只需與該類交互。外觀模式通過引入一個外觀角色(Facade)來簡化客戶端與子系統(Subsystem)之間的交互,為複雜的子系統調用提供一個統一的入口,降低子系統與客戶端的耦合度,使得客戶端調用非常方便。

模式定義

外觀模式中,一個子系統的外部與其內部的通信通過一個統一的外觀類進行,外觀類將客戶類與子系統的內部複雜性分隔開,使得客戶類只需要與外觀角色打交道,而不需要與子系統內部的很多對象打交道。

外觀模式(Facade Pattern):為子系統中的一組接口提供一個統一的入口。外觀模式定義了一個高層接口,這個接口使得這一子系統更加容易使用

外觀模式又稱為門面模式,它是一種對象結構型模式。外觀模式是迪米特法則的一種具體實現,通過引入一個新的外觀角色可以降低原有系統的複雜度,同時降低客戶類與子系統的耦合度。

模式結構圖

外觀模式沒有一個一般化的類圖描述,下圖所示的類圖也可以作為描述外觀模式的結構圖:

外觀模式包含如下兩個角色:

  • Facade(外觀角色):在客戶端可以調用它的方法,在外觀角色中可以知道相關的(一個或者多個)子系統的功能和責任;在正常情況下,它將所有從客戶端發來的請求委派到相應的子系統去,傳遞給相應的子系統對象處理。

  • SubSystem(子系統角色):在軟件系統中可以有一個或者多個子系統角色,每一個子系統可以不是一個單獨的類,而是一個類的集合,它實現子系統的功能;每一個子系統都可以被客戶端直接調用,或者被外觀角色調用,它處理由外觀類傳過來的請求;子系統並不知道外觀的存在,對於子系統而言,外觀角色僅僅是另外一個客戶端而已。

模式偽代碼

外觀模式中所指的子系統是一個廣義的概念,它可以是一個類、一個功能模塊、系統的一個組成部分或者一個完整的系統。子系統類通常是一些業務類,實現了一些具體的、獨立的業務功能,其典型代碼如下:

public class SubSystemA {

    public void methodA() {
        //業務實現代碼
    }
}

public class SubSystemB {

    public void methodB() {
        //業務實現代碼
    }
}

public class SubSystemC {

    public void methodC() {
        //業務實現代碼
    }
}

引入外觀類,與子系統業務類之間的交互統一由外觀類來完成

public class Facade {
    private SubSystemA obj1 = new SubSystemA();
    private SubSystemB obj2 = new SubSystemB();
    private SubSystemC obj3 = new SubSystemC();

    public void method() {
        obj1.methodA();
        obj2.methodB();
        obj3.methodC();
    }
}

由於在外觀類中維持了對子系統對象的引用,客戶端可以通過外觀類來間接調用子系統對象的業務方法,而無須與子系統對象直接交互。引入外觀類后,客戶端代碼變得非常簡單,典型代碼如下:

public static void main(String[] args) {
    Facade facade = new Facade();
    facade.method();
}

模式改進

在標準的外觀模式中,如果需要增加、刪除或更換與外觀類交互的子系統類,必須修改外觀類或客戶端的源代碼,這將違背開閉原則,因此可以通過引入抽象外觀類來對系統進行改進,在一定程度上可以解決該問題。在引入抽象外觀類之後,客戶端可以針對抽象外觀類進行編程,對於新的業務需求,不需要修改原有外觀類,而對應增加一個新的具體外觀類,由新的具體外觀類來關聯新的子系統對象。

定義抽象外觀類

public abstract class AbstractFacade {
    public abstract void method();
}

根據具體的場景,實現具體的外觀類

public class Facade1 extends AbstractFacade {

    private SubSystemA obj1 = new SubSystemA();
    private SubSystemB obj2 = new SubSystemB();

    @Override
    public void method() {
        obj1.methodA();
        obj2.methodB();
    }
}

public class Facade2 extends AbstractFacade {

    private SubSystemB obj1 = new SubSystemB();
    private SubSystemC obj2 = new SubSystemC();

    @Override
    public void method() {
        obj1.methodB();
        obj2.methodC();
    }
}

客戶端針對抽象外觀類進行編程,代碼片段如下:

public static void main(String[] args) {
    AbstractFacade facade = new Facade1();
    // facade = new Facade2();
    facade.method();
}

模式應用

個人認為外觀模式某些情況下可以看成是對既有系統的再次封裝,所以各種類庫、工具庫(比如hutool)、框架基本都有外觀模式的影子。外觀模式讓調用方更加簡潔,不用關心內部的實現,與此同時,也讓越來越多的程序猿多了個調包俠的昵稱(當然了這其中也包括筆者●´ω`●行無際)。

所以,你可能在很多開源代碼中看到類似XxxBootstrapXxxContextXxxMain等類似的Class,再追進去看一眼,你可能發現裏面關聯了一大堆的複雜的對象,這些對象對於外層調用者來說幾乎是透明的。

例子太多,以致於不知道舉啥例子(實際是偷懶的借口O(∩_∩)O哈哈~)。

模式總結

外觀模式並不給系統增加任何新功能,它僅僅是簡化調用接口。在幾乎所有的軟件中都能夠找到外觀模式的應用。所有涉及到與多個業務對象交互的場景都可以考慮使用外觀模式進行重構。

主要優點

(1) 它對客戶端屏蔽了子系統組件,減少了客戶端所需處理的對象數目,並使得子系統使用起來更加容易。通過引入外觀模式,客戶端代碼將變得很簡單,與之關聯的對象也很少。

(2) 它實現了子系統與客戶端之間的松耦合關係,這使得子系統的變化不會影響到調用它的客戶端,只需要調整外觀類即可。

(3) 一個子系統的修改對其他子系統沒有任何影響,而且子系統內部變化也不會影響到外觀對象。

適用場景

(1) 當要為訪問一系列複雜的子系統提供一個簡單入口時可以使用外觀模式。

(2) 客戶端程序與多個子系統之間存在很大的依賴性。引入外觀類可以將子系統與客戶端解耦,從而提高子系統的獨立性和可移植性。

(3) 在層次化結構中,可以使用外觀模式定義系統中每一層的入口,層與層之間不直接產生聯繫,而通過外觀類建立聯繫,降低層之間的耦合度。

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

【其他文章推薦】

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

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

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

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

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

Java 多線程基礎(十二)生產者與消費者

 Java 多線程基礎(十二)生產者與消費者

一、生產者與消費者模型

生產者與消費者問題是個非常典型的多線程問題,涉及到的對象包括“生產者”、“消費者”、“倉庫”和“產品”。他們之間的關係如下:

①、生產者僅僅在倉儲未滿時候生產,倉滿則停止生產。
②、消費者僅僅在倉儲有產品時候才能消費,倉空則等待。
③、當消費者發現倉儲沒產品可消費時候會通知生產者生產。
④、生產者在生產出可消費產品時候,應該通知等待的消費者去消費。

生產者消費者模型具體來講,就是在一個系統中,存在生產者和消費者兩種角色,他們通過內存緩衝區進行通信,生產者生產消費者需要的資料,消費者把資料做成產品。生產消費者模式如下圖。

在日益發展的服務類型中,譬如註冊用戶這種服務,它可能解耦成好幾種獨立的服務(賬號驗證,郵箱驗證碼,手機短信碼等)。它們作為消費者,等待用戶輸入數據,在前台數據提交之後會經過分解併發送到各個服務所在的url,分發的那個角色就相當於生產者。消費者在獲取數據時候有可能一次不能處理完,那麼它們各自有一個請求隊列,那就是內存緩衝區了。做這項工作的框架叫做消息隊列。

二、生產者與消費者實現

下面通過生產包子的例子及wait()/notify()方式實現該模型(後面學習線程池相關內容之後,再通過其它方式實現生產/者消費者模型)。

麵包類:

public class Bread {
    private int capacity;    // 麵包的容量
    private int size;        // 麵包的實際數量
    public Bread(int capacity) {
        this.capacity = capacity;
        this.size = 0;
    }
    
    // 生產麵包
    public synchronized void produce(int val) {
        try {
             // left 表示“想要生產的數量”(有可能生產量太多,需多此生產)
            int left = val;
            while (left > 0) {
                // 庫存已滿時,等待“消費者”消費產品。
                while (size >= capacity)
                    wait();
                // 獲取“實際生產的數量”(即庫存中新增的數量)
                // 如果“庫存”+“想要生產的數量”>“總的容量”,則“實際增量”=“總的容量”-“當前容量”。(此時填滿倉庫)
                // 否則“實際增量”=“想要生產的數量”
                int inc = (size+left)>capacity ? (capacity-size) : left;
                size += inc;
                left -= inc;
                System.out.printf("%s produce(%3d) --> left=%3d, inc=%3d, size=%3d\n",
                        Thread.currentThread().getName(), val, left, inc, size);
                // 通知“消費者”可以消費了。
                notifyAll();
            }
        }catch(InterruptedException e) {
            e.printStackTrace();
        }
    }
    // 消費麵包
    public synchronized void consume(int val) {
        try {
             // left 表示“客戶要消費數量”(有可能消費量太大,庫存不夠,需多此消費)
            int left = val;
            while (left > 0) {
                // 庫存為0時,等待“生產者”生產產品。
                while (size <= 0)
                    wait();
                // 獲取“實際消費的數量”(即庫存中實際減少的數量)
                // 如果“庫存”<“客戶要消費的數量”,則“實際消費量”=“庫存”;
                // 否則,“實際消費量”=“客戶要消費的數量”。
                int dec = (size<left) ? size : left;
                size -= dec;
                left -= dec;
                System.out.printf("%s consume(%3d) <-- left=%3d, dec=%3d, size=%3d\n",
                        Thread.currentThread().getName(), val, left, dec, size);
                notifyAll();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

生產者類

public class Producer{
    Bread bread;
    public Producer(Bread bread) {
        this.bread = bread;
    }
    public void produce(final int val) {
        new Thread(() -> {
            bread.produce(val);
        }).start();;
    }
}

消費者類

public class Customer {
    private Bread bread;
    public Customer(Bread bread) {
        this.bread = bread;
    }
    public void consume(final int val) {
        new Thread(() -> {
            bread.consume(val);
        }).start();;
    }
}

測試類代碼

public class Demo {
    public static void main(String[] args) {
        Bread bread = new Bread(100);
        Producer producer = new Producer(bread);
        Cunstomer customer = new Customer(bread);
        
        producer.produce(60);
        producer.produce(120);
        consumer.consume(90);
        consumer.consume(150);
        producer.produce(110);
    }
}
// 運行結果
Thread-1 produce( 60) --> left=  0, inc= 60, size= 60
Thread-5 produce(110) --> left= 70, inc= 40, size=100
Thread-4 consume(150) <-- left= 50, dec=100, size=  0
Thread-2 produce(120) --> left= 20, inc=100, size=100
Thread-3 consume( 90) <-- left=  0, dec= 90, size= 10
Thread-4 consume(150) <-- left= 40, dec= 10, size=  0
Thread-5 produce(110) --> left=  0, inc= 70, size= 70
Thread-4 consume(150) <-- left=  0, dec= 40, size= 30
Thread-2 produce(120) --> left=  0, inc= 20, size= 50

說明:

①、Producer是“生產者”類,它與“麵包(bread)”關聯。當調用“生產者”的produce()方法時,它會新建一個線程並向“麵包類”中生產產品。
②、Customer是“消費者”類,它與“麵包(bread)”關聯。當調用“消費者”的consume()方法時,它會新建一個線程並消費“麵包類”中的產品。
③、Bread是麵包類,記錄“麵包的產量(capacity)”以及麵包當前實際數目(size)”。
        麵包類的生產方法produce()和消費方法consume()方法都是synchronized方法,進入synchronized方法體,意味着這個線程獲取到了該“麵包”對象的同步鎖。這也就是說,同一時間,生產者和消費者線程只能有一個能運行。通過同步鎖,實現了對“殘酷”的互斥訪問。
       對於生產方法 produce() 而言:當麵包量滿時,生產者線程等待,需要等待消費者消費產品之後,生產線程才能生產;生產者線程生產完麵包之後,會通過 notifyAll() 喚醒同步鎖上的所有線程,包括“消費者線程”,即我們所說的“通知消費者進行消費”。
      對於消費方法consume()而言:當倉庫為空時,消費者線程等待,需要等待生產者生產產品之後,消費者線程才能消費;消費者線程消費完產品之後,會通過 notifyAll() 喚醒同步鎖上的所有線程,包括“生產者線程”,即我們所說的“通知生產者進行生產”。

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

【其他文章推薦】

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

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

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

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

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

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

基於用戶的協同過濾來構建推薦系統

1.概述

之前介紹了如何構建一個推薦系統,今天給大家介紹如何基於用戶的協同過濾來構建推薦的實戰篇。

2.內容

協同過濾技術在推薦系統中應用的比較廣泛,它是一個快速發展的研究領域。它比較常用的兩種方法是基於內存(Memory-Based)和基於模型(Model-Based)。

  • 基於內存:主要通過計算近似度來進行推薦,比如基於用戶(Used-Based)和基於物品(Item-Based)的協同過濾,這兩個模式中都會首先構建用戶交互矩陣,然後矩陣的行向量和列向量可以用來表示用戶和物品,然後計算用戶和物品的相似度來進行推薦;
  • 基於模型:主要是對交互矩陣進行填充,預測用戶購買某個物品的可能性。

為了解決這些問題,可以通過建立協同過濾模型,利用購買數據向客戶推薦產品。下面,我們通過基於用戶的協同過濾(基於內存),通過實戰來一步步實現其中的細節。基於用戶的系統過濾體現在具有相似特徵的人擁有相似的喜好。比如,用戶A向用戶B推薦了物品C,而B購買過很多類似C的物品,並且評價也高。那麼,在未來,用戶B也會有很大的可能會去購買物品C,並且用戶B會基於相似度度量來推薦物品C。

2.1 基於用戶與用戶的協同過濾

這種方式識別與查詢用戶相似的用戶,並估計期望的評分為這些相似用戶評分的加權平均值。實戰所使用的Python語言,這裏需要依賴的庫如下:

  • pandas
  • numpy
  • sklearn

Python環境:

  • 版本3.7.6
  • Anaconda3

2.2 評分函數

這裏給非個性化協同過濾(不包含活躍用戶的喜歡、不喜歡、以及歷史評分),返回一個以用戶U和物品I作為輸入參數的分數。該函數輸出一個分數,用於量化用戶U喜歡 / 偏愛物品I的程度。這通常是通過對與用戶相似的人的評分來完成的。涉及的公式如下:

 

 這裏其中s為預測得分,u為用戶,i為物品,r為用戶給出的評分,w為權重。在這種情況下,我們的分數等於每個用戶對該項目的評價減去該用戶的平均評價再乘以某個權重的總和,這個權重表示該用戶與其他用戶有多少相似之處,或者對其他用戶的預測有多少貢獻。這是用戶u和v之間的權重,分數在0到1之間,其中0是最低的,1是最高的。理論上看起來非常完美,那為啥需要從每個用戶的評分中減去平均評分,為啥要使用加權平均而不是簡單平均?這是因為我們所處理的用戶類型,首先,人們通常在不同的尺度上打分,用戶A可能是一個积極樂觀的用戶,會給用戶A自己喜歡的電影平均高分(例如4分、或者5分)。而用戶B是一個不樂觀或者對評分標準比較高的用戶,他可能對最喜歡的電影評分為2分到5分之間。用戶B的2分對應到用戶A的4分。改進之處是可以通過規範化用戶評分來提高算法效率。一種方法是計算s(u,i)的分數,它是用戶對每件物品的平均評價加上一些偏差。通過使用餘弦相似度來計算上述公式中給出的權重,同時,按照上述方式對數據進行歸一化,在pandas中進行一些數據分析。

2.2.1 導入Python依賴包

import pandas as pd
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity
from sklearn.metrics import pairwise_distances

2.2.2 加載數據源

加載數據示例代碼如下所示:

movies = pd.read_csv("data/movies.csv")
Ratings = pd.read_csv("data/ratings.csv")
Tags = pd.read_csv("data/tags.csv")

結果預覽如下:

print(movies.head())
print(Ratings.head())
print(Tags.head())

 

 構建數據:

Mean = Ratings.groupby(by="userId", as_index=False)['rating'].mean()
Rating_avg = pd.merge(Ratings, Mean, on='userId')
Rating_avg['adg_rating'] = Rating_avg['rating_x'] - Rating_avg['rating_y']
print(Rating_avg.head())

結果如下:

 

2.3 餘弦相似度

 對於上面的公式,我們需要找到有相似想法的用戶。找到一個喜歡和不喜歡的用戶聽起來很有意思,但是我們如何找到相似性呢?那麼這裏我們就需要用到餘弦相似度,看看用戶有多相似。它通常是根據用戶過去的評分來計算的。

這裏使用到Python的的sklearn的cosine_similarity函數來計算相似性,並做一些數據預處理和數據清洗。實例代碼如下:

check = pd.pivot_table(Rating_avg,values='rating_x',index='userId',columns='movieId')
print(check.head())
final = pd.pivot_table(Rating_avg,values='adg_rating',index='userId',columns='movieId')
print(final.head())

結果如下:

上圖中包含了很多NaN的值,這是因為每個用戶都沒有看過所有的電影,所以這種類型的矩陣被稱為稀疏矩陣。類似矩陣分解的方法被用來處理這種稀疏性,接下來,我們來對這些NaN值做相關替換。

這裏通常有兩種方式:

  1. 使用行上的用戶平均值;
  2. 用戶在列上的電影平均值

代碼如下:

# Replacing NaN by Movie Average
final_movie = final.fillna(final.mean(axis=0))
print(final_movie.head())

# Replacing NaN by user Average
final_user = final.apply(lambda row: row.fillna(row.mean()), axis=1)
print(final_user.head())

結果如下:

 

 接着,我們開始計算用戶之間的相似性,代碼如下:

# user similarity on replacing NAN by item(movie) avg
cosine = cosine_similarity(final_movie)
np.fill_diagonal(cosine, 0)
similarity_with_movie = pd.DataFrame(cosine, index=final_movie.index)
similarity_with_movie.columns = final_user.index
# print(similarity_with_movie.head())

# user similarity on replacing NAN by user avg
b = cosine_similarity(final_user)
np.fill_diagonal(b, 0 )
similarity_with_user = pd.DataFrame(b,index=final_user.index)
similarity_with_user.columns=final_user.index
# print(similarity_with_user.head())

結果如下:

 

 然後,我們來檢驗一下我們的相似度是否有效,代碼如下:

def get_user_similar_movies( user1, user2 ):
    common_movies = Rating_avg[Rating_avg.userId == user1].merge(
    Rating_avg[Rating_avg.userId == user2],
    on = "movieId",
    how = "inner" )
    return common_movies.merge( movies, on = 'movieId' )

a = get_user_similar_movies(370,86309)
a = a.loc[ : , ['rating_x_x','rating_x_y','title']]
print(a.head())

結果如下:

 

 從上圖中,我們可以看出產生的相似度幾乎是相同的,符合真實性。

2.4 相鄰用戶

在2.3中計算了所有用戶的相似度,但是在大數據領域,推薦系統與大數據相結合是至關重要的。以電影推薦為例子,構建一個矩陣(862 * 862),這個與實際的用戶數據(百萬、千萬或者更多)相比,這是一個很小的矩陣。因此在計算任何物品的分數時,如果總是查看所有其他用戶將不是一個好的解決方案或者方法。因此,採用相鄰用戶的思路,對於特定用戶,只取K個類似用戶的集合。

下面,我們對K取值30,所有的用戶都有30個相鄰用戶,代碼如下:

def find_n_neighbours(df,n):
    order = np.argsort(df.values, axis=1)[:, :n]
    df = df.apply(lambda x: pd.Series(x.sort_values(ascending=False)
           .iloc[:n].index, 
          index=['top{}'.format(i) for i in range(1, n+1)]), axis=1)
    return df

# top 30 neighbours for each user
sim_user_30_u = find_n_neighbours(similarity_with_user,30)
print(sim_user_30_u.head())

sim_user_30_m = find_n_neighbours(similarity_with_movie,30)
print(sim_user_30_m.head())

結果如下:

 

 2.5 計算最後得分

實現代碼如下所示:

def User_item_score(user,item):
    a = sim_user_30_m[sim_user_30_m.index==user].values
    b = a.squeeze().tolist()
    c = final_movie.loc[:,item]
    d = c[c.index.isin(b)]
    f = d[d.notnull()]
    avg_user = Mean.loc[Mean['userId'] == user,'rating'].values[0]
    index = f.index.values.squeeze().tolist()
    corr = similarity_with_movie.loc[user,index]
    fin = pd.concat([f, corr], axis=1)
    fin.columns = ['adg_score','correlation']
    fin['score']=fin.apply(lambda x:x['adg_score'] * x['correlation'],axis=1)
    nume = fin['score'].sum()
    deno = fin['correlation'].sum()
    final_score = avg_user + (nume/deno)
    return final_score

score = User_item_score(320,7371)
print("score (u,i) is",score)

結果如下:

 

這裏我們算出來的預測分數是4.25,因此可以認為用戶(370),可能喜歡ID(7371)的電影。接下來,我們給用戶(370)做電影推薦,實現代碼如下:

Rating_avg = Rating_avg.astype({"movieId": str})
Movie_user = Rating_avg.groupby(by = 'userId')['movieId'].apply(lambda x:','.join(x))

def User_item_score1(user):
    Movie_seen_by_user = check.columns[check[check.index==user].notna().any()].tolist()
    a = sim_user_30_m[sim_user_30_m.index==user].values
    b = a.squeeze().tolist()
    d = Movie_user[Movie_user.index.isin(b)]
    l = ','.join(d.values)
    Movie_seen_by_similar_users = l.split(',')
    Movies_under_consideration = list(set(Movie_seen_by_similar_users)-set(list(map(str, Movie_seen_by_user))))
    Movies_under_consideration = list(map(int, Movies_under_consideration))
    score = []
    for item in Movies_under_consideration:
        c = final_movie.loc[:,item]
        d = c[c.index.isin(b)]
        f = d[d.notnull()]
        avg_user = Mean.loc[Mean['userId'] == user,'rating'].values[0]
        index = f.index.values.squeeze().tolist()
        corr = similarity_with_movie.loc[user,index]
        fin = pd.concat([f, corr], axis=1)
        fin.columns = ['adg_score','correlation']
        fin['score']=fin.apply(lambda x:x['adg_score'] * x['correlation'],axis=1)
        nume = fin['score'].sum()
        deno = fin['correlation'].sum()
        final_score = avg_user + (nume/deno)
        score.append(final_score)
    data = pd.DataFrame({'movieId':Movies_under_consideration,'score':score})
    top_5_recommendation = data.sort_values(by='score',ascending=False).head(5)
    Movie_Name = top_5_recommendation.merge(movies, how='inner', on='movieId')
    Movie_Names = Movie_Name.title.values.tolist()
    return Movie_Names

user = int(input("Enter the user id to whom you want to recommend : "))
predicted_movies = User_item_score1(user)
print(" ")
print("The Recommendations for User Id : 370")
print("   ")
for i in predicted_movies:
    print(i)

結果如下:

 

3.總結

基於用戶的協同過濾,流程簡述如下:

  1. 採集數據 & 存儲數據
  2. 加載數據
  3. 數據建模(數據預處理 & 數據清洗)
  4. 計算相似性(餘弦相似度、相鄰計算)
  5. 得分預測(預測和最終得分計算)
  6. 物品推薦

4.結束語

這篇博客就和大家分享到這裏,如果大家在研究學習的過程當中有什麼問題,可以加群進行討論或發送郵件給我,我會盡我所能為您解答,與君共勉!

另外,博主出書了《Kafka並不難學》和《Hadoop大數據挖掘從入門到進階實戰》,喜歡的朋友或同學, 可以在公告欄那裡點擊購買鏈接購買博主的書進行學習,在此感謝大家的支持。關注下面公眾號,根據提示,可免費獲取書籍的教學視頻

,

這篇博客就和大家分享到這裏,如果大家在研究學習的過程當中有什麼問題,可以加群進行討論或發送郵件給我,我會盡我所能為您解答,與君共勉!

另外,博主出書了《Kafka並不難學》和《Hadoop大數據挖掘從入門到進階實戰》,喜歡的朋友或同學, 可以在公告欄那裡點擊購買鏈接購買博主的書進行學習,在此感謝大家的支持。關注下面公眾號,根據提示,可免費獲取書籍的教學視頻

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

【其他文章推薦】

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

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

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

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

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

程序員不能說自己不行啊

二哥,最近我剛進了一家公司,之前跟你說過,培訓出身剛剛畢業,打算在北京打拚。最近進公司,給安排了工作,今天第一次沒人帶,自己上手搞代碼,搞不出,明明挺簡單的功能,自己還是做不出,不知道從哪裡學習,想趕快熟悉工作,可是自己的能力不行,在地鐵上常看二哥原創的作品,平常积極在看,超級希望能自己學到本事,但自己的能力真的有點問題,工作搞不完,害怕被問,害怕任務完不成被辭退。

以上是讀者西瓜向我提的一個問題,我覺得挺具有代表性的,所以決定拉出來單獨寫一篇文章答疑解惑一下。

可以肯定的一點是,任何時候都要說自己不行啊,尤其是男性同胞,可以認慫,但是“不行”這個兩個字千萬不要輕易說出口,為什麼?你懂吧?

人的能力各有不同,但如果你自己都不自信,那又能做好什麼事情呢?心理建設非常重要。

記得之前看一個短片,一個小男孩跳了無數次,都無法越過障礙物,但是呢,他身邊的同學一直為他加油吶喊,小男孩呢,也從來沒有放棄的打算,最後的結果我都快看哭了,他真的跳過去了,他出色地完成了自我挑戰。

他的成功,離不開同學們的鼓勵,但更重要的是他鍥而不舍的精神,心裏素質比一般的成年人都要強大。

我現在已經為人父了,雖然我一直標榜自己只有 18 歲,但叫二叔的讀者真的越來越多,我已經逆來順受了。在我的教育觀念里,我覺得我家女兒最優秀的一點品質,就是,如果她喜歡一件事,她就會主動去鑽研,去摸索,在沒有任何外人的幫助下。

你比如說,現在比較流行的平衡車,就是不帶腳踏板和鏈條的自行車。一開始,我想給她報個班,至少有個老師教教,對吧?

但是,她的表現完全出乎我的意料,我只要把車買回來,放到她的面前,怎麼騎,完全靠她自己去體驗。一開始小心翼翼,很保本,但她不滿足於現狀,就找一些小坡騎,然後是再大一點的坡,就這樣,挑戰一次又一次,自己就完全掌握了騎行的技術。

對於我來說,我沒有騎平衡車的經驗,小時候也沒有這玩意。我能做的除了買車,就是給她鼓勵,摔倒了沒事,哭了也沒事,有些事情,痛苦的同時,伴隨着挑戰和突破。

對於我們成年人來說,其實道理都懂的,但人與人之間的差距之所以拉開,除了選擇的正確有否,最大的因素我想就是,你有沒有自己主動去做

西瓜說自己明明很簡單的功能,就是做不出來。這種感覺我也有過,即便是現在有了十年多的編程經驗,仍然在某些時刻感到举手無措,無從下手。

對,這就是為什麼人要終身學習的原因啊。我們做不出來,除了思維上、認知上的局限性,另外一個重要的點就在於,你有沒有經驗。

對於新人來說,經驗肯定是欠缺的,這點毫無疑問,對吧?但是只要公司招你進去了,無論是不是培訓班出身,負責任的公司都會給你充足的時間和空間去進步,就看你自己有沒有主動。

我大三出去實習的時候,公司要求我做一個計算器,那時候覺得好難啊,因為加減乘除,再帶上小括號,運算是有優先級的,還要考慮到小括號的自動補齊,對於那時候菜得一筆的我來說,真特么難啊。

但能怎麼辦?做不出來就意味着要被辭退,那只有一個辦法,就是上網搜,找別人的例子模仿,拆分,融化,把它變成是自己的。

那時候,我還不會玩 GitHub、碼雲和開源中國,私下里主動學習的地方只有一個,好像是叫編程入門網,現在已經沒有了。我就是照着上面的例子,一個個手敲,當你例子敲多了,很多編程知識就融會貫通了。

現在好了,優秀的案例數不勝數。我的兩個好朋友,macrozheng 開源了他的電商平台 mall,江南一點雨開源了他的微人事系統 vhr,這兩個開源項目我一直強烈推薦新手去下載到本地,去學習。

很多時候,對於編程天賦一般的我們來說,不需要主動去造輪子,我們只需要去發現輪子,對吧?

我在一開始做 Web 管理系統的時候,找了一個企業級的開源系統,叫做 DWZ,不知道有沒有讀者朋友用過,當年非常火,我們公司的後台管理系統現在還在用,雖然說界面已經很古董了,但對於我們公司來說,足夠用了。

這套 DWZ 就封裝了很多前端組件,對於我一個 Java 程序員來說,非常友好,直接可以上手操作,如果一些組件不滿足,我就去改造。改造的過程中,就積攢了大把解決問題的實戰經驗,這是彌足珍貴的。

我在《Web全棧開發進階之路》這本書里,就借鑒了不少 DWZ 的優秀思想。不要覺得不會造輪子是可恥的,會用輪子也是真本領啊。

就西瓜來說,平常喜歡看我的原創文,那我文章涉及到的例子有沒有去敲呢?如果你敲了,你就會發現,文章里涉及到的例子能解決大部分新人在工作中遇到的問題,直接把這些作為自己的工具包,下次遇到拿來即用就可以了。

對於 Java 程序員來說,JDK 的原生 API 不能滿足需求的話,還有很多第三方的類庫,比如說 Apache 的,封裝了大量常用的工具類和方法。前提條件是,你必須得知道有這些東西,如果不知道的話,那就無從下手了,對吧?

那怎麼見多識廣呢?這就回到了之前所說的,你得去練,動手去練,無論是書本里的,還是文章里的,還是開源項目里的,你得去手操一遍,不要眼高手低,敲多了,自然就形成了自己解決問題的思路和方法。

擔心自己被辭退是一件好事,這會督促我們前進,對吧?有的人,有自驅力,不需要外力的干預就能奮發圖強,有的人,就需要一條看不見的鞭子抽打着,才會有前進的動力。

別懷疑自己,真的,人嘛,總是有能力強弱之分的,要學會接納自己,像二哥一樣自信點,腳踏實地,一點一點去進步,當你取得一點成績的時候就把這些當做是里程碑,隨着時間的推移,你就會發現,自己變禿了,不不不,變強了。

加油,西瓜!

如果覺得文章對你有點幫助,請微信搜索「 沉默王二 」第一時間閱讀。

本文已收錄 GitHub,傳送門~ ,裏面更有大廠面試完整考點,歡迎 Star。

我是沉默王二,一枚有顏值卻靠才華苟且的程序員。關注即可提升學習效率,別忘了三連啊,點贊、收藏、留言,我不挑,嘻嘻

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

【其他文章推薦】

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

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

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

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

※超省錢租車方案

線性表的鏈式存儲–單鏈表

Java之線性表的鏈式存儲——單鏈表

我們都知道,線性表的存儲結構分為兩種,順序存儲結構和鏈式存儲結構,線性表的分類可以參考下圖來學習記憶。今天我們主要來學習一下鏈式存儲結構。

一、鏈式存儲介紹

“鏈式存儲結構,地址可以連續也可以不連續的存儲單元存儲數據元素”——來自定義。

其實,你可以想象這樣一個場景,你想找一個人(他的名字叫小譚),於是你首先去問 A , A 說他不知道,但是他說 B 可能知道,並告訴了你 B 在哪裡,於是你找到 B ,B 說他不知道,但是他說 C 可能知道,並告訴了你 C 的地址,於是你去找到 C ,C 真的知道小譚在何處。

上面場景其實可以幫助我們去理解鏈表,其實每一個鏈表都包含多個節點,節點又包含兩個部分,一個是數據域(儲存節點含有的信息),一個是指針域(儲存下一個節點或者上一個節點的地址),而這個指針域就相當於你去問B,B知道C的地址,這個指針域就是存放的 C 的地址。

鏈表下面其實又細分了3種:單鏈表、雙向鏈表和循環鏈表。今天我們先講單鏈表。

二、單鏈表介紹

什麼是單鏈表呢?單鏈表就是每一個節點只有一個指針域的鏈表。如下圖所示,就是一個帶頭節點的單鏈表。下面我們需要知道什麼是頭指針,頭節點和首元節點。

頭指針:指向鏈表節點的第一個節點的指針

頭節點:指在鏈表的首元節點之前附設的一個節點

首元節點:指在鏈表中存儲第一個實際數據元素的節點(比如上圖的 a1 節點)

三、單鏈表的創建

單鏈表的創建有兩種方式,分別是頭插法和尾插法。

1、頭插法

頭插法,顧名思義就是把新元素插入到頭部的位置,每次新加的元素都作為鏈表的第一個節點。那麼頭插入法在Java中怎麼實現呢。首先我們需要定義一個節點,如下

public class ListNode {
  public int val; //數據域
  public ListNode next;//指針域
}

然後我們就創建一個頭指針(不帶頭節點)

//元素個數
int n = 5;
//創建一個頭指針
ListNode headNode = new ListNode();
//頭插入法
headNode= createHead(headNode, n);

然後創建一個私有方法去實現頭插法,這裏我們插入5個新元素,頭插入的核心是要先斷開首元節點和頭指針的連接,也就是需要先將原來首元節點的地址存放到新節點的指針域里,也就是 newNode.next = headNode.next,然後再讓頭指針指向新的節點 headNode.next = newNode,這兩步是頭插入的核心,一定要理解。

/**
 * 頭插法
 * 新的節點放在頭節點的後面,之前的就放在新節點的後面
 * @param headNode 頭指針
 * @return
 */
private static ListNode createHead(ListNode headNode, int n) {
  //插入5個新節點
  for (int i = 1; i <= n; i++) {
    ListNode newNode = new ListNode();
    newNode.val = i;
    //將之前的所有節點指向新的節點(也就是新節點指向之前的所有節點)
    newNode.next = headNode.next;
    //將頭指針指向新的節點
    headNode.next = newNode;
  }
  return headNode;
}

最後我把鏈表打印輸出一下(其實也是單鏈表的遍歷),判斷條件就是只有當指針域為空的時候才是最後一個節點。

private static void printLinkedList(ListNode headNode) {
  int countNode = 0;
  while (headNode.next != null){
    countNode++;
    System.out.println(headNode.next.val);
    headNode = headNode.next;
  }
  System.out.println("該單鏈表的節點總數:" +countNode);
}

最後的輸出結果顯然是逆序,因為沒一個新的元素都是從頭部插入的,自然第一個就是最後一個,最後一個就是第一個:

2、尾插法

尾插法,顧名思義就是把新元素插入到尾部的位置(也就是最後一個位置),每次新加的元素都作為鏈表的第最後節點。那麼尾插法在 Java 中怎麼實現呢,這裏還是採用不帶頭節點的實現方式,頭節點和頭指針和頭插入的實現方式一樣,這裏我就直接將如何實現:

/**
 * 尾插法
 * 找到鏈表的末尾結點,把新添加的數據作為末尾結點的後續結點
 * @param headNode
 */
private static ListNode createByTail(ListNode headNode, int n) {
  //讓尾指針也指向頭指針
  ListNode tailNode = headNode;
  for (int i = 1; i <= n; i++) {
    ListNode newNode = new ListNode();
    newNode.val = i;
    newNode.next = null;

    //插入到鏈表尾部
    tailNode.next = newNode;
    //指向新的尾節點,tailer永遠存儲最後一個節點的地址
    tailNode = newNode;

  }
  return headNode;
}

和頭插入不同的是,我們需要聲明一個尾指針來輔助我們實現,最開始,尾指針指向頭指針,每插入一個元素,尾指針就后移一下,這裏我們來講一下原理:每次往末尾新加一個節點,我們就需要把原來的連接斷開,那怎麼斷開呢,我們首先需要讓尾指針指向新的節點,也就是 tailNode.next = newNode; 然後再讓尾指針后移一個位置,讓尾指針指向最後一個節點。也就是尾指針始終指向最後一個節點,最後將頭指針返回,輸出最後結果:

四、單鏈表的刪除

既然單鏈表創建好了,怎麼在鏈表裡面刪除元素呢,單鏈表的刪除,我分為了兩種情況刪除,分別是刪除第i個節點和刪除指定元素的節點。

1、刪除第i個節點

我們可以先來理一下思路:在單鏈表裡,節點與節點之間都是通過指針域鏈接起來的,所以如果我們想實現刪除的操作,實際上是需要我們去改變相應指針域對應得地址的。當想去刪除第i個元素的時候,比如要刪除上圖的第3個元素(也就是3),實際上我們要做的就是要讓2號元素指向4號元素(其實就是需要修改2號元素的指針域,讓2號元素的指針域存儲4號元素)。那麼怎麼做才能實現這一步呢?很顯然,要實現這個步驟,我們必須要找到4號元素和2號元素,但是再仔細想一下,其實我們只需要找到2號元素就可以了,因為4號元素的地址存儲再2號的下一個元素的指針域裏面。

所以綜上所述分析我們可以得出刪除的兩個核心步驟:

1.刪除第i個節點,需要先找到第 i-1 個個節點,也就是第i個節點的前一個節點;

2.然後讓第 i-1 個節點指向第 i-1 個節點的下下個節點

下面的代碼具體實現了怎麼刪除第i個元素。

/**
 * 刪除第i個節點
 * 1,2 4,4,5
 * 刪除之後應該是1,2,4,5
 * @param headNode
 * @param index
 * @return
 */
public static ListNode deleteNodeByIndex(ListNode headNode, int index) {
  int count = 1;
  //將引用給它
  ListNode preNode = headNode;
  //看計數器是不是到了i-1,如果到了i-1,就找到了第i-1個節點
  while (preNode.next != null && count <= index -1){
    //尋找要刪除的當前節點的前一個節點
    count++;
    preNode = preNode.next;
  }
  if (preNode != null){
    preNode.next = preNode.next.next;
  }
  return headNode;
}

2、刪除指定元素的那個節點

刪除指定元素節點的實現方法有兩種,第一種就是先找到指定元素對應的鏈表的位置( index ),然後再按照刪除第 i 個節點的思路刪除即可。實現方法如下圖所示:

/**
 * 刪除鏈表指定數值的節點
 * @param headNode
 * @param val
 * @return
 */
private static ListNode deleteNodeByNum(ListNode headNode, int val) {
  ListNode deleteOne = headNode;
  int countByDeleteOne = 1;
  while (deleteOne.next != null){
    if (deleteOne.next.val == val){
      deleteOne = deleteOne.next;
      break;
    }
    countByDeleteOne ++;
    deleteOne = deleteOne.next;
  }
  return deleteNodeByIndex(headNode, countByDeleteOne);
}

第二種方法的實現就很精妙(前提是此節點不是尾節點)

public void deleteNode(ListNode node) {
  //刪除node即通過將後面的值賦給node,然後更改node的指針指向下下一個結點即可
  node.val = node.next.val;
  node.next = node.next.next;
}

五、單鏈表的查詢(及修改)

單鏈表的查詢實現很簡單,就是遍歷當前單鏈表,然後用一個計數器累加到當前下標,那麼當前的這個節點就是要查詢的那個節點,然後再返回即可,當然需要判斷傳過來的這個下標是否合法。當然如果需要修改,就需要把當前找到的節點的數據域重新賦上需要修改的值即可,這裏就不上代碼了。具體實現如下:

private static ListNode searchLinkedList(ListNode headNode, int index) {
  //如果下標是不合法的下標就表示找不到
  if (index < 1 || index > getLinkedListLength(headNode)){
      return null;
  }
  for (int i = 0; i < index; i++) {
    headNode = headNode.next;
  }
  return headNode;
}

獲取單鏈表的長度(注意我這裏定義的 headNode 是頭指針不是頭節點)

/**
 * 求單鏈表長度
 * @param headNode
 * @return
 */
private static int getLinkedListLength(ListNode headNode) {
  int countNode = 0;
  while (headNode.next != null){
    countNode++;
    headNode = headNode.next;
  }
 return countNode;
}

六、小結

單鏈表的相關操作就講解完了,其實通過上面對單鏈表的相關操作,我們不難發現,單鏈表的刪除和插入其實很方便,只需要改變指針的指向就可以完成,但是查找元素的時候就比較麻煩,因為在查找的時候,需要把整個鏈表從頭到尾遍歷一次。

公眾號:良許Linux

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

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

【其他文章推薦】

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

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

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

※超省錢租車方案

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

Docker 基礎知識 – Docker 概述

Docker 是一個開發、發布和運行應用程序的開放平台。Docker使您能夠將應用程序與基礎架構分離,以便快速交付軟件。有了 Docker,你可以像管理應用程序一樣管理你的基礎設施。通過利用 Docker 快速發布、測試和部署代碼的方法,您可以顯著減少編寫代碼和在生產環境中運行它之間的延遲。

Docker 平台

Docker 提供了在鬆散隔離的環境(稱為容器)中打包和運行應用程序的能力。隔離和安全性允許您在給定的主機上同時運行多個容器。容器是輕量級的,因為它們不需要額外的hypervisor負載,而是直接在主機的內核中運行。這意味着您可以在給定的硬件組合上運行比使用虛擬機時更多的容器。你甚至可以在實際是虛擬機的主機中運行 Docker 容器!

Docker 提供了工具和平台來管理容器的生命周期:

  • 使用容器開發應用程序及其支持組件。
  • 容器成為分發和測試應用程序的單元。
  • 準備就緒后,將應用程序作為容器或編排好的服務部署到生產環境中。無論您的生產環境是本地數據中心、雲提供商還是兩者的混合,操作都是一樣的。

Docker 引擎

Docker 引擎是一個 客戶端-服務器 應用程序,具有以下主要組件:

  • 一個服務器,它是一種稱為守護進程(dockerd 命令)的長時間運行程序。
  • 一個 REST API,它指定程序可以用來與守護進程對話並指示它做什麼的接口。
  • 一個命令行界面(CLI)客戶端(docker命令)。

CLI 使用Docker REST API通過腳本或直接CLI命令控制Docker守護進程或與之交互。
許多其他Docker應用程序使用底層API和CLI。

這個守護進程創建和管理 Docker 對象,如鏡像、容器、網絡和卷(images, containers, networks, and volumes)。

注意: Docker使用的是開源 Apache 2.0 許可證。

有關更多細節,請參閱下面的 Docker 架構。

我可以用 Docker 做什麼?

快速、一致地交付應用程序

Docker 允許開發人員使用提供應用程序和服務的本地容器,在標準化的環境中工作,從而簡化了開發生命周期。容器對於持續集成和持續交付(CI/CD)工作流非常有用。

考慮以下示例場景:

  • 開發人員在本地編寫代碼,並使用 Docker 容器與同事共享他們的工作。
  • 他們使用 Docker 將應用程序推送到測試環境,並執行自動和手動測試。
  • 當開發人員發現 bug 時,他們可以在開發環境中修復它們,並將它們重新部署到測試環境中進行測試和驗證。
  • 當測試完成時,向客戶提供修復就像將更新后的鏡像推送到生產環境一樣簡單。

響應式部署和擴展

Docker 的基於容器的平台允許高度可移植的工作負載。Docker 容器可以運行在開發人員的本地筆記本電腦上、數據中心的物理或虛擬機上、雲提供商上或在混合的環境中。

Docker 的可移植性和輕量級性質也使得它可以很容易地動態管理工作負載,根據業務需要,在接近實時的情況下擴展或拆除應用程序和服務。

在相同硬件上運行更多工作負載

Docker 是輕量級和快速的。它為基於管理程序的虛擬機提供了一種可行的、經濟有效的替代方案,因此您可以使用更多的計算能力來實現業務目標。Docker 非常適合高密度環境和中小型部署,在這些環境中,您需要用更少的資源做更多的事情。

Docker 架構

Docker 使用客戶端-服務器架構。Docker 客戶端與 Docker 守護進程通信,後者負責構建、運行和分發Docker 容器等繁重的工作。Docker 客戶端和守護進程可以運行在同一個系統上,或者您可以將一個 Docker 客戶端連接到一個遠程 Docker 守護進程。Docker 客戶端和守護進程通過 UNIX 套接字或網絡接口使用 REST API 進行通信。

Docker 守護進程

Docker 守護進程(dockerd)偵聽 Docker API 請求並管理 Docker 對象,如鏡像、容器、網絡和卷。
守護進程還可以與其他守護進程通信來管理 Docker 服務。

Docker 客戶端

Docker 客戶端(docker)是許多 Docker 用戶與 Docker 交互的主要方式。當您使用諸如docker run之類的命令時,客戶端將這些命令發送給dockerd, dockerd 會執行這些命令。docker 命令使用 Docker API。Docker 客戶端可以與多個守護進程通信。

Docker 註冊表

Docker 註冊表存儲 Docker 鏡像。
Docker Hub 是一個任何人都可以使用的公共註冊表,默認情況下 Docker 被配置為在 Docker Hub 上尋找鏡像。您甚至可以運行自己的私有註冊表。如果您使用 Docker 數據中心(DDC),它包括 Docker 可信註冊表(DTR)。

當您使用 docker pulldocker run 命令時,所需的鏡像將從配置的註冊表中拉取。當您使用 docker push 命令時,您的鏡像將被推送到您配置的註冊表中。

Docker 對象

當您使用 Docker 時,您正在創建和使用鏡像、容器、網絡、卷、插件和其他對象。本節簡要介紹其中一些對象。

鏡像(IMAGES)

鏡像是一個只讀模板,帶有創建 Docker 容器的指令。鏡像通常基於另一個鏡像,並進行一些額外的定製。例如,您可以構建基於 ubuntu 鏡像的鏡像,但是安裝了 Apache web server 和您的應用程序,以及運行應用程序所需的配置細節。

您可以創建自己的鏡像,也可以只使用其他人創建併發布在註冊表中的鏡像。要構建自己的鏡像,需要創建一個 Dockerfile,其中包含一個簡單的語法,用於定義創建鏡像並運行它所需的步驟。Dockerfile 中的每條指令都會在鏡像中創建一個層。當你改變 Dockerfile 並重建鏡像時,只有那些已經改變的層才會重建。這是使鏡像與其他虛擬化技術相比如此輕量級、小巧和快速的原因之一。

容器(CONTAINERS)

容器是鏡像的可運行實例。您可以使用 Docker API 或 CLI 創建、啟動、停止、移動或刪除容器。您可以將一個容器連接到一個或多個網絡,將存儲附加到該容器,甚至基於其當前狀態創建一個新鏡像。

默認情況下,容器與其他容器及其主機相對隔離良好。您可以控制容器的網絡、存儲或其他底層子系統與其他容器或主機的隔離程度。

容器是由它的鏡像以及創建或啟動它時提供給它的任何配置選項定義的。當刪除容器時,對其狀態的任何未存儲在持久存儲中的更改都會消失。

docker run 命令示例

下面的命令運行一個 ubuntu 容器,以交互方式連接到本地命令行會話,並運行 /bin/bash

$ docker run -i -t ubuntu /bin/bash

當你運行這個命令時,會發生以下情況(假設你使用默認的註冊表配置):

  1. 如果你沒有本地的 ubuntu 鏡像,Docker會從你配置的註冊表中拉取它,就像你已經手動運行 docker pull ubuntu 一樣。
  2. Docker 創建一個新的容器,就像手動運行 docker container create 命令一樣。
  3. Docker 為容器分配一個讀寫文件系統,作為容器的最後一層。這允許運行中的容器在其本地文件系統中創建或修改文件和目錄。
  4. Docker 創建一個網絡接口,將容器連接到默認網絡,因為您沒有指定任何網絡選項。這包括為容器分配IP地址。默認情況下,容器可以使用主機的網絡連接連接到外部網絡。
  5. Docker 啟動容器並執行 /bin/bash。由於容器以交互方式運行並連接到你的終端(由於有-i-t標誌),所以可以將輸出記錄到終端,同時你可以使用鍵盤提供輸入。
  6. 當您鍵入 exit 終止 /bin/bash 命令時,容器將停止,但不會被刪除。您可以重新啟動或刪除它。

服務(SERVICES)

服務允許您跨多個 Docker 守護進程擴展容器,這些守護進程組成一個集群,多個管理者和工作者一起工作。一個集群的每個成員都是一個 Docker 守護進程,所有的守護進程都使用 Docker API 進行通信。服務允許您定義所需的狀態,例如在任何給定時間必須可用的服務副本的數量。默認情況下,服務在所有工作節點之間進行負載均衡。對於消費者來說,Docker 服務看起來像一個單獨的應用程序。Docker 引擎在 Docker 1.12 及更高的版本支持集群模式。

底層技術

Docker 是用 Go 編寫的,並利用 Linux 內核的幾個特性來實現其功能。

命名空間

Docker 使用名為命名空間的技術來提供稱為容器的隔離工作區。當您運行一個容器時,Docker 為該容器創建一組命名空間。

這些命名空間提供了一個隔離層。容器的每個方面都在一個單獨的命名空間中運行,其訪問權限僅限於該命名空間。

Docker 引擎在 Linux 上使用如下命名空間:

  • pid 命名空間: 進程隔離 (PID: 進程ID)。
  • net 命名空間: 管理網絡接口 (NET: Networking)。
  • ipc 命名空間: 管理對 IPC 資源的訪問 (IPC: 進程間通信)。
  • mnt 命名空間: 管理文件系統掛載點 (MNT: Mount)。
  • uts 命名空間: 隔離內核標識符和版本標識符 (UTS: Unix分時系統)。

控制組

Linux 上的 Docker 引擎還依賴於另一種稱為控制組(cgroups)的技術。cgroup 將應用程序限製為特定的資源集。控制組允許 Docker 引擎將可用的硬件資源共享給容器,並可以選擇強制限制和約束。例如,可以限制特定容器的可用內存。

聯合文件系統

聯合文件系統,或 UnionFS,是通過創建層來操作的文件系統,使其非常輕便和快速。Docker 引擎使用 UnionFS 為容器提供構建塊。Docker 引擎可以使用多種 UnionFS 變體,包括 AUFS、btrfs、vfs 和 DeviceMapper。

容器格式

Docker 引擎將命名空間、控制組和 UnionFS 組合到一個稱為容器格式的包裝器中。默認的容器格式是 libcontainer。未來,Docker 可能會通過與 BSD Jails 或 Solaris Zones 等技術集成來支持其他容器格式。

作者 : Docker 官網
譯者 : 技術譯民
出品 : 技術譯站
鏈接 : 英文原文
公眾號:技術譯站

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

【C++和C#的區別雜談】后自增運算符的結算時機

C++和C#的前自增++n和后自增n++,都是先自增后取值先取值后自增的含義,但在複雜一點的賦值語句中,我發現細節上有很大的差異。

發現這個問題主要是一個無聊的晚上,我想搞清楚后自增是什麼時候結算,自己搗鼓了一下之後我把我自己的測試結果告訴了朋友,結果學java的朋友和我爭論了半天,最後發現同樣的代碼,大家輸出是不一樣的。之後我又用了C#寫了同樣的測試代碼,發現輸出和他java是一樣的,這讓我豁然開朗,遂寫下這篇博客希望分享我的結論,如果有錯誤的話,歡迎評論區指正。

前排提醒:C++和C#我都是用的VS2017,不同編譯環境的C++代碼輸出結果可能不一樣

先說我的結論:

C++:i++遇到順序點(逗號,分號)之後i才自增,即我一直以來認為的一條語句結束后結算。但++i前自增的值會影響賦值語句所有i的值。

C#:C#不允許使用‘,’逗號運算符,但也並非到’;’分號才結算,賦值語句是從左到右,按序取值,i++在取完i的值之後馬上就自增了,但不影響之前取的i的值,隻影響後續i的取值。

心得:實際開發還是盡量自增單行寫,第一個是可讀性好,第二個是不容易出問題。(應該也就只有筆試會出現以下實驗的代碼了)

結論看不明白的可以看看我無聊的小實驗:

最簡單常見的版本:

int arr[] = { 0,0,0 };
int n = 0;
arr[n] = n++;//語句a
for (int num : arr)
{
	cout << num << ' ';
}
//輸出是0 0 0,語句a結束后n自增到1

上面這段代碼其實在C#也是一樣的輸出,但如果下面這樣寫,結果就不一樣了。

C++版:

int arr[] = { 0,0,0 };
int n = 0;
arr[n++] = n;//語句a
for (int num : arr)
{
	cout << num << ' ';
}
//輸出是0 0 0,語句a結束后n自增到1,C++結果和上面的一樣

C#版:

int[] arr = { 0, 0, 0 };
int n = 0;
arr[n++] = n;//語句a
foreach (int num in arr)
{
	Console.Write(num + " ");
}
//輸出是1 0 0,現在應該能理解結論了,從左到右,先取n作為索引值,然後自增之後影響到了等號右邊的n的值
//語句a則為arr[0] = 1;

就是這樣的差異開始引申出了問題,也是讓我迷惑的開始,接下來我會通過更複雜的例子和反彙編的指令來解釋和證明我的結論,先列舉C++的部分,之後再C#,感興趣的可以往下看。

C++部分:

//代碼01
int arr[] = { 0,0,0 };
int n = 0;
arr[n++] = n + (n++) + ++n;
for (int num : arr)
{
	cout << num << ' ';
}
//輸出為0 3 0,具體操作流程為語句先結算了++n,使得n自增到1.而出現的兩個n++都在賦值語句結束后結算
//所以編譯結果為arr[1] = 1 + 1 + 1; 然後i自增兩次到3

以下為反彙編的結果:

一開始看到這個我有點懵逼,但是通過比對下面這段代碼的反彙編指令,就能看出來了。

//代碼02
int[] arr = { 0, 0, 0 };
int n = 0;
arr[++n] = n + (n++) + ++n;//把索引處的n++改為了++n
foreach (int num in arr)
{
	Console.Write(num + " ");
}
//輸出為0 0 6,具體操作流程為語句先結算兩次++n,使得n自增到2.而n++在賦值語句結束后結算
//所以編譯結果為arr[2] = 2 + 2 + 2; 然後i自增到3
//這下應該發現n在這條語句中取值的時候都是同樣的值了

以下為上面代碼02的反彙編結果:

通過和上面的指令比對可以看出來了,具體差異在00251F3A這段指令,arr[++n]這段代碼02在累加前多進行了一次對n的自增,然後將自增后的值賦給了n,然後開始進行累加,而上面arr[n++]那段代碼01是在累加結束之後,在01161F4A那條指令對n++進行自增的結算,所以就算看不懂全部的指令,也應該能通過比對這兩段代碼看出他們的差異,從而證明了C++對於后自增的處理,是在語句結束之後結算

說到語句結束,我之前寫了一篇關於逗號運算符的博客,可以結合今天這個結論看看下面這段代碼的輸出結果。

int arr[] = { 0,0,0 };
int n = 0;
arr[n] = (n++, n + (n++) + ++n);//在逗號左邊添加了n++的語句
for (int num : arr)
{
	cout << num << ' ';
}
//輸出為0 0 6
//原因為逗號也屬於一條語句結束的標誌,所以結算了n++,n=1,然後執行新的語句n + (n++) + ++n
//逗號右邊的語句先結算了++n,n=2,所以最後賦值語句為arr[2] = 2 + 2 + 2; 然後n++到3

至此C++的部分結束。

接下來C#的測試結果將顛覆上面的結論,如果不用java或者C#的話,下面的內容可能沒有用(朋友java的輸出結果和我C#一樣,不過也是希望大家自己試試,我自己沒有試過)

C#部分:

//代碼01 C#版
int[] arr = { 0, 0, 0 };
int n = 0;
arr[n++] = n + (n++) + ++n;
foreach (int num in arr)
{
	Console.Write(num + " ");
}
//輸出為5 0 0 和VC++完全不一樣對吧
//具體原因為C#的賦值語句是從左到右,先取了n的值作為索引,然後馬上對n自增,n=1
//來到等號右邊,第一個n取值為1,第二個n取值為1,然後自增到2,第三個n先自增到3,然後取值為3
//所以最終編譯結果為arr[0] = 1 + 1 + 3;  即5 0 0的原因
//為了節省篇幅,我直接告訴你arr[++n] = n + (n++) + ++n的結果為0 5 0,如果你上面看懂了這個應該也懂

貼一下C#這段代碼的反彙編結果:

可以看到第一行是取n的值,然後把n作為索引值,之後inc指令對n自增1。證明了上面的結論。

即:C#的賦值語句為從左到右,按序取值,n++在取完n的值之後馬上自增,但不影響之前取的值,隻影響後續n的取值

小結:

所以++n 先自增后取值n++ 先取值后自增是能直接套在C#上的,而對於VC++來說,后自增的結算是非常“緩慢”的,到語句結束才結算。

感謝您的觀看。

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

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

特斯拉與 BMW 談合作 可望發展碳纖維與電池技術

特斯拉(Tesla)明星執行長 Elon Musk 爆料,德國豪華車品牌 BMW 與特斯拉曾洽商合作,範圍涵蓋電池與輕量化車體零件。   Musk 在接受德國媒體 Der Spiegel 專訪時表示,對 BMW 以碳纖維材料強化車體感到非常有興趣,認為相當具成本效益,雙方在非正式場合會面時,有討論過合作的可能,但尚未達成決議。   據路透社報導,BMW 為發展碳纖維原料而成立合資公司 SGL,i3 電動車與 i8 油電混合動力跑車的駕駛艙與後蓋零件均採用到碳纖維材料。除此之外,Musk 還透露有意與 BMW 一起搞電池科技,以及電動車充電站,他甚至於計畫 5~6 年內在德國蓋一座電池廠。

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

【其他文章推薦】

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

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

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

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

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

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

不需充電的零排放氫燃料電池機車 北市府試用評估成效

    繼新北市電動機車 E-bike 於 10 月上路後,台北市政府也採用能源局補助業者研發旳氫燃料電池機車,每台配備 2 支金屬儲氫罐,交換費用 30 元,行駛中排水,但二氧化碳排放為 0,續航力定速時可達 86 公里,不過,若在行駛市區時,遇上紅綠燈走走停停,續航力會降為 50 公里。環保局表示,廠商提供試騎的 15 輛氫燃料電池機車將用於環保稽查、工地巡查、土地丈量等業務。   環保局表示,氫燃料電池首創用於機車,金屬容器包覆氫氧化物粉末,相較氣體狀較安全,業者提供的 15 部試騎機車將停於於市府公務停車場,也會提供交換氣體,試用 3 個月後評估成效。   負責研發的亞太燃料電池科技專案經理陳建豪表示,氣燃料電池不須充電,而是利用能源轉換,將氫氣透過觸媒轉換成電能,轉換過程僅會排放水,該款氫燃料電池機車時速最快可達 60 公里。業者說,量產後機車售價約 7 萬元,但免燃料稅。陳建豪表示,全球各國多有氫燃料電池的安全使用規範,但台灣沒有,盼政府能協助立法。     (Source:)

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

【其他文章推薦】

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

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

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

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

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

2015第六屆廣州國際新能源汽車工業展覽會

綠色科技   領航未來

時間:2015年5月16-18日

地點:廣州琶洲保利世貿博覽館

主辦單位:廣州中汽國際展覽有限公司

  • 專業的組織機構——展會是中汽國際展覽有限公司按照“專業化、國際化、品牌化”原則舉辦的新能源汽車及配套設施行業國際品牌盛會。國內外多家單位協辦,既有政府支持,又有行業權威的參與。展會舉行期間,將有商務部及省市領導、業界權威人士蒞臨展會參觀指導並出席開幕剪綵。
  • 龐大採購團隊——盛會將邀請來自中國、美國、德國、法國、英國、義大利、巴西、墨西哥、西班牙、俄羅斯、瑞典、捷克、匈牙利、中東、日本、韓國、印度、土耳其、新加坡、越南、泰國、臺灣、香港、澳門等國家和地區眾多新能源汽車及配套設施進出口貿易商、代理商、經銷商及國際著名相關採購協會組織率團到會參觀採購。
  • 高端論壇——展會將邀請新能源汽車及配套設施行業權威專家,討論中外新能源汽車及配套設施行業最新動態、發展趨勢、分工格局、相關對策等多個熱門議題。就中國新能源汽車及配套設施行業發展現狀及所面臨的問題作深入報告,針對中國新能源汽車及配套設施行業市場行情作研究報告,並對產品開發、技術創新等作細緻的演講。屆時,將安排我們認為在國際新能源汽車及配套設施領域,具有影響力的企業介紹產品概況、最新技術動向等進行講座和交流。擬邀請中國政府高級官員,有關專家、學者,國際著名機構代表,著名新能源汽車及配套設施產品供應商、採購商,中國新能源汽車及配套設施市場企業代表和其他專業觀眾出席,其行業的引導性和權威性令人期待,是中國新能源汽車及配套設施產業發展和國際交流合作的風向標,將是獲取新能源汽車及配套設施行業資訊和把握國際市場的最佳平臺。

展會介紹

在能源匱乏的時代,綠色、節能、環保成為經濟發展的核心主題,新能源汽車具有良好的環保性能和燃料經濟性好、運行成本低等優勢,既可以保護環境,又可以緩解能源的短缺並能調整能源的結構,保障能源的安全。

21世紀是一個面臨能源和環境巨大挑戰的世紀,傳統燃油汽車將向高效低排放的電動汽車及混合動力車方向發展。大力發展新能源汽車是能源與環境的必然要求,而且,中國發展新能源汽車的壓力更為緊迫。根據國家《汽車與新能源汽車產業發展規劃》(2011-2020),將在“十二五”期間重點發展清潔能源汽車,未來十年僅中央財政就投入上千億元用來支持以純電動車、混合動力汽車為代表的節能與新能源汽車的研發與推廣。2014年多地出臺補貼政策,2014年5月24日上午,國家主席習近平在上海汽車集團考察時強調,發展新能源汽車是我國從汽車大國邁向汽車強國的必由之路,要加大研發力度,認真研究市場,用好用活政策,開發適應各種需求的產品,使之成為一個強勁的增長點。可以預見,未來我國新能源汽車將會快速發展。

廣州是廣東省會,改革開放前沿城市,中國對外貿易的重要視窗,經濟實力雄厚,市場潛力巨大。廣東是泛珠三角經濟區域的中心,毗鄰港、澳、台,輻射東南亞,海、陸、空交通便利,市場輻射面廣,經濟發達。隨著CEPA的實施,粵、港、澳以及泛珠三角(9+2)區域合作與發展的良性互動,必將給新能源汽車及配套設施產業創造無限美好的發展前景。為順應高速發展的新能源汽車及配套設施行業,廣州中汽國際展覽有限公司聯合行業權威機構定于2015年6月8-10日在廣州琶洲保利世貿博覽館舉辦“2015第三屆廣州國際新能源汽車工業展覽會”(NEA CHINA 2015),展會將深化活動內涵,秉乘推動行業發展、為企業服務的宗旨,為商家提供一個拓展業務、技術交流、展示實力、獲取資訊、結交客戶、推廣新產品、尋找合作夥伴的國際商貿平臺。

我們將以“突出品牌、開拓創新、注重實效、強化服務”的辦展宗旨,憑藉獨特的創意,科學的組織管理和卓越的服務,以全新的理念為廣大中外參展商提供一個“高水準、高品味、高品質”的展示交流平臺,為全球新能源汽車及配套設施行業提供更多的合作機會,有力推動中國新能源汽車及配套設施產品全面進入全球採購體系,與世界各國新能源汽車及配套設施產業協調合作、互利共贏、共同發展進步。

展品範圍

  • 純電動車:轎車、大巴、公車、各旅行車、各種純電動特種車(環衛車、電力車、郵政車、小型客貨車、高爾夫車、房車、叉車、搬運車、旅遊觀光車、醫療車、警用車、摩托車、三輪車等);
  • 混合動力車:轎車、大巴、公車、各型旅行車等;
  • 其他能源車:超級電容、燃料電池、氫能、生物燃料、太陽能及氫能源、天然氣等各種新能源、清潔燃料及低排放、環保節能型車等;
  • 零部件:低排放節能型發動機、混合動力發動機及清潔燃料發動機;動力電池與管理系統;整車匯流排與控制系統;電機與電控系統;充電裝置;儲能裝置等;能源管理系統;電力電容器、飛輪、逆變器、電熱泵、電動助力轉向、電動空調、功率模組等;相關材料、工藝、技術;相關檢測、監控、試驗、安全防護裝備;維修、製造設備和工具等;
  • 充電設施:充電站智慧型網路專案規劃及成果展示,加油站擴建充(換)電站、加油充電綜合服務站展示,太陽能、風能互補新能源汽車充電站技術產品,充電站充電機、充電樁、配電設備、變壓器、更換設備、電能、監控系統、有源濾波裝置、配電櫃、電覽、直接充電設備、管理輔助設備、充換電池及電池管理系統、停車場充電設施、智慧監控、充電站供電解決方案、充電站等。
  • 其他:新能源汽車的整車及系統控制設計等。

目標觀眾

主辦單位將重點邀請的目標觀眾包括:

1、商務部、發改委、科技部、工信部、國家環保局等各局、司、中心、所領導;
2、全國各省市主管部門領導、大型企事業、機關單位領導;
3、全國各高校、科研單位、設計院、研究院、協(學)會領導;
4、公交、出租、環衛、郵政等單位負責人;車站、機場、碼頭、房地產、大型物業公司、高爾夫球場、旅遊景點、公園、體育場館、大專院校、醫院、療養院、度假村等單位負責人;
5、國內外著名生產、代理、經銷商、貿易公司等業內人士參觀、參展、技術交流。

展會日程
報到布展:2015年5月14-15日
展出時間:2015年5月16-18日
撤展時間:2015年5月18日下午

歡迎業界同仁踴躍報名參展,現正接受申請,請速與組委會聯繫,索取參展申請表及展位平面圖!
充分利用NEA CHINA 2015,鞏固您的市場地位!

Official website/大會官方網站:

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

【其他文章推薦】

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

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

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

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

※超省錢租車方案