從壹開始 [ Design Pattern ] 之二 ║ 單例模式 與 Singleton

前言

這一篇來源我的公眾號,如果你沒看過,正好直接看看,如果看過了也可以再看看,我稍微修改了一些內容,今天講解的內容如下

 

 

 

 

 

 

 

一、什麼是單例模式

 

【單例模式】,英文名稱:Singleton Pattern,這個模式很簡單,一個類型只需要一個實例,他是屬於創建類型的一種常用的軟件設計模式。通過單例模式的方法創建的類在當前進程中只有一個實例(根據需要,也有可能一個線程中屬於單例,如:僅線程上下文內使用同一個實例)。

1、單例類只能有一個實例。

2、單例類必須自己創建自己的唯一實例。

3、單例類必須給所有其他對象提供這一實例。

 

那咱們大概知道了,其實說白了,就是我們整個項目周期內,只會有一個實例,當項目停止的時候,實例銷毀,當重新啟動的時候,我們的實例又會產品。

上文中說到了一個名詞【創建類型】的設計模式,那什麼是創建類型的設計模式呢?

創建型(Creational)模式:負責對象創建,我們使用這個模式,就是為了創建我們需要的對象實例的。

 

那除了創建型還有其他兩種類型的模式:

結構型(Structural)模式:處理類與對象間的組合

行為型(Behavioral)模式:類與對象交互中的職責分

這兩種設計模式,以後會慢慢說到,這裏先按下不表。

咱們就重點從0開始分析分析如何創建一個單例模式的對象實例。

 

二、如何創建單例模式

 

實現單例模式有很多方法:從“懶漢式”到“餓漢式”,最後“雙檢鎖”模式,這裏咱們就慢慢的,從一步一步的開始講解如何創建單例。

 

1、正常的思考邏輯順序

 

既然要創建單一的實例,那我們首先需要學會如何去創建一個實例,這個很簡單,相信每個人都會創建實例,就比如說這樣的:

/// <summary>
/// 定義一個天氣類
/// </summary>
public class WeatherForecast
{
    public WeatherForecast()
    {
        Date = DateTime.Now;
    }
    public DateTime Date { get; set; }
    public int TemperatureC { get; set; }
    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
    public string Summary { get; set; }
}


 [HttpGet]
 public WeatherForecast Get()
 {
     // 實例化一個對象實例
     WeatherForecast weather = new WeatherForecast();
     return weather;
 }

 

我們每次訪問的時候,時間都是會變化,所以我們的實例也是一直在創建,在變化:

 

 

相信每個人都能看到這個代碼是什麼意思,不多說,直接往下走,我們知道,單例模式的核心目的就是:

必須保證這個實例在整個系統的運行周期內是唯一的,這樣可以保證中間不會出現問題。

 

那好,我們改進改進,不是說要唯一一個么,好說!我直接返回不就行了:

 

 /// <summary>
 /// 定義一個天氣類
 /// </summary>
 public class WeatherForecast
 {
     // 定義一個靜態變量來保存類的唯一實例
     private static WeatherForecast uniqueInstance;

     // 定義私有構造函數,使外界不能創建該類實例
     private WeatherForecast()
     {
         Date = DateTime.Now;
     }
     /// <summary>
     /// 靜態方法,來返回唯一實例
     /// 如果存在,則返回
     /// </summary>
     /// <returns></returns>
     public static WeatherForecast GetInstance()
     {
         // 如果類的實例不存在則創建,否則直接返回
         // 其實嚴格意義上來說,這個不屬於【單例】
         if (uniqueInstance == null)
         {
             uniqueInstance = new WeatherForecast();
         }
         return uniqueInstance;
     }
     public DateTime Date { get; set; }public int TemperatureC { get; set; }
     public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
     public string Summary { get; set; }
 }

 

 

然後我們修改一下調用方法,因為我們的默認構造函數已經私有化了,不允許再創建實例了,所以我們直接這麼調用:

[HttpGet]
 public WeatherForecast Get()
 {
     // 實例化一個對象實例
     WeatherForecast weather = WeatherForecast.GetInstance();
     return weather;
 }

 

最後來看看效果:

 

 

這個時候,我們可以看到,時間已經不發生變化了,也就是說我們的實例是唯一的了,大功告成!是不是很開心!

 

但是,別著急,問題來了,我們目前是單線程的,所以只有一個,那如果多線程呢,如果多個線程同時訪問,會不會也會正常呢?

這裏我們做一個測試,我們在項目啟動的時候,用多線程去調用:

 

 [HttpGet]
 public WeatherForecast Get()
 {
     // 實例化一個對象實例
     //WeatherForecast weather = WeatherForecast.GetInstance();

     // 多線程去調用
     for (int i = 0; i < 3; i++)
     {
         var th = new Thread(
         new ParameterizedThreadStart((state) =>
         {
             WeatherForecast.GetInstance();
         })
         );
         th.Start(i);
     }
     return null;
 }

 

然後我們看看效果是怎樣的,按照我們的思路,應該是只會走一遍構造函數,其實不是:

 

 

 

 

 

 

3個線程在第一次訪問GetInstance方法時,同時判斷(uniqueInstance ==null)這個條件時都返回真,然後都去創建了實例,這個肯定是不對的。那怎麼辦呢,只要讓GetInstance方法只運行一個線程運行就好了,我們可以加一個鎖來控制他,代碼如下:

public class WeatherForecast
{
    // 定義一個靜態變量來保存類的唯一實例
    private static WeatherForecast uniqueInstance;
    // 定義一個鎖,防止多線程
    private static readonly object locker = new object();

    // 定義私有構造函數,使外界不能創建該類實例
    private WeatherForecast()
    {
        Date = DateTime.Now;
    }
    /// <summary>
    /// 靜態方法,來返回唯一實例
    /// 如果存在,則返回
    /// </summary>
    /// <returns></returns>
    public static WeatherForecast GetInstance()
    {
        // 當第一個線程執行的時候,會對locker對象 "加鎖",
        // 當其他線程執行的時候,會等待 locker 執行完解鎖
        lock (locker)
        {
            // 如果類的實例不存在則創建,否則直接返回
            if (uniqueInstance == null)
            {
                uniqueInstance = new WeatherForecast();
            }
        }

        return uniqueInstance;
    }
    public DateTime Date { get; set; }

    public int TemperatureC { get; set; }

    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);

    public string Summary { get; set; }
}

 

這個時候,我們再併發測試,發現已經都一樣了,這樣就達到了我們想要的效果,但是這樣真的是最完美的么,其實不是的,因為我們加鎖,只是第一次判斷是否為空,如果創建好了以後,以後就不用去管這個 lock 鎖了,我們只關心的是 uniqueInstance 是否為空,那我們再完善一下:

 

/// <summary>
/// 定義一個天氣類
/// </summary>
public class WeatherForecast
{
    // 定義一個靜態變量來保存類的唯一實例
    private static WeatherForecast uniqueInstance;
    // 定義一個鎖,防止多線程
    private static readonly object locker = new object();

    // 定義私有構造函數,使外界不能創建該類實例
    private WeatherForecast()
    {
        Date = DateTime.Now;
    }
    /// <summary>
    /// 靜態方法,來返回唯一實例
    /// 如果存在,則返回
    /// </summary>
    /// <returns></returns>
    public static WeatherForecast GetInstance()
    {
        // 當第一個線程執行的時候,會對locker對象 "加鎖",
        // 當其他線程執行的時候,會等待 locker 執行完解鎖
        if (uniqueInstance == null)
        {
            lock (locker)
            {
                // 如果類的實例不存在則創建,否則直接返回
                if (uniqueInstance == null)
                {
                    uniqueInstance = new WeatherForecast();
                }
            }
        }

        return uniqueInstance;
    }
    public DateTime Date { get; set; }
    public int TemperatureC { get; set; }
    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
    public string Summary { get; set; }
}

 

這樣才最終的完美實現我們的單例模式!搞定。

 

2、幽靈事件:指令重排

當然,如果你看完了上邊的那四步已經可以出師了,平時我們就是這麼使用的,也是這麼想的,但是真的就是萬無一失么,有一個 JAVA 的朋友提出了這個問題,C# 中我沒有聽說過,是我孤陋寡聞了么:

單例模式的幽靈事件,時令重排會偶爾導致單例模式失效。

 

是不是聽起來感覺很高大上,而不知所云,沒關係,咱們平時用不到,但是可以了解了解:

為何要指令重排?       

指令重排是指的 volatile,現在的CPU一般採用流水線來執行指令。一個指令的執行被分成:取指、譯碼、訪存、執行、寫回、等若干個階段。然後,多條指令可以同時存在於流水線中,同時被執行。
指令流水線並不是串行的,並不會因為一個耗時很長的指令在“執行”階段呆很長時間,而導致後續的指令都卡在“執行”之前的階段上。
相反,流水線是并行的,多個指令可以同時處於同一個階段,只要CPU內部相應的處理部件未被佔滿即可。比如說CPU有一個加法器和一個除法器,那麼一條加法指令和一條除法指令就可能同時處於“執行”階段, 而兩條加法指令在“執行”階段就只能串行工作。
相比於串行+阻塞的方式,流水線像這樣并行的工作,效率是非常高的。

然而,這樣一來,亂序可能就產生了。比如一條加法指令原本出現在一條除法指令的後面,但是由於除法的執行時間很長,在它執行完之前,加法可能先執行完了。再比如兩條訪存指令,可能由於第二條指令命中了cache而導致它先於第一條指令完成。
一般情況下,指令亂序並不是CPU在執行指令之前刻意去調整順序。CPU總是順序的去內存裏面取指令,然後將其順序的放入指令流水線。但是指令執行時的各種條件,指令與指令之間的相互影響,可能導致順序放入流水線的指令,最終亂序執行完成。這就是所謂的“順序流入,亂序流出”。

 

這個是從網上摘錄的,大概意思看看就行,理解雙檢鎖失效原因有兩個重點

1、編譯器的寫操作重排問題.
例 : B b = new B();

上面這一句並不是原子性的操作,一部分是new一個B對象,一部分是將new出來的對象賦值給b.

直覺來說我們可能認為是先構造對象再賦值.但是很遺憾,這個順序並不是固定的.再編譯器的重排作用下,可能會出現先賦值再構造對象的情況.

2、結合上下文,結合使用情景.

理解了1中的寫操作重排以後,我卡住了一下.因為我真不知道這種重排到底會帶來什麼影響.實際上是因為我看代碼看的不夠仔細,沒有意識到使用場景.雙檢鎖的一種常見使用場景就是在單例模式下初始化一個單例並返回,然後調用初始化方法的方法體內使用初始化完成的單例對象.

 

三、Singleton = 單例 ?

 上邊我們說了很多,也介紹了很多單例的原理和步驟,那這裏問題來了,我們在學習依賴注入的時候,用到的 Singleton 的單例注入,是不是和上邊說的一回事兒呢,這裏咱們直接多多線程測試一下就行:

 

