前端必備性能知識 – http2.0

前端開發中,性能是一定繞不開的,今天就來說一下前後台通信中最重要的一個通道–HTTP2.0

 最開始的通訊協議叫http1.0,作為始祖級的它,定義了最基本的數據結構,請求頭和請求體,以及每一個字段的含義,它順應了當時的互聯網需求,首次實現了瀏覽器與後端的交互,但它有一個時代烙印,就是短連接,每次請求就會建立一個TCP連接,三次握手四次揮手,用完就關閉,假如瀏覽器有300個請求,那麼它就建立了300個連接,這樣就給服務端帶來的很大的壓力,即使它只是一個很小很小的請求,後來,大家發現這樣不行啊,內容一多,服務端就頂不住了,然後就開始想辦法擴展它,

這樣http1.1就出現了,建立了長連接,通過keepalived開啟連接復用,什麼意思?還拿這300個請求來說,瀏覽器默認一次支持6個請求,當這6個請求結束以後,會繼續復用這6個請求,每個請求都是異步的,不會讓這6個連接閑着,直到300個請求結束。

好像這樣就解決了問題,可是細想一下好像不對,硬件更新快,後端硬件性能提升很快,它可以支持很多線程進行計算,但瀏覽器還是6個,那豈不是白白浪費了硬件設備,所以http2.0就出來了,多路復用的單一長連接

什麼意思呢,看這個

 

 

http1.1中,當建立連接,並響應完以後,會繼續復用該條連接去請求資源,但這6條請求是不變的,只不過復用了而已,在2.0中就不一樣了,只用這一條長連接,請求多個資源,一下子就減少了5條連接(包括每次連接時的三次握手和四次揮手),還有tcp慢啟動帶來的網絡延時,而它之所以一個連接上能放這麼多內容,底層是由於它以數據幀的形式進行傳輸的,一個數據包中包含多個資源。

http頭部壓縮和緩存

我們在請求內容的時候,有時候會出現你請求的內容很少,但是請求頭字段的體積比內容的體積都大的情況,而且每次請求就帶着這個相同請求頭,一旦1萬個請求過來了,壓力就明顯了,下面是例子。

壓縮以後減少了一般的體積,而且它還會緩存請求頭,因為每次的請求頭都一樣,所以在底層,它將這個請求頭用一個符號比如1來表示去發請求,而後端也會去解析這個1進而進行處理,所以原來是一大段的字段內容,而現在就是一個符號就表示出來了。

兼容http1.1,基於https進行部署,服務端主動推送內容

如果發現瀏覽器不支持2.0,則自動向下兼容

部署升級,則如下

瀏覽器與nginx交互用https進行加密傳輸,反向代理nginx與服務器的comact是明文傳輸

 

【精選推薦文章】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

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

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

[simple-orm-mybaits]基於Mybatis的ORM封裝介紹

目錄

  • 前言
  • ORM框架現狀
  • Mybatis優缺點
  • simple-orm-mybatis設計思路介紹
  • simple-orm-mybatis使用說明
  • simple-orm-mybatis實際使用
  • 推薦最佳實踐
  • 更多說明

simple-orm-mybatis在github開源,直接跳轉查看,歡迎大家fork和star。有什麼建議也可以提出,我會儘快修復或實現。

前言

最早接觸Java的web開發框架就是SSH,其中的H就是Hibernate。Hibernate作為最出名的Java的ORM框架,現在的版本已經到了5.3.10.Final,6.0.0.Alpha2。圍繞數據持久化或者DAL層也發展了多種工具,Hibernate Validator目前也是在很多的企業框架中被用作數據有效性檢查。
接下來還有用過JPA來實現ORM。JPA全稱Java Persistence API,是JSR-220(EJB3.0)規範的一部分,在JSR-220中規定實體對象(EntityBean)由JPA進行支持。在我的使用中,實際上底層實現仍然使用了Hibernate,只是標註或者操作類都是使用了JPA的接口標準而沒有直接使用Hibernate。
Hibernate具有強大的ORM封裝能力,極大的簡化了CUD的操作,而且無需做太多的配置,使用標準註解就能解決很多問題。簡單的查詢操作也不在話下,多級關聯、延遲查詢等豐富特性基本上也可以說是極大覆蓋了開發過程的各種需求。但是實際上在這麼多團隊中,很多人的反饋是這樣的:“Hibernate的ORM確實很方便,但是在一些特殊條件下很難用,比如複雜查詢就很難控制語句生成的效率”。Hibernate有三種查詢方式:HQL、Criteria、Native Sql。確實在複雜查詢下,如果使用Criteria,確實能夠拼出想要的語句,但是可能對於其中的方法要非常熟悉,學習曲線很高,沒辦法做到團隊中每個成員都能數量輕易的掌握,而且後期的審核很困難無法直接看清邏輯。HQL和NativeSql對我的感覺,似乎回到了JSP時代,HTML和Java代碼混寫,很難忍受。這時候大家找到了另外的框架Mybatis。

ORM框架現狀

截止(2019/5/27)Mybatis的Star是10806,UsedBy140381;Hibernate的Star是3817,UsedBy157879。看使用量Hibernate和Mybatis其實已經差不多了,實際企業開發中,Mybatis可能還會更多一些。

Mybatis優缺點

Mybatis放棄了Hibernate的強封裝,主要包含了映射的部分,而且放棄了自動解析生成Sql,而直接使用用戶XML配置Sql的方式,只是在Sql的拼接上提供了一些標籤來避免重複代碼。這樣的討巧之處在於:

  • 門檻降低:開發人員不需要了解框架的內部語法,只需要了解Sql語法即可。
  • 可讀性提高:審核代碼時,很容易的就能看清楚多級關聯以及關聯使用的條件、語法是否正確。
  • 維護方便:當查詢語法出錯時,Mybatis只需要修改XML,而Hibernate則需要修改代碼重新編譯,操作相對複雜。

缺點也有幾處:

  • 簡單操作複雜:由於放棄了自動解析生成Sql,所以普通的CUD也需要手動編寫Sql
  • 實體映射複雜:必須在XML文件中配置大量的字段映射
  • SqlMapper中的sql id風險:由於XML中的sql id是一個字符串,只能複製粘貼出來,所以出錯的幾率也比較大。

實際上,針對上面缺點,Mybatis也提供了解決方案。
先說sql id的,在新的Mybatis中,實際上已經採用了面向接口的編碼方式,如下面的例子:

接口類

public interface UserMapper {
    public User findUserById(Integer id);
}

mapper的xml文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.software5000.mapper.dao.UserMapper">
    <select id="findUserById" resultType="com.software5000.entity.User" parameterType="int" >
        select * from user where id =#{id}
    </select>
</mapper>

這樣就可以直接調用sql語句:

UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.findUserById(1);

再說映射複雜的,Mybatis提供了MyBatis Generator作為解決方案,一個命令生成下列內容:

  • 匹配表結構的Java POJOs。可能包括:
    • 表結構中主鍵字段(如果存在主鍵)
    • 表結構中的非主鍵字段(除了BLOB字段)
    • 表結構中的BLOB字段(如果有BLOB字段)
    • 動態查詢、更新和刪除的接口類

自動生成也能夠合理的生成類之間的繼承關係。注意生成器也能夠定義生產不同的POJO層次 – 例如如果你想要可以為每個表格生成一個單獨的領域對象。

  • Mybatis/iBATIS兼容的SQL MAP的XML文件。按照配置針對每個表生成簡單的增刪改查的SQL方法。生成的SQL包括:
    • 插入
    • 按主鍵更新
    • 使用example更新 (使用動態的where條件)
    • 按主鍵刪除
    • 使用example刪除 (使用動態的where條件)
    • 按主鍵查詢
    • 使用example查詢 (使用動態的where條件)
    • 使用example統計

生成的SQL語句取決於表格的結構(例如,表格如果沒有主鍵,就不會生成按主鍵更新的方法)

Java客戶端生成類能夠合理使用上面的對象。生成的Java客戶端類也是有選擇性的。

  • 使用Mybatis 3.x的會生成:
    • 一個mapper接口和XML中的語句id相同,用於直接調用。
  • 使用iBATIS 2.x的會生成:
    • 適用於Spring框架的DAO層
    • 使用IBATIS SQL映射API的DAO層。這些DAO可以使用兩種方式:使用構造函數提供SqlMapClient,或者通過注入方式提供。
    • DAOs that conform to the iBATIS DAO Framework (an optional part of iBATIS, this framework is now deprecated and we suggest that you use the Spring framework instead)
    • iBATIS的DAO框架符合的DAO層(iBATIS的一個額外部分,但是現在這個框架已經失效了,所以建議使用Spring DAO的框架代替)

但是,我對於MyBatis Generator的態度是堅定的反對。原因是我認為自動生成違反了簡單的原則,“如無必要,勿增實體”。自動生成的可復用性和可讀性一定是比較差的。我覺得最好的代碼就是不存在的代碼。因此我希望能夠有一個簡單框架在Mybatis之上,接入簡單無侵入,能夠提供基本的增刪改查方法。這就是下面的 simple-orm-mybatis 。

simple-orm-mybatis設計思路介紹

simple-orm-mybatis設計的初衷就是希望提供一個簡單無侵入,而且無需編寫或者生成任何代碼就可以使用直接操作對象的方式來進行增刪改查的操作。要實現這樣的要求,主要是兩個主要技術點:

  1. 利用反射機制對應實體對象與數據庫結構
  2. 解析對象並且生成對應操作的SQL語句

第一點核心就是反射,設計要點如下:

  • Java對象與數據庫結構的對應規則
  • 虛字段(無數據庫對應字段)的處理
  • 考慮多層繼承的對象解析
  • 值的設置與獲取方式

第二點核心在於SQL解析,設計要點如下:

  • 根據不同入口區分基本CRUD語法結構
  • 字段(列名)需要分為值字段與查詢字段兩類
  • 更新操作的時候,Null,空,有值的區分
  • 支持多種匹配操作符(大於、小於、Like等)

simple-orm-mybatis使用說明

  1. 首先引入依賴,項目已經發布在Maven Central上,可以直接引入。
<dependency>
    <groupId>com.software5000</groupId>
    <artifactId>simple-orm-mybatis</artifactId>
    <version>1.0.2</version>
