前端必備性能知識 – 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網頁設計已成為網頁設計推薦首選

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

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

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

超級簡單的跨平台高性能音視頻播放框架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網頁設計已成為網頁設計推薦首選

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

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

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

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

一.寫在前面

  本系列中的內容來自對李宏毅老師的機器學習教程的理解和一些個人理解的筆記,主要用於之後方便查看複習。李宏毅老師的機器學習教程地址: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網頁設計已成為網頁設計推薦首選

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

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

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

【劍指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;
}

【精選推薦文章】

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

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

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

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

多線程與高併發(一)多線程入門

一、基礎概念

多線程的學習從一些概念開始,進程和線程,併發與并行,同步與異步,高併發。

1.1 進程與線程

幾乎所有的操作系統都支持同時運行期多個任務,所有運行中的任務通常就是一個進程,進程是處於運行過程中的程序,進程是操作系統進行資源分配和調度的一個獨立單位。

進程有三個如下特徵:

  • 獨立性:進程是系統中獨立存在的實體,它可以擁有自己獨立的資源,每一個進程都擁有自己私有的地址空間。在沒有經過進程本身允許的情況下,一個用戶進程不可以直接訪問其他進程的地址空間。

  • 動態性:進程與程序的區別在於,程序只是一個靜態的指令集合,而進程是一個正在系統中活動的指令集合。在進程中加入了時間的概念,進程具有自己的生命周期和各種不同的狀態,這些概念在程序中部是不具備的。

  • 併發性:多個進程可以在單個處理器上併發執行,多個進程之間不會互相影響。

線程是進程的組成部分,一個進程可以擁有多個線程,而線程必須有一個父進程,線程可以有自己的堆棧、自己的程序計數器和自己的局部變量,但不擁有系統資源。比如使用QQ時,我們可以同事傳文件,發送圖片,聊天,這就是多個線程在進行。

線程可以完成一定的任務,線程能夠獨立運行的,它不知道有其他線程的存在,線程的執行是搶佔式的,當前線程隨時可能被掛起。

總之:一個程序運行后至少有一個進程,一個進程里可以有多個線程,但至少要有一個線程。

1.2 併發和并行

併發和并行是比較容易混淆的概念,他們都表示兩個或者多個任務一起執行,但併發側重多個任務交替執行,同一時刻只能有一條指令執行,但多個進程指令被快速輪換執行,使得在宏觀上具有多個進程同時執行的效果。而并行確實真正的同時執行,有多條指令在多個處理器上同時執行,并行的前提條件就是多核CPU。

1.3 同步和異步

同步和異步通常用來形容一次方法調用。同步方法調用一旦開始,調用者必須等到方法調用返回后,才能繼續後續的行為。異步方法調用更像一個消息傳遞,一旦開始,方法調用就會立即返回,調用者可以繼續後續的操作。

1.4 高併發

高併發一般是指在短時間內遇到大量操作請求,非常具有代表性的場景是秒殺活動與搶票,高併發是互聯網分佈式系統架構設計中必須考慮的因素之一,高併發相關常用的一些指標有響應時間(Response Time),吞吐量(Throughput),每秒查詢率QPS(Query Per Second),併發用戶數等。

多線程在這裏只是在同/異步角度上解決高併發問題的其中的一個方法手段,是在同一時刻利用計算機閑置資源的一種方式

1.5 多線程的好處

線程在程序中是獨立的、併發的執行流,擁有獨立的內存單元,多個線程共享父進程里的全部資源,線程共享的環境有進程的代碼段,進程的公有數據等,利用這些共享數據,線程很容易實現相互之間的通信,可以提高程序的運行效率。

多線程的好處主要有:

  • 進程之間不能共享內存,但線程之間共享內存非常容易。

  • 系統創建進程時需要給進程重新分配系統資源,但創建線程代價小得多,所以使用多線程實現多任務併發比多進程效率高

  • Java語言內置了多線程功能支持。

二、使用多線程

上面講了多線程的一些概念,都有些抽象,下面將學習如何使用多線程,創建多線程的方式有三種。