/// <summary>
/// 定義一個心情類
/// </summary>
public class Feeling
{
    public Feeling()
    {
        Date = DateTime.Now;
    }
    public DateTime Date { get; set; }
}


 // 單例注入
 services.AddSingleton<Feeling>();


[HttpGet]
public WeatherForecast Get()
{

    // 多線程去調用
    for (int i = 0; i < 3; i++)
    {
        var th = new Thread(
        new ParameterizedThreadStart((state) =>
        {
            //WeatherForecast.GetInstance();
            
            // 此刻的心情
            Feeling feeling = new Feeling();
            Console.WriteLine(feeling.Date);
        })
        );
        th.Start(i);
    }
    return null;
}

 

測試的結果,情理之中,也是意料之外:

 

 

竟然和我們上邊說的是一樣的, 
Singleton是一種懶漢模式 的單例, 因為結論可以看出,有時候我們使用單例模式,並不是寫一個 Sigleton 就能滿足的。    

四、單例模式的優缺點

 

        【優】、單例模式的優點:

             (1)、保證唯一性:防止其他對象實例化,保證實例的唯一性;

             (2)、全局性:定義好數據后,可以再整個項目種的任何地方使用當前實例,以及數據;

        【劣】、單例模式的缺點: 

             (1)、內存常駐:因為單例的生命周期最長,存在整個開發系統內,如果一直添加數據,或者是常駐的話,會造成一定的內存消耗。

 

以下內容來自百度百科:

優點

一、實例控制 單例模式會阻止其他對象實例化其自己的單例對象的副本,從而確保所有對象都訪問唯一實例。
二、靈活性 因為類控制了實例化過程,所以類可以靈活更改實例化過程。  

缺點

一、開銷 雖然數量很少,但如果每次對象請求引用時都要檢查是否存在類的實例,將仍然需要一些開銷。可以通過使用靜態初始化解決此問題。
二、可能的開發混淆 使用單例對象(尤其在類庫中定義的對象)時,開發人員必須記住自己不能使用
new關鍵字實例化對象。因為可能無法訪問庫源代碼,因此應用程序開發人員可能會意外發現自己無法直接實例化此類。
三、對象生存期 不能解決刪除單個對象的問題。在提供內存管理的語言中(例如基於.NET Framework的語言),只有單例類能夠導致實例被取消分配,因為它包含對該實例的私有引用。在某些語言中(如 C++),其他類可以刪除對象實例,但這樣會導致單例類中出現懸浮引用。

 

五、示例代碼

 

 

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

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

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

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

如何打造一款m3u8視頻爬蟲

0.前言

m3u8是一種很常見的網頁視頻播放器的視頻源,比如說中國大學MOOC中課程就是使用了該種視頻格式。

隨便打開一門課程,就可以發現在網絡請求中存在一個m3u8的文件,在preview中預覽,它並不像我們想象中是亂碼的視頻流。

裏面是一個列表,有一堆ts結尾的文件名,每個下面還跟了一個EXTINF的字段,好像是時間,在我們播放視頻時,網絡請求中會不斷出現請求ts的內容。

隨便打開一個ts文件,它的內容卻是如圖視頻流一般亂碼的。

說到這裏,你可能有猜測了,m3u8並不是視頻流的文件,而有可能是組織ts文件的規範,EXTINF代表播放每多少秒去請求下一片ts流。

這種邊看邊加載的方法無疑可以減少我們的網絡負荷。

要用爬蟲爬取這類視頻的方法也很簡單,我們只需要獲得m3u8文件,就可以得到視頻的ts地址了,將所有ts請求下來之後進行合併,就可以得到視頻文件了。

不過要提的一點是,很多視頻網站會對他們的ts進行加密,我們下載下來合併之後可能視頻能看,但是播放器放着放着就卡住了,然後之後黑屏畫面。

1.編碼部分

我們先根據m3u8來判斷一下創建咋樣一個代表M3U8視頻對象的類。

我們首先需要定義一個list,來存放這個m3u8視頻下所有的ts文件,也就是後面說到的TS類。

這裏提一點,m3u8裏面的ts的路徑一般對路徑,會和m3u8在同一文件夾,我們代碼中也是這麼認為了,但是難免有些網站會單獨存放m3u8和ts文件,如果遇到這種情況,修改一下代碼即可。

有了ts的名稱,我們還需要URL的前綴,也就是圖中紫色劃線部分,也就是basepath。

此外,我們還需要一個TS對象。

這個對象中存儲TS文件名稱以及時間EXTINF。

定義完實體類,就需要編寫下載視頻的過程了。

首先需要請求到m3u8的文件,此處使用Java的HttpURLConnection來請求獲取,其它語言類似,只需要請求到文件即可。

請求到了m3u8的文本內容,我們還需要解析它 ,從中得到ts的名稱。

得到了M3U8視頻對象之後,我們就可以遍歷請求它的list中TS對象的名稱屬性來下載ts文件了。

這麼多ts文件如果我們在單線程中遍歷請求,會很耗費時間,Java給我們提供了Stream,其中parallel可以讓我們併發去遍歷集合,效率會提升不少。

依舊是使用HttpURLConnection來做請求,不過最好本次設置超時時間。

這樣就可以請求到所有ts文件了。

最後要做的就是合併這些ts文件成為一個MP4文件。

對於未加密的正常ts文件,我們只需要按照編號順序直接拼接即可。

這樣就算是完成了M3U8視頻抓取了。

2.打包使用

下載地址:

在命令行中java -jar m3u8-down.jar [m3u8地址],會显示報錯信息。

也可以直接m3u8-down.jar [m3u8地址],不會显示保存信息,會在後台執行。

最終會在同目錄下生成一個output.mp4的文件,temp文件可以刪除。

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

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

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

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

.NET Core 3.0中用 Code-First 方式創建 gRPC 服務與客戶端

.NET Core love gRPC

千呼萬喚的 .NET Core 3.0 終於在 9 月份正式發布,在它的眾多新特性中,除了性能得到了大大提高,比較受關注的應該是 ASP.NET Core 3.0 對 gRPC 的集成了。
它的源碼託管在 grpc-dotnet 這個 Github 庫中,由微軟 .NET 團隊與谷歌 gRPC 團隊共同維護.

.NET Core 對 gRPC 的支持在 grpc 官方倉庫早已有實現(grpc/csharp),但服務端沒有很好地與 ASP.NET Core 集成,使用起來還需要自己進行一些集成擴展。
而 ASP.NET Core 3.0 新增了 gRPC 服務的託管功能,能讓 gRPC 與 ASP.NET Core 框架本身的特性很好地結合,如日誌、依賴注入、身份認證和授權,並由 Kestrel 服務器提供 HTTP/2 鏈接,性能上得到充分保障。

推薦把項目中已有的 RPC 框架或者內部服務間 REST 調用都遷移到 gRPC 上,因為它已經是雲原生應用的標準 RPC 框架,在整個 CNCF 主導下的雲原生應用開發生態里 gRpc 有着舉足輕重的地位。

對於 gRPC 的使用方式,前段時間已經有其他大神寫的幾篇文章了,這裏就不再贅述了。
本文主要介紹的是區別於標準使用規範的,但對.NET 應用更加友好的使用方式,最後會提供源碼來展示。

作為對比,還是要列一下標準的使用步驟:

  1. 定義 proto 文件,包含服務、方法、消息對象的定義
  2. 引入 Grpc.Tools Nuget 包並添加指定 proto 路徑和生成模式
  3. 生成項目,得到服務端的抽象類或客戶端的調用客戶端組件
  4. 實現服務端抽象類,並在 ASP.NET Core 註冊這個服務的路由端點
  5. DI 註冊 gRPC 服務。
  6. 客戶端用 Grpc.Net.ClientFactory Nuget 包進行統一配置和依賴注入

.NET Core 對 gRPC 的大力支持使開發者開發效率大大提高,入門難度也減少了許多,完全可以成為跟 WebApi 等一樣的 .NET Core 技術棧的標配。

proto 在單一語言系統架構中的局限性

使用 proto 文件的好處是多語言支持,同一份 proto 可以生成各種語言的服務和客戶端,可以讓用不同語言開發的微服務直接互相遠程調用。但 proto 文件作為不同服務間的契約,不可以經常修改,否則就會對使用了它的服務造成不同程度的影響,因此對 proto 文件的版本控制需要得到重視。

另外,我們的應用程序還不應該與 gRPC 耦合,否則就會導致系統架構被這些實現細節所綁架。直接依賴 proto 文件和由它生成的代碼,就是對 gRPC 的強耦合。

例如,當應用程序在演進的過程中,複雜度還未達到完全部署隔離的必要時,為了避免因“完全邊界”引入的部署運維複雜性,又能預留隔離的可能性,需要有一層接口層作為“不完全邊界”。

又比如,目前在 windows 系統的 iis 上還不支持 grpc-dotnet,當有 windows 上的應用程序需要使用 RPC,就需要換成 REST 的實現了。

因此,為了不讓應用程序對 gRPC 過於依賴,還應該使用一層抽象(接口)層與其解耦,用接口來隔離對 RPC 實現的依賴,這樣在需要使用不同的實現時,可以通過註冊不同的實現來方便地切換。

在這些場景下,本文要介紹的 Code-First gRPC 使用方法就發揮作用了。

Code-First gRPC

說了這麼久,我好像還沒正式介紹 Code-First gRPC,到底他有多適合在單一語言系統架構中實現 gRPC 呢?下面要介紹的就是基於大名鼎鼎的 protobuf-net 實現的 gRPC 框架,protobuf-net.Grpc

protobuf-net 是在過去十幾年前到現在一直在 .NET 中有名的 Protobuf 庫,想用 Protobuf 序列化時就會用到這個庫。他的特性就是可以把 C# 代碼編寫的類能以 Protobuf 的協議進行序列化和反序列化,而不是 proto 文件再生成這些類。而 protobuf-net.Grpc 則是一脈相承,可以把 C# 寫的接口,在服務端方便地把接口的實現類註冊成 ASP.NET Core 的 gRPC 服務,在客戶端把接口動態代理實現為調用客戶端,調用前面的這個服務端。

用法很簡單,只要聲明一個接口為您的服務契約:

[ServiceContract]
public interface IMyAmazingService {
    ValueTask<SearchResponse> SearchAsync(SearchRequest request);
    // ...
}

然後實現該接口的服務端:

public class MyServer : IMyAmazingService {
    // ...
}

或者向系統獲取客戶端:

var client = http.CreateGrpcService<IMyAmazingService>();
var results = await client.SearchAsync(request);

這相當於以下 .proto 中的服務:

service MyAmazingService {
    rpc Search (SearchRequest) returns (SearchResponse) {}
    // ...
}

protobuf-net.Grpc 同樣通過普通類型定義支持 gRPC 的四種模式,把 C# 8.0 中最新的 IAsyncEnumerable 類型識別成 proto 中的 stream,單向流、雙向流都可以實現!而且用 IAsyncEnumerable 實現可比 proto 生成的類方便很多。

例如 proto 雙向流定義:

rpc chat(stream ChatRequest) returns ( stream ChatResponse);

生成出來的方法是:

Task BathTheCat(IAsyncStreamReader<ChatRequest> requestStream, IServerStreamWriter<ChatResponse> responseStream)

protobuf-net.Grpc 只要定義一個方法:

IAsyncEnumerable<ChatResponse> SubscribeAsync(IAsyncEnumerable<ChatRequest> requestStream);

由此可見,protobuf-net.Grpc 無需在契約層引入第三方庫,充分運用了 C# 類型系統,把方法、類型映射到兼容了 gRPC 的服務定義上。

上文所說的 proto 局限也迎刃而解了,函數調用、gRPC、REST 都能方便切換。(REST 實現可以參考我的開源框架 shriek-fx 中的 Shriek.ServiceProxy.Http )組件。

下一篇,我將主要介紹利用 protobuf-net.Grpc 的 gRPC 雙向流模式與 Blazor 實現一個簡單的在線即時聊天室。

相關鏈接:

  • protobuf-net.Grpc:
  • shriek-fx:
  • GrpcChat:

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

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

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

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

Java多線程系列——多線程方法詳解

Java多線程系列文章是Java多線程的詳解介紹,對多線程還不熟悉的同學可以先去看一下我的這篇博客,這篇博客從宏觀層面介紹了多線程的整體概況,接下來的幾篇文章是對多線程的深入剖析。

 

多線程的常用方法

1、currentThread()方法:

介紹:currentThread()方法可返回該代碼正在被哪個線程調用的信息。

示例

例1:

public class Test01 {

	public static void main(String[] args) {
		System.out.println(Thread.currentThread().getName());
	}
	
}

結果:
main

  

結果說明,main方法被名為main的線程調用

 

例2:

class Mythread extends Thread{
	
	public Mythread() {
		System.out.println("構造方法的打印:"+Thread.currentThread().getName());
	}
	
	@Override
	public void run() {
		System.out.println("run方法的打印:"+Thread.currentThread().getName());
	}
}

public class Test01 {

	public static void main(String[] args) {
		Mythread t=new Mythread();
		t.start();//①
	}
	
}

結果:
構造方法的打印:main
run方法的打印:Thread-0

  

從結果可知:Mythread的構造方法是被main線程調用的,而run方法是被名稱為Thread-0的線程調用的,run方法是線程自動調用的

現在我們將①處的代碼改為t.run(),現在的輸出結果如下:

構造方法的打印:main
run方法的打印:main

  

從結果中我們可以看到兩次的結果显示都是main線程調用了方法,因為當你使用t.start()方法的時候是線程自動調用的run()方法,所以輸出的是Thread-0,當你直接調用run()方法時,和調用普通方法沒有什麼區別,所以是main線程調用run()

 

2、isAlive()方法:

介紹:isAlive()方法的功能是判斷當前的線程是否處於活動狀態

示例:

例1:

class Mythread extends Thread{
	
	@Override
	public void run() {
		System.out.println("run =="+this.isAlive());
	}
	
}

public class Test01 {

	public static void main(String[] args) {
		Mythread thread=new Mythread();
		System.out.println("begin =="+thread.isAlive());//①
		thread.start();//②
		System.out.println("end =="+thread.isAlive());//③
	}
	
}

結果:
begin ==false
end ==true
run ==true

  

方法isAlive()的作用是測試線程是否處於活動狀態。那麼什麼情況下是活動狀態呢?活動狀態就是線程已經啟動且尚未停止。線程處於正在運行或準備開始運行的狀態,就認為線程是存活的

①處代碼的結果為false,因為此時線程還未啟動;

②處代碼調用了run()方法輸出結果為run ==true,此時線程處於活動狀態;

③處代碼的結果為true,有的同學看到這個輸出可能會不理解,不是說線程處於活動狀態isAlive()方法的結果才是true,現在程序都已經運行結束了為什麼還是true?這裏的輸出結果是不確定的,我們再來看下面一段代碼

我們將例1中的代碼稍做修改,代碼如下:

public class Test01 {

	public static void main(String[] args) throws InterruptedException {
		Mythread thread=new Mythread();
		System.out.println("begin =="+thread.isAlive());//①
		thread.start();//②
		Thread.sleep(1000);//這裏加了一行代碼,讓當前線程沉睡1秒
		System.out.println("end =="+thread.isAlive());//③
	}
	
}

結果:
begin ==false
run ==true
end ==false

  

現在我們看到③處的代碼結果為end ==false,因為thread對象已經在1秒內執行完畢,而上面代碼輸出結果為true是因為thread線程未執行完畢。

 

3、sleep()方法:

介紹:

方法sleep()的作用是在指定的毫秒數內讓當前“正在執行的線程”休眠(暫停執行),這個“正在執行的線程”是指this.currentThread()返回的線程。

示例:

class Mythread extends Thread{
	