</dependency>
  1. 接着需要將 BaseDaoMapper.xml文件放在你的 mapper 的掃描文件夾內。

  2. 最後需要在你的代碼中添加一個 BaseDao 實現類,用於提供數據庫操作服務(注意:需要在spring的掃描包內,因為需要注入某些屬性),整個複製即可,類名可以改為你自己需要的名字
import com.software5000.base.BaseDao;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

/**
 * 整個類以 <code>org.mybatis:mybatis-spring:2.0.0</code> 的 <code>org.mybatis.spring.supportSqlSessionDaoSupport</code>
 * 為參考編寫而成
 */
@Component
public class MyBaseDao extends BaseDao {
    private SqlSessionTemplate sqlSessionTemplate;
    
    public MyBaseDao() {
            // 在默認構造函數中設置 數據庫是否蛇形, 數據庫格式大小寫, 通用忽略的字段名稱
            this.initConfig(true,false,"serialVersionUID");
    }
        
    @Resource
    public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
        if (this.sqlSessionTemplate == null || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) {
            this.sqlSessionTemplate = this.createSqlSessionTemplate(sqlSessionFactory);
        }
    }

    protected SqlSessionTemplate createSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }

    @Override
    public SqlSession getSqlSession() {
        return this.sqlSessionTemplate;
    }
}

simple-orm-mybatis實際使用

這裏給了一個單元測試的例子,實際一般是在service層直接使用,無需添加任何代碼。
示例中的 DailyRecord是一個普通實體類,沒有任何繼承。

// 獲取啟動類,加載配置,確定裝載 Spring 程序的裝載方法,它回去尋找 主配置啟動類(被 @SpringBootApplication 註解的)
@SpringBootTest
// 讓 JUnit 運行 Spring 的測試環境, 獲得 Spring 環境的上下文的支持
@RunWith(SpringRunner.class)
public class BaseDaoTest {

    @Autowired
    private MyBaseDao myBaseDao;

    @Test
    public void testBaseDao(){
        DailyRecord dailyRecord = new DailyRecord();
        List<DailyRecord> dailyRecords = myBaseDao.selectEntity(dailyRecord);
        System.out.println(dailyRecords.size());
    }
}

推薦最佳實踐

雖說整體的設計基於無侵入,基本沒有任何前提,但是還是有一些推薦的實踐希望大家能夠去嘗試:

1、 數據結構設計建議包含int類型的自增主鍵設計,名稱叫id。
原因:很多時候我們的業務主鍵是有一套特定規則,但是很有可能面對修改,所以底層關聯主鍵統一使用id關聯在後期面對修改時影響較小。
弊端:
1. 如mysql中int最長2147483647,考慮到自增id可能會有跳過不連續的情況,需要考慮實際可用的存儲量。不過大部分的業務表是到不了這個數量級的。
2. mysql的InnoDB有自增主鍵鎖表的問題,超大併發插入可能會影響效率。不過在5.1.22有提供了改進的策略。

2、 數據結構與實體結構盡量符合統一轉換規則。
原因:這樣研發過程中,實體與數據庫的映射會比較簡單,無需大量的自定義。建議的規則有三類:
– 兩邊都使用駝峰。
– 實體使用駝峰,數據庫使用全小寫蛇形
– 實體使用駝峰,數據庫使用全大寫蛇形

3、 代碼中實體的字段類型使用封裝類型而不是基本類型
原因:基本類型是有默認值存在,而數據庫中我們一旦設置字段可空,就會有NULL值存在。所以建議全部使用封裝類型。下面附上各種基本類型的默認值

基本類型 默認值
byte 0
short 0
int 0
long 0L
float 0.0f
double 0.0d
char ‘\u0000’
boolean false

4、 分頁算法
原因:分頁推薦使用PageHelper,是利用Mybatis的底層插件機制修改Sql語句,也是無侵入。

更多說明

更多說明,可以參考github上的wiki頁面

【精選推薦文章】

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

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

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

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

【面試】如果把線程當作一個人來對待,所有問題都瞬間明白了

多線程的問題都曾經困擾過每個開發人員,今天將從全新視角來解說,希望讀者都能明白。

強烈建議去運行下文章中的示例代碼,自己體會下。



問題究竟出在哪裡?

一個線程執行,固然是安全的,但是有時太慢了,怎麼辦?

老祖宗告訴我們,“一方有難,八方支援”,那不就是多叫幾個線程來幫忙嘛,好辦呀,多new幾個不就行了,又不要錢。這樣能管用嗎?繼續往下看。

俗話說,“在家靠父母,出門靠朋友”。有了朋友的幫助,就會事半功倍。是這樣的嗎?

不一定,如果朋友“不靠譜”,結果竟是在“添亂”。於是就演變為,“不怕神一樣的對手,就怕豬一樣的隊友”。可見“人多力量大”縱然是對的,但也要配合好才能成事。

人和人是朋友,那線程和線程也是“朋友”,如果多線程之間不能配合好的話,最終也會變為“豬一樣的隊友”。事實證明,這也不是一件易事。且容我慢慢道來。

開發是一門技術,管理是一門藝術。也許你正想帶着兄弟們大幹一場,可偏偏就有人要辭職。或者你付出了這麼多,但別人從來沒有感動過。為什麼會這樣呢?

因為你面對的是人。每個人都是獨立的個體,有思想,有靈魂,有情感,有三觀。能夠接受外界的“輸入”,經過“處理”后,能夠產生“輸出”。

說白了就是會自主的分析問題,並做出決定。這叫什麼呢?答案就是,主觀能動性。

擁有主觀能動性的物體(比如人),你需要和它協商着或配合著來共同完成一件事情,而不能“強迫”它去做什麼,因為這樣往往不會有好的結果。

費了這麼多口舌,就是希望把問題盡量的簡單化。終於可以回到程序了,那線程的情況是不是類似的呢?答案是肯定的。

一個線程準備好后,經過CPU的調度,就可以自主的運行了。此時它儼然成了一個獨立的個體,且具有主觀能動性。

這本是一件好事,但卻也有不好的一面,那就是你對它的“掌控”能力變弱了,頗有一種“將在外,君命有所不受”的感覺。

可能你不同意這種看法,說我可以“強迫”它停止運行,調用Thread類的stop()方法來直接把它“掐死”,不好意思,該方法已廢棄。

因為線程可能在運行一些“關鍵”代碼(比如轉賬),此刻不能被終止。Thread類還有一些其它的方法也都廢棄了,大抵原因其實都差不多。

講了這麼多,相信你已經明白了,簡單總結一下:

事情起因:線程可以獨立自主的運行,可以認為它具有主觀能動性。

造成結果:對它的掌控能力變弱了,而且又不能直接把它“幹掉”。

解決方案:凡事商量着來,互相配合著把事情完成。

作者觀點:其實就是把線程當作人來對待。



小試牛刀一下

一旦把線程當成人,就來到了人類的世界,這我們太熟悉了,所以很多問題都會變得非常簡單明了。一起來看看吧。

場景一,停止

“大胖,大胖,12點了,該去吃飯了,別寫了”

“好的,好的,稍等片刻,把這幾行代碼寫完就走”

要點:把停止的信號傳達給別人,別人處理完手頭的事情就自己主動停止了。

 static void stopByFlag() {
    ARunnable ar = new ARunnable();
    new Thread(ar).start();
    ar.tellToStop();
  }

  static class ARunnable implements Runnable {

    volatile boolean stop;

    void tellToStop() {
      stop = true;
    }

    @Override
    public void run() {
      println("進入不可停止區域 1。。。");
      doingLongTime(5);
      println("退出不可停止區域 1。。。");
      println("檢測標誌stop = %s", String.valueOf(stop));
      if (stop) {
        println("停止執行");
        return;
      }
      println("進入不可停止區域 2。。。");
      doingLongTime(5);
      println("退出不可停止區域 2。。。");
    }

  }

 

解說:線程在預設的地點檢測flag,來決定是否停止。


場景二,暫停/恢復

“大胖,大胖,先別發請求了,對方服務器快掛了”

“好的,好的,等這個執行完就不發了”

過了一會

“大胖,大胖,可以重新發請求了”

“好的,好的”

要點:把暫停的信號傳達給別人,別人處理完手頭的事情就自己主動暫停了。但是恢復是無法自主進行的,只能由操作系統來恢複線程的執行。

 