2.1 繼承Thread類創建

繼承Thread創建並啟動多線程有三個步驟:

  1. 定義類並繼承Thread,重寫run()方法,run()方法中為需要多線程執行的任務。

  2. 創建該類的實例,即創建了線程對象。

  3. 調用實例的start()方法啟動線程。

public class FirstThread extends Thread {

    private int i=0;
    public void run() {
        for (; i < 100; i++) {
            //獲取當前線程名稱
            System.out.println(this.getName() + " " + i);
        }
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            //Thread的靜態方法currentThread,獲取當前線程
            System.out.println(Thread.currentThread().getName());
            if (i == 20) {
                //創建線程並啟動
                new FirstThread().start();
                new FirstThread().start();
            }

        }
    }
}

運行結果可以看到兩個線程的i並不是連續的,說明他們並不共享數據。

2.2 實現Runnable接口

實現Runnable接口創建並啟動多線程也有以下步驟:

  1. 定義類並繼承Runnable接口,重寫run()方法,run()方法中為需要多線程執行的任務。

  2. 創建該類的實例,並以此實例作為target為參數來創建Thread對象,這個Thread對象才是真正的多線程對象。

public class SecondThread implements Runnable {
    private int i = 0;
    
    @Override
    public void run() {
        for (; i < 100; i++) {
            //此時想要獲取到多線程對象,只能使用Thread.currentThread()方法
            System.out.println(Thread.currentThread().getName() + " " + i);
        }
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            //Thread的靜態方法currentThread,獲取當前線程
            System.out.println(Thread.currentThread().getName());
            if (i == 20) {
                //創建線程並啟動
                SecondThread secondThread=new SecondThread();
                new Thread(secondThread,"線程一").start();
                new Thread(secondThread,"線程二").start();
            }

        }
    }
}

2.3 使用Callable和Future

Callable是Runnable的增加版,主要是接口中的call()方法可以有返回值,並且可以申明拋出異常,使用Callable創建的步驟如下:

  1. 定義類並繼承Callable接口,重寫call()方法,run()方法中為需要多線程執行的任務。

  2. 創建類實例,使用FutureTask來包裝對象實例,

  3. 使用FutureTask對象作為Thread的target來創建多線程,並啟動線程。

  4. 調用FutureTask對象的get()方法來獲取子線程結束后的返回值。

public class ThirdThread {