	@Override
	public void run() {
		
		try {
			System.out.println("run threadName="+this.currentThread().getName()+" begin");
			Thread.sleep(2000);
			System.out.println("run threadName="+this.currentThread().getName()+" end");
			
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
	
}

public class Test01 {

	public static void main(String[] args) throws InterruptedException {
		Mythread thread=new Mythread();
		System.out.println("begin ="+System.currentTimeMillis());
		thread.run();//①
		System.out.println("end ="+System.currentTimeMillis());
	}
	
}

結果:
begin =1574660731663
run threadName=main begin
run threadName=main end
end =1574660733665

  

從結果中可以看出main線程暫停了2秒(因為這裏調用的是thread.run())

下面我們將①處的代碼改成thread.start(),再來看下運行結果:

begin =1574661491412
end =1574661491412
run threadName=Thread-0 begin
run threadName=Thread-0 end

  

由於main線程與thread線程是異步執行的,所以首先打印的信息為begin和end,而thread線程是隨後運行的,在最後兩行打印run begin和run end的信息。

 

4、getId()方法:

介紹:getId()方法的作用是取得線程的唯一標識

示例

public class Test01 {

	public static void main(String[] args) throws InterruptedException {
		Thread thread=Thread.currentThread();
		System.out.println(thread.getName()+" "+thread.getId());
	}
	
}

結果:main 1

  

從運行結果可以看出,當前執行代碼的線程名稱是main,線程id值為1

 

5、停止線程:

介紹:停止線程是在多線程開發時很重要的技術點,掌握此技術可以對線程的停止進行有效的處理。停止線程在Java語言中並不像break語句那樣乾脆,需要一些技巧性的處理。

在java中有三種方法可以停止線程

  1. 使用退出標誌,讓線程正常退出,也就是當run方法執行完之後終止
  2. 使用stop方法強制終止線程,但是不推薦使用,因為stop和suspend及resume一樣,是java廢棄的方法
  3. 使用interrupt方法中斷線程(推薦使用)

示例:

例1:

class Mythread extends Thread{
	
	@Override
	public void run() {
		
		for(int i=0;i<5000;i++) {
			System.out.println("i="+(i+1));
		}
	}
	
}

public class Test01 {

	public static void main(String[] args) throws InterruptedException {
		Mythread thread=new Mythread();
		thread.start();
		Thread.sleep(2000);
		thread.interrupt();
	}
	
}

  

運行結果:

 

從運行結果我們可以看出最後i=500000,調用interrupt方法沒有停止線程,那麼該如何停止線程呢?

在介紹如何停止線程時,我們先來介紹一下如何判斷線程是否處於停止狀態

Thread類中提供了兩種方法用來判斷線程是否停止:

1、this.interrupted():測試當前線程是否已經中斷,執行后具有將狀態標誌清除為false的功能

public static boolean interrupted() {
        return currentThread().isInterrupted(true);
}

  

2、this.isInterrupted():測試線程Thread對象是否已經中斷,但是不清除狀態標誌

public boolean isInterrupted() {
        return isInterrupted(false);
}

  

讀者可以仔細觀看一下這兩個方法的聲明有什麼不同?

 

例2:

class Mythread extends Thread{
	
	@Override
	public void run() {
		
		for(int i=0;i<5000;i++) {
			System.out.println("i="+(i+1));
		}
	}
	
}

public class Test01 {

	public static void main(String[] args) throws InterruptedException {
		Mythread thread=new Mythread();
		thread.start();
		Thread.sleep(1000);
		thread.interrupt();
		System.out.println("是否停止1?="+thread.interrupted());
		System.out.println("是否停止2?="+thread.interrupted());
		System.out.println("end!");
	}
	
}

  

結果:

 

 

 輸出結果显示調用了thread.interrupt()方法后線程並未停止,這也就證明了interrupted()方法的解釋:測試當前線程是否已經中斷。這個當前線程是main,它從未斷過,所以打印的結果是兩個false。

如果想讓main線程結束該怎麼做?

將main方法改成如下:

public class Test01 {

	public static void main(String[] args) throws InterruptedException {
		Thread.currentThread().interrupt();
		System.out.println("是否停止1?="+Thread.interrupted());
		System.out.println("是否停止2?="+Thread.interrupted());
		System.out.println("end!");
	}
	
}

結果:
是否停止1?=true
是否停止2?=false
end!

  

從輸出結果我們可以看出,方法interrupted()的確判斷出當前線程是否是停止狀態。但為什麼第2個值是false?

查看一下官方文檔的介紹:

測試當前線程是否已經中斷。線程的中斷狀態由該方法清除。換句話說,如果連續兩次調用該方法,則第二次調用將返回false(在第一次調用已清除了其中斷狀態之後,且第二次調用檢驗完中斷狀態前,當前線程再次中斷的情況除外)。

 文檔中說明的非常清楚,interrupted()方法具有清除狀態的功能,所以第二次調用interrupted方法返回的值時false。

下面我們來看一下isInterrupted()方法,將main方法改成如下代碼:

public static void main(String[] args) throws InterruptedException {
		Mythread thread=new Mythread();
		thread.start();
		thread.interrupt();
		Thread.sleep(1000);
		System.out.println("是否停止1?="+thread.isInterrupted());
		System.out.println("是否停止2?="+thread.isInterrupted());
		System.out.println("end");
}
結果:

是否停止1?=true
是否停止2?=true
end

  

從結果可以看出,方法isInterrrupted()並未清除狀態,所以結果為兩個true。

 

例3:在沉睡中停止

當線程調用sleep()方法后再調用interrupt()方法後會有什麼結果:

class Mythread extends Thread{
	
	@Override
	public void run() {
		
		try {
			System.out.println("run begin");
			Thread.sleep(200000);
			System.out.println("run end");
		} catch (InterruptedException e) {
			System.out.println("在沉睡中被停止,進入catch!"+this.isInterrupted());
			e.printStackTrace();
		}
	}
	
}

public class Test01 {

	public static void main(String[] args) throws InterruptedException {
		try {
			Mythread thread=new Mythread();
			thread.start();
			Thread.sleep(200);
			thread.interrupt();
		}catch(Exception e) {
			System.out.println("main catch");
			e.printStackTrace();
		}
		System.out.println("end!");
	}
	
}

  

 

 

6、暫停線程:

暫停線程意味着此線程還可以恢復運行。在java多線程中,可以使用suspend()方法暫停線程,使用resume()方法恢複線程的執行

例1:

class Mythread extends Thread{
	
	private long i=0;
	public long getI() {
		return i;
	}
	
    public void setI(long i) {
		this.i = i;
	}
	
	@Override
	public void run() {
		while(true) {
			i++;
		}
	}
	
}

public class Test01 {

	public static void main(String[] args) throws InterruptedException {
		
		Mythread thread=new Mythread();
		thread.start();
		Thread.sleep(5000);
		//A段
		thread.suspend();
		System.out.println("A= "+System.currentTimeMillis()+" i="+thread.getI());
		Thread.sleep(5000);
		System.out.println("A= "+System.currentTimeMillis()+" i="+thread.getI());
		
		//B段
		thread.resume();
		Thread.sleep(5000);
		
		//C段
		thread.suspend();
		System.out.println("B= "+System.currentTimeMillis()+" i="+thread.getI());
		Thread.sleep(5000);
		System.out.println("B= "+System.currentTimeMillis()+" i="+thread.getI());
		
	}
	
}

  

結果:

 


從控制台打印的時間上來看,線程的確被暫停了,而且還可以恢復成運行狀態。

 

7、yield方法:

介紹:yield()方法的作用是放棄當前的CPU資源,將它讓給其他的任務去佔用CPU執行時間。但放棄的時間不確定,有可能剛剛放棄,馬上又獲得CPU時間片

示例:

class Mythread extends Thread{
	
	@Override
	public void run() {
		long beginTime=System.currentTimeMillis();
		int count=0;
		for(int i=0;i<500000;i++) {
               //Thread.yield();① count=count+(i+1); } long endTime=System.currentTimeMillis(); System.out.println("用時:"+(endTime-beginTime)+"毫秒!"); } } public class Test01 { public static void main(String[] args) throws InterruptedException { Mythread thread=new Mythread(); thread.start(); } }

結果:用時:2毫秒!

  

現在將①處的代碼取消註釋,我們再來看一下運行結果:

用時:213毫秒!

  

將CPU讓給其他資源導致速度變慢

 

8、線程優先級:

介紹:

在操作系統中,線程可以劃分優先級,優先級較高的線程得到的CPU資源較多,也就是CPU優先執行優先級較高的線程對象中的任務。

設置線程優先級有助於幫“線程規劃器”確定在下一次選擇哪一個線程來優先執行。

設置線程的優先級使用setPriority()方法,此方法在JDK的源代碼如下:

public final void setPriority(int newPriority) {
        ThreadGroup g;
        checkAccess();
        if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
            throw new IllegalArgumentException();
        }
        if((g = getThreadGroup()) != null) {
            if (newPriority > g.getMaxPriority()) {
                newPriority = g.getMaxPriority();
            }
            setPriority0(priority = newPriority);
        }
}

  

在Java中,線程的優先級為1-10這10個等級,如果小於1或大於10,則JDK拋出異常throw new IllegalArgumentException()。

通常高優先級的線程總是先執行完,但是並不是一定的,高優先級和低優先級的線程會交替進行,高優先級執行的次數多一些

 

線程優先級的繼承特性:

在Java中,線程的優先級具有繼承性,比如A線程啟動B線程,則B線程的優先級與A是一樣的。

class Mythread2 extends Thread{
	@Override
	public void run() {
		System.out.println("Mythread2 run priority="+this.getPriority());
	}
}


class Mythread1 extends Thread{
	
	@Override
	public void run() {
		System.out.println("Mythread run priority="+this.getPriority());
		Mythread2 thread2=new Mythread2();
		thread2.start();
	}
	
}

public class Test01 {

	public static void main(String[] args) throws InterruptedException {
		
		System.out.println("main thread begin priority="+Thread.currentThread().getPriority());
    	        //Thread.currentThread().setPriority(6);①
		System.out.println("main thread end priority="+Thread.currentThread().getPriority());
		Mythread1 thread1=new Mythread1();
		thread1.start();
	}
	
}

結果:
main thread begin priority=5
main thread end priority=5
Mythread run priority=5
Mythread2 run priority=5

  

可以看到上面幾個線程的優先級都為5

現在將①處的代碼註釋掉后的結果是:

main thread begin priority=5
main thread end priority=6
Mythread run priority=6
Mythread2 run priority=6

  

優先級被更改后再繼續繼承

 

9、守護線程:

在java中有兩種線程,一種是用戶線程,另一種是守護線程。

守護線程是一種特殊的線程,它的特性有“陪伴”的含義,當進程中不存在非守護線程了,則守護線程自動銷毀。典型的守護線程就是垃圾回收線程,當進程中沒有非守護線程了,則垃圾回收線程也就沒有存在的必要了,自動銷毀。用個比較通俗的比喻來解釋一下:“守護線程”:任何一個守護線程都是整個JVM中所有非守護線程的“保姆”,只要當前JVM實例中存在任何一個非守護線程沒有結束,守護線程就在工作,只有當最後一個非守護線程結束時,守護線程才隨着JVM一同結束工作。Daemon的作用是為其他線程的運行提供便利服務,守護線程最典型的應用就是GC(垃圾回收器),它就是一個很稱職的守護者。

 

class Mythread extends Thread{
	
	private int i=0;
	
	
	@Override
	public void run() {
		
		try {
			while(true) {
				i++;
				System.out.println("i="+(i));
				Thread.sleep(1000);
			}
		}catch(Exception e) {
			e.printStackTrace();
		}
		
	}
	
}

public class Test01 {

	public static void main(String[] args) throws InterruptedException {
		
		try {
			Mythread thread=new Mythread();
			thread.setDaemon(true);
			thread.start();
			Thread.sleep(5000);
			System.out.println("我離開Thread對象就不再打印了,也就是停止了");
		}catch(Exception e) {
			e.printStackTrace();
		}
	}
	
}

  

 

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

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

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

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

Java開發中常用jar包整理及使用

本文整理了我自己在Java開發中常用的jar包以及常用的API記錄。

<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.8</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.6</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.8</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>

common-lang3

簡介

一個現在最為常用的jar包,封裝了許多常用的工具包

依賴:

<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.4</version>
</dependency>

主要常見的類如下:

  • 數組工具類 ArrayUtils
  • 日期工具類 DateUtils DateFormatUtils
  • 字符串工具類 StringUtils
  • 数字工具類 NumberUtils
  • 布爾工具類 BooleanUtils
  • 反射相關工具類 FieldUtils、MethodUtils、MemberUtils、TypeUtils、ConstructorUtils
  • 對象工具類 ObjectUtils
  • 序列化工具類 SerializationUtils

API介紹

這裏我只介紹經常使用的幾個工具類及方法,ArrayUtils,StringUtils,NumberUtils,DateUtils,其他的請查看官方API文檔吧

1.ArrayUtils

方法名 說明
add
remove
clone 複製數組
addAll
removeAll 第二個參數傳入需要刪除的下標(可以指定多個下標)
toObject 把數值(int[],double[])轉為包裝類(Int[],Double[])
indexOf 在數組按順序查找,找到第一個滿足對應的數值的下標
lastIndexOf 在數組按順序查找,找到最後一個滿足對應的數值的下標
contains 數組是否包含某個值
isEmpty 判斷數組是否為空
isNotEmpty 判斷數組是否不為空
reverse 數組反轉
subarray 指定區間截取數組,區間為半開區間,不包含末尾
toArray 接收一個多個對象,把這幾個對象轉為對應類型的數組
toMap 將一個二維數組轉為Map

2.NumberUtils

方法名 說明
min 比較三個數,返回最小值 或比較指定的幾個數,返回最小值
max 比較三個數,返回最大值 或比較指定的幾個數,返回最大值
createInt 從傳入的String中創建對應類型的數值,createDouble,createFloat…
toInt 將指定字符串轉為Int類型,可以選擇指定默認數值,如果字符串為null則返回默認數值,除此之外,還有toDouble,toLong…等轉為不同類型的方法
compare 比較兩個同類型數值的大小
isDigits 判斷字符串是否只包含数字
isParsable 判斷字符串是否可轉換為Long,Int等類型
isNumber 判斷字符串是否為數值(0x,0X開頭等進制數值)

3.DateUtils

方法名 說明
parseDate 將Date對象轉為字符串
isSameDay 判斷兩個Dated對象是否為同一天
isSameDay 判斷兩個Dated對象是否為同一天
addHour 將指定的Date對象加上指定小時,除此之外,還有addMonth,addDay..等

DateFormatUtils正如其名,是用來把時間轉為字符串,這裏就不再多說

4.StringUtils

方法名 說明
join 將指定的數組連接成字符串,並添加指定的分割字符
containOnly 字符串是否只包含某個字符串
substringBefore 截取指定字符串前面的內容
substringAfter 截取指定字符串後面的內容(不包括指定字符串)
substringBetween 截取字符串某區間內容,如substringBetween(“abcde”,”a”,”e”)=”bcd”
difference 比較兩個字符串,返回兩個字符串不同的內容,具體可以看API文檔給出的示例
isBlank 判斷字符串是否為空白,null,””,” “這三個結果都是為true
isEmpty 判斷字符串是否為空(只要不為null,或傳入的String對象的長度不為0即為true)
countMatches 判斷指定的字符串在某個字符串中出現的次數
deleteWhitespace 刪除字符串中的空格
defaultIfBlank 如果字符串為空白,則返回一個指定的默認值(null或某個String)
defaultIfEmpty 如果字符串為空,則返回一個指定的默認值(null或某個String)
capitalize 將指定字符串首字母大寫
abbreviate 將指定字符串的後面三位轉為…
swapCase 將字符串中的字母大小寫反轉,如aBc變為AbC
lowerCase 將字符串的字母全部轉為小寫
upperCase 將字符串的字母全部轉為大寫
left 取字符串左邊幾個字符,如left(“hello”,3)=”hel”,right與此相反
leftPad 字符串的長度不夠,則使用指定字符填充指定字符串,如leftPad(“hel”,5,”z”)=”zzhel”,rightPad方法與此相反
prependIfMissing 指定字符串不以某段字符串開頭,則自動添加開頭,如prependIfMissing(“hello”,”li”)=”lihello”
prependIfMissing 指定字符串不以某段字符串開頭(忽略大小寫),則自動添加開頭
getCommonPrefix 獲得多個字符串相同的開頭內容,接收參數為多個字符串
removeEnd 刪除字符串中結尾(滿足是以某段內容結尾),如removeEnd(“hello”,”llo”)=”he”
removeEndIgnoreCase 與上面一樣,忽略大小寫
removeStart 與上面的相反
remove 刪除字符串中的指定內容,如remove(“hello”,”l”)=”heo”
removeIgnoreCase 刪除字符串中的指定內容,如remove(“hello”,”l”)=”heo”
strip 清除字符串開頭和末尾指定的字符(第二個參數為null,用來清除字符串開頭和末尾的空格),如strip(” abcxy”,”xy”)=” abc”,strip(” abcxy”,”yx”)=” abc”
stripStart 清除字符串開頭指定字符
stripEnd 清除字符串末尾指定的字符

common-io

簡介

常用的IO流工具包

<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.6</version>
</dependency>

API

我們主要關心的就是Utils後綴的那幾個類即可,可以看到,common-io庫提供了FileUtils,FileSystemUtils,FileNameUtils,FileFilterUtils,IOUtils

FileUtils

  • 寫出文件
  • 讀取文件
  • 創建一個有父級文件夾的文件夾
  • 複製文件和文件夾
  • 刪除文件和文件夾
  • URL轉文件
  • 通過過濾器和擴展名來篩選文件和文件夾
  • 比較文件內容
  • 文件最後修改時間
  • 文件校驗

FileSystemUtils

關於文件系統的相關操作,如查看C盤的大小,剩餘大小等操作

IOUtils

字面意思,是封裝了IO流的各種操作的工具類

Log4j

簡介

Log4J 是 Apache 的一個開源項目,通過在項目中使用 Log4J,我們可以控制日誌信息輸出到控制台、文件、GUI 組件、甚至是數據庫中。

我們可以控制每一條日誌的輸出格式,通過定義日誌的輸出級別,可以更靈活的控制日誌的輸出過程,方便項目的調試。

依賴:

<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

結構

Log4J 主要由 Loggers (日誌記錄器)、Appenders(輸出端)和 Layout(日誌格式化器)組成。

其中Loggers 控制日誌的輸出級別與日誌是否輸出;
Appenders 指定日誌的輸出方式(輸出到控制台、文件等);
Layout 控制日誌信息的輸出格式。

日誌級別:

級別 說明
OFF 最高日誌級別,關閉左右日誌
FATAL 將會導致應用程序退出的錯誤
ERROR 發生錯誤事件,但仍不影響系統的繼續運行
WARN 警告,即潛在的錯誤情形
INFO 一般和在粗粒度級別上,強調應用程序的運行全程
DEBUG 一般用於細粒度級別上,對調試應用程序非常有幫助
ALL 最低等級,打開所有日誌記錄

我們主要使用這四個:Error>Warn>Info>Debug

使用

我們可以使用兩種方式來運行Log4j,一種是java代碼方式,另外一種則是配置文件方式

例子(Java方式)

public class Log4JTest {
    public static void main(String[] args) {   
        //獲取Logger對象的實例(傳入當前類)         
        Logger logger = Logger.getLogger(Log4JTest.class);
        //使用默認的配置信息,不需要寫log4j.properties
        BasicConfigurator.configure();
        //設置日誌輸出級別為WARN,這將覆蓋配置文件中設置的級別,只有日誌級別低於WARN的日誌才輸出
        logger.setLevel(Level.WARN);
        logger.debug("這是debug");
        logger.info("這是info");
        logger.warn("這是warn");
        logger.error("這是error");
        logger.fatal("這是fatal");
    }
}

例子(配置文件方式)

上面的例子,我們想要實現打印Log,但是每次都要寫一遍,浪費時間和精力,所以,Log4j提供了另外一種方式來配置好我們的信息

創建一個名為log4j.properties的文件,此文件需要放在項目的根目錄(約定),如果是maven項目,直接放在resources文件夾中即可

log4j.properties

#控制台
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n

#log jdbc
log4j.logger.java.sql.ResultSet=INFO
log4j.logger.org.apache=WARN
log4j.logger.java.sql.Connection=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG

#log mybatis設置
#log4j.logger.org.apache.ibatis=DEBUG
log4j.logger.org.apache.ibatis.jdbc=error
log4j.logger.org.apache.ibatis.io=info
log4j.logger.org.apache.ibatis.datasource=info

#springMVC日誌
log4j.logger.org.springframework.web=WARN

# 文件輸出配置
log4j.appender.A = org.apache.log4j.DailyRollingFileAppender
log4j.appender.A.File = D:/log.txt #指定日誌的輸出路徑
log4j.appender.A.Append = true
log4j.appender.A.Threshold = DEBUG
log4j.appender.A.layout = org.apache.log4j.PatternLayout #使用自定義日誌格式化器
log4j.appender.A.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss}  [ %t:%r ] - [ %p ]  %m%n #指定日誌的輸出格式
log4j.appender.A.encoding=UTF-8 #指定日誌的文件編碼

#指定日誌的輸出級別與輸出端
log4j.rootLogger=DEBUG,Console,A

#指定某個包名日誌級別(不能超過上面定義的級別,否則日誌不會輸出)
log4j.logger.com.wan=DEBUG

之後使用的話就比較簡單了

//Logger的初始化(這個推薦定義為全局變量,方便使用)
Logger logger = Logger.getLogger(Log4JTest.class);
//輸出Log
logger.info("這是info");

參考鏈接:

lombok

簡介

平常我們創建實體類的時候,需要get/set方法,極其麻煩,雖然IDEA等IDE都是有提供了快捷生成,不過,最好的解決方法還是省略不寫

而lombok就是這樣的一個框架,實現省略get/set方法,當然,lombok的功能不只有此,還有equal,toString方法也可以由此框架自動生成

lombok的原理是使用註解,之後就會在編譯過程中,給Class文件自動加上get/set等方法

不過IDEA似乎無法識別,代碼檢查還是會報錯,所以,使用IDEA的時候還得安裝一個插件,在plugin搜索lombok,之後安裝重啟即可,如圖

之後為Java項目添加依賴

<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.8</version>
    <scope>provided</scope>
</dependency>

使用示例

1.實體類省略get/set
估計Kotlin中的data關鍵字就是參照着lombok實現的

//這裏我們只需要為類添加Data註解,就會自動生成對應屬性的get/set方法,toString,equal等方法
@Data
public class User {
    private String username;
    private String password;
}

2.需要無參構造以及get/set方法

@Getter
@Setter
@NoArgsConstructor
public class User {
    private String username;
    private String password;
}

3.鏈式調用set方法

@Data
@Accessors(chain = true)
public class User {
    private String username;
    private String password;
}

//使用
User user = new User();
user.setUsername("helo").setPassword("123");

4.參數不為空

//如果調用此方法,就會抱一個空指針錯誤
public String print(@NotNull String str){
    ...
}

5.只需要toString

@ToString(callSuper=true, includeFieldNames=true)
public class User {
    private String username;
    private String password;
    //省略的get/set方法
}

6.builder模式創建實體類對象

@Data
@Builder
public class User {
    private String username;
    private String password;
}
//使用
User user1 = User.builder().username("user1").password("123").build();

7.工具類

@UtilityClass
public class MyUtils{
    //會將此方法自動轉為靜態方法
    public void print(String str){
        ...
    }
}
//使用
MyUtils.print("hello");

8.自動關閉流

public static void main(String[] args) throws Exception {
    //使用Cleanup會自動調用close方法
    @Cleanup InputStream in = new FileInputStream(args[0]);
    @Cleanup OutputStream out = new FileOutputStream(args[1]);
    byte[] b = new byte[1024];
    while (true) {
        int r = in.read(b);
        if (r == -1) break;
        out.write(b, 0, r);
    }
}

9.省略Logger時的初始化

@Log4j
@Log
public class User{
    //會自動添加此語句
    //Logger logger = Logger.getLogger(User.class);
    ...
}

參考:

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

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

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

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

.NET高級特性-Emit(2)類的定義,.NET高級特性-Emit(1)

  在上一篇博文發了一天左右的時間,就收到了博客園許多讀者的評論和推薦,非常感謝,我也會及時回復讀者的評論。之後我也將繼續撰寫博文,梳理相關.NET的知識,希望.NET的圈子能越來越大,開發者能了解/深入.NET的本質,將工作做的簡單又高效,拒絕重複勞動,拒絕CRUD。

  ok,咱們開始繼續Emit的探索。在這之前,我先放一下我往期關於Emit的文章,方便讀者閱讀。

  《》

一、基礎知識

  既然C#作為一門面向對象的語言,所以首當其沖的我們需要讓Emit為我們動態構建類。

  廢話不多說,首先,我們先來回顧一下C#類的內部由什麼東西組成:

  (1) 字段-C#類中保存數據的地方,由訪問修飾符、類型和名稱組成;

  (2) 屬性-C#類中特有的東西,由訪問修飾符、類型、名稱和get/set訪問器組成,屬性的是用來控制類中字段數據的訪問,以實現類的封裝性;在Java當中寫作getXXX()和setXXX(val),C#當中將其變成了屬性這種語法糖;

  (3) 方法-C#類中對邏輯進行操作的基本單元,由訪問修飾符、方法名、泛型參數、入參、出參構成;

  (4) 構造器-C#類中一種特殊的方法,該方法是專門用來創建對象的方法,由訪問修飾符、與類名相同的方法名、入參構成。

  接着,我們再觀察C#類本身又具備哪些東西:

  (1) 訪問修飾符-實現對C#類的訪問控制

  (2) 繼承-C#類可以繼承一個父類,並需要實現父類當中所有抽象的方法以及選擇實現父類的虛方法,還有就是子類需要調用父類的構造器以實現對象的創建

  (3) 實現-C#類可以實現多個接口,並實現接口中的所有方法

  (4) 泛型-C#類可以包含泛型參數,此外,類還可以對泛型實現約束

  以上就是C#類所具備的一些元素,以下為樣例:

public abstract class Bar
{
    public abstract void PrintName();
}
public interface IFoo<T> { public T Name { get; set; } } //繼承Bar基類,實現IFoo接口,泛型參數T
public class Foo<T> : Bar, IFoo<T>
  //泛型約束
  where T : struct {
//構造器 public Foo(T name):base() { _name = name; } //字段 private T _name; //屬性 public T Name { get => _name; set => _name = value; } //方法 public override void PrintName() {
    Console.WriteLine(_name.ToString()); }
}

  在探索完了C#類及其定義后,我們要來了解C#的項目結構組成。我們知道C#的一個csproj項目最終會對應生成一個dll文件或者exe文件,這一個文件我們稱之為程序集Assembly;而在一個程序集中,我們內部包含和定義了許多命名空間,這些命令空間在C#當中被稱為模塊Module,而模塊正是由一個一個的C#類Type組成。

 

 

 

   所以,當我們需要定義C#類時,就必須首先定義Assembly以及Module,如此才能進行下一步工作。

二、IL概覽

   由於Emit實質是通過IL來生成C#代碼,故我們可以反向生成,先將寫好的目標代碼寫成cs文件,通過編譯器生成dll,再通過ildasm查看IL代碼,即可依葫蘆畫瓢的編寫出Emit代碼。所以我們來查看以下上節Foo所生成的IL代碼。

  

 

 

   從上圖我們可以很清晰的看到.NET的層級結構,位於樹頂層淺藍色圓點表示一個程序集Assembly,第二層藍色表示模塊Module,在模塊下的均為我們所定義的類,類中包含類的泛型參數、繼承類信息、實現接口信息,類的內部包含構造器、方法、字段、屬性以及它的get/set方法,由此,我們可以開始編寫Emit代碼了

三、Emit編寫

  有了以上的對C#類的解讀和IL的解讀,我們知道了C#類本身所需要哪些元素,我們就開始根據這些元素來開始編寫Emit代碼了。這裏的代碼量會比較大,請讀者慢慢閱讀,也可以參照以上我寫的類生成il代碼進行比對。

  在Emit當中所有創建類型的幫助類均以Builder結尾,從下錶中我們可以看的非常清楚

元素中文 元素名稱 對應Emit構建器名稱
程序集  Assembly AssemblyBuilder
模塊  Module ModuleBuilder
 Type TypeBuilder
構造器  Constructor ConstructorBuilder
屬性  Property PropertyBuilder
字段  Field FieldBuilder
方法  Method MethodBuilder

  由於創建類需要從Assembly開始創建,所以我們的入口是AssemblyBuilder

  (1) 首先,我們先引入命名空間,我們以上節Foo類為樣例進行編寫

using System.Reflection.Emit;

  (2) 獲取基類和接口的類型

var barType = typeof(Bar);
var interfaceType = typeof(IFoo<>);

  (3) 定義Foo類型,我們可以看到在定義類之前我們需要創建Assembly和Module

//定義類
var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("Edwin.Blog.Emit"), AssemblyBuilderAccess.Run);
var moduleBuilder = assemblyBuilder.DefineDynamicModule("Edwin.Blog.Emit");
var typeBuilder = moduleBuilder.DefineType("Foo", TypeAttributes.Public | TypeAttributes.Class | TypeAttributes.AutoClass | TypeAttributes.AnsiClass | TypeAttributes.BeforeFieldInit);