static void pauseByFlag() {
    BRunnable br = new BRunnable();
    new Thread(br).start();
    br.tellToPause();
    sleep(8);
    br.tellToResume();
  }

  static class BRunnable implements Runnable {

    volatile boolean pause;

    void tellToPause() {
      pause = true;
    }

    void tellToResume() {
      synchronized (this) {
        this.notify();
      }
    }

    @Override
    public void run() {
      println("進入不可暫停區域 1。。。");
      doingLongTime(5);
      println("退出不可暫停區域 1。。。");
      println("檢測標誌pause = %s", String.valueOf(pause));
      if (pause) {
        println("暫停執行");
        try {
          synchronized (this) {
            this.wait();
          }
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
        println("恢復執行");
      }
      println("進入不可暫停區域 2。。。");
      doingLongTime(5);
      println("退出不可暫停區域 2。。。");
    }

  }

解說:還是在預設的地點檢測flag。然後就是wait/notify配合使用。


場景三,插隊

“大胖,大胖,讓我站到你前面,不想排隊了”

“好吧”

要點:別人插隊到你前面,必須等他完事後才輪到你。

static void jqByJoin() {
    CRunnable cr = new CRunnable();
    Thread t = new Thread(cr);
    t.start();
    sleep(1);
    try {
      t.join();
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    println("終於輪到我了");
  }

  static class CRunnable implements Runnable {

    @Override
    public void run() {
      println("進入不可暫停區域 1。。。");
      doingLongTime(5);
      println("退出不可暫停區域 1。。。");
    }

  }

 

解說:join方法可以讓某個線程插到自己前面,等它執行完,自己才會繼續執行。


場景四,叫醒

“大胖,大胖,醒醒,醒醒,看誰來了”

“誰啊,我去”

要點:要把別人從睡夢中叫醒,一定要採取稍微暴力一點的手段。

static void stopByInterrupt() {
    DRunnable dr = new DRunnable();
    Thread t = new Thread(dr);
    t.start();
    sleep(2);
    t.interrupt();
  }

  static class DRunnable implements Runnable {

    @Override
    public void run() {
      println("進入暫停。。。");
      try {
        sleep2(5);
      } catch (InterruptedException e) {
        println("收到中斷異常。。。");
        println("做一些相關處理。。。");
      }
      println("繼續執行或選擇退出。。。");
    }

  }

 

解說:線程在sleep或wait時,是處於無法交互的狀態的,此時只能使用interrupt方法中斷它,線程會被激活並收到中斷異常。



常見的協作配合

上面那些場景,其實都是對一個線程的操作,下面來看多線程間的一些配合。

事件一,考試

假設今天考試,20個學生,1個監考老師。規定學生可以提前交卷,即把卷子留下,直接走人就行了。

但老師必須等到所有的學生都走後,才可以收卷子,然後裝訂打包。

如果把學生和老師都看作線程,就是1個線程和20個線程的配合問題,即等20個線程都結束了,這1個線程才開始。

比如20個線程分別在計算數據,等它們都結束后得到20个中間結果,最後這1個線程再進行後續匯總、處理等。

  static final int COUNT = 20;
  static CountDownLatch cdl = new CountDownLatch(COUNT);

  public static void main(String[] args) throws Exception {
    new Thread(new Teacher(cdl)).start();
    sleep(1);
    for (int i = 0; i < COUNT; i++) {
      new Thread(new Student(i, cdl)).start();
    }
    synchronized (ThreadCo1.class) {
      ThreadCo1.class.wait();
    }
  }

  static class Teacher implements Runnable {

    CountDownLatch cdl;

    Teacher(CountDownLatch cdl) {
      this.cdl = cdl;
    }

    @Override
    public void run() {
      println("老師髮捲子。。。");
      try {
        cdl.await();
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      println("老師收卷子。。。");
    }

  }

  static class Student implements Runnable {

    CountDownLatch cdl;
    int num;

    Student(int num, CountDownLatch cdl) {
      this.num = num;
      this.cdl = cdl;
    }

    @Override
    public void run() {
      println("學生(%d)寫卷子。。。"num);
      doingLongTime();
      println("學生(%d)交卷子。。。"num);
      cdl.countDown();
    }

  }

 

解說:每完成一個線程,計數器減1,當減到0時,被阻塞的線程自動執行。


事件二,旅遊

最近景色宜人,公司組織去登山,大夥都來到了山腳下,登山過程自由進行。

但為了在特定的地點拍集體照,規定1個小時后在半山腰集合,誰最後到的,要給大家表演一個節目。

然後繼續登山,在2個小時后,在山頂集合拍照,還是誰最後到的表演節目。

接着開始下山了,在2個小時后在山腳下集合,點名回家,最後到的照例表演節目。

  static final int COUNT = 5;
  static CyclicBarrier cb = new CyclicBarrier(COUNT, new Singer());

  public static void main(String[] args) throws Exception {
    for (int i = 0; i < COUNT; i++) {
      new Thread(new Staff(i, cb)).start();
    }
    synchronized (ThreadCo2.class) {
      ThreadCo2.class.wait();
    }
  }

  static class Singer implements Runnable {

    @Override
    public void run() {
      println("為大家唱歌。。。");
    }

  }

  static class Staff implements Runnable {

    CyclicBarrier cb;
    int num

    Staff(int num, CyclicBarrier cb) {
      this.num = num;
      this.cb = cb;
    }

    @Override
    public void run() {
      println("員工(%d)出發。。。"num);
      doingLongTime();
      println("員工(%d)到達地點一。。。"num);
      try {
        cb.await();
      } catch (Exception e) {
        e.printStackTrace();
      }
      println("員工(%d)再出發。。。"num);
      doingLongTime();
      println("員工(%d)到達地點二。。。"num);
      try {
        cb.await();
      } catch (Exception e) {
        e.printStackTrace();
      }
      println("員工(%d)再出發。。。"num);
      doingLongTime();
      println("員工(%d)到達地點三。。。"num);
      try {
        cb.await();
      } catch (Exception e) {
        e.printStackTrace();
      }
      println("員工(%d)結束。。。"num);
    }

  }

 

解說:某個線程到達預設點時就在此等待,等所有的線程都到達時,大家再一起向下個預設點出發。如此循環反覆下去。


事件三,勞動

大胖和小白去了創業公司,公司為了節約開支,沒有請專門的保潔人員。讓員工自己掃地和擦桌。

大胖覺得擦桌輕鬆,就讓小白去掃地。可小白覺得掃地太累,也想擦桌。

為了公平起見,於是決定,每人先干一半,然後交換工具,再接着干對方剩下的那一個半。

  static Exchanger<Tool> ex = new Exchanger<>();

  public static void main(String[] args) throws Exception {
    new Thread(new Staff("大胖"new Tool("笤帚""掃地"), ex)).start();
    new Thread(new Staff("小白"new Tool("抹布""擦桌"), ex)).start();
    synchronized (ThreadCo3.class) {
      ThreadCo3.class.wait();
    }
  }

  static class Staff implements Runnable {

    String name;
    Tool tool;
    Exchanger<Tool> ex;

    Staff(String name, Tool tool, Exchanger<Tool> ex) {
      this.name = name;
      this.tool = tool;
      this.ex = ex;
    }

    @Override
    public void run() {
      println("%s拿的工具是[%s],他開始[%s]。。。", name, tool.name, tool.work);
      doingLongTime();
      println("%s開始交換工具。。。", name);
      try {
        tool = ex.exchange(tool);
      } catch (Exception e) {
        e.printStackTrace();
      }

      println("%s的工具變為[%s],他開始[%s]。。。", name, tool.name, tool.work);
    }

  }

  static class Tool {

    String name;
    String work;

    Tool(String name, String work) {
      this.name = name;
      this.work = work;
    }

  }

 

解說:兩個線程在預設點交換變量,先到達的等待對方。


事件四,魔性遊戲

這是一個充滿魔性的小遊戲,由一個團隊一起參加。所有人每隔5秒鐘抽一次簽,每個人有50%的概率留下來或被淘汰。

留下來的人下次抽籤時同樣有50%的概率被淘汰。被淘汰的人下次抽籤時同樣有50%的概率復活。

團隊所有成員都被淘汰完,為挑戰失敗,團隊所有成員都回到遊戲中(除剛開始外),為挑戰成功。

比如一開始10人參與遊戲,第一輪抽籤后,6人留下,4人淘汰。

第二輪抽籤后,留下的6人中4人被淘汰,淘汰的4人中2人復活,那麼目前是4人在遊戲中,6人被淘汰。

一直如此繼續下去,直到10人全部被淘汰,或全部回到遊戲中。

可見,人數越多,全部被淘汰的概率越小,但全部回到遊戲中的概率也越小。

反之,人數越少,全部回到遊戲中的概率越大,但全部被淘汰的概率也越大。

是不是很有魔性啊。哈哈。

  static final int COUNT = 6;
  static Phaser ph = new Phaser() {
    protected boolean onAdvance(int phase, int registeredParties) {
      println2("第(%d)局,剩餘[%d]人", phase, registeredParties);
      return registeredParties == 0 ||
          (phase != 0 && registeredParties == COUNT);
    };
  };

  public static void main(String[] args) throws Exception {
    new Thread(new Challenger("張三")).start();
    new Thread(new Challenger("李四")).start();
    new Thread(new Challenger("王五")).start();
    new Thread(new Challenger("趙六")).start();
    new Thread(new Challenger("大胖")).start();
    new Thread(new Challenger("小白")).start();
    synchronized (ThreadCo4.class) {
      ThreadCo4.class.wait();
    }
  }

  static class Challenger implements Runnable {

    String name;
    int state;

    Challenger(String name) {
      this.name = name;
      this.state = 0;
    }

    @Override
    public void run() {
      println("[%s]開始挑戰。。。", name);
      ph.register();
      int phase = 0;
      int h;
      while (!ph.isTerminated() && phase < 100) {
        doingLongTime(5);
        if (state == 0) {
          if (Decide.goon()) {
            h = ph.arriveAndAwaitAdvance();
            if (h < 0)
              println("No%d.[%s]繼續,但已勝利。。。", phase, name);
            else
              println("No%d.[%s]繼續at(%d)。。。", phase, name, h);
          } else {
            state = -1;
            h = ph.arriveAndDeregister();
            println("No%d.[%s]退出at(%d)。。。", phase, name, h);
          }
        } else {
          if (Decide.revive()) {
            state = 0;
            h = ph.register();
            if (h < 0)
              println("No%d.[%s]復活,但已失敗。。。", phase, name);
            else
              println("No%d.[%s]復活at(%d)。。。", phase, name, h);
          } else {
            println("No%d.[%s]沒有復活。。。", phase, name);
          }
        }
        phase++;
      }
      if (state == 0) {
        ph.arriveAndDeregister();
      }
      println("[%s]結束。。。", name);
    }

  }

  static class Decide {

    static boolean goon() {
      return random(9) > 4;
    }

    static boolean revive() {
      return random(9) < 5;
    }
  }

 

解說:某個線程到達預設點后,可以選擇等待同伴或自己退出,等大家都到達后,再一起向下一個預設點出發,隨時都可以有新的線程加入,退出的也可以再次加入。



生產與銷售的問題

在現實中,工廠生產出來的產品會先放到倉庫存儲,銷售人員簽了單子后,會從倉庫把產品發給客戶。

如果生產的過快,倉庫里產品越堆越多,直到把倉庫堆滿,那就必須停止生產,因為沒地方放了。

此時只能讓銷售人員趕緊出去簽單子,把產品發出去,倉庫就有了空間,可以恢復生產了。

如果銷售的過快,倉庫里產品越來越少,直到把倉庫清空,那就必須停止銷售,因為沒產品了。

此時只能讓生產人員趕緊生產產品,把產品放到倉庫里,倉庫里就有了產品,可以恢復銷售了。

可能會有人問,為什麼不讓生產和銷售直接掛鈎呢,把倉庫這個環節去掉?

這樣會造成兩種不好的情況:

一是突然來了很多單子,生產人員累成死Dog也生產不出來。

二是很長時間沒有單子,生產人員閑成廢Dog也無事可做。

用稍微“專業”點的術語就是此時的生產和銷售是一種強耦合的關係,銷售的波動對生產影響太大。

倉庫就是一個緩衝區,能有效的吸收波動,很大程度上減少波動的傳遞,起到一種解耦作用,由強耦合變成一種鬆散耦合。

這其實就對應計算機里經典的生產者和消費者問題。

經典的生產者和消費者

一到多個線程充當生產者,生產元素。一到多個線程充當消費者,消費元素。

在兩者之間插入一個隊列(Queue)充當緩衝區,建立起生產者和消費者的鬆散耦合。

正常情況下,即生產元素的速度和消費元素的速度差不多時,生產者和消費者其實是不需要去關注對方的。

生產者可以一直生產,因為隊列里總是有空間。消費者可以一直消費,因為隊列里總是有元素。即達到一個動態的平衡。

但在特殊情況下,比如生產元素的速度很快,隊列里沒有了空間,此時生產者必須自我“ba工”,開始“睡大覺”。

一旦消費者消費了元素之後,隊列里才會有空間,生產者才可以重啟生產,所以,消費者在消費完元素後有義務去叫醒生產者復工。

更準確的說法應該是,只有在生產者“睡大覺”時,消費者消費完元素后才需要去叫醒生產者。否則,其實可以不用叫醒,因為人家本來就沒睡。

反之,如果消費元素的速度很快,隊列里沒有了元素,只需把上述情況顛倒過來即可。

但這樣的話就會引入一個新的問題,就是要能夠準備的判斷出對方有沒有在睡大覺,為此就必須定義一個狀態變量,在自己即將開始睡大覺時,自己設置下這個變量。

對方通過檢測這個變量,來決定是否進行叫醒操作。當自己被叫醒后,首先要做的就是清除一下這個變量,表明我已經醒來複工了。

這樣就需要多維護一個變量和多了一部分判斷邏輯。可能有些人會覺得可以通過判斷隊列的“空”或“滿”(即隊列中的元素數目)來決定是否進行叫醒操作。

在高併發下,可能剛剛判斷隊列不為空,瞬間之後隊列可能已經變為空的了,這樣會導致邏輯出錯。線程可能永遠無法被叫醒。

因此,綜合所有,生產者每生產一個元素后,都會通知消費者,“現在有元素的,你可以消費”。

同樣,消費者每消費一個元素后,也會通知生產者,“現在有空間的,你可以生產”。

很明顯,這些通知很多時候(即對方沒有睡大覺時)是沒有真正意義的,不過無所謂,只要忽略它們就行了。

就是“寧可錯殺一千,也不放過一個”。首先要保證是正確的,然後才有資格去BB別的。

  public static void main(String[] args) {
    Queue queue = new Queue();
    new Thread(new Producer(queue)).start();
    new Thread(new Producer(queue)).start();
    new Thread(new Consumer(queue)).start();
  }

  static class Producer implements Runnable {

    Queue queue;

    Producer(Queue queue) {
      this.queue = queue;
    }

    @Override
    public void run() 
{
      try {
        for (int i = 0; i < 10000; i++) {
          doingLongTime();
          queue.putEle(random(10000));
        }
      } catch (Exception e) {
        e.printStackTrace();
      }
    }

  }

  static class Consumer implements Runnable {

    Queue queue;

    Consumer(Queue queue) {
      this.queue = queue;
    }

    @Override
    public void run() 
{
      try {
        for (int i = 0; i < 10000; i++) {
          doingLongTime();
          queue.takeEle();
        }
      } catch (Exception e) {
        e.printStackTrace();
      }
    }

  }

  static class Queue {
    Lock lock = new ReentrantLock();
    Condition prodCond  = lock.newCondition();
    Condition consCond = lock.newCondition();

    final int CAPACITY = 10;
    Object[] container = new Object[CAPACITY];
    int count = 0;
    int putIndex = 0;
    int takeIndex = 0;

    public void putEle(Object ele) throws InterruptedException {
      try {
        lock.lock();
        while (count == CAPACITY) {
          println("隊列已滿:%d,生產者開始睡大覺。。。", count);
          prodCond.await();
        }
        container[putIndex] = ele;
        println("生產元素:%d", ele);
        putIndex++;
        if (putIndex >= CAPACITY) {
          putIndex = 0;
        }
        count++;
        println("通知消費者去消費。。。");
        consCond.signalAll();
      } finally {
        lock.unlock();
      }
    }

    public Object takeEle() throws InterruptedException {
      try {
        lock.lock();
        while (count == 0) {
          println("隊列已空:%d,消費者開始睡大覺。。。", count);
          consCond.await();
        }
        Object ele = container[takeIndex];
        println("消費元素:%d", ele);
        takeIndex++;
        if (takeIndex >= CAPACITY) {
          takeIndex = 0;
        }
        count--;
        println("通知生產者去生產。。。");
        prodCond.signalAll();
        return ele;
      } finally {
        lock.unlock();
      }
    }
  }

 

解說:其實就是對await/signalAll的應用,幾乎面試必問。


源代碼

https://github.com/coding-new-talking/java-code-demo.git

 

 

(END)

 

作者是工作超過10年的碼農,現在任架構師。喜歡研究技術,崇尚簡單快樂。追求以通俗易懂的語言解說技術,希望所有的讀者都能看懂並記住。下面是公眾號和知識星球的二維碼,歡迎關注!

       

【精選推薦文章】

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

想要讓你的商品在網路上成為最夯、最多人討論的話題?

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

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

想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師"嚨底家"!!

超級簡單的跨平台高性能音視頻播放框架QtAv編譯指南

目錄

  • 一、了解QtAv
  • 二、相關文章
  • 三、下載QtAv源碼
  • 四、下載QtAv依賴庫
  • 五、設置環境變量
    • 1、gcc設置方式
    • 2、msvc(cl)設置方式
  • 六、編譯
  • 七、測試

一、了解QtAv

這幾天抱着試一試的心態,嘗試着了解了下QtAv這個庫,感覺確實挺不錯的,因此就打算學習下這個庫。

斷斷續續的看了不少文章,大多數都是通過百度搜索出來的文章。說實話百度上大多數文章內容都差不多,而且很少有文章說清楚了編譯時的環境配置和編譯器上的區別,導致我自己也一度認為這個庫很難編譯。其實真的不難

網上的資源真的很多,但是有點兒雜亂,新手上來一看可能很容易就懵逼了。可是我這裏要告訴大家,真的不需要害怕,這個庫的編譯真的炒雞簡單,不信看我第三小節開始的編譯步驟,簡單到不敢相信。

因為我看到了windows編譯qtav這篇文章,文章中清楚的說明了環境變量配置是需要根據編譯器進行選擇設置的,這時自己的思路也一下子就開闊了。

我這裏使用的是QtCreator編輯器,編譯器使用的是是MSVC,是vs2013的編譯器。所以頭文件需要配置到Include上,庫文件需要配置到Lib目錄上。

如果是gcc的編譯器,配置才可能像下邊這樣。這個我沒有測試,因為我自己是msvc環境,不過網上這麼多人說了,估計應該也沒啥問題。這也是為啥我開頭說網上資源亂,因為我看的大多數是Mingw集成環境下的文章。

CPATH : C:\Users\Administrator\Desktop\QtAV-depends-windows-x86+x64\QtAV-depends-windows-x86+x64\include
LIBRARY_PATH : C:\Users\Administrator\Desktop\QtAV-depends-windows-x86+x64\QtAV-depends-windows-x86+x64\lib

首先說明我的編譯環境:

  • Qt版本:Qt5.7.1
  • 編譯器:vs2013上的MSVC編譯器
  • 編輯工具:QtCreator 4.2,其實跟這個關係不大,只是一個ide而已,我們使用的編譯器仍然是微軟的msvc編譯器。
  • 系統:Win10 64位

重點強調下,windows編譯qtav這篇文章一定要看,內容真的很實用。主要是告訴你在編譯前期,msvc和gcc兩種編譯器下,怎麼去配置環境變量。

二、相關文章

編譯步驟:Qt5.5.0編譯QtAV

不同編譯器下環境變量配置:windows編譯qtav

我自己是看着Qt5.5.0編譯QtAV這篇文章進行編譯的,最起碼資源都是在文章中的連接里下載的,包括QtAv源碼和依賴庫QtAV-depends-windows-x86+x64

但是參考這篇文章中配置環境變量時,一定要注意,這篇文章中的作者是GCC編譯器。而我們自己去要根據自己的編譯環境來配置環境變量,如果你是MINGW集成環境,也就是說你是GCC編譯器,那麼恭喜你,直接按原文配置即可。

但是,如果你不是GCC編譯器,那麼你就需要看windows編譯qtav
這篇文章,他會告訴你,其他編譯器怎麼配置環境變量

MSVC編譯器,配置方法如下。把頭文件和庫文件分別配置在Include和LIB目錄上。
如果是gcc的編譯器,需要把頭文件和庫文件分別配置在CPATH和LIBTRARY_PATH環境變量上。

三、下載QtAv源碼

源碼下載時直接上官方的github即可,QtAv

四、下載QtAv依賴庫

由於QtAv是基於ffmpeg開發的,因此我們需要下載相關依賴庫。QtAV-depends-windows-x86+x64

五、設置環境變量

根據不同編譯器設置方法不一樣,具體參看windows編譯qtav這篇文章

1、gcc設置方式

CPATH : C:\Users\Administrator\Desktop\QtAV-depends-windows-x86+x64\QtAV-depends-windows-x86+x64\include
LIBRARY_PATH : C:\Users\Administrator\Desktop\QtAV-depends-windows-x86+x64\QtAV-depends-windows-x86+x64\lib

2、msvc(cl)設置方式

圖中環境變量列表中加粗的字即是我添加的環境變量,msvc編譯器下INCLUDE和LIB這個兩個變量本身就是存在的,所以我們只需要在值那一列把include和lib目錄添加上即可。

注意:需要添加自己的QtAV-depends-windows-x86+x64依賴庫路徑

六、編譯

環境變量配置好之後,直接點擊構建即可,編譯成功后,效果如下

七、測試

編譯完成之後,我們會發現bin目錄下會有很多可執行文件,這個時候我們可以執行其中某一個可執行文件對我們編譯的庫進行測試。

首先拷貝QtAv的依賴庫ffmpeg,找到之前解壓的QtAV-depends-windows-x86+x64文件夾,把裡邊的bin目錄下的資源文件都拷貝到我們剛才編譯出來的QtAv目錄下。

找到我們剛才編譯生成的bin目錄,打開裡邊的simpleplayer.exe可執行程序。選擇一個本地的資源文件進行播放,效果圖可能如下圖所示,這裡是只放了一張圖,主要作為示意。

到這裏,我們的QtAv就編譯完成了。

後續有時間我會嘗試使用這個庫,然後做更進一步的分析

很重要–轉載聲明

  1. 本站文章無特別說明,皆為原創,版權所有,轉載時請用鏈接的方式,給出原文出處。同時寫上原作者:朝十晚八 or Twowords

  2. 如要轉載,請原文轉載,如在轉載時修改本文,請事先告知,謝絕在轉載時通過修改本文達到有利於轉載者的目的。

【精選推薦文章】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

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

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

談談實現瀑布流布局的幾種思路

最近遇到這麼一個需求,需要在手機上做一個兩列的瀑布流布局,後來就把這個問題研究了一下,做個記錄。

一般來講,這種布局可以分為兩種情況:

  1. 圖片的數量是一定的,不需要頁面滾動到底部時,再動態加載圖片,只需要將圖片排成若干列

  2. 圖片的數量的不定的,頁面觸底時,需要從遠程加載圖片。

前者使用css的方法即可解決,後者則需要js來幫忙。

css解法

一、CSS多列布局

當我們展示的圖片數量一定時,可以優先採用css解法。其中一種方法是藉助css的多欄布局:


.photos{
  
  column-count: 3;
  column-gap: 10px;
}

得到的效果如下:

&lt;p&gt;See the Pen &lt;a href=’https://codepen.io/imgss/pen/Kjmjmq/’&gt;Kjmjmq&lt;/a&gt; by imgss&lt;br /&gt; (&lt;a href=’https://codepen.io/imgss’&gt;@imgss&lt;/a&gt;) on &lt;a href=’https://codepen.io’&gt;CodePen&lt;/a&gt;.&lt;br /&gt;

二、flex布局

flex布局同樣可以做到這一點,訣竅在於將flex-direction設為column;但是相對於多列布局,需要根據瀑布流的列數,計算一個合適的容器高度,不然可能會導致多出一行。如果你在下面的demo 中,看到了4列,不要懷疑,就是我計算的容器高度不合適導致的。。。

&lt;p&gt;See the Pen &lt;a href=’https://codepen.io/imgss/pen/XLRvmQ/’&gt;waterflow-flex&lt;/a&gt; by imgss&lt;br /&gt; (&lt;a href=’https://codepen.io/imgss’&gt;@imgss&lt;/a&gt;) on &lt;a href=’https://codepen.io’&gt;CodePen&lt;/a&gt;.&lt;br /&gt;

js解法

當圖片需要動態插入時,上面的兩種方法就不合適了,因為他們本質上是將圖片按照縱向進行排列的。圖片動態插入時通常我們希望圖片是按橫向插入到容器中的。這時候就需要js來幫忙了。首先,我們看看瀑布流和背包問題的關係。

瀑布流的基本思路是將一堆圖片放到若干列中,列與列之間的高度比較均勻,而不會相差太大。假如我們要分成兩列,那麼,問題就變成了,從 n 張圖片中挑出 m 張,使這 m 張圖片的總高度盡量接近 n 張圖片總高度的 1 / 2。於是這就變成了一個背包問題

01背包問題

背包問題是啥這裏不做展開,說白了是將一個複雜的問題分解為幾個簡單的問題,大佬們講的都比我好,網上也有各個語言版本的實現,不太了解的同學可以查看上面的鏈接。這裏直接放一個函數


function dp(ws, vs, limit) {
    let len = ws.length;
    let tables = new Array(len).fill().map(x => [])
    tables[-1] = new Array(limit + 1).fill(0);

    for(let i = 0; i < len; i++) {
        for (let w = 0; w <= limit; w++) {
            if (ws[i] > w) {
                tables[i][w] = tables[i-1][w]
            } else {
                tables[i][w] = Math.max(tables[i-1][w], tables[i-1][w-ws[i]] + vs[i])
            }
        }
    }
    // 回溯得到應該選哪些
    let max = limit;
    let selected = [];
    for (let idx = len - 1; idx >= 0; idx--) {
        if (ws[idx] <= max) {
            let isSelected = tables[idx-1][max] < tables[idx-1][max-ws[idx]] + vs[idx]
            if(isSelected) {
                selected.push(idx);
                max = max - ws[idx];
            }
        }
    }
    return selected;
}

有了這個解法之後,我們也就不難寫出一個瀑布流布局。具體思路是:假設我們要做一個3列的瀑布流布局,那麼可以不斷從圖片數組中選出一組圖片,使圖片的高度接近總高度的1/3,最終得到3組圖片。下面是一個代碼片段

      // colCount 表示要生成幾列
      while(colCount--) {
          // 獲取被選出的照片索引
          let idxs = dp(photoHeights, photoHeights, aver)
          // 得到被選出的一組圖片
          let photoCol = photos.filter((p,idx) => idxs.includes(idx)) 
          this.cols.push(photoCol)
          photoHeights.forEach((v,i) => {
          if (idxs.includes(i)) {
            photoHeights[i] = null
          }
        })
      }

下面這個demo就是按上面的思路實現的,可以拖動下面的滑塊來改變列數,觀察底部的間隙。在使用背包算法解決瀑布流問題時,一個需要我們注意的地方是,要將圖片高度轉化成整數。

&lt;p&gt;See the Pen &lt;a href=’https://codepen.io/imgss/pen/agwbgx/’&gt;waterflow-js&lt;/a&gt; by imgss&lt;br /&gt; (&lt;a href=’https://codepen.io/imgss’&gt;@imgss&lt;/a&gt;) on &lt;a href=’https://codepen.io’&gt;CodePen&lt;/a&gt;.&lt;br /&gt;

參考文章:https://www.cnblogs.com/ainyi/p/8766281.html
https://www.cnblogs.com/AndyJee/p/4543424.html
https://segmentfault.com/a/1190000012829866

【精選推薦文章】

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

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

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

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

Python深入淺出property特性屬性

導語

在Java中,通常在類中定義的成員變量為私有變量,在類的實例中不能直接通過對象.屬性直接操作,而是要通過getter和setter來操作私有變量。
而在Python中,因為有property這個概念,所以不需要寫getter和setter一堆重複的代碼來操作私有變量。Python“私有變量”通常在變量前加上“_”或者“__”,例如_attr或者__attr,這是約定俗成的規範。

把私有屬性變成只讀特性

class MyClass:

    def __init__(self, x):
        self._x = x

這裏定義了一個MyClass類,它有一個實例變量_x,綁定了用戶傳來的x值。_x是私有變量,通過obj._x獲取私有變量不符合語言規範,進而我們要使_x變成property(特性),通過obj.x直接訪問。

改造后的代碼如下:

class MyClass:

    def __init__(self, x):
        self._x = x

    @property
    def x(self):
        return self._x
    
>>> obj = MyClass(10)
>>> obj.x
10

我們把_x變成了property特性,以只讀的方式獲取x的值。

我們現在想為x賦值該怎樣做呢?

>>> obj.x = 999
Traceback (most recent call last):
  File "xxx", line 14, in <module>
    obj.x = 23
AttributeError: can't set attribute

可以看到,拋出了AttributeError: can’t set attribute。顯然,只讀方法不支持賦值。

把私有變量變成可賦值的特性

我們只需要在上述代碼改造成:

class MyClass:

    def __init__(self, x):
        self._x = x

    @property
    def x(self):
        return self._x
    
    @x.setter
    def x(self, value):
        self._x = value

>>> obj = MyClass(10)
>>> obj.x = 999
>>> obj.x
999

可以看到,我們為x添加了setter,可以直接為obj.x賦值操作

property屬性能夠遮蓋實例屬性

繼續上面的代碼,我們看看以下的操作:

>>> obj = MyClass(10)
>>> obj.__dict__
{'_x': 999}  #此時實例變量中有_x的值
>>> obj.__dict__['x'] = 99999  #設置obj的實例變量有x值,跟property屬性重名!
>>> obj.__dict__
{'_x': 999, 'x': 99999}  #此時實例變量中有_x和x的值

>>> obj.x     #結果是obj的實例變量還是property屬性?
10

如上代碼所示,obj對象有一個_x實例變量和一個x的property屬性,我們又強行為obj增加了一個x實例變量,這個實例變量x和property屬性x同名!
通過obj.x我們得知,返回的是property屬性,說明property屬性會遮蓋實例屬性!也可以理解為property屬性的優先級更大!

property類解析

我們通常使用內置的@property裝飾器。但其實property是一個類,python中類和函數的調用方式都差不多,他們都是可調用對象
property的構造方法如下:

class property(object):
    def __init__(self, fget=None, fset=None, fdel=None, doc=None):
        """"""

它最大接受4個參數,都可以為空。
第一個為getter,第二個為setter,第三個為delete函數,第四個為文檔。

上述代碼的另一種寫法

class MyClass:

    def __init__(self, x):
        self._x = x

    def get_x(self):
        return self._x

    def set_x(self, value):
        self._x = value

    x = property(get_x, set_x)

>>> obj = MyClass(10)
>>> obj.x
10

如上,x是property的實例,設置了getter和setter,作為類變量放在MyClass類中。

以上就是property屬性的解析。

【精選推薦文章】

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

想要讓你的商品在網路上成為最夯、最多人討論的話題?

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

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

想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師"嚨底家"!!

機器學習實記(一)簡介機器學習

一.寫在前面

  本系列中的內容來自對李宏毅老師的機器學習教程的理解和一些個人理解的筆記,主要用於之後方便查看複習。李宏毅老師的機器學習教程地址:https://www.bilibili.com/video/av35932863?from=search&seid=3902369523652897681。

二.機器學習概念

2.1人工智能、機器學習、深度學習這幾個概念的關係

  要想理解清楚機器學習的基本概念,首先要明白機器學習在人工智能領域中所處的位置,再簡單明了些首先要明白人工智能、機器學習、深度學習這幾個概念的關係。三者關係如下圖2-1

                      圖2-1 人工智能、機器學習、深度學習三者關係

從圖可以直觀的看出,人工智能的範圍>機器學習的範圍>深度學習的範圍,而平時常說的神經網絡的這些有關概念則是屬於深度學習下的分支。再具體而言這三者的關係,其實人工智能是作為我們最終所要達到的目標,所謂的人工智能概念我覺的用之前某科學家提過的一種說法簡單理解一下就行,即當用一塊黑幕將計算機遮蓋起來,黑幕外的人無法區分所交互的對象是人還是計算機就算是達到了人工智能標準,再簡單點說就是使計算機做到過去只有人才能做到的事,其他更為深入的解釋歡迎查看百度詞條https://baike.baidu.com/item/人工智能/9180?fr=aladdin。而機器學習則是實現人工智能這一目標的手段,這說明還有其他手段有興趣的同學可以自行去了解一下,不過個人認為其餘的手段不是現在的主流研究方向吧。而深度學習則是作為機器學習下的一個重要分支。

2.2 理解機器學習

  所謂機器學習概念,簡單的理解就是教會計算機學習,舉個具體的例子,當你給它看完貓的某一張圖片后,你告訴機器這是貓,當你給它看完一張狗的圖片后,你告訴它這是狗,不斷重複這一過程讓機器學習大量不同的貓狗照片,學習結束后給予機器一張全新的貓或狗的照片,機器能夠成功的識別出貓和狗。再進一步抽象的理解,我們可以發現這一過程十分類似於數學上求解函數表達式,即這個過程其實是在尋找一個函數f,當輸入某張貓的照片函數f的輸出為貓,當輸入某張狗的照片函數f的輸出為狗。再進一步具體而言整個機器學習框架可以理解為如圖2-2

                                                                            圖2-2 機器學習框架描述

整個機器學習框架的步驟大體上分為三步:第一步定義一個函數池,其中有大量的備選函數f1、f2、f3…..fn;第二步對各個函數進行評價,這裏的話其實我們可以將每個函數理解為一個模型,所謂的評價的標準可以理解為對每個輸入照片各個模型所能識別出來的精確度,優秀的模型識別照片的準確度更高;第三部選出最優的函數fbest,即選出最優的模型。

2.3 理解機器學習的學習圖

  機器學習的學習圖如圖2-3,這張圖看起來很複雜,其實結構很清晰,主要分為三中顏色藍、橙、綠三種顏色,分別代表運用情景,所要解決問題的目標,以及解決問題用到的方法。

                           圖2-3 機器學習學習圖

  首先是藍色部分,即運用情景從圖中可以看出有supervise learning、semi-supervise learning、transer learning、unsupervised learning、reinforcement learning,看到這麼多運用情景不經想問一個問題,為什麼會有這麼多運用場景的劃分?原因其實很簡單,從前面的對機器學習的介紹中我們可以發現,機器學習的過程中是需要大量的帶標籤的數據,所謂帶標籤的數據就是指給出了輸入數據的正確結果,比如說輸入一張貓的圖片,這個貓就是這張圖片的標籤,但其實當數據量巨大的時候,給每個數據標上標籤是需要消耗大量時間的,所以由輸入的數據是否帶有標籤就產生了不同的運用場景。

  supervise learning 即監督學習,訓練所使用的數據均帶標籤,即告訴機器輸入數據的正確答案。semi-supervise learning 即半監督學習,訓練中的數據有一部分帶標籤,其餘數據不帶標籤交給機器自己學習。transer learning 即遷移學習 訓練數據中有一部分帶標籤,剩下的數據來自其他模型帶標籤或不帶標籤的數據,舉個例子我們做貓和狗的分類,我們有少量的已經有標籤的貓和狗的數據,還有大量的可能是帶標籤或不帶標籤的其他動物的數據,將這些數據也交由機器學習。unsupervised learning 即無監督學習,訓練中的數據均不帶標籤。reinforcement learning 即強化學習,這種學習方式與前面的學習方式均有所不同,前面的學習方式中均為告訴機器輸入數據的正確答案或者直接將數據交由機器,在強化學習中數據均不帶標籤,它是通評價告訴機器這次學習的結果,舉個例子阿爾法狗,當它完成一次棋局對戰最終取得勝利,則給與機器較高的評價,讓機器自身進行調整,雖然機器本身並不知道自己到底哪下的好,但知道這麼下贏了。

  其次是橙色部分,即所要解決問題的目標,分為regression和classification以及structured learning。regression即所要解決的問題的解是個數值,比方說預測某日pm2.5的值;classification即所要解決的問題為分類問題,包括二分類即回答是或者否,比方說判斷郵件是否為垃圾郵件,多分類對輸入的數據進行多個類別分類,比方說判斷輸入的文章屬於哪個板塊是娛樂版塊還是體育板塊還是金融板塊等等;structured learning即所要解決的問題是結構化的,比方說輸入一段語言判斷語言的內容。

  最後是綠色部分,這一塊的話還記的我在2.2中說過的函數池嗎,所謂的函數其實就是模型,我們可以使用不同的模型來解決不同的問題,這些模型有linear model即線性模型、non-linear model 即非線性模型,非線性模型中就包括之前提到的深度學習還有一些非線性模型。

三.寫在最後

  這一部分主要是對機器學習要有個整體的大概理解,包括理解機器學習的整體框架的大致模樣,尤其是要明白框架中所說的函數其實就是模型的概念,再由就是要對為何要這樣劃分學習圖有自己的理解,至於是否要記住學習圖的內容倒是其次。

【精選推薦文章】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

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

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

【工具篇】在.Net中實現HTML生成圖片或PDF的幾種方式

前段時間由於項目上的需求,要在.Net平台下實現把HTML內容生成圖片或PDF文件的功能,特意在網上研究了幾種方案,這裏記錄一下以備日後再次使用。當時想着找一種開發部署都比較清爽並且運行穩定的方案,但實際上兩者同時滿足基本不可能,只能做一個自己覺得合適的取捨,下面從兩個維度(清爽指數和功能指數)逐一對比。


1.   WebBrowser

這種方案在開發時不依賴任務外部程序集和nuget包,部署時也不需要安裝額外的工具和服務,可以說是非常清爽了。它藉助了WinForm下的WebBrowser控件實現HTML內容渲染,並把渲染結果繪製在Bitmap中,進而保存成圖片或PDF文件。這種方案簡單粗暴,是C#中最基礎的實現方式,也是網上搜索結果最多的一種,下面看它的核心代碼(從網上拼湊得來):

 1     class WebBrowserPage2Image
 2     {
 3         Bitmap m_Bitmap;
 4 
 5         string m_Url;               
 6 
 7         public void Convert(string pageUrl, string fileName)
 8         {
 9             m_Url = pageUrl;
10             Thread m_thread = new Thread(new ThreadStart(HtmlDrawToBitmap));
11             m_thread.SetApartmentState(ApartmentState.STA);
12             m_thread.IsBackground = true;
13             m_thread.Start();
14             m_thread.Join();
15             MemoryStream stream = new MemoryStream();
16             m_Bitmap.Save(stream, System.Drawing.Imaging.ImageFormat.Png);
17             byte[] buff = stream.ToArray();
18             FileStream fs = new FileStream(fileName, FileMode.Create);
19             stream.WriteTo(fs);
20             stream.Dispose();
21             stream.Close();
22             fs.Close();
23         }
24 
25         private void HtmlDrawToBitmap()
26         {
27             WebBrowser browser = new WebBrowser();
28             browser.ScrollBarsEnabled = false;
29             browser.Navigate(m_Url);
30             browser.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(delegate (object sender, WebBrowserDocumentCompletedEventArgs bdce)
31             {
32                 if (browser.ReadyState == WebBrowserReadyState.Complete)
33                 {
34                     //myWebBrowser.Document.Body.Style = "zoom:180%";
35                     Rectangle r = browser.Document.Body.ScrollRectangle;
36                     browser.Height = r.Height;
37                     browser.Width = r.Width;
38                     m_Bitmap = new Bitmap(browser.Width, browser.Height);
39                     browser.BringToFront();
40                     browser.DrawToBitmap(m_Bitmap, new Rectangle() { Width = browser.Width, Height = browser.Height });
41                 }
42             });
43             while (browser.ReadyState != WebBrowserReadyState.Complete)
44             {
45                 Application.DoEvents();
46             }
47             browser.Dispose();
48         }
49     }

View Code

雖然開發起來非常簡潔,但是問題也很明顯。WebBrowser是Winform下的一個組件,在非Winform項目中運行會出現不可知的異常,即使在Winform項目中,數據量比較大的時候依然會出現卡死的情況。我做過500次循環的測試,在執行到100多次的時候程序出現假死不動也無異常拋出。除此之外,生成的圖片失真也比較嚴重,特殊字體和部分CSS樣式無法渲染。總的來說,基本無法達到生成環境需求。

清爽指數:★★★★★    功能指數:★


2.         Wkhtmltox

這也是網上廣泛流傳的一個方案,wkhtmltox是一套開源的命令行工具,提供了圖片和PDF的轉換能力,它採用C++編寫,使用Webkit作為渲染引擎,開源地址是https://github.com/wkhtmltopdf/wkhtmltopdf。使用方法就是在命令行工具中執行命令,例如:

wkhtmltopdf --grayscale  https://www.baidu.com  baidu.pdf

如果要在.Net項目中使用的話,核心問題就是用程序喚起命令行,同時指定參數執行即可,類似於下面的代碼:

       System.Diagnostics.ProcessStartInfo Info = new System.Diagnostics.ProcessStartInfo();
       Info.FileName = @"D:\dev\wkhtmltox\bin\wkhtmltopdf.exe";
       Info.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
       Info.CreateNoWindow = true;
       Info.Arguments = @"-q --orientation Landscape https://www.baidu.com D:\\baidu.pdf";
       System.Diagnostics.Process proc = System.Diagnostics.Process.Start(Info);
       proc.WaitForExit();
       proc.Close();

更多強大的功能例如加水印、分頁、改樣式等可以參考這篇文章:https://www.cnblogs.com/82xb/p/7837597.html

詳細的參數說明可以查看文檔:https://wkhtmltopdf.org/usage/wkhtmltopdf.txt

GitHub上有很多針對各個開發語言的封裝,使用起來比較方便,唯一不爽的是部署項目前要先安裝好這個工具。

清爽指數:★★★★    功能指數:★★★★


3.         PuppeteerSharp

這個就更厲害了,說到這個就不得不先介紹下Puppeteer,因為PuppeteerSharp正是從Puppeteer衍生而來。

Puppeteer是由谷歌開源的一個Node項目,它提供了和Chrome DevTools的通信能力,基本上我們能在Chrome實現的操作通過它的API都可以實現,強大到讓你不敢相信。主要的應用有:

  • 生成頁面快照(圖片、PDF)
  • 爬蟲,網站內容抓取
  • 自動化測試(模擬鍵盤鼠標輸入,表單提交,UI測試等)
  • 網站性能分析(追蹤,時間線捕獲等)

開源地址是https://github.com/GoogleChrome/puppeteer

在Node項目中使用Puppeteer非常簡單,先安裝npm包:

npm i puppeteer

安裝過程可能會有點慢,因為在安裝的時候會下載一個最近版本的Chromium(Mac下大概170M,Linux下大概282M,Windows下大概280M)。當然,如果你本地已經有一個Chromium,可以設置npm的全局配置PUPPETEER_SKIP_CHROMIUM_DOWNLOAD 跳過下載,然後在程序中手動指定Chromium的位置。

生成圖片和PDF文件例子:

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('https://www.baidu.com');
  await page.screenshot({path: 'baidu.png'});
  await page.pdf({path: 'baidu.pdf', format: 'A4'});
  await browser.close();
})(); 

Puppeteer默認使用無界面模式(headless:true),如果想看到完整的瀏覽器界面,可以通過下面的設置開啟:

  const browser = await puppeteer.launch({headless: false});

Puppeteer提供了豐富的選擇器接口,可以輕鬆實現模擬輸入和鼠標點擊,例如:

  await page.type('#index-kw', 'cnblogs');
  await page.click('#index-bn');

      還支持指定使用設備:

  const devices = require('puppeteer/DeviceDescriptors');
  await page.emulate(devices['iPhone 8']);

      詳細的API文檔可以參考:https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md

Puppeteer確實非常強大,但由於它是一個Node包無法直接在C#項目中使用,那怎麼辦呢?好在有國外的大神把Puppeteer移植到了.Net平台,也就是PuppeteerSharp。

注意:PuppeteerSharp是基於NetStandard 2.0開發的,所以項目的平台最低版本要是.NET Framework 4.6.1和.NET Core 2.0。

首先通過nuget安裝:

PM > Install-Package PuppeteerSharp

導入命名空間:

  using PuppeteerSharp;

下面是我在ASP.NET Core 2.1下封裝的測試方法:

        [HttpPost, Route("page2img")]
        public async Task<string> PageToImage(string url, int? width, int? height)
        {
            await new BrowserFetcher().DownloadAsync(BrowserFetcher.DefaultRevision);
            var browser = await Puppeteer.LaunchAsync(new LaunchOptions
            {
                Headless = true,
                //ExecutablePath="",
                Args = new string[] { "--no-sandbox" }
            });
            var page = await browser.NewPageAsync();
            bool fullPage = true;
            if (width.HasValue && height.HasValue)
            {
                await page.SetViewportAsync(new ViewPortOptions
                {
                    Width = width.Value,
                    Height = height.Value
                });
                fullPage = false;
            }
            await page.GoToAsync(System.Web.HttpUtility.UrlDecode(url));
            string fileName = $"/Files/{Guid.NewGuid().ToString()}.png";
            await page.ScreenshotAsync($"{AppDomain.CurrentDomain.BaseDirectory}{fileName}", new ScreenshotOptions { FullPage = fullPage });
            return $"{Request.Host.ToString()}{fileName}";
        }

上面方法的第一行:

  await new BrowserFetcher().DownloadAsync(BrowserFetcher.DefaultRevision);

程序會判斷本地環境有沒有可用的Chromium,如果沒有的話會自動下載一個默認版本的Chromium,這個過程可能會有點長,下載成功後會在項目根目錄多一個這樣的文件夾:

和前面說的一樣,如果本地已經下載過Chromium,可以通過LaunchOptionsExecutablePath字段指定一個目錄。目前PuppeteerSharp在網上的資料還不是很多,但是得益於它與Puppeteer高度完整和相似的API,Puppeteer的文檔對它基本都能適用。

總體來說,這個工具功能強大並且比較穩定(我在Windows和Linux下都測試通過),是一個不錯的選擇,但是由於它必須依賴於Chromium來運行,打包部署並不是很方便,我建議把它作為一個獨立的web服務。

清爽指數:★★★    功能指數:★★★★★


4.         IronPdf

    除了一些開源的項目和工具能提供HTML轉圖片或PDF的功能,很多商業軟件公司也提供了這樣的產品,IronPdf算是裏面比較有代表性的一個。和其他收費軟件不同的是,IronPdf有一個對開發者免費試用的license:

    IronPdf的主要特性包括:

  • 任何類型的HTML文件、代碼片段、URL生成PDF
  • PDF編輯
  • 圖片與PDF互轉
  • 支持HTML5和CSS3,支持響應式布局,支持JS腳本,豐富的配置選項
  • 支持C#、VB、Webform、ASP.NET MVC、.NET CORE

    我們可以在官網下載DLL文件直接引用到項目,也可以通過nuget來安裝:

PM > Install-Package IronPdf

    導入命名空間:

  using IronPdf;

    一個最簡單的例子:

// Create a PDF from any existing web page
var Renderer = new IronPdf.HtmlToPdf();
Renderer.PrintOptions.EnableJavaScript = true;
Renderer.PrintOptions.PaperOrientation = IronPdf.PdfPrintOptions.PdfPaperOrientation.Landscape;
var PDF = Renderer.RenderUrlAsPdf("https://www.baidu.com");
PDF.SaveAs("baidu.pdf");

// This neat trick opens our PDF file so we can see the result
System.Diagnostics.Process.Start("baidu.pdf");

    添加水印:

  pdf.WatermarkAllPages("<h2 style='color:red'>SAMPLE</h2>", PdfDocument.WaterMarkLocation.MiddleCenter, 50, -45, "https://www.baidu.com");

    用圖片生成PDF文檔:

// Select one or more images.  This example selects all JPEG images in a specific folder.
var ImageFiles = Directory.EnumerateFiles(@"C:\project\assets").Where(f => f.EndsWith(".jpg") || f.EndsWith(".jpeg"));

// Convert the images to a PDF and save it.
ImageToPdfConverter.ImageToPdf(ImageFiles).SaveAs(@"C:\project\composite.pdf");

    更多高級功能和配置可以參考官網例子:https://ironpdf.com/examples/image-to-pdf/

 清爽指數:★★★★    功能指數:★★★★

    

寫在最後

    以上幾種方式,都是我在本次實踐中總結出來的,可能不是很全面,歡迎大家不吝補充。

    遺憾的是,最終項目沒有用上面的任何一種方式,而是抓取到HTML內容後用正則解析,然後用Bitmap一點一點重新畫圖生成圖片文件保存。因為我要截取的頁面內容很少,就是一個簡單的电子處方箋,需求上也沒有要求必須完全和原網頁100%一致,繪圖也算是一個不錯的方案,但是缺點是一旦HTML結構或樣式發生變化,那這套東西就失效了,好在這個不會輕易變更,也算是一個折中方案。

 

 

 

【精選推薦文章】

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

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

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

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

docker (2)—存儲、網絡(利用docker容器上線靜態網站)

一、docker底層依賴的核心技術

1、命名空間 (Namespaces)

2、控制組 (Control Groups)

3、聯合文件系統 (Union File System)

4Linux 虛擬網絡支持:本地和容器內創建虛擬接口

(1) 命名空間(Namespaces):

實現了容器間資源的隔離,每個容器擁有自己獨立的命名空間 , 運行其中的應用就像是運行在獨立的操作系統中一樣 , 我們都可以看到文件系統,網卡等資源保證了容器之間互不影響,namesaces管理進程號 , 每個進程命名空間有一套自己的進程號管理方法 ,

程命名空間是一個父子關係的結構 , 子空間中的進程對於父空間是可見的。

(2) 控制組 (Control Groups) :

  控制組 (Control groups)–CGroups Linux 內核的一個特性 ,主要用來對共享資源進行隔離、限制、審計等 。cgroups 允許對於進程或進程組公平( 不公平 ) 的分配 CPU 時間、內存分配和 I/O 帶寬。

容器通過 cgroups 來得到所能夠管理資源的分配和使用。因此容器所獲得資源僅為所有系統資源的一個部分

1、資源限制 : 內存子系統為進程組設置內存使用上限,內存達到上限后再申請內存,就會發出 Out of Memory

2、 優先級 : 通過優先級讓一些組得到更多 CPU 等資源

3、 資源審計 : 用來統計系統上實際把多少資源用到適合的目的上 , 可以使用 cpuacct 子系統記錄某個進程組使用的 CPU 時間

4、 隔離 : 為組隔離名字空間 , 這樣一個組不會看到其他組的進程 .網絡連接和文件系統

5、 控制 : 掛起 . 恢復和啟動等操作

(3)聯合文件系統 (Union File System) :

  docker 中使用AUFS(another Union File System v2 版本以後的Advanced multi-layered Unification File System) 控製為每一個成員目錄設定只讀 / 讀寫 / 寫出權限 , 同時 AUFS 有一個類似分層的概念 , 對只讀權限的分支可以邏輯上進行增量的修改.

二、docker的存儲

docker兩種存儲資源類型:

1Data Volume (數據卷)

2Data Volume Dontainers — 數據卷容器

(1) Data Volume (數據卷):

Data Volume 本質上是 Docker Host 文件系統中的目錄或文件,使用類似與 Linux 下對目錄或者文件進行 mount 操作。數據卷可以在容器之間共享和重用,對數據卷的更改會立馬生效,對數據卷的更新不會影響鏡像,卷會一直存在,直到沒有容器使用。

Data Volume(數據卷)的特點:

1Data Volume 是目錄或文件,而非沒有格式化的磁盤(塊設備)。

2、容器可以讀寫 volume 中的數據。

3volume 數據可以被永久的保存,即使使用它的容器已經銷毀。

 Data Volume 的使用:

1、在宿主機根目錄下創建一個目錄(數據卷)

 

2、啟動一個容器並將數據卷掛載到容器的目錄下

 

3、驗證  ( 持久化的需要映射目錄)

 

#

 

(2)Data Volume Dontainers — 數據卷容器

數據卷容器就是一個普通的容器,只不過是專門用它提供數據卷供其他容器掛載使用

Data Volume Dontainers使用:

1、創建一個名為 dbdata 的數據卷,並在其中創建一個數據卷掛載到 /dbdata

docker run -dti -v /dbdata --name dbser centos:latest

2、再啟動兩個容器,並使用數據卷容器

docker run -dti --volumes-from dbser --name db1 centos:latest

#

3、驗證

 

#

容器 db1 db2 同時掛載了同一個數據卷到本地相同 /dbdata目錄。三個容器任何一個目錄下的寫入,都可以時時同步到另外兩個

三、docker 三種網絡

  docker 網絡從覆蓋範圍可分為單個 host 上的容器網絡和跨多個 host 的網絡,docker 目前提供了映射容器端口到宿主主機和容器互聯機制來為容器提供網絡服務,在啟動容器的時候,如果不指定參數,在容器外部是沒有辦法通過網絡來訪問容器內部的網絡應用和服務的

docker 安裝時會自動在host上創建三個網絡

docker   network  ls   (查看docker  網絡)

(1) docker–none網絡

none 網絡就是什麼都沒有的網絡。掛在這個網絡下的容器除了 lo,沒有其他 任何網卡。容器創建時,可以通過 –network=none 指定使用 none 網絡

 

none網絡的應用

封閉的網絡意味着隔離,一些對安全性要求高並且不需要聯網的應用可以使用 none 網絡。

(2)docker–host網絡

連接到 host 網絡的容器,共享 docker host 的網絡棧,容器的網絡配置與host 完全一樣。可以通過 –network=host 指定使用 host 網絡

 

host 網絡的應用

  直接使用 Docker host 的網絡最大的好處就是性能,如果容器對網絡傳輸效率有較高要求,就可以選擇 host 網絡。當然不便之處就是犧牲一些靈活性,比如要考慮端口衝突問題,Docker host上已經使用的端口就不能再用了。

Docker host 的另一個用途是讓容器可以直接配置 host 網路。比如某些跨host 的網絡解決方案,其本身也是以容器方式運行的,這些方案需要對網絡進行配置,比如管理 iptables

(3) docker–bridge 網絡

docker 安裝時會創建一個 命名為 docker0 linux bridge。如果不指定–network,創建的容器默認都會掛到 docker0

 

#

 

eth0@if29      veth04c5851 是一對 veth pair

#

  veth pair 是一種成對出現的特殊網絡設備,可以把它們想象成由一根虛擬網線連接起來的一對網卡,網卡的一頭(eth0@if29)在容器中,另一頭( veth04c5851)掛在網橋 docker0 上,其效果就是將 eth0@if29也掛在了docker0 上。

# 查看網絡配置信息 ( 設置容器ip 網段、網關)

docker network inspect bridge

 

#

注:容器創建時,docker 會自動從 172.17.0.0/16 中分配一個 IP,這裏 16 位的掩碼保證有足夠多的 IP 可以供容器。

四、創建 user-defined網絡 (自定義網絡)

通過 bridge 驅動創建類似前面默認的 bridge 網絡

1、利用bridge驅動創建名為my-net2網橋(docker會自動分配網段)

docker network create --driver bridge my-net2

# 查看網絡配置信息

 

# 查看網橋

 

2、利用bridge驅動創建名為my-net3網橋(user-defined (自定義)網段及網關)

docker network create --driver bridge --subnet 172.33.1.0/24 --gateway 172.33.1.1 my-net3

# 查看網絡配置信息

 

# 查看網橋

 

3、啟動容器使用新建的my-net3網絡

docker run -it  --network=my-net3  busybox:latest

4、啟動容器使用my-net3網絡並指定ip(只有使用 –subnet 創建的網絡才能指定靜態 IP,如果是docker自動分配的網段不可以指定ip

docker run -it --network=my-net3  --ip 172.33.1.100  busybox:latest

 

5、讓已啟動不同vlanbusybox容器,可以連接到my-net2(其實在busybox中新建了my-net2的網卡)(添加網卡。訪問不同的網段)

 

# #docker network connect my-net3  08493ae30117   ( 連接)

6、使用–name指定啟動容器名字,可以使用docker自帶DNS通信,但只能工作在user-defined 網絡,默認的 bridge 網絡是無法使用 DNS 的。

#docker run -it --network=my-net3 --name=bbox1 busybox:latest

#docker run -it --network=my-net3 --name=bbox2 busybox:latest

7、容器之間的網絡互聯

&1、創建一個 db 容器

docker run -dti --name db centos:latest

&2、創建一個 web 容器,並使其連接到 容器db

docker run -dti --name web --link db:dblink centos:latest /bin/bash

–link db:dblink 實際是連接對端的名字和這個鏈接的名字,也就是和 db 容器建立一個叫做 dblink 的鏈接

 

# 測試  

 

注:此鏈接通信是單向的

8、容器端口映射

在啟動容器的時候,如果不指定參數,在容器外部是沒有辦法通過網絡來訪問容器內部的網絡應用和服務的,當容器需要通信時,我們可以使用 -P (大) &&-p (小)來指定端口映射

(1)   -P Docker 會隨機映射一個 49000 49900 的端口到容器內部開放的網絡端口

(2)   -p :則可以指定要映射的端口,並且在一個指定的端口上只可以綁定一個容器。

支持的格式

 IP HostPort ContainerPort

 IP : : ContainerPort

 IP HostPort

&1、 查看映射

docker port   CONTAINER ID/NAMES

&2、映射所有接口地址,此時綁定本地所有接口上的 5200 到容器的 5200 接口,訪問任何一個本地接口的 5000 ,都會直接訪問到容器內部

docker run -dti -p 5200:5200 centos:latest  /bin/bash

&3、多次使用可以實現多個接口的映射

docker run -dti -p 5400:5400  -p 5300:5300 centos:latest  /bin/bash

&4、映射到指定地址的指定接口

此時會綁定本地 192.168.226.147 接口上的 5100 到容器的 5100 接口

docker run -dti -p 192.168.226.147:5100:5100 centos:latest /bin/bash

 

&5、映射到指定地址的任意接口

此時會綁定本地 192.168.226.147 接口上的任意一個接口到容器的 5500 接口

docker run -dti -p 192.168.226.147::5500 centos:latest /bin/bash

實驗、通過端口映射實現訪問本地的 IP:PORT 可以訪問到容器內的 web

1、將容器80端口映射到主機8090端口

docker run -itd -p 192.168.226.147:8090:80 --name http-test httpd:latest

2、查看剛運行docker容器

docker  ps

 

3、進入 容器

 

4、容器內部編輯網頁文件 index.html

 

5、到宿主機上打開瀏覽器輸入 IP:PORT 訪問驗證

http://192.168.226.147:8090/

 

6、宿主機上傳靜態網站測試文件

 

7、解壓

 

8、把解壓的目錄上傳至容器下的網站根目錄

docker cp jd 67b3daf15a40:/usr/local/apache2/htdocs

9、進入容器,刪除原來的index.html 文件

 

10、展開目錄

 

11web 訪問

http://192.168.226.147:8090/

 

 

【精選推薦文章】

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

想要讓你的商品在網路上成為最夯、最多人討論的話題?

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

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

想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師"嚨底家"!!

【劍指Offer】調整數組順序使奇數位於偶數前面

題目描述

輸入一個整數數組,實現一個函數來調整該數組中数字的順序,使得所有的奇數位於數組的前半部分,所有的偶數位於數組的後半部分,並保證奇數和奇數,偶數和偶數之間的相對位置不變。

解法1

最直接的思路是再構建一個新數組,先遍歷一遍原數組,把其中的奇數依次添加到新數組中,再遍歷一遍原數組把其中的偶數依次添加到新數組中,時間複雜度為O(2n)。實現代碼如下

實現代碼

public int[] reOrderArray2(int[] array)
{
    int[] arr = new int[array.Length];
    int index = 0;
    for (int i = 0; i < array.Length; i++)
    {
        // 奇數
        if ((array[i] % 2) != 0)
        {
            arr[index] = array[i];
            index++;
        }
    }
    for (int i = 0; i < array.Length; i++)
    {
        // 偶數
        if ((array[i] % 2) == 0)
        {
            arr[index] = array[i];
            index++;
        }
    }
    return arr;
}

解法2

C#的數組是不支持動態添加元素的,我們可以使用泛型List,來實現在指定位置插入元素。基本思路是遍歷原數組,依次將元素插入到List中,如果是偶數元素,默認插入到List的末尾。如果是奇數元素,則插入到所有的偶數元素之前(已插入的所有奇數元素之後),因此需要記錄最後插入的奇數元素的索引。實現代碼如下,算法的時間複雜度是O(n)

實現代碼

public int[] reOrderArray(int[] array)
{
    List<int> list = new List<int>();
    // 最後插入奇數元素的索引
    int index = 0;
    foreach (int i in array)
    {
        if ((i % 2) == 0)
            list.Add(i);
        else
        {
            list.Insert(index, i);
            index++;
        }
    }
    return list.ToArray();
}

解法3

上面的兩種解法都用到臨時數組或List,空間複雜度是O(n),某些情況下可能希望空間複雜度越低越好。下面這種解法雖然時間複雜度提高了,但降低了空間複雜度,不再需要額外的空間。基本思路是遍歷原數組,如果遇到了奇數元素,就將該元素向前移動,該元素前面的偶數元素都依次向後移動。
舉個例子:比如數組{1, 2, 4, 3, 5}
遍曆數組,得到第一個元素是奇數1,其前面沒有元素所以不做移動
第二個,第三個是偶數,不做處理。
第四個元素是奇數3,所以將3往前移動,3前面的偶數元素{2, 4}都向後移動。移動后的數組為{1, 3, 2, 4, 5}
接着第五個元素是奇數5,所以將5往前移動,5前面的偶數元素{2, 4}都向後移動。移動后的數組為{1, 3, 5, 2, 4}
可以這樣理解,每發現一個奇數時,就將這個奇數移動到了它最終應該在的位置上。

實現代碼

public int[] reOrderArray(int[] array)
{
    for(int i = 0; i < array.Length; i++)
    {
        if((array[i] % 2) != 0)
        {
            int temp = array[i];
            int j = i - 1;
            for(; j >= 0; j--)
            {
                if ((array[j] % 2) != 0)
                    break;
                array[j + 1] = array[j]; 
            }
            array[j + 1] = temp;
        }
    }
    return array;
}

【精選推薦文章】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

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

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"