    public static void main(String[] args) {
        //使用lambda表達式
        FutureTask<Integer> task = new FutureTask<>((Callable<Integer>) () -> {
            int i = 0;
            for (; i < 100; i++) {
                System.out.println(Thread.currentThread().getName() + "的循環變量i的值:" + i);
            }
            return i;
        });
        for (int i = 0; i < 100; i++) {
            //Thread的靜態方法currentThread,獲取當前線程
            System.out.println(Thread.currentThread().getName());
            if (i == 20) {
                //創建線程並啟動
                new Thread(task, "有返回值的線程").start();
            }
        }
        try {
            System.out.println("線程的返回值:" + task.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

這裏使用了lambda表達式,不使用表達式的方式也很簡單,可以去源碼中查看。Callable與Runnable方式基本相同,只不過增加了返回值且可允許聲明拋出異常。

使用三種方式都可以創建線程,且方式也相對簡單,大體分為實現接口和實現Thread類兩種,這兩種都各有優缺點。

繼承接口實現:

  • 優點:除了繼承接口之外,還可以繼承其他類。這種方式多個線程共享一個target對象,可以處理用於共同資源的情況。
  • 缺點:編程稍微複雜一些,並且沒有直接獲取當前線程對象的方式,必須使用Thread.currentThread()方式。

基礎Thread類:

  • 優點:編程簡單

  • 缺點:不能繼承其他類

三、多線程的生命周期

線程狀態是線程中非常重要的一個概念,然而我看過很多資料,線程的狀態理解有很多種方式,很多人將其分為五個基本狀態:新建、就緒、運行、阻塞、死亡,但在狀態枚舉中並不是這五個狀態,我不知道是什麼原因(有大神可以解答更好),只能按照枚舉中的狀態根據自己的理解。

  1. 初始(NEW):新創建了一個線程對象,但還沒有調用start()方法,而且就算調用了改方法也不代表狀態立即改變。

  2. 運行(RUNNABLE):在運行的狀態肯定就處於RUNNABLE狀態。

  3. 阻塞(BLOCKED):表示線程阻塞,或者說線程已經被掛起了。

  4. 等待(WAITING):進入該狀態的線程需要等待其他線程做出一些特定動作(通知或中斷)。

  5. 超時等待(TIMED_WAITING):該狀態不同於WAITING,它可以在指定的時間后自行返回。

  6. 終止(TERMINATED):表示該線程已經執行完畢。

狀態流程圖如下:

理解:初始狀態很好理解,這個時候其實還不能被稱為一個線程,因為他還沒被啟動,當調用start()方法后,線程正式啟動,但是也不代表立即就改變了狀態。

運行狀態中其實包含兩種狀態,運行中(RUNING)就緒(READY)

就緒狀態表示你有資格運行,只要CPU還未調度到你,就處於就緒狀態,有幾個狀態會是線程狀態編程就緒狀態

  • 調用線程的start()方法。

  • 當前線程sleep()方法結束,其他線程join()結束,等待用戶輸入完畢,某個線程拿到對象鎖。

  • 當前線程時間片用完了,調用當前線程的yield()方法。

  • 鎖池裡的線程拿到對象鎖后。

運行中(RUNING)狀態比較好理解,線程調度程序選擇了當前線程作。

阻塞狀態是線程阻塞在進入synchronized關鍵字修飾的方法或代碼塊(獲取鎖)時的狀態。

等待狀態是指線程沒有被CPU分配執行時間,需要等待,這種等待是需要被显示的喚醒,否則會無限等待下去。

超時等待狀態是這現在沒有被CPU分配執行時間,需要等待,不過這種等待不需要被显示的喚醒,會設置一定的時間后zi懂喚醒。

死亡狀態也很好理解,說明線程方法被執行完成,或者出錯了,線程一旦進入這個狀態就代表徹底的結束

 

【精選推薦文章】

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

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

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

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

kubeadm1.14.1 安裝Metrics Server

Metrics API

介紹Metrics-Server之前,必須要提一下Metrics API的概念

Metrics API相比於之前的監控採集方式(hepaster)是一種新的思路,官方希望核心指標的監控應該是穩定的,版本可控的,且可以直接被用戶訪問(例如通過使用 kubectl top 命令),或由集群中的控制器使用(如HPA),和其他的Kubernetes APIs一樣。

官方廢棄heapster項目,就是為了將核心資源監控作為一等公民對待,即像pod、service那樣直接通過api-server或者client直接訪問,不再是安裝一個hepater來匯聚且由heapster單獨管理。

假設每個pod和node我們收集10個指標,從k8s的1.6開始,支持5000節點,每個節點30個pod,假設採集粒度為1分鐘一次,則:

10 x 5000 x 30 / 60 = 25000 平均每分鐘2萬多個採集指標

因為k8s的api-server將所有的數據持久化到了etcd中,顯然k8s本身不能處理這種頻率的採集,而且這種監控數據變化快且都是臨時數據,因此需要有一個組件單獨處理他們,k8s版本只存放部分在內存中,於是metric-server的概念誕生了。

其實hepaster已經有暴露了api,但是用戶和Kubernetes的其他組件必須通過master proxy的方式才能訪問到,且heapster的接口不像api-server一樣,有完整的鑒權以及client集成。這個api現在還在alpha階段(18年8月),希望能到GA階段。類api-server風格的寫法:generic apiserver

有了Metrics Server組件,也採集到了該有的數據,也暴露了api,但因為api要統一,如何將請求到api-server的/apis/metrics請求轉發給Metrics Server呢,解決方案就是:kube-aggregator,在k8s的1.7中已經完成,之前Metrics Server一直沒有面世,就是耽誤在了kube-aggregator這一步。

kube-aggregator(聚合api)主要提供:

  • Provide an API for registering API servers.

  • Summarize discovery information from all the servers.

  • Proxy client requests to individual servers.

詳細設計文檔:參考鏈接

metric api的使用:

  • Metrics API 只可以查詢當前的度量數據,並不保存歷史數據

  • Metrics API URI 為 /apis/metrics.k8s.io/,在 k8s.io/metrics 維護

  • 必須部署 metrics-server 才能使用該 API,metrics-server 通過調用 Kubelet Summary API 獲取數據

如:

http://127.0.0.1:8001/apis/metrics.k8s.io/v1beta1/nodes
​
http://127.0.0.1:8001/apis/metrics.k8s.io/v1beta1/nodes/<node-name>
​
http://127.0.0.1:8001/apis/metrics.k8s.io/v1beta1/namespace/<namespace-name>/pods/<pod-name>

Metrics-Server

Metrics server定時從Kubelet的Summary API(類似/ap1/v1/nodes/nodename/stats/summary)採集指標信息,這些聚合過的數據將存儲在內存中,且以metric-api的形式暴露出去。

Metrics server復用了api-server的庫來實現自己的功能,比如鑒權、版本等,為了實現將數據存放在內存中嗎,去掉了默認的etcd存儲,引入了內存存儲(即實現Storage interface)。因為存放在內存中,因此監控數據是沒有持久化的,可以通過第三方存儲來拓展,這個和heapster是一致的。

 

Metrics server出現后,新的​Kubernetes 監控架構將變成上圖的樣子

  • 核心流程(黑色部分):這是 Kubernetes正常工作所需要的核心度量,從 Kubelet、cAdvisor 等獲取度量數據,再由metrics-server提供給 Dashboard、HPA 控制器等使用。

  • 監控流程(藍色部分):基於核心度量構建的監控流程,比如 Prometheus 可以從 metrics-server 獲取核心度量,從其他數據源(如 Node Exporter 等)獲取非核心度量,再基於它們構建監控告警系統。

官方地址:https://github.com/kubernetes-incubator/metrics-server

部署

mkdir metrics;cd metics
git clone https://github.com/kubernetes-incubator/metrics-server.git
cd metrics-server/deploy/1.8+/

 修改metrics-server-deployment.yaml,紅色command部分。

---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: metrics-server
  namespace: kube-system
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: metrics-server
  namespace: kube-system
  labels:
    k8s-app: metrics-server
spec:
  selector:
    matchLabels:
      k8s-app: metrics-server
  template:
    metadata:
      name: metrics-server
      labels:
        k8s-app: metrics-server
    spec:
      serviceAccountName: metrics-server
      volumes:
      # mount in tmp so we can safely use from-scratch images and/or read-only containers
      - name: tmp-dir
        emptyDir: {}
      containers:
      - name: metrics-server
        image: k8s.gcr.io/metrics-server-amd64:v0.3.3
        command:
        - /metrics-server
        - --metric-resolution=30s
        - --kubelet-preferred-address-types=InternalIP,Hostname,InternalDNS,ExternalDNS,ExternalIP
        - --kubelet-insecure-tls
        imagePullPolicy: Always
        volumeMounts:
        - name: tmp-dir
          mountPath: /tmp

 創建

[root@cn-hongkong 1.8+]# kubectl apply -f .
clusterrole.rbac.authorization.k8s.io/system:aggregated-metrics-reader unchanged
clusterrolebinding.rbac.authorization.k8s.io/metrics-server:system:auth-delegator unchanged
rolebinding.rbac.authorization.k8s.io/metrics-server-auth-reader unchanged
apiservice.apiregistration.k8s.io/v1beta1.metrics.k8s.io unchanged
serviceaccount/metrics-server unchanged
deployment.extensions/metrics-server configured

 等待一會就可以看下集群的資源使用情況了!

 

【精選推薦文章】

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

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

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

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

微信小程序入口場景的問題整理與相關解決方案

前言

最近一段時間都在做小程序。

雖然是第二次開發小程序,但是上次做小程序已經是一年前的事了,所以最終還是被坑得死去活來。

這次是從零開始開發一個小程序,其實除了一些莫名其妙的兼容性問題,大多數坑點都是在微信小程序的各個入口場景處。

所以這裏整理一下微信小程序的各個入口場景,以及從這些入口場景進入小程序會面臨的問題以及解決方案。

這裏只列出常用的幾種場景:

  • [簡單場景]啟動小程序並進入
  • [簡單場景]退出重進(啟動小程序后,退出小程序,再次進入小程序)
  • [簡單場景]退出重進首頁(啟動小程序后,退出小程序,通過掃二維碼再次進入小程序)
  • [複雜場景]啟動並進入指定頁面(從小程序的分享卡片或者微信發送的通知消息進入小程序)
  • [複雜場景]退出重進指定頁面(啟動小程序后,退出小程序,從小程序的分享卡片或者微信發送的通知消息進入小程序)

啟動小程序並進入

微信小程序的入口場景光微信提供的場景值就有幾十種,但是絕大多數都可以劃分為啟動小程序並進入。

這是最常用的一種進入小程序的方式,比如通過搜索進入或者點擊最近使用小程序的方式進入,都算是這種類型。

這一場景下,首先我們需要明白髮生了什麼:

下載小程序 => 啟動小程序 onLaunch事件觸發 => 加載首頁 onLoad事件觸發 => 首頁 onShow事件

然後在這個場景下,需要注意以下幾個問題:

  1. 這個場景下一般會涉及到登錄。
    所謂登錄,不一定是要在這個階段做,但是登錄信息的判斷這個階段是一定要做的。
    通常前端肯定是要將登錄的這些信息存儲在小程序的storage里,然後在onLaunch事件中判斷是否登錄,沒登錄就跳轉到登錄頁面,登錄了就跳轉到首頁。
    這裏的登錄判斷一定要放在onLaunch,而不要放在首頁的onLoad裏面,因為小程序啟動一定會進入onLaunch,而不一定會進入首頁的onLoad。
  2. 而登錄頁面在設計的時候最好要加上一個url參數,傳入登錄成功后跳轉到的頁面地址,而不是登錄之後始終跳轉到首頁,後面會講為什麼需要這麼做。
  3. onLaunch階段是否有發出請求,並在請求完成後進行了頁面跳轉,或者請求完成設置storage,並在onLoad頁面中使用?
    這種情況的出現,會導致在請求時間過長時,首頁的onLoad已經執行了,此時就會出現BUG。
    對於這個問題,有的人會用定時器去判斷是否完成這個操作,但是我的建議是盡量避免在onLaunch中進行這些操作。
    如果一定要有,那麼最好的方式就是做一個加載頁面去承載這些功能。
  4. 首頁數據的初始化,一般是放在onLoad中執行。當然總是有些特殊的需求是要放在onShow裏面的。
    關於onLoad和onShow,最常見的處理區別就在跳轉頁面時。
    當載入首頁時,先觸發onLoad,再觸發onShow。
    此時通過wx.navigateTo 的方式跳轉到頁面A,這個時候首頁並沒有被關閉,那麼從頁面A再返回首頁時,onLoad就不會觸發,但onShow會觸發。
    通常在加載數據時,一般會用到onLoad。
    但是如果說頁面A更新了數據,然後返回首頁時,首頁的相關數據也需要更新。
    那麼初始化數據就不能放在onLoad里,而需要放在onShow里。
    (當然還有一種方式是通過getCurrentPages的方式在頁面A中調用首頁的方法。但是這裏極不推薦這種方式,屬於某個頁面的事情一定要給這個頁面。最好不要將頁面間的職責通過這種方式打亂,容易引起代碼混亂,不易維護。)

退出重進(啟動小程序后,退出小程序,再次進入小程序)

這種場景實際上是對第一種場景的擴展。

而所謂的退出小程序不管你是點右上角的退出按鈕還是Home鍵直接切出都算是這類退出。

但是退出后再立即進入小程序的時候,依然會進入你退出小程序時所在的頁面,而不會觸發onLaunch,也不會觸發這個頁面的onLoad,不過onShow是肯定會觸發的。

這一場景下,首先我們需要明白髮生了什麼:

再次進入小程序 => 進入退出小程序時所在頁面 觸發onShow

在這個場景下,只需要注意onShow中是否有不可重複執行的操作。

例如onShow中會獲取用戶喜歡吃的食物,加載到頁面的列表中,在這種場景下,如果不清空之前的列表或者加個判斷的話,就會出現重複數據。

退出重進首頁(啟動小程序后,退出小程序,通過掃二維碼再次進入小程序)

這種場景實際上是對第二種場景的擴展。

我們通常給二維碼配置的是一個無參數的小程序首頁地址,當我們退出小程序,通過掃二維碼再次進入小程序時會進入首頁。

這一場景下,首先我們需要明白髮生了什麼:

再次進入小程序 => 進入退出小程序時所在頁面A 不觸發onShow => 觸發頁面A onHide => 觸發頁面A onUnload=> 進入首頁 onLoad => 首頁onShow

在這個場景下,除了需要注意第二種場景存在的問題,還需要注意頁面A的onHide事件中是否會觸發奇怪的操作,例如頁面跳轉。

啟動並進入指定頁面(從小程序的分享卡片或者微信發送的通知消息進入小程序)

這塊場景常見於邀請他人進入小程序,需要注意的是他們往往被賦予了更多的業務功能,也就往往增大了小程序的實現難度。

這一場景下,首先我們需要明白髮生了什麼:

下載小程序 => 啟動小程序 onLaunch事件觸發 => 加載指定頁面 onLoad事件觸發 =>指定頁面  onShow事件

這裏就可以看出,並不是進入小程序就一定會進入首頁的onLoad。

所以這就是為什麼之前強調不要將登錄判斷放在首頁的onLoad中,而一定要放在onLaunch里。

但是這裏又和掃二維碼不同,掃二維碼的鏈接一般都是指定的首頁。

而這裏通常跳轉到的是非首頁的頁面,而且可能還多了複雜的業務功能。

我們在需求分析和設計階段應該更多地考慮到這裏可能會引發的複雜問題,而盡量將此處的業務邏輯簡化,或者加大估時。

接下來,我們將根據業務從簡單到複雜,慢慢講解這個場景下可能存在的問題。

最簡單的邀請函(進入小程序首頁)

和第一種場景差不多,這裏略過

進階邀請函(進入小程序指定頁面,帶參數,需要根據參數初始化頁面)

這種情況下,需要考慮以下幾個問題:

  1. 首先在onLaunch階段會判斷是否登錄,沒登錄那麼就需要跳轉到登錄頁面,登錄頁面登錄之後,肯定要跳轉到這個頁面,而不是首頁。
    所以之前說過登錄頁面設計的時候需要傳入一個url參數,來明確登錄成功后跳轉到哪個頁面。
  2. 這種跳轉到指定頁面的情況通常都需要一個回到首頁的按鈕。
    就比如邀請某人查看一篇文章,點擊邀請卡片後會進入小程序內的文章詳情。
    一般在小程序內通常是通過點擊文章列表跳轉到文章詳情,那麼這個時候可以逐級返回到首頁。
    但是在點擊邀請函進入的情況是沒有返回功能的,此時如果沒有回到首頁功能,那麼用戶可能就永遠沒法回到首頁。
    (其實是可以的,但是小程序的的這個功能藏得比較深,不要指望所有用戶都那麼熱愛摸索)
  3. 這裏一定要特別注意第一種場景的第三個應該注意的問題,對於第一種場景而言那個問題因為啟動次數很多容易出現,但是在當前的場景下可能很容易被忽略掉。

涉及身份的邀請函(進入小程序指定頁面,帶參數,需要根據參數切換身份,更可能涉及到登錄)

為了更好地說明這種情況,我們來列舉一個場景。

如果有一個打車軟件,進入這個軟件後有兩種身份,一種是乘客,一種是司機。

用戶是司機,那麼看到的是頁面A或者選定了TabA,如果是乘客,那麼看到的是頁面B或者選定了TabB。

而且還有一個需求,用戶上次登陸時什麼身份,這次登陸也是什麼身份。

考慮到換手機的場景,那麼這個信息肯定是存儲在服務端的,所以進入小程序的時候會去請求服務端進行判斷。

現在我用司機的身份發了個單,微信給了個通知消息,我沒點開。然後切換到乘客的身份了,再去點擊通知消息,那麼我會以司機的身份去打開這個消息。

這個場景其實在業務上來看是很合理的,但是對於我們的程序實現來看,複雜度一下子就上來了。

  1. 首先我們確定一下這個請求身份信息的請求在哪個階段發出?
    onLaunch?
    那麼是不是需要在onLoad階段去獲取這個身份的信息然後給出不同的頁面?
    這樣一下子就會出現進階邀請函的第三個問題,而且還不僅僅是這一個問題,之後我們會講到。
    所以這個地方需要做一個專門的邀請加載頁面去處理這個事情。
  2. 分離出一個單獨的加載頁面之後,其實我們的工作會變的簡單清晰起來。
    因為我們只需要去做我們這個頁面所需要做的事情就行了。
    根據參數去獲取我們現在的身份,然後以這種身份跳轉到相應的頁面。
  3. 這裏還涉及到一個問題,那就是正常啟動而不是通過通知消息進入的時候,也需要去請求服務端獲取身份信息。
    我給的建議是一定要另外單獨建一個頁面去承載這個功能,而不要將這兩個加載頁面糅合到一起。
    裏面的頁面展示我們可以用組件化的方式去做,但是頁面的邏輯一點更要分開。
    因為這兩種情況真的很容易混雜,也是為了利於後面的維護工作。
  4. 正常啟動時的加載頁面也可以看情況糅合到首頁的onLoad裏面。
    但是如果有可能,還是希望放在單獨的頁面里。
    首頁往往功能很多,代碼量比較大,不要將本來可以分離出去的功能放進去。
    還是那句話,頁面的職責分開。

我這裏講的其實還是一個比較常見的功能,通常我們的業務也不一定像上面這樣簡單。

所以如果涉及到這方面的操作,在需求分析和設計的時候就應該考慮清楚。

如果等到功能開發的時候再去考慮這些事情,那麼等待你的一定是延期或者加班。

退出重進指定頁面(啟動小程序后,退出小程序,從小程序的分享卡片或者微信發送的通知消息進入小程序)

這種場景同樣是第四種場景的進階,但是如果你在第四種場景中使用了我所說的加載頁面,那麼接下來的問題會簡單很多。

這一場景下,首先我們需要明白髮生了什麼:

再次進入小程序 => 進入退出小程序時所在頁面A 不觸發onShow => 觸發頁面A onHide => 觸發頁面A onUnload => 進入邀請加載頁面onLoad => 加載頁面onShow

對於第四種場景中的打車小程序而言,如果按照我們先前所說沒有在onLaunch中獲取身份信息,而是放在了加載頁中,那麼現在什麼都不用改。

如果獲取身份信息的請求放在onLaunch中,現在又得在onLoad中加一道邏輯。

當然這裏還是得注意一個問題,對於這一類型的進入小程序的方式,比如從分享卡片進入和微信的通知消息進入。

即使他們所進入的頁面不同,但是他們都可以使用這個載入頁面去做判斷。

與正常啟動場景的載入頁面是不同的,他們本來就是同一種入口場景。

所以該共用的地方還是得共用,用不同的業務code判斷即可。

總結

總的來說,以上的幾種情況應該能涵蓋絕大多數小程序的入口場景。

整理的目的其實主要是為了做需求分析和設計時參考使用,以避免在考慮業務問題時漏過這些場景導致後期的工作計劃受到影響。

所謂加班和項目延期發布,大都是前期需求分析和設計考慮不周。

我們不可能考慮到所有的場景,但是應該盡善盡美。

謀定而後動,前事不忘後事之師,也算是PDCA了。

【精選推薦文章】

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

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

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

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