  (4) 定義泛型參數T,並添加約束

//定義泛型參數
var genericTypeBuilder = typeBuilder.DefineGenericParameters("T")[0];
//設置泛型約束
genericTypeBuilder.SetGenericParameterAttributes(GenericParameterAttributes.NotNullableValueTypeConstraint);

  (5) 繼承和實現接口,注意當實現類的泛型參數需傳遞給接口時,需要將泛型接口添加泛型參數后再調用AddInterfaceImplementation方法

//繼承基類
typeBuilder.SetParent(barType);
//實現接口
typeBuilder.AddInterfaceImplementation(interfaceType.MakeGenericType(genericTypeBuilder));

  (6) 定義字段,因為字段在構造器值需要使用,故先創建

//定義字段
var fieldBuilder = typeBuilder.DefineField("_name", genericTypeBuilder, FieldAttributes.Private);

  (7) 定義構造器,並編寫內部邏輯

//定義構造器
var ctorBuilder = typeBuilder.DefineConstructor(MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, CallingConventions.Standard, new Type[] { genericTypeBuilder });
var ctorIL = ctorBuilder.GetILGenerator();
//Ldarg_0在實例方法中表示this,在靜態方法中表示第一個參數
ctorIL.Emit(OpCodes.Ldarg_0);
ctorIL.Emit(OpCodes.Ldarg_1);
//為field賦值
ctorIL.Emit(OpCodes.Stfld, fieldBuilder);
ctorIL.Emit(OpCodes.Ret);

  (8) 定義Name屬性

//定義屬性
var propertyBuilder = typeBuilder.DefineProperty("Name", PropertyAttributes.None, genericTypeBuilder, Type.EmptyTypes);

  (9) 編寫Name屬性的get/set訪問器

//定義get方法
var getMethodBuilder = typeBuilder.DefineMethod("get_Name", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.SpecialName | MethodAttributes.Virtual, CallingConventions.Standard, genericTypeBuilder, Type.EmptyTypes);
var getIL = getMethodBuilder.GetILGenerator();
getIL.Emit(OpCodes.Ldarg_0);
getIL.Emit(OpCodes.Ldfld, fieldBuilder);
getIL.Emit(OpCodes.Ret);
typeBuilder.DefineMethodOverride(getMethodBuilder, interfaceType.GetProperty("Name").GetGetMethod()); //實現對接口方法的重載
propertyBuilder.SetGetMethod(getMethodBuilder); //設置為屬性的get方法
//定義set方法
var setMethodBuilder = typeBuilder.DefineMethod("set_Name", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.SpecialName | MethodAttributes.Virtual, CallingConventions.Standard, null, new Type[] { genericTypeBuilder });
var setIL = setMethodBuilder.GetILGenerator();
setIL.Emit(OpCodes.Ldarg_0);
setIL.Emit(OpCodes.Ldarg_1);
setIL.Emit(OpCodes.Stfld, fieldBuilder);
setIL.Emit(OpCodes.Ret);
typeBuilder.DefineMethodOverride(setMethodBuilder, interfaceType.GetProperty("Name").GetSetMethod()); //實現對接口方法的重載
propertyBuilder.SetSetMethod(setMethodBuilder); //設置為屬性的set方法

   (10) 定義並實現PrintName方法

//定義方法
var printMethodBuilder = typeBuilder.DefineMethod("PrintName", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Virtual, CallingConventions.Standard, null, Type.EmptyTypes);
var printIL = printMethodBuilder.GetILGenerator();
printIL.Emit(OpCodes.Ldarg_0);
printIL.Emit(OpCodes.Ldflda, fieldBuilder);
printIL.Emit(OpCodes.Constrained, genericTypeBuilder);
printIL.Emit(OpCodes.Callvirt, typeof(object).GetMethod("ToString", Type.EmptyTypes));
printIL.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }));
printIL.Emit(OpCodes.Ret);
//實現對基類方法的重載
typeBuilder.DefineMethodOverride(printMethodBuilder, barType.GetMethod("PrintName", Type.EmptyTypes));

  (11) 創建類

var type = typeBuilder.CreateType(); //netstandard中請使用CreateTypeInfo().AsType()

  (12) 調用

var obj = Activator.CreateInstance(type.MakeGenericType(typeof(DateTime)), DateTime.Now);
(obj as Bar).PrintName();
Console.WriteLine((obj as IFoo<DateTime>).Name);

四、應用

  上面的樣例僅供學習只用,無法運用在實際項目當中,那麼,Emit構建類在實際項目中我們可以有什麼應用,提高我們的編碼效率

  (1) 動態DTO-當我們需要將實體映射到某個DTO時,可以用動態DTO來代替你手寫的DTO,選擇你需要的字段回傳給前端,或者前端把他想要的字段傳給後端

  (2) DynamicLinq-我的第一篇博文有個讀者提到了表達式樹,而linq使用的正是表達式樹,當表達式樹+Emit時,我們就可以用像SQL或者GraphQL那樣的查詢語句實現動態查詢

  (3) 對象合併-我們可以編寫實現一個像js當中Object.assign()一樣的方法,實現對兩個實體的合併

  (4) AOP動態代理-AOP的核心就是代理模式,但是與其對應的是需要手寫代理類,而Emit就可以幫你動態創建代理類,實現切面編程

  (5) …

五、小結

  對於Emit,確實初學者會對其感到複雜和難以學習,但是只要搞懂其中的原理,其實最終就是C#和.NET語言的本質所在,在學習Emit的同時,也是在鍛煉你的基本功是否紮實,你是否對這門語言精通,是否有各種簡化代碼的應用。

  保持學習,勇於實踐;Write Less,Do More;作者之後還會繼續.NET高級特性系列,感謝閱讀!

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

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

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

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

Elasticsearch從入門到放棄:文檔CRUD要牢記

在Elasticsearch中,文檔(document)是所有可搜索數據的最小單位。它被序列化成JSON存儲在Elasticsearch中。每個文檔都會有一個唯一ID,這個ID你可以自己指定或者交給Elasticsearch自動生成。

如果延續我們之前不恰當的對比RDMS的話,我認為文檔可以類比成關係型數據庫中的表。

元數據

前面我們提到,每個文檔都有一個唯一ID來標識,獲取文檔時,“_id”字段記錄的就是文檔的唯一ID,它是元數據之一。當然,文檔還有一些其他的元數據,下面我們來一一介紹

  • _index:文檔所屬的索引名
  • _type:文檔所屬的type
  • _id:文檔的唯一ID

有了這三個,我們就可以唯一確定一個document了,當然,7.0版本以後我們已經不需要_type了。接下來我們再來看看其他的一些元數據

  • _source:文檔的原始JSON數據
  • _field_names:該字段用於索引文檔中值不為null的字段名,主要用於exists請求查找指定字段是否為空
  • _ignore:這個字段用於索引和存儲文檔中每個由於異常(開啟了ignore_malformed)而被忽略的字段的名稱
  • _meta:該字段用於存儲一些自定義的元數據信息
  • _routing:用來指定數據落在哪個分片上,默認值是Id
  • _version:文檔的版本信息
  • _score:相關性打分

創建文檔

創建文檔有以下4種方法:

  • PUT /<index>/_doc/<_id>
  • POST /<index>/_doc/
  • PUT /<index>/_create/<_id>
  • POST /<index>/_create/<_id>

這四種方法的區別是,如果不指定id,則Elasticsearch會自動生成一個id。如果使用_create的方法,則必須保證文檔不存在,而使用_doc方法的話,既可以創建新的文檔,也可以更新已存在的文檔。

在創建文檔時,還可以選擇一些參數。

請求參數

  • if_seq_no:當文檔的序列號是指定值時才更新
  • if_primary_term:當文檔的primary term是指定值時才更新
  • op_type:如果設置為create則指定id的文檔必須不存在,否則操作失敗。有效值為index或create,默認為index
  • op_type:指定預處理的管道id
  • refresh:如果設置為true,則立即刷新受影響的分片。如果是wait_for,則會等到刷新分片后,此次操作才對搜索可見。如果是false,則不會刷新分片。默認值為false
  • routing:指定路由到的主分片
  • timeout:指定響應時間,默認是30秒
  • master_timeout:連接主節點的響應時長,默認是30秒
  • version:顯式的指定版本號
  • version_type:指定版本號類型:internal、 external、external_gte、force
  • wait_for_active_shards:處理操作之前,必須保持活躍的分片副本數量,可以設置為all或者任意正整數。默認是1,即只需要主分片活躍。

響應包體

  • **_shards**:提供分片的信息
  • **_shards.total**:創建了文檔的總分片數量
  • **_shards.successful**:成功創建文檔分片的數量
  • **_shards.failed**:創建文檔失敗的分片數量
  • **_index**:文檔所屬索引
  • **_type**:文檔所屬type,目前只支持_doc
  • **_id**:文檔的id
  • **_version**:文檔的版本號
  • **_seq_no**:文檔的序列號
  • **_primary_term**:文檔的主要術語
  • result:索引的結果,created或者updated

我們在創建文檔時,如果指定的索引不存在,則ES會自動為我們創建索引。這一操作是可以通過設置中的action.auto_create_index字段來控制的,默認是true。你可以修改這個字段,實現指定某些索引可以自動創建或者所有索引都不能自動創建的目的。

更新文檔

了解了如何創建文檔之後,我們再來看看應該如何更新一個已經存在的文檔。其實在創建文檔時我們就提到過,使用PUT /<index>/_doc/<id>的方法就可以更新一個已存在的文檔。除此之外,我們還有另一種更新文檔的方法:

POST /<index>/_update/<_id>

這兩種更新有所不同。_doc方法是先刪除原有的文檔,再創建新的。而_update方法則是增量更新,它的更新過程是先檢索到文檔,然後運行指定腳本,最後重新索引。

還有一個區別就是_update方法支持使用腳本更新,默認的語言是painless,你可以通過參數lang來進行設置。在請求參數方面,_update相較於_doc多了以下幾個參數:

  • lang:指定腳本語言
  • retry_on_conflict:發生衝突時重試次數,默認是0
  • **_source**:設置為false,則不返回任何檢索字段
  • **_source_excludes**:指定要從檢索結果排除的source字段
  • **_source_includes**:指定要返回的檢索source字段

下面的一個例子是用腳本來更新文檔

curl -X POST "localhost:9200/test/_update/1?pretty" -H 'Content-Type: application/json' -d'
{
    "script" : {
        "source": "ctx._source.counter += params.count",
        "lang": "painless",
        "params" : {
            "count" : 4
        }
    }
}
'

Upsert

curl -X POST "localhost:9200/test/_update/1?pretty" -H 'Content-Type: application/json' -d'
{
    "script" : {
        "source": "ctx._source.counter += params.count",
        "lang": "painless",
        "params" : {
            "count" : 4
        }
    },
    "upsert" : {
        "counter" : 1
    }
}
'

當指定的文檔不存在時,可以使用upsert參數,創建一個新的文檔,而當指定的文檔存在時,該請求會執行script中的腳本。如果不想使用腳本,而只想新增/更新文檔的話,可以使用doc_as_upsert。

curl -X POST "localhost:9200/test/_update/1?pretty" -H 'Content-Type: application/json' -d'
{
    "doc" : {
        "name" : "new_name"
    },
    "doc_as_upsert" : true
}
'

update by query

這個API是用於批量更新檢索出的文檔的,具體可以通過一個例子來了解。

curl -X POST "localhost:9200/twitter/_update_by_query?pretty" -H 'Content-Type: application/json' -d'
{
  "script": {
    "source": "ctx._source.likes++",
    "lang": "painless"
  },
  "query": {
    "term": {
      "user": "kimchy"
    }
  }
}
'

獲取文檔

ES獲取文檔用的是GET API,請求的格式是:

GET /<index>/_doc/<_id>

它會返迴文檔的數據和一些元數據,如果你只想要文檔的內容而不需要元數據時,可以使用

GET /<index>/_source/<_id>

請求參數

獲取文檔的有幾個請求參數之前已經提到過,這裏不再贅述,它們分別是:

  • refresh
  • routing
  • **_source**
  • **_source_excludes**
  • **_source_includes**
  • version
  • version_type

而還有一些之前沒提到過的參數,我們來具體看一下

  • preference:用來 指定執行請求的node或shard,如果設置為_local,則會優先在本地的分片執行
  • realtime:如果設置為true,則請求是實時的而不是近實時。默認是true
  • stored_fields:返回指定的字段中,store為true的字段

mget

mget是批量獲取的方法之一,請求的格式有兩種:

  • GET /_mget
  • GET /<index>/_mget

第一種是在請求體中寫index。第二種是把index放到url中,不過這種方式可能會觸發ES的安全檢查。

mget的請求參數和get相同,只是需要在請求體中指定doc的相關檢索條件

request

GET /_mget
{
    "docs" : [
        {
            "_index" : "jackey",
            "_id" : "1"
        },
        {
            "_index" : "jackey",
            "_id" : "2"
        }
    ]
}

response

{
  "docs" : [
    {
      "_index" : "jackey",
      "_type" : "_doc",
      "_id" : "1",
      "_version" : 5,
      "_seq_no" : 6,
      "_primary_term" : 1,
      "found" : true,
      "_source" : {
        "user" : "ja",
        "tool" : "ES",
        "message" : "qwer"
      }
    },
    {
      "_index" : "jackey",
      "_type" : "_doc",
      "_id" : "2",
      "_version" : 1,
      "_seq_no" : 2,
      "_primary_term" : 1,
      "found" : true,
      "_source" : {
        "user" : "zhe",
        "post_date" : "2019-11-15T14:12:12",
        "message" : "learning Elasticsearch"
      }
    }
  ]
}

刪除文檔

CURD操作只剩下最後一個D了,下面我們就一起來看看ES中如何刪除一個文檔。

刪除指定id使用的請求是

DELETE /<index>/_doc/<_id>

在併發量比較大的情況下,我們在刪除時通常會指定版本,以確定刪除的文檔是我們真正想要刪除的文檔。刪除請求的參數我們在之前也都介紹過,想要具體了解的同學可以直接查看。

delete by query

類似於update,delete也有一個delete by query的API。

POST /<index>/_delete_by_query

它也是要先按照條件來查詢匹配的文檔,然後刪除這些文檔。在執行查詢之前,Elasticsearch會先為指定索引做一個快照,如果在執行刪除過程中,要索引發生改變,則會導致操作衝突,同時返回刪除失敗。

如果刪除的文檔比較多,也可以使這個請求異步執行,只需要設置wait_for_completion=false即可。

這個API的refresh與delete API的refresh參數有所不同,delete中的refresh參數是設置操作是否立即可見,即只刷新一個分片,而這個API中的refresh參數則是需要刷新受影響的所有分片。

Bulk API

最後,我們再來介紹一種特殊的API,批量操作的API。它支持兩種寫法,可以將索引名寫到url中,也可以寫到請求體中。

  • POST /_bulk

  • POST /<index>/_bulk

在這個請求中,你可以任意使用之前的CRUD請求的組合。

curl -X POST "localhost:9200/_bulk?pretty" -H 'Content-Type: application/json' -d'
{ "index" : { "_index" : "test", "_id" : "1" } }
{ "field1" : "value1" }
{ "delete" : { "_index" : "test", "_id" : "2" } }
{ "create" : { "_index" : "test", "_id" : "3" } }
{ "field1" : "value3" }
{ "update" : {"_id" : "1", "_index" : "test"} }
{ "doc" : {"field2" : "value2"} }
'

請求體中使用的語法是newline delimited JSON(NDJSON)。具體怎麼用呢?其實我們在上面的例子中已經有所展現了,對於index或create這樣的請求,如果請求本身是有包體的,那麼用換行符來表示下面的內容與子請求分隔,即為包體的開始。

例如上面例子中的index請求,它的包體就是{ “field1” : “value1” },所以它會在index請求的下一行出現。

對於批量執行操作來說,單條操作失敗並不會影響其他操作,而最終每條操作的結果也都會返回。

上面的例子執行完之後,我們得到的結果應該是

{
   "took": 30,
   "errors": false,
   "items": [
      {
         "index": {
            "_index": "test",
            "_type": "_doc",
            "_id": "1",
            "_version": 1,
            "result": "created",
            "_shards": {
               "total": 2,
               "successful": 1,
               "failed": 0
            },
            "status": 201,
            "_seq_no" : 0,
            "_primary_term": 1
         }
      },
      {
         "delete": {
            "_index": "test",
            "_type": "_doc",
            "_id": "2",
            "_version": 1,
            "result": "not_found",
            "_shards": {
               "total": 2,
               "successful": 1,
               "failed": 0
            },
            "status": 404,
            "_seq_no" : 1,
            "_primary_term" : 2
         }
      },
      {
         "create": {
            "_index": "test",
            "_type": "_doc",
            "_id": "3",
            "_version": 1,
            "result": "created",
            "_shards": {
               "total": 2,
               "successful": 1,
               "failed": 0
            },
            "status": 201,
            "_seq_no" : 2,
            "_primary_term" : 3
         }
      },
      {
         "update": {
            "_index": "test",
            "_type": "_doc",
            "_id": "1",
            "_version": 2,
            "result": "updated",
            "_shards": {
                "total": 2,
                "successful": 1,
                "failed": 0
            },
            "status": 200,
            "_seq_no" : 3,
            "_primary_term" : 4
         }
      }
   ]
}

批量操作的執行過程相比多次單個操作而言,在性能上會有一定的提升。但同時也會有一定的風險,所以我們在使用的時候要非常的謹慎。

總結

本文我們先介紹了文檔的基本概念和文檔的元數據。接着又介紹了文檔的CRUD操作和Bulk API。相信看完文章你對Elasticsearch的文檔也會有一定的了解。那最後就請你啟動你的Elasticsearch,然後親自動手試一試這些操作,看看各種請求的參數究竟有什麼作用。相信親手實驗過一遍之後你會對這些API有更深的印象。

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

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

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

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

Java併發之volatile關鍵字

引言

說到多線程,我覺得我們最重要的是要理解一個臨界區概念。

舉個例子,一個班上1個女孩子(臨界區),49個男孩子(線程),男孩子的目標就是這一個女孩子,就是會有競爭關係(線程安全問題)。推廣到實際場景,例如對一個數相加或者相減等等情形,因為操作對象就只有一個,在多線程環境下,就會產生線程安全問題。理解臨界區概念,我們對多線程問題可以有一個好意識。

Jav內存模型(JMM)

談到多線程就應該了解一下Java內存模型(JMM)的抽象示意圖.下圖:

線程A和線程B執行的是時候,會去讀取共享變量(臨界區),然後各自拷貝一份回到自己的本地內存,執行後續操作。
JMM模型是一種規範,就像Java的接口一樣。JMM會涉及到三個問題:原子性,可見性,有序性。
所謂原子性。就是說一個線程的執行會不會被其他線程影響的。他是不可中斷的。舉個例子:

int i=1

這個語句在Jmm中就是原子性的。無論是一個線程執行還是多個線程執行這個語句,讀出來的i就是等於1。那什麼是非原子性呢,按道理如果Java的代碼都是原子性,應該就不會有線程問題了啊。其實JMM這是規定某些語句是原子性罷了。舉個非原子性例子:

i ++;

這個操作就不是原子性的了。因為他就是包含了三個操作:第一讀取i的值,第二將i加上1,第三將結果賦值回來給i,更新i的值。
所謂可見性。可見性表示如果一個值在線程A修改了,線程B就會馬上知道這個結果。
所謂有序性。所謂有序性值的是語意的有序性。就是說代碼順序可能會發生變化。因為有一個指令重排機制。所謂指令重排,他會改變代碼執行順序,為了讓cpu執行效率更高。為了防止重排序出錯,JMM有個happen-before規則,這個規則限制了那些語句執行在前,那些語句執行在後。
Happen-before:
程序順序原則:一個線程內保證語義的串行性
volatile原則:volatile變量的寫發生在讀之前
鎖規則:先加鎖再解鎖
傳遞性:a先於b,b先於c,則a必定先於c
線程的start方法先於他的每一個操作
線程所有的操作先於線程的終結
對象的構造函數執行、結束先於finalize()方法。

volatile

進入正題,volatile可以保證變量(臨界區)的可見性以及有序性,但是不能保證原子性。舉個例子:

public class VolatileTest implements Runnable{
    private static VolatileTest volatileTest = new VolatileTest();
    private  static volatile int i= 0;
    public static void main(String[] args) throws InterruptedException {
        for (int j = 0; j < 20; j++) {
            Thread a = new Thread(new VolatileTest());
            Thread b = new Thread(new VolatileTest());
            a.start();b.start();
            a.join();b.join();
            System.out.print(i+"&&");
        }

    }
    
    @Override
    public void run() {
        for (int j = 0; j < 1000; j++) {
            i++;
        }
    }

}

// 輸出結果
// 2000&&4000&&5852&&7852&&9852&&11852&&13655&&15655&&17655&&19655&&21306     
//&&22566&&24566&&26189&&28189&&30189&&32189&&34189&&36189&&38089&&

有結果看到有問題,雖然i已經添加了volatile關鍵字,說明volatile關鍵字不能保證i++的原子性。

那什麼場景適合使用volatile關鍵字

  1. 輕量級的“讀-寫鎖”策略
private volatile int value;
public int getValue(){ return value;}
public synchronized void doubleValue(){ value = value*value; }

2.單例模式(雙檢查鎖機制

private volatile static Singleton instace;   
public static Singleton getInstance(){  // 沒有使用同步方法,而是同步方法塊
    //第一次null檢查 ,利用volatile的線程間可見性,不需要加鎖,性能提高    
    if(instance == null){            
        synchronized(Singleton.class) {    //鎖住類對象,阻塞其他線程
            //第二次null檢查,以保證不會創建重複的實例       
            if(instance == null){       
                instance = new Singleton(); // 禁止重排序
            }  
        }           
    }  
    return instance;

參考

《現代操作系統(第三版)中文版》
《實戰Java高併發程序設計》
《Java併發編程的藝術》

如果我的文章幫助到您,可以關注我的微信公眾號,第一時間分享文章給您

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

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

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

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

將Swagger2文檔導出為HTML或markdown等格式離線閱讀

網上有很多《使用swagger2構建API文檔》的文章,該文檔是一個在線文檔,需要使用HTTP訪問。但是在我們日常使用swagger接口文檔的時候,有的時候需要接口文檔離線訪問,如將文檔導出為html、markdown格式。又或者我們不希望應用系統與swagger接口文檔使用同一個服務,而是導出HTML之後單獨部署,這樣做保證了對接口文檔的訪問不影響業務系統,也一定程度提高了接口文檔的安全性。核心的實現過程就是:

  • 在swagger2接口文檔所在的應用內,利用swagger2markup將接口文檔導出為adoc文件,也可以導出markdown文件。
  • 然後將adoc文件轉換為靜態的html格式,可以將html發布到nginx或者其他的web應用容器,提供訪問(本文不會講html靜態部署,只講HTML導出)。

注意:adoc是一種文件格式,不是我的筆誤。不是doc文件也不是docx文件。

一、maven依賴類庫

在已經集成了swagger2的應用內,通過maven坐標引入相關依賴類庫,pom.xml代碼如下:

<dependency>
    <groupId>io.github.swagger2markup</groupId>
    <artifactId>swagger2markup</artifactId>
    <version>1.3.1</version>
</dependency>
<dependency>
    <groupId>io.swagger</groupId>
    <artifactId>swagger-core</artifactId>
    <version>1.5.16</version>
</dependency>
<dependency>
    <groupId>io.swagger</groupId>
    <artifactId>swagger-models</artifactId>
    <version>1.5.16</version>
</dependency>

swagger2markup用於將swagger2在線接口文檔導出為html,markdown,adoc等格式文檔,用於靜態部署或離線閱讀。其中第一個maven坐標是必須的。后兩個maven坐標,當你在執行後面的代碼過程中報下圖中的ERROR,或者有的類無法import的時候使用。

產生異常的原因已經有人在github的issues上給出解釋了:當你使用swagger-core版本大於等於1.5.11,並且swagger-models版本小於1.5.11就會有異常發生。所以我們顯式的引入這兩個jar,替換掉swagger2默認引入的這兩個jar。

二、生成adoc格式文件

下面的代碼是通過編碼方式實現的生成adoc格式文件的方式

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
public class DemoApplicationTests {
    @Test
    public void generateAsciiDocs() throws Exception {
        //    輸出Ascii格式
        Swagger2MarkupConfig config = new Swagger2MarkupConfigBuilder()
                .withMarkupLanguage(MarkupLanguage.ASCIIDOC) //設置生成格式
                .withOutputLanguage(Language.ZH)  //設置語言中文還是其他語言
                .withPathsGroupedBy(GroupBy.TAGS)
                .withGeneratedExamples()
                .withoutInlineSchema()
                .build();

        Swagger2MarkupConverter.from(new URL("http://localhost:8888/v2/api-docs"))
                .withConfig(config)
                .build()
                .toFile(Paths.get("src/main/resources/docs/asciidoc"));
    }
}
  • 使用RunWith註解和SpringBootTest註解,啟動應用服務容器。 SpringBootTest.WebEnvironment.DEFINED_PORT表示使用application.yml定義的端口,而不是隨機使用一個端口進行測試,這很重要。
  • Swagger2MarkupConfig 是輸出文件的配置,如文件的格式和文件中的自然語言等
  • Swagger2MarkupConverter的from表示哪一個HTTP服務作為資源導出的源頭(JSON格式),可以自己訪問試一下這個鏈接。8888是我的服務端口,需要根據你自己的應用配置修改。
  • toFile表示將導出文件存放的位置,不用加後綴名。也可以使用toFolder表示文件導出存放的路徑。二者區別在於使用toFolder導出為文件目錄下按標籤TAGS分類的多個文件,使用toFile是導出一個文件(toFolder多個文件的合集)。
@Test
public void generateMarkdownDocsToFile() throws Exception {
    //    輸出Markdown到單文件
    Swagger2MarkupConfig config = new Swagger2MarkupConfigBuilder()
            .withMarkupLanguage(MarkupLanguage.MARKDOWN)
            .withOutputLanguage(Language.ZH)
            .withPathsGroupedBy(GroupBy.TAGS)
            .withGeneratedExamples()
            .withoutInlineSchema()
            .build();

    Swagger2MarkupConverter.from(new URL("http://localhost:8888/v2/api-docs"))
            .withConfig(config)
            .build()
            .toFile(Paths.get("src/main/resources/docs/markdown"));
}

上面的這一段代碼是生成markdown格式接口文件的代碼。執行上面的2段單元測試代碼,就可以生產對應格式的接口文件。

還有一種方式是通過maven插件的方式,生成adoc和markdown格式的接口文件。筆者不常使用這種方式,沒有使用代碼生成的方式配置靈活,很多配置都放到pom.xml感覺很臃腫。但還是介紹一下,首先配置maven插件swagger2markup-maven-plugin。

<plugin>
    <groupId>io.github.swagger2markup</groupId>
    <artifactId>swagger2markup-maven-plugin</artifactId>
    <version>1.3.1</version>
    <configuration>
        <swaggerInput>http://localhost:8888/v2/api-docs</swaggerInput><!---swagger-api-json路徑-->
        <outputDir>src/main/resources/docs/asciidoc</outputDir><!---生成路徑-->
        <config>
            <swagger2markup.markupLanguage>ASCIIDOC</swagger2markup.markupLanguage><!--生成格式-->
        </config>
    </configuration>
</plugin>

然後運行插件就可以了,如下圖:

三、通過maven插件生成HTML文檔

<plugin>
    <groupId>org.asciidoctor</groupId>
    <artifactId>asciidoctor-maven-plugin</artifactId>
    <version>1.5.6</version>
    <configuration>
         <!--asciidoc文件目錄-->
        <sourceDirectory>src/main/resources/docs</sourceDirectory>
        <!---生成html的路徑-->
        <outputDirectory>src/main/resources/html</outputDirectory>
        <backend>html</backend>
        <sourceHighlighter>coderay</sourceHighlighter>
        <attributes>
            <!--導航欄在左-->
            <toc>left</toc>
            <!--显示層級數-->
            <!--<toclevels>3</toclevels>-->
            <!--自動打数字序號-->
            <sectnums>true</sectnums>
        </attributes>
    </configuration>
</plugin>

adoc的sourceDirectory路徑必須和第三小節中生成的adoc文件路徑一致。然後按照下圖方式運行插件。

HTMl接口文檔显示的效果如下,有了HTML接口文檔你想轉成其他各種格式的文檔就太方便了,有很多工具可以使用。這裏就不一一介紹了。

期待您的關注

  • 向您推薦博主的系列文檔:
  • 本文轉載註明出處(必須帶連接,不能只轉文字):。

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

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

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

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

科大訊飛研發的全球中文學習平台上線

  隨着中國國際影響力的日益提升,漢語學習的需求與日俱增,為此,教育部、國家語委在《國家語言文字事業“十三五”發展規劃》中明確提出要“建設適應面廣、影響力大、權威性強的全球中文學習網絡平台”。10 月 25 日,在教育部、國家語委的指導下,由科大訊飛研發的全球中文學習平台正式上線。

  該平台上線發布儀式在京舉行。教育部副部長、國家語委主任田學軍,北京市人民政府副秘書長韓耕、科大訊飛股份有限公司董事長劉慶峰和人民教育出版社社長黃強共同為平台發布舉行了啟動儀式,200 多位中外嘉賓見證了全球中文學習平台(www.chinese-learning.cn)的正式上線。

  針對海外學習者的“譯學中文”模塊,學習者可以通過語音或文本輸入其母語內容,實時翻譯出中文並自動分句。學習者學習每個語句的標準音並錄音跟讀,系統會實時反饋評價,指出發音問題;針對錯誤字詞,可以反覆學習,直到掌握正確中文發音。

  這是科大訊飛承建國家語委的又一個重大項目!2004 年,科大訊飛承擔了國家語委“十五”重點科研項目“智能語音技術在普通話輔助學習中的應用研究”;2016 年,承擔國家語委“十三五”重大科研項目“智能語音及人工智能技術在語言學習中的應用研究”。目前,上述兩大項目均已成功落地,並取得了良好的社會效益。

  全球中文學習平台,匯聚各類中文學習資源,以更好地為廣大中文學習者提供優質服務為宗旨,於 2016 年底啟動建設,是落實《國家語言文字事業“十三五”發展規劃》相關任務要求的具體舉措。在教育部、國家語委與科大訊飛的共同努力下,歷經兩年多時間的不斷完善和改進,平台建設取得积極成效,相關基礎研究取得重要進展,為平台提供了堅實技術保障。其中智能語音、智能寫作和批改等關鍵技術研究成果在中小學語言能力評價、少數民族國家通用語言學習等方面得到實際應用。平台示範功能已分別在“砥礪奮進的五年”大型成就展、第二屆語博會、第十二屆孔子學院大會等不同場合進行展示,得到了各方好評。

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

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

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

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