程序員實用JDK小工具歸納,工作用得到

在JDK的安用裝目錄bin下,有一些有非常實用的小工具,可用於分析JVM初始配置、內存溢出異常等問題,我們接下來將對些常用的工具進行一些說明。

JDK小工具簡介

在JDK的bin目錄下面有一些小工具,如javac,jar,jstack,jstat等,在日常編譯運行過程中有着不少的“額外”功能,那麼它們是怎麼工作的呢?雖然這些文件本身已經被編譯成可執行二進制文件了,但是其實它們的功能都是由tools.jar這個工具包(配合一些dll或者so本地庫)完成的,每個可執行文件都對應一個包含main函數入口的java類(有興趣可以閱讀openJDK相關的源碼,它們的對應關係如下(更多可去openJDK查閱):

javac com.sun.tools.javac.Main
jar sun.tools.jar.Main
jps sun.tools.jps.Jps
jstat sun.tools.jstat.Jstat
jstack    sun.tools.jstack.JStack
...

tools.jar的使用

我們一般開發機器上都會安裝JDK+jre,這時候,要用這些工具,直接運行二進制可執行文件就行了,但是有時候,機器上只有jre而沒有JDK,我們就無法用了么?

如果你知道如上的對應關係的話,我們就可以”構造”出這些工具來(當然也可以把JDK安裝一遍,本篇只是介紹另一種選擇),比如我們編寫

//Hello.java
public class Hello{
    public static void main(String[] args)throws Exception{
        while(true){
            test1();
            Thread.sleep(1000L);
        }
    }
    public static void test1(){
        test2();
    }
    public static void test2(){
        System.out.println("invoke test2");
    }
}

可以驗證如下功能轉換關係

1.編譯源文件:

javac Hello.java => java -cp tools.jar com.sun.tools.javac.Main Hello.java

結果一樣,都可以生成Hello.class文件
然後我們開始運行java -cp . Hello

2.查看java進程:

jps => java -cp tools.jar sun.tools.jps.Jps

結果一樣,如下:

4615 Jps
11048 jar
3003 Hello

3.動態查看內存:

jstat -gcutil 3003 100 3 => java -cp tools.jar sun.tools.jstat.Jstat -gcutil 3003 100 3

發現結果是一樣的

  S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
  0.00 0.00 4.00 0.00 17.42 19.65 0 0.000 0 0.000 0.000
  0.00 0.00 4.00 0.00 17.42 19.65 0 0.000 0 0.000 0.000
  0.00 0.00 4.00 0.00 17.42 19.65 0 0.000 0 0.000 0.000

4.查看當前運行棧信息
正常情況,執行如下命令結果也是一樣,可以正常輸出

jstack 3003 =》 java -cp tools.jar sun.tools.jstack.JStack 3003

但是有的jre安裝不正常的時候,會報如下錯誤

Exception in thread "main" java.lang.UnsatisfiedLinkError: no attach in java.library.path

這是因為jstack的運行需要attach本地庫的支持,我們需要在系統變量裏面配置上其路徑,假如路徑為/home/JDK/jre/bin/libattach.so
命令轉換成

jstack 3003 =》 java -Djava.library.path=/home/JDK/jre/bin -cp tools.jar sun.tools.jstack.JStack 3003

就可以實現了
在linux系統中是libattach.so,而在windows系統中是attach.dll,它提供了一個與本機jvm通信的能力,利用它可以與本地的jvm進行通信,許多java小工具就可能通過它來獲取jvm運行時狀態,也可以對jvm執行一些操作

attach使用

1. 編寫agent.jar代理包

  • 編寫一個Agent類
//Agent.java
public class Agent{
    public static void agentmain(String args, java.lang.instrument.Instrumentation inst) {
        System.out.println("agent : " + args);
    }
}
  • 編譯Agent
java -cp tools.jar com.sun.tools.javac.Main Agent.java
//或者
javac Agent.java
  • 再編manifest.mf文件
//manifest.mf
Manifest-Version: 1.0
Agent-Class: Agent
Can-Redefine-Classes: true
Can-Retransform-Classes: true
  • 把Agent.class和manifest.mf進行打包成agent.jar
java -cp tools.jar sun.tools.jar.Main -cmf manifest.mf agent.jar Agent.class
//或者
jar -cmf manifest.mf agent.jar Agent.class

2.attach進程

  • 編寫如下attach類,編譯並執行
//AttachMain.java
public class AttachMain {
    public static void main(String[] args) throws Exception {
        com.sun.tools.attach.VirtualMachine vm = com.sun.tools.attach.VirtualMachine.attach(args[0]);
        vm.loadAgent("agent.jar", "inject params");
        vm.detach();
    }
}
  • 編譯:
java -cp tools.jar com.sun.tools.javac.Main -cp tools.jar AttachMain.java
//或者
javac -cp tools.jar AttachMain.java
  • 執行attach
java -cp .:tools.jar AttachMain 3003
  • 查看Hello進程有如下輸出:
invoke test2
invoke test2
invoke test2
invoke test2
invoke test2
invoke test2
invoke test2
agent : inject params
invoke test2

說明attach成功了,而且在目標java進程中引入了agent.jar這個包,並且在其中一個線程中執行了manifest文件中agentmain類的agentmain方法,詳細原理可以見JVMTI的介紹,例如oracle的介紹

3. 用attach製作小工具

  • 寫一個使進程OutOfMemory/StackOverFlow的工具
    有了attach的方便使用,我們可以在agentmain中新起動一個線程(為避免把attach線程污染掉),在裏面無限分配內存但不回收,就可以產生OOM或者stackoverflow
    代碼如下:
//Agent.java for OOM
public class Agent{
    public static void agentmain(String args, java.lang.instrument.Instrumentation inst) {
        new Thread() {
            @Override
            public void run() {
                java.util.List<byte[]> list = new java.util.ArrayList<byte[]>();
                try {
                    while(true) {
                        list.add(new byte[100*1024*1024]);
                        Thread.sleep(100L);
                    }
                } catch (InterruptedException e) {
                }
            }
        }.start();
    }
}
//Agent.java for stackoverflow
public class Agent{
    public static void agentmain(String args, java.lang.instrument.Instrumentation inst) {
        new Thread() {
            @Override
            public void run() {
                stackOver();
            }
            private void stackOver(){
                stackOver();
            }
        }.start();
    }
}

當測試OOM的時候,hello進程的輸出為:

invoke test2
Exception in thread "Thread-0" java.lang.OutOfMemoryError: Java heap space
        at Agent$1.run(Agent.java:9)
invoke test2
invoke test2
invoke test2

說明發生OOM了, 但是OOM線程退出了,其它線程還在正常運行。

如果我們需要進程在OOM的時候產生一些動作,我們可以在進程啟動的時候增加一些OOM相關的VM參數

  • OOM的時候直接kill掉進程:-XX:OnOutOfMemoryError=”kill -9 %p”
    結果如下:
invoke test2
invoke test2
#
# java.lang.OutOfMemoryError: Java heap space
# -XX:OnOutOfMemoryError="kill -9 %p"
#   Executing /bin/sh -c "kill -9 26829"...
Killed
  • OOM的時候直接退出進程:-XX:+ExitOnOutOfMemoryError
    結果如下:
invoke test2
invoke test2
Terminating due to java.lang.OutOfMemoryError: Java heap space
  • OOM的時候進程crash掉:-XX:+CrashOnOutOfMemoryError
    結果如下:
invoke test2
invoke test2
Aborting due to java.lang.OutOfMemoryError: Java heap space
invoke test2#
# A fatal error has been detected by the Java Runtime Environment:
#
#  Internal Error (debug.cpp:308)
, pid=42675, tid=0x00007f3710bf4700
#  fatal error: OutOfMemory encountered: Java heap space
#
# JRE version: Java(TM) SE Runtime Environment (8.0_171-b11) (build 1.8.0_171-b11)
# Java VM: Java HotSpot(TM) 64-Bit Server VM (25.171-b11 mixed mode linux-amd64 compressed oops)
# Failed to write core dump. Core dumps have been disabled. To enable core dumping, try "ulimit -c unlimited" before starting Java again
#
# An error report file with more information is saved as:
# /root/hanlang/test/hs_err_pid42675.log
#
# If you would like to submit a bug report, please visit:
#   http://bugreport.java.com/bugreport/crash.jsp
#
Aborted
  • OOM的時候dump內存:-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/dump.hprof
    結果生成dump文件

asm的應用

1.asm使用原理

asm是一個java字節碼工具,提供一種方便的函數/屬性級別修改已經編譯好的.class文件的方法, asm的簡單使用原理介紹如下:

  • 通過ClassReader讀取.class文件的字節碼內容,並生成語法樹;
  • ClassReader的方法accept(ClassVisitor classVisitor, int parsingOptions)功能是讓classVisitor遍歷語法樹,默認ClassVisitor是一個代理類,需要有一個具體的實現在遍歷語法樹的時候做一些處理;
  • 用ClassWriter是ClassVisitor的一個實現,它的功能是把語法樹轉換成字節碼;
  • 通常我們會定義一個自己的ClassVisitor,可以重寫裏面的一些方法來改寫類處理邏輯,然後讓ClassWriter把處理之後的語法樹轉換成字節碼;

2.下面是具體的實現步驟:

  • 引入asm依賴包
<dependency>
    <groupId>org.ow2.asm</groupId>
    <artifactId>asm</artifactId>
    <version>7.0</version>
</dependency>
<dependency>
    <groupId>org.ow2.asm</groupId>
    <artifactId>asm-commons</artifactId>
    <version>7.0</version>
</dependency>
//或者引入如下包
asm-commons-7.0.jar
asm-analysis-7.0.jar
asm-tree-7.0.jar
asm-7.0.jar
  • 定義一個ClassVisitor,功能是在所有方法調用前和調用後分別通過System.out.println打印一些信息
    輸入為字節碼,輸出也是字節碼
//MyClassVisitor.java
public class MyClassVisitor extends ClassVisitor {
    private static final Type SYSTEM;
    private static final Type OUT;
    private static final Method PRINTLN;
    static {
        java.lang.reflect.Method m = null;
        try {
            m = PrintStream.class.getMethod("println", new Class<?>[] {String.class});
        } catch (Exception e) {
        }
        SYSTEM = Type.getType(System.class);
        OUT = Type.getType(PrintStream.class);
        PRINTLN = Method.getMethod(m);
    }

    private String cName;

    public MyClassVisitor(byte[] bytes) {
        super(Opcodes.ASM7, new ClassWriter(ClassWriter.COMPUTE_FRAMES));
        new ClassReader(bytes).accept(this, ClassReader.EXPAND_FRAMES);
    }
    String format(String name) {
        return name.replaceAll("<", "_").replaceAll("\\$|>", "");
    }
    @Override
    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
        cName = format(name);
        super.visit(version, access, name, signature, superName, interfaces);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        if ((access & 256) != 0) {
            return super.visitMethod(access, name, desc, signature, exceptions);
        }
        return new MyMethodAdapter(super.visitMethod(access, name, desc, signature, exceptions), access, name, desc);
    }

    public byte[] getBytes() {
        return ((ClassWriter) cv).toByteArray();
    }

    class MyMethodAdapter extends AdviceAdapter {
        private String mName;

        public MyMethodAdapter(MethodVisitor methodVisitor, int acc, String name, String desc) {
            super(Opcodes.ASM7, methodVisitor, acc, name, desc);
            this.mName = format(name);
        }

        @Override
        protected void onMethodEnter() {
            getStatic(SYSTEM, "out", OUT);
            push(cName + "." + mName + " start");
            this.invokeVirtual(OUT, PRINTLN);
        }

        @Override
        protected void onMethodExit(int opcode) {
            getStatic(SYSTEM, "out", OUT);
            push(cName + "." + mName + " end");
            this.invokeVirtual(OUT, PRINTLN);
        }
    }
}
  • 定義一個簡單的classLoader來加載轉換后的字節碼
//MyLoader.java
class MyLoader extends ClassLoader {
    private String cname;
    private byte[] bytes;
    public MyLoader(String cname, byte[] bytes) {
        this.cname = cname;
        this.bytes = bytes;
    }

    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        Class<?> clazz = null;
        if (clazz == null && cname.equals(name)) {
            try {
                clazz = findClass(name);
            } catch (ClassNotFoundException e) {
            }
        }
        if (clazz == null) {
            clazz = super.loadClass(name, resolve);
        }
        return clazz;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        Class<?> clazz = this.findLoadedClass(name);
        if (clazz == null) {
            clazz = defineClass(name, bytes, 0, bytes.length);
        }
        return clazz;
    }
}
  • 加載轉換Hello類,然後反向調用其方法

//將如下main函數加入MyClassVisitor.java中

public static void main(String[] args) throws Exception {
    try (InputStream in = Hello.class.getResourceAsStream("Hello.class")) {
        byte[] bytes = new byte[in.available()];
        in.read(bytes);
        String cname = Hello.class.getName();
        Class<?> clazz = new MyLoader(cname, new MyClassVisitor(bytes).getBytes()).loadClass(cname);
        clazz.getMethod("test1").invoke(null);
    }
}
  • 編譯
java -cp tools.jar com.sun.tools.javac.Main -cp asm-commons-7.0.jar:asm-analysis-7.0.jar:asm-tree-7.0.jar:asm-7.0.jar:. *.java
//或者
javac -cp asm-commons-7.0.jar:asm-analysis-7.0.jar:asm-tree-7.0.jar:asm-7.0.jar:. *.java
  • 運行
java -cp asm-commons-7.0.jar:asm-analysis-7.0.jar:asm-tree-7.0.jar:asm-7.0.jar:. MyClassVisitor
//結果如下:
Hello.test1 start
Hello.test2 start
invoke test2
Hello.test2 end
Hello.test1 end

asm的使用很廣泛,最常用的是在spring aop裏面切面的功能就是通過asm來完成的

3. 利用asm與Instrument製作調試工具

  • Instrument工具

Instrument類有如下方法,可以增加一個類轉換器

addTransformer(ClassFileTransformer transformer, boolean canRetransform)

執行如下方法的時候,對應的類將會被重新定義

retransformClasses(Class<?>... classes)
  • 與asm配合使用
    當我們修改Agent.java代碼為下面內容
//Agent
public class Agent {
    public static void agentmain(String args, Instrumentation inst) {
        try {
            URLClassLoader loader = (URLClassLoader)Agent.class.getClassLoader();
            Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
            method.setAccessible(true);//代碼級引入依賴包
            method.invoke(loader, new File("asm-7.0.jar").toURI().toURL());
            method.invoke(loader, new File("asm-analysis-7.0.jar").toURI().toURL());
            method.invoke(loader, new File("asm-tree-7.0.jar").toURI().toURL());
            method.invoke(loader, new File("asm-commons-7.0.jar").toURI().toURL());
            inst.addTransformer(new ClassFileTransformer() {
                @Override
                public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
                    ProtectionDomain protectionDomain, byte[] bytes) {
                    return new MyClassVisitor(bytes).getBytes();
                }
            }, true);
            inst.retransformClasses(Class.forName("Hello"));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  • 編譯並打包成agent.jar
//編譯
javac -cp asm-commons-7.0.jar:asm-analysis-7.0.jar:asm-tree-7.0.jar:asm-7.0.jar:. *.java
//打包
jar -cmf manifest.mf agent.jar MyLoader.class MyClassVisitor.class MyClassVisitor\$MyMethodAdapter.class Agent.class Agent\$1.class
  • attach進程修改字節碼
//執行
java -cp .:tools.jar AttachMain 3003
//執行前後Hello進程的輸出變化為
invoke test2
invoke test2
invoke test2
Hello.test1 start
Hello.test2 start
invoke test2
Hello.test2 end
Hello.test1 end
Hello.test1 start
Hello.test2 start
invoke test2
Hello.test2 end
Hello.test1 end

利用asm及instrument工具來實現熱修改字節碼現在有許多成熟的工具,如btrace(https://github.com/btraceio/btrace,jvm-sandbox https://github.com/alibaba/jvm-sandbox)

 

點擊關注,第一時間了解華為雲新鮮技術~

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

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

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

※台北網頁設計公司全省服務真心推薦

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

※推薦評價好的iphone維修中心

網頁設計最專業,超強功能平台可客製化

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

墨西哥地震規模7.4釀5死 震央附近交通中斷傷亡恐增

摘錄自2020年6月24日中央社報導

墨西哥南部太平洋岸今(23日)發生規模7.4強震,造成至少5人喪生,偏遠村落交通中斷,地震威力之強,連遠在震央數百公里外的首都墨西哥市(Mexico City)都有建築物受損。

這起強震震央在瓦哈卡州(Oaxaca)附近,死傷也是在這裡傳出。由於州首府瓦哈卡市(Oaxaca)與海岸間的蜿蜒山路因落石中斷,搜救人員尚未抵達受災村落,傷亡可能更進一步攀升。

社群媒體影像顯示,震央附近山村的診所與老舊教堂也都嚴重受損。

美國地質調查所(USGS)表示,這起地震震源深度僅26公里,屬淺層地震。美國地質調查所初步測得規模達7.7,稍後下修為7.4。

土地水文
土地利用
國際新聞
墨西哥
地震
災害

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計最專業,超強功能平台可客製化

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

※回頭車貨運收費標準

※推薦評價好的iphone維修中心

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

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

台中搬家公司費用怎麼算?

委內瑞拉燃油短缺 單車通勤成生活新常態

摘錄自2020年6月19日公視報導

全球儲油量第一的委內瑞拉,卻因為美國加強禁運和經濟制裁,國內陷入石油危機,民眾排隊加油竟然要等上三天三夜,也促使許多人開始改以自行車代步。

在委內瑞拉,石油長年比水還便宜,民眾大多開車上班,現在因為沒油可買,許多民眾轉而騎單車通勤。自行車技師塞古拉表示:「修理自行車的量變多了,我計算至少多出20至30倍」

該地煉油設備老舊到不堪使用,過去以原油和俄羅斯交換燃油,如今交易也因為受到美國制裁而停擺,面臨歷年來最嚴重的汽油短缺,更有民眾說,沒油可用比新冠病毒更讓人苦惱。

現在委內瑞拉最低月薪3塊美金左右,台幣不到100塊,等於只夠買六公升左右的汽油,無疑是生活一大負擔,也難怪騎單車通勤,成了越來越多委內瑞拉民眾生活的新常態。

生活環境
能源議題
能源轉型
國際新聞
委內瑞拉
單車
自行車
石油危機
石油
交通運輸

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※產品缺大量曝光嗎?你需要的是一流包裝設計!

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

※回頭車貨運收費標準

※推薦評價好的iphone維修中心

※超省錢租車方案

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

※推薦台中搬家公司優質服務,可到府估價

為綠能發電犧牲演化寶庫 水壩工程威脅印尼古老波索湖

環境資訊中心綜合外電;黃鈺婷 翻譯;林大利 審校;稿源:Mongabay

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※回頭車貨運收費標準

※產品缺大量曝光嗎?你需要的是一流包裝設計!

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

※推薦評價好的iphone維修中心

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

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

用隊列實現棧,用棧實現隊列,聽起來有點繞,都搞懂了就掌握了精髓!

目錄

  • 一、背景
  • 二、概念
    • 2.1 棧
    • 2.2 隊列
  • 三、棧和隊列的相互實現
    • 3.1 用隊列實現棧
    • 3.2 用棧實現隊列
  • 四、總結

一、背景

棧和隊列是數據結構中最常用到的兩種結構,有非常廣泛的運用,該篇文章將通過動畫的手段,展示棧和隊列相互實現的底層原理,讓我們真正搞懂棧和隊列的特性。

二、概念

2.1 棧

棧[Stack]:是一種限定僅在表尾進行插入和刪除操作的線性表;即後進先出(LIFO-last in first out),最後插入的元素最先出來

  • 入棧(push)
  • 出棧 (pop)
2.2 隊列

 隊列[Queue]:是一種限定僅在表頭進行刪除操作,僅在表尾進行插入操作的線性表;即先進先出(FIFO-first in first out):最先插入的元素最先出來。

  • 入隊(enqueue)
  • 出隊(dequeue)

三、棧和隊列的相互實現

3.1 用隊列實現棧
  • 模擬入棧的實現原理
    — 棧的特性是新加入的元素出現在棧頂,保證後進先出。
    — 隊列的特性為新加入的元素出現在隊尾,隊列的隊尾元素最後出隊。
    按以上兩個前提,我們可以讓隊頭至隊尾前的其它所有元素依次出隊再入隊,直至在隊尾新加入的元素被移到隊頭,也即實現了讓新壓入的元素保留在棧頂

  • 模擬出棧的實現原理
    — 由於在入棧時保證隊列中新加入隊尾的元素被移到了隊頭,出棧只需彈出隊頭元素即可。

  • 完整代碼實現

/**
 * 用隊列模擬實現棧
 *
 * @author zhuhuix
 * @date 2020-06-09
 */
public class QueueImplStack {

    // 定義隊列
    private Queue<Integer> queue;

    public QueueImplStack() {
        queue = new LinkedList();
    }

    // 入棧--在隊尾加入元素后,讓其他元素按順序出隊再入隊,保持新加入的元素永遠在隊頭
    public void push(Integer e) {
        queue.offer(e);
        int size = queue.size();
        int i = 0;
        while (i < size - 1) {
            queue.offer(queue.poll());
            i++;
        }
    }

    // 出棧--將隊尾前的其它所有元素出隊再入隊,直至隊尾元素移到隊頭
    public Integer pop() {
        return queue.poll();
    }

    // 查看棧頂元素--即隊頭元素
    public Integer peek() {
        return queue.peek();
    }

    // 是否為空
    public boolean isEmpty() {
        return queue.isEmpty();
    }

    public static void main(String[] args) {
        QueueImplStack stack = new QueueImplStack();
        stack.push(1);
        System.out.println(stack.peek());
        stack.push(2);
        System.out.println(stack.peek());
        stack.push(3);
        System.out.println(stack.peek());
        System.out.println("=============");
        System.out.println(stack.pop());
        System.out.println(stack.pop());
        System.out.println(stack.pop());
        System.out.println(stack.isEmpty());

    }
}

3.2 用棧實現隊列
  • 模擬入隊的實現原理
    — 隊列的特性最新入隊的元素需排在隊尾,最先入隊的元素排在隊頭,按隊頭到隊尾的順序依次出隊。
    — 對應到棧的數據結構上,也即需將新加入的元素保留在棧頂,保證先進先出。
    按以上兩個前提,需在存放數據的棧的基礎上再增加一個輔助棧,在每次入隊時,先將存放數據的棧彈入輔助棧,再把需加入的新元素壓入數據棧底,最後把輔助棧中的元素彈出依次壓入數據棧,這樣保證了新加入的元素,沉在棧底。

    模擬出隊的實現原理
    — 由於在入隊時,通過數據棧與輔助棧的交換,實現了后加入的元素沉在棧底,先進入的元素保留在棧頂,直接通過出棧彈出即可。

    • 完整代碼實現
/**
 * 用棧模擬實現隊列
 *
 * @author zhuhuix
 * @date 2020-06-09
 */
public class StackImplQueue {
    // 數據棧
    private Stack<Integer> stack;
    // 輔助棧
    private Stack<Integer> aux;

    StackImplQueue() {
        stack = new Stack<>();
        aux = new Stack<>();
    }

    // 入隊--通過數據棧與輔助棧相互交換,保證新加入的元素沉在數據棧底
    public void enqueue(Integer e) {
        while (!stack.isEmpty()) {
            aux.push(stack.pop());
        }
        stack.push(e);
        while(!aux.isEmpty()){
            stack.push(aux.pop());
        }

    }

    // 出隊--彈出數據棧元素
    public Integer dequeue(){
        return stack.pop();
    }

    // 查看隊頭元素
    public Integer peek(){
        return stack.peek();
    }

    // 是否為空
    public boolean isEmpty(){
        return stack.isEmpty();
    }

    public static void main(String[] args) {
        StackImplQueue queue = new StackImplQueue();
        queue.enqueue(1);
        System.out.println(queue.peek());
        queue.enqueue(2);
        System.out.println(queue.peek());
        queue.enqueue(3);
        System.out.println(queue.peek());
        System.out.println("=============");
        System.out.println(queue.dequeue());
        System.out.println(queue.dequeue());
        System.out.println(queue.dequeue());

    }
}

四、總結

通過以上棧和隊列相互交叉的實踐,我們對棧和隊列的重大特性有了深入了解:

  • 棧和隊列都是線性連續結構,增加和刪除元素不會影響破此連續性
  • 棧通過棧頂的操作實現元素的增加與刪除,也即只能在一端進行操作
  • 隊列通過隊尾增加元素,隊頭刪除元素,也即可以在兩端操作

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

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

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

※台北網頁設計公司全省服務真心推薦

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

※推薦評價好的iphone維修中心

網頁設計最專業,超強功能平台可客製化

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

香港環署縱容 回收亂龍

摘錄自2020年6月22日東方日報香港報導

歷時一年多的反送中條例加上疫情衝擊,雖然近日稍為平息,卻凸顯出香港政府環保政策漏洞。回收玻璃瓶行業開始恢復,但卻屢發越區搶收違規行為,環境保護署袖手旁觀。

政府按港九新界地區批出三份玻璃管理合約,由兩個承辦商承辦免市場壟斷,然而近期九龍區接連發生跨區「插旗」收瓶,更有承辦商司機越區偷瓶事件,負責監管的環保署卻沒介入糾正,使得分區收瓶制度名存實亡。

另外,廢棄塑膠回收業在沒有管理規劃下,近期大批回收店關門,令大批廢棄塑膠沒人要。有地方議員批評,署方監管不力,默許發生打擊對手的行為,長遠恐怕出現市場壟斷惡果,窒礙本地環保業發展。

公害污染
污染治理
國際新聞
香港
回收
玻璃回收
廢棄物

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計最專業,超強功能平台可客製化

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

※回頭車貨運收費標準

※推薦評價好的iphone維修中心

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

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

台中搬家公司費用怎麼算?

英醫學權威警告政界 小心出現第二波疫情

摘錄自2020年6月24日中央廣播電台報導

路透社今天(24日)報導,資深醫界人士警告英國各政黨,當地俗稱武漢肺炎的2019年冠狀病毒疾病(COVID-19)疫情可能升溫,而且第二波疫情有實質風險。

這些醫學界人士在一封寫給英國政界領袖的公開信中說:「雖然這場疫情未來在英國的情勢難以預估,現有證據顯示,當地爆發疫情的可能性愈來愈大,而第二波(疫情)有實質風險。」

在「英國醫學期刊」(British Medical Journal, BMJ)上簽署這封公開信的人士,包含英國皇家外科醫學院(Royal College of Surgeons)院長奧德森(Derek Alderson)、皇家內科醫學院(Royal College of Physicians)院長戈達(Andrew Goddard),以及皇家緊急醫藥大學院長韓德森(Katherine Henderson)。

他們表示:「遏止病毒必備的許多基礎要素已準備就緒,但重大的挑戰依然存在。」

生活環境
國際新聞
英國
武漢肺炎
疫情
新冠肺炎疫情
公共衛生

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※產品缺大量曝光嗎?你需要的是一流包裝設計!

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

※回頭車貨運收費標準

※推薦評價好的iphone維修中心

※超省錢租車方案

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

※推薦台中搬家公司優質服務,可到府估價

封城後空氣污染大減 增印度德里太陽能產電量達 8%

摘錄自2020年6月24日立場新聞報導

武漢肺炎(COVID-19)下,幾乎全球國家都史無前例封城停擺,經濟發展停滯,但卻拯救了無數生命免受感染,也間接令空氣污染大幅減輕,造成意料不到的連鎖效應。最新刊於 Joule 的研究指,空氣污染減輕,令更多陽光直達太陽能電池板,增加了電力產量。

由德國基爾亥姆霍茲可再生能源研究所物理學家 Ian Marius Peters 領導的團隊,分析了疫情期間印度德里的太陽能發電量變化。團隊在疫情前已在多個城市研究霧霾和空氣污染如何阻擋陽光到達地面,以及其對太陽能電池板輸出的影響。

團隊以全天太陽輻射計(pyranometer)測量指定太陽光波長的輻射強度,並使用過去的研究數據,計算了德里到達地面的日照量變化。印度德里是全世界其中一個污染最嚴重的城市,團隊發現,與 2017 年至 2019 年同一段時間數據相比,德里 3 月 24 日封城後,空氣污染量下跌 45%–50% ,而3月下旬德里太陽能電池板接收到的陽光量增加了約 8% , 4 月則增加了 6% ,團隊推斷,污染減少是更多陽光照射電池板的主要原因。

公害污染
空氣污染
污染治理
國際新聞
印度德里
太陽能
COVID-19

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※回頭車貨運收費標準

※產品缺大量曝光嗎?你需要的是一流包裝設計!

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

※推薦評價好的iphone維修中心

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

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

這台後驅SUV只要5萬多,而且還是大廠出品

實際動力表現來看,這副發動機的低扭會略有不足,在2000轉時感覺動力並不是很充沛,上到3000轉以後動力會有所改善。不過,這種小排量發動機最大的問題就是後勁不足,上到100km/h之後就很難有什麼作為。底盤表現幻速S2的前懸架使用麥弗遜式獨立懸架,而後懸架為五連桿式非獨立懸架。

各個級別的SUV市場早已進入百家爭鳴的狀態,小型SUV市場更是如此,各路廠商都會使出渾身解數想搶佔這個市場,北汽也不例外。它們旗下的幻速S2可以說是一款重要產品,下面編者我就給大家分析一下幻速S2。

之所以要寫幻速S2是因為熱門的小型SUV大家已聽了不少,而幻速S2這種中規中矩的產品大家可能了解較少,但銷量並不能很好地反應出其真實產品力,因此編者我想給大家好好科普一下。

北汽銀翔-北汽幻速S2

指導價:5.18-6.08萬

外觀設計

幻速S2的車身尺寸為4250*1730*1735mm,軸距為2560mm。從前臉看,引擎蓋上由兩側向中間匯聚的凹線讓前臉有個很好的中心感。兩側向側面上揚的大燈組,看起來有一點點關二哥的威武感。

側面來看,車身顯得較為緊湊,門把手上方的腰線讓側面不至於過於單調。不過,輪胎的尺寸略微偏小。

後方來看,備胎外漏的設計野性十足,地台的高度較低,容易裝卸貨物。整個尾部給人一種方正厚實的感覺。

內飾設計

中控台的設計相當平庸也符合這個價位應有的水平,不過布局還是比較整齊,上手難度不高。擋把上的烤黑鋼琴漆帶來一絲亮點。

動力總成

幻速S2搭載了1.5L自然吸氣發動機+5擋手動變速箱,最大輸出113馬力和150牛米。實際動力表現來看,這副發動機的低扭會略有不足,在2000轉時感覺動力並不是很充沛,上到3000轉以後動力會有所改善。不過,這種小排量發動機最大的問題就是後勁不足,上到100km/h之後就很難有什麼作為。

底盤表現

幻速S2的前懸架使用麥弗遜式獨立懸架,而後懸架為五連桿式非獨立懸架。具體表現來看,幻速S2的底盤會偏硬一些,對震動的過濾並不是很到位,尤其面對連續震動會有點應付不過來。

乘坐空間

SUV在空間表現中會有一定的優勢,幻速S2也不例外。身高175cm的體驗者坐於後排,能有兩拳左右的腿部空間和4指左右的頭部空間,這個表現還是不錯的。

由於幻速S2是一款后驅車,所以它的後排中央拱起有點高,對乘坐有點影響,同時後排中央也缺乏頭枕,看來對後排中央的乘客並不友好。

油耗與保養費用

多位車主反映的幻速S2百公里綜合油耗為8.3L,對於它這個級別的產品來說,還是有一點點偏高。

保養費用方面,幻速S2的6萬公里總保養費用為4914元,其中更換機油機濾的價格為220元,這個價錢還是比較實惠的。

配置分析

幻速S2總共有8個車型可以買,但是只有4個版本,每個版本都有國四和國五的車型,這點要注意。

經過分析,我推薦買指導價為5.78萬的手動舒適型,這也是次低配的車型。因為它雖然比最低配貴了5000元,但是多了副駕駛安全氣囊、車頂行李架、倒車雷達、后視鏡電動調節和后雨刷。這堆配置之中,我覺得最重要的是副駕駛安全氣囊,這也是給家人朋友的一個保障。

編者總結:

幻速S2是小型SUV中少有的后驅車型,所以它的操控性會好一點,前輪胎的磨損也不會那麼快。如果你有一個后驅夢,而預算又正好卡在這個區間,何不買台幻速S2。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

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

※台北網頁設計公司全省服務真心推薦

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

※推薦評價好的iphone維修中心

網頁設計最專業,超強功能平台可客製化

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

Spring Boot 教程 – Elasticsearch

1. Elasticsearch簡介

Elasticsearch是一個基於Lucene的搜索服務器。它提供了一個分佈式多用戶能力的全文搜索引擎,基於RESTful web接口。Elasticsearch是用Java語言開發的,並作為Apache許可條款下的開放源碼發布,是一種流行的企業級搜索引擎。Elasticsearch用於雲計算中,能夠達到實時搜索,穩定,可靠,快速,安裝使用方便。官方客戶端在Java、.NET(C#)、PHP、Python、Apache Groovy、Ruby和許多其他語言中都是可用的。根據DB-Engines的排名显示,Elasticsearch是最受歡迎的企業搜索引擎,其次是Apache Solr,也是基於Lucene。以後再給大家詳細介紹solr。

它能很方便的使大量數據具有搜索、分析和探索的能力。充分利用Elasticsearch的水平伸縮性,能使數據在生產環境變得更有價值。Elasticsearch 的實現原理主要分為以下幾個步驟,首先用戶將數據提交到Elasticsearch 數據庫中,再通過分詞控制器去將對應的語句分詞,將其權重和分詞結果一併存入數據,當用戶搜索數據時候,再根據權重將結果排名,打分,再將返回結果呈現給用戶。

Elasticsearch可以用於搜索各種文檔。它提供可擴展的搜索,具有接近實時的搜索,並支持多租戶。”Elasticsearch是分佈式的,這意味着索引可以被分成分片,每個分片可以有0個或多個副本。每個節點託管一個或多個分片,並充當協調器將操作委託給正確的分片。再平衡和路由是自動完成的。“相關數據通常存儲在同一個索引中,該索引由一個或多個主分片和零個或多個複製分片組成。一旦創建了索引,就不能更改主分片的數量。

Elasticsearch使用Lucene,並試圖通過JSON和Java API提供其所有特性。它支持facetting和percolating,如果新文檔與註冊查詢匹配,這對於通知非常有用。另一個特性稱為“網關”,處理索引的長期持久性;例如,在服務器崩潰的情況下,可以從網關恢復索引。Elasticsearch支持實時GET請求,適合作為NoSQL數據存儲,但缺少分佈式事務。

2. Elasticsearch深入了解

2.1 Elasticsearch的底層實現

  • 2.1.1 lucene

    Es是一個比較複雜的搜索服務器,本身也是使用Java語言編寫的,在上面的簡介中,說明了ES是一個基於lucene的搜索服務器,lucene是什麼呢?Lucene是apache軟件基金會4 jakarta項目組的一個子項目,是一個開放源代碼的全文檢索引擎工具包,但它不是一個完整的全文檢索引擎,而是一個全文檢索引擎的架構,提供了完整的查詢引擎和索引引擎,部分文本分析引擎。lucene也是使用Java語言編寫的,Java天下第一!

    Lucene是一套用於全文檢索和搜尋的開源程式庫,由Apache軟件基金會支持和提供。Lucene提供了一個簡單卻強大的應用程式接口,能夠做全文索引和搜尋。在Java開發環境里Lucene是一個成熟的免費開源工具。就其本身而言,Lucene是當前以及最近幾年最受歡迎的免費Java信息檢索程序庫。至於lucene到底是怎麼實現的,牛牛們可能要自己去百度或者谷歌一下啦。

  • 2.1.2 Elasticsearch的基本概念

    1. 集群(Cluster):就是多台ES服務器在一起構成搜索服務器,現在很多應用基本上都有集群的概念,提高性能,讓應用具有高可用性,一台服務器掛掉,可以很快有另一台ES服務器補上。

    2. 節點(Node):節點就是集群中的某一台ES服務器就稱為一個節點。

    3. 索引庫(Index Indices):就是ES服務器上的某一個索引,相當於Mysql數據庫中的數據庫的概念,一個節點可以有很多個索引庫。

    4. 文檔類型(Type):這個概念就相當於Mysql數據庫中表的概念,一個索引庫可以有很多個文檔類型,但是這個概念現在慢慢淡化了,因為在ES中一個索引庫直接存數據文檔就挺好的,這個概念現在來說有點多餘了,所以ES官方也在淡化這個概念,在ES8中,這個概念將會徹底的消失。

    5. 文檔(Doc):文檔就相當於Mysql是數據庫中某個表的一條數據記錄,現在ES已經到7.7版本了,我們也就忽略type這個概念,直接在索引庫中存文檔即可。另外需要說一下,我們一般把數據文檔存到Es服務器的某個索引庫的這個動作稱之為索引

      最後還有兩個比較重要的概念,但是可能不是那麼直觀的可以感受得到:

      分片(Shards)和副本(Replicas)

      索引可能會存儲大量數據,這些數據可能超過單個節點的硬件限制。例如,十億個文檔的單個索引佔用了1TB的磁盤空間,可能不適合單個節點的磁盤,或者可能太慢而無法單獨滿足來自單個節點的搜索請求。

      為了解決此問題,Elasticsearch提供了將索引細分為多個碎片的功能。創建索引時,只需定義所需的分片數量即可。每個分片本身就是一個功能齊全且獨立的“索引”,可以託管在群集中的任何節點上。

      分片很重要,主要有兩個原因:

      • 它允許您水平分割/縮放內容量
      • 它允許您跨碎片(可能在多個節點上)分佈和并行化操作,從而提高性能/吞吐量

      分片如何分佈以及其文檔如何聚合回到搜索請求中的機制由Elasticsearch完全管理,並且對您作為用戶是透明的。

      在隨時可能發生故障的網絡/雲環境中,非常有用,強烈建議您使用故障轉移機制,以防碎片/節點因某種原因脫機或消失。為此,Elasticsearch允許您將索引分片的一個或多個副本製作為所謂的副本分片(簡稱副本)。

      複製很重要,主要有兩個原因:

      • 如果分片/節點發生故障,它可提供高可用性。因此,重要的是要注意,副本碎片永遠不會與從其複製原始/主要碎片的節點分配在同一節點上。
      • 由於可以在所有副本上并行執行搜索,因此它可以擴展搜索量/吞吐量。

      總而言之,每個索引可以分為多個碎片。索引也可以複製零(表示沒有副本)或多次。複製后,每個索引將具有主碎片(從中進行複製的原始碎片)和副本碎片(主碎片的副本)。可以在創建索引時為每個索引定義分片和副本的數量。創建索引后,您可以隨時動態更改副本數,但不能事後更改分片數。

      默認情況下,Elasticsearch中的每個索引分配有5個主碎片和1個副本,這意味着如果集群中至少有兩個節點,則索引將具有5個主碎片和另外5個副本碎片(1個完整副本),總共每個索引10個碎片。

  • 2.1.3 Elasticsearch的索引原理

    Es作為一個全文檢索服務器,那麼它在搜索方面肯定很在行啦!那它是怎麼做到的呢?

    Es官方有這麼一句話:一切設計都是為了提高搜索的性能!

    Es能夠快速的搜索出我們需要的內容,靠的就是倒排索引的思想,或者說是一種設計!

    在沒有使用倒排索引的情況下,正常思路是根據搜索關鍵字去查找相應的內容,但是使用了倒排索引之後,ES會先將文檔的所有內容拆分成多個詞條,創建一個包含所有不重複詞條的排序列表,然後列出每個詞條出現在哪個文檔。

    例如,假設我們有兩個文檔,每個文檔的 content 域包含如下內容:

    ​ Doc_1:The quick brown fox jumped over the lazy dog

    ​ Doc_2:Quick brown foxes leap over lazy dogs in summer

    ES首先會將這兩個文檔拆分成多個單獨的詞,或者叫做詞條,然後為所有的詞條創建一個排序列表,並記錄每個詞條出現的文檔的信息。就像下面這樣:

    Term      Doc_1  Doc_2
    -------------------------
    Quick   |       |  X                        /*
    The     |   X   |								Term就是詞條,比如第一個Term就是Quick關鍵字,在Doc_1中不存
    brown   |   X   |  X							在,在Doc_2中存在,其他的以此類推。
    dog     |   X   |							*/
    dogs    |       |  X
    fox     |   X   |
    foxes   |       |  X
    in      |       |  X
    jumped  |   X   |
    lazy    |   X   |  X
    leap    |       |  X
    over    |   X   |  X
    quick   |   X   |
    summer  |       |  X
    the     |   X   |
    ------------------------
    

    現在,如果我們想搜索 quickbrown這兩個關鍵字,我們只需要查找包含每個詞條的文檔,就相當於我們查詢的時候,是通過這個索引表找到文檔,在通過文檔去找文檔內容中的搜索關鍵字,與傳統的通過關鍵字去找內容是不同的。

    倒排索引到底是個怎麼實現的,怎麼個思想,我在這裏就不一一說明了,大家可以看下官方的詳細介紹:倒排索引的原理

    還有es官方的一系列的說明也都可以了解一下:什麼是Elasticsearch?

2.2 Elasticsearch的安裝

本演示項目ES版本為7.0.0版本,其他版本的ES的maven依賴與其他的jar包關係請自行查閱官方文檔,保證不衝突。

  • Windows

    Es服務器的安裝很簡單,Windows版本特別的簡單,直接去官網下載,運行 bin/elasticsearch 或者bin\elasticsearch.bat

  • Linux(CentOS7)

    首先我們去官網下載ES的tar.gz包,然後自建一個文件夾放好,然後解壓tar.zg壓縮包:

    tar -xvf elasticsearch-7.0.0.tar.gz
    

    然後進入到bin目錄下:

    cd elasticsearch-7.0.0/bin
    

    然後運行elasticsearch:

    ./elasticsearch
    

    這個時候肯定會報錯的,因為沒有進行配置,所以我們先對es進行一些簡單的配置,保證能單機運行,進入elasticsearch-7.7.0/config目錄,對es的核心配置文件進行編輯:

    vim elasticsearch.yml
    

    進入到了elasticsearch.yml文件的編輯頁面:

    首先我們配置集群名稱,集群名稱自己取一個喜歡的名字就好:

    接下來配置節點名稱,就是在這個集群中,這個es服務器的名稱:

    接下來配置一些必要的參數:

    bootstrap.memory_lock: 是否鎖住內存,避免交換(swapped)帶來的性能損失,默認值是: false。

    bootstrap.system_call_filter: 是否支持過濾掉系統調用。elasticsearch 5.2以後引入的功能,在bootstrap的時候check是否支持seccomp。

    配置network為所有人都可以訪問,因為我們一般是使用ssh連接工具在其他的電腦上操作Linux系統,所以我們需要配置一下:

    到這裏就配置完成了,但是當你重新去運行.elasticsearch的可執行文件的時候,依然會報錯。

    報錯信息中可能包含以下幾個錯誤:

    • max file descriptors [4096] for elasticsearch process is too low, increase to at least [65536]

      原因:無法創建本地文件問題,用戶最大可創建文件數太小。

      解決方法:切換到root賬戶下,進入Linux系統文件夾,編輯limits.conf文件:

      vim /etc/security/limits.conf
      

      在文件的末尾加上:

      *                soft    nofile          65536
      *                hard    nofile          65536
      *                soft    nproc           4096
      *                hard    nproc           4096
      
    • max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]

      原因:最大虛擬內存太小,需要修改系統變量的最大值。

      解決方法:切換到root賬戶下,進入Linux系統文件夾,編輯sysctl.conf文件:

      vim /etc/sysctl.conf
      

      在文件的末尾加上:

      vm.max_map_count=262144
      
    • max number of threads [1024] for user [es] likely too low, increase to at least [2048]

      原因:無法創建本地線程問題,用戶最大可創建線程數太小。

      解決方法:如果你是CentOS6及以下系統,編輯的文件是90-nproc.conf這個文件,如果你和我一樣使用的是CentOS7的話,編輯的文件是20-nproc.conf文件,其實這兩個文件是一樣的,只是在不同CentOS系統中名稱不一樣而已。

      CentOS7使用這個命令:

      vim /etc/security/limits.d/20-nproc.conf
      

      CentOS6使用這個命令:

      vim /etc/security/limits.d/90-nproc.conf
      

      只需要在文件中加上以下配置:

      *          soft    nproc     4096
      

      這個配置的意思是說賦予其他用戶的可創建本地線程數為4096。在這個文件中本來就有一個配置,意思是說賦予root賬戶創建線程數不受限制。我們就把上面的配置加在本來存在的配置的下面一行就可以了。

      如果是CentOS7的使用者,還需要配置另一個文件,否則這個最大線程數是不會生效的。CentOS 7 使用systemd替換了SysV,Systemd目的是要取代Unix時代以來一直在使用的init系統,兼容SysV和LSB的啟動腳本,而且夠在進程啟動過程中更有效地引導加載服務。在/etc/systemd目錄下有一個系統的默認管理配置,這裡有登陸、日誌、服務、系統等。所以CentOS7的使用者還需要配置下面這個文件:

      vim /etc/systemd/system.conf
      

      對其中的選項進行配置,在文件的末尾加上:

      DefaultLimitNOFILE=65536
      DefaultLimitNPROC=4096
      

    上面的所以錯誤解決完畢之後,我們再運行.elasticsearch可執行文件,es才可以啟動成功。

2.3 Elasticsearch的使用

首先給大家介紹一個谷歌瀏覽器插件,這個插件是用來可視化展示es的索引庫數據的,這個插件叫做ElasticVue,個人感覺挺好用的,展示也比較方便,給大家截個圖看看:

大家可以使用這個建立索引庫,然後調用es官方的es專用的語法操作es服務器進行CRUD操作,但是此處我只介紹Java語言如何調用es服務器API,廢話不多說,我們直接開始下一步。

  • 2.3.1 引入依賴

    搭建工程的過程我就不演示了,直接上pom.xml依賴文件。

    pom.xml

    <!--springboot父工程-->
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.2.2.RELEASE</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
    
        <dependencies>
            <!--springboot-web組件-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
                <version>2.2.2.RELEASE</version>
            </dependency>
            <!--elasticsearch-rest-client組件-->
            <dependency>
                <groupId>org.elasticsearch.client</groupId>
                <artifactId>elasticsearch-rest-client</artifactId>
                <version>7.7.0</version>
            </dependency>
            <!--elasticsearch-rest-high-level-client組件-->
            <dependency>
                <groupId>org.elasticsearch.client</groupId>
                <artifactId>elasticsearch-rest-high-level-client</artifactId>
                <version>7.7.0</version>
            </dependency>
            <!--elasticsearch組件-->
            <dependency>
                <groupId>org.elasticsearch</groupId>
                <artifactId>elasticsearch</artifactId>
                <version>7.7.0</version>
            </dependency>
            <!--mybatis整合springboot組件-->
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>2.1.0</version>
            </dependency>
            <!--mysql數據庫連接驅動-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>8.0.18</version>
            </dependency>
            <!--lombok組件-->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.10</version>
            </dependency>
            <!--json組件gson-->
            <dependency>
                <groupId>com.google.code.gson</groupId>
                <artifactId>gson</artifactId>
                <version>2.8.5</version>
            </dependency>
            <!--springboot-test組件-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-test</artifactId>
            </dependency>
            <!--單元測試junit組件-->
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.12</version>
                <scope>test</scope>
            </dependency>
            <!--spring-test組件-->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-test</artifactId>
                <version>5.2.2.RELEASE</version>
                <scope>test</scope>
            </dependency>
        </dependencies>
    
        <build>
            <!--springboot的maven插件-->
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <configuration>
                        <compilerArgs>
                            <arg>-parameters</arg>
                        </compilerArgs>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    
  • 2.3.2 Elasticsearch的配置類和Gson配置類和應用配置文件

    application.yml

    butterflytri:
      databaseurl-port: 127.0.0.1:3306 # 數據庫端口
      database-name: student_db # 數據庫名
      host: 192.168.129.100:9200 # es服務端
    server:
      port: 8080 # 應用端口
      servlet:
        context-path: /butterflytri # 應用映射
    spring:
      application:
        name: mybatis # 應用名稱
      datasource:
        url: jdbc:mysql://${butterflytri.databaseurl-port}/${butterflytri.database-name}?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
        driver-class-name: com.mysql.jdbc.Driver
        username: root
        password: root
    mybatis:
      type-aliases-package: com.butterflytri.entity # entity別名
      mapper-locations: classpath:com/butterflytri/mapper/*Mapper.xml # mapper映射包掃描
    

    注意:yml文件中的192.168.129.100:9200是es對外的端口,使用的http協議進行操作,es服務器還有個9300端口,這個端口是es集群中各個節點進行交流的端口,使用的是tcp協議。所以我們連接的時候,端口要使用9200端口。

    項目啟動類沒有什麼特別的東西,就不展示了。

    ElasticsearchConfig.java

    package com.butterflytri.config;
    
    import org.apache.http.HttpHost;
    import org.elasticsearch.client.RestClient;
    import org.elasticsearch.client.RestHighLevelClient;
    import org.springframework.beans.factory.DisposableBean;
    import org.springframework.beans.factory.FactoryBean;
    import org.springframework.beans.factory.InitializingBean;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Configuration;
    
    /**
     * @author: WJF
     * @date: 2020/5/22
     * @description: ElasticSearchConfig
     */
    @Configuration
    public class ElasticSearchConfig implements FactoryBean<RestHighLevelClient>, InitializingBean, DisposableBean {
    
        /**
         * {@link FactoryBean<T>}:FactoryBean<T>是spring對外提供的對接接口,當向spring對象使用getBean("..")方法時,
         *                         spring會使用FactoryBean<T>的getObject 方法返回對象。所以當一個類實現的factoryBean<T>接口時,
         *                         那麼每次向spring要這個類時,spring就返回T對象。
         *
         * {@link InitializingBean}:InitializingBean接口為bean提供了初始化方法的方式,它只包括afterPropertiesSet方法,
         *                          凡是繼承該接口的類,在初始化bean的時候會執行該方法。在spring初始化bean的時候,如果該bean是
         *                          實現了InitializingBean接口,並且同時在配置文件中指定了init-method,系統則是
         *                          先調用afterPropertiesSet方法,然後在調用init-method中指定的方法。
         *
         * {@link DisposableBean}:DisposableBean接口為bean提供了銷毀方法destroy-method,會在程序關閉前銷毀對象。
         */
    
        @Value("#{'${butterflytri.host}'.split(':')}")
        private String[] host;
    
        private RestHighLevelClient restHighLevelClient;
    
        private RestHighLevelClient restHighLevelClient() {
            restHighLevelClient = new RestHighLevelClient(
    
                    RestClient.builder(new HttpHost(host[0],Integer.valueOf(host[1]),"http"))
    
            );
            return restHighLevelClient;
        }
    
        @Override
        public void destroy() throws Exception {
            restHighLevelClient.close();
        }
    
        @Override
        public RestHighLevelClient getObject() throws Exception {
            return restHighLevelClient;
        }
    
        @Override
        public Class<?> getObjectType() {
            return RestHighLevelClient.class;
        }
    
        @Override
        public void afterPropertiesSet() throws Exception {
            restHighLevelClient();
        }
    
    }
    

    ES的配置類,這個配置類實現了三個接口,三個接口的作用我也寫上了註釋,大家可以看下,需要注意的是FactoryBean這個接口,一但實現了這個接口,每當你需要使用泛型表示的對象T的時候,Spring不會從容器中去拿這個對象,而是會調用這個FactoryBean.getObject()方法去拿對象。其他的就沒有什麼了。

    Gson.java

    Gson是一個操作json數據的類,它的執行效率可能會慢一點,但是它在解析json數據的時候不會出Bug。

    package com.butterflytri.config;
    
    import com.google.gson.Gson;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    /**
     * @author: WJF
     * @date: 2020/5/22
     * @description: GsonConfig
     */
    @Configuration
    public class GsonConfig {
    
        /**
         * {@link Gson}:一個操作json的對象,有比較好的json操作體驗,相對於Alibaba的FastJson來說速度慢一些,但是FastJson在解析
         *              複雜的的json字符串時有可能會出現bug。
         * @return Gson
         */
    
        @Bean
        public Gson gson() {
            return new Gson();
        }
    
    }
    

    Constants.java

    這是我寫的常量類,放一些ES使用的常量,直接寫字符串也行,但是我建議這樣做。

    package com.butterflytri.constants;
    
    
    /**
     * @author: WJF
     * @date: 2020/5/22
     * @description: Constants
     */
    public class Constants {
    
        /**
         * es搜索關鍵字
         */
        public static final String KEYWORD = ".keyword";
    
        /**
         * es的type類型:type字段將在 elasticsearch-version:8 中徹底刪除,本來就覺得沒得啥用。
         */
        public static final String DOC_TYPE = "_doc";
    
        /**
         * 學生信息索引類型
         */
        public static final String INDEX_STUDENT = "student_info";
    
    
        /**
         * 自定連接符
         */
        public static final String CONNECTOR = " --> ";
    
    }
    

    Student.java

    package com.butterflytri.entity;
    
    import lombok.Getter;
    import lombok.Setter;
    import lombok.ToString;
    
    import java.io.Serializable;
    
    /**
     * @author: WJF
     * @date: 2020/5/16
     * @description: Student
     */
    
    @ToString
    @Getter
    @Setter
    public class Student implements Serializable {
    
        private Long id;
    
        private String studentName;
    
        private String studentNo;
    
        private String sex;
    
        private Integer age;
    
        private String clazz;
    
    }
    

    StudentMapper.java

    package com.butterflytri.mapper;
    
    import com.butterflytri.entity.Student;
    import org.apache.ibatis.annotations.Mapper;
    
    import java.util.List;
    
    /**
     * @author: WJF
     * @date: 2020/5/16
     * @description: StudentMapper
     */
    @Mapper
    public interface StudentMapper {
    
        /**
         * 查詢所有學生信息
         * @return List<Student>
         */
        List<Student> findAll();
    
        /**
         * 通過id查詢學生信息
         * @param id:學生id
         * @return Student
         */
        Student findOne(Long id);
    
        /**
         * 通過學號查詢學生信息
         * @param studentNo:學生學號
         * @return Student
         */
        Student findByStudentNo(String studentNo);
    
    }
    

    mybatis的SQL映射文件我就不展示了,也很簡單,大家看接口方法名就應該可以想象得到SQL語句是怎樣的。

  • 2.3.3 索引數據到ES服務器

    IndexServiceImpl.java

    package com.butterflytri.service.impl;
    
    import com.butterflytri.constants.Constants;
    import com.butterflytri.entity.Student;
    import com.butterflytri.service.IndexService;
    import com.google.gson.Gson;
    import org.elasticsearch.action.ActionListener;
    import org.elasticsearch.action.index.IndexRequest;
    import org.elasticsearch.action.index.IndexResponse;
    import org.elasticsearch.client.RequestOptions;
    import org.elasticsearch.client.RestHighLevelClient;
    import org.elasticsearch.common.xcontent.XContentType;
    import org.springframework.stereotype.Service;
    
    import javax.annotation.Resource;
    import java.io.IOException;
    
    /**
     * @author: WJF
     * @date: 2020/5/22
     * @description: IndexServiceImpl
     */
    @Service
    public class IndexServiceImpl implements IndexService {
    
        @Resource
        private Gson gson;
    
        @Resource
        private RestHighLevelClient restHighLevelClient;
    
        @Override
        public String index(Student student) {
            StringBuilder builder = new StringBuilder();
            IndexRequest indexRequest = this.initIndexRequest(student);
            try {
                // 同步索引到elasticsearch服務器,獲取索引響應IndexResponse
                IndexResponse indexResponse = restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
                String statusName = indexResponse.status().name();
                int statusCode = indexResponse.status().getStatus();
                builder.append(statusName).append(Constants.CONNECTOR).append(statusCode);
            } catch (IOException e) {
                builder.append("Fail").append(Constants.CONNECTOR).append(e.getMessage());
            }
            return builder.toString();
        }
    
    
        @Override
        public String indexAsync(Student student) {
            StringBuilder builder = new StringBuilder();
            IndexRequest indexRequest = this.initIndexRequest(student);
            // 異步索引到elasticsearch服務器,獲取索引響應IndexResponse
            restHighLevelClient.indexAsync(indexRequest, RequestOptions.DEFAULT,actionListener(builder));
            return builder.toString();
        }
    
    
    
        /**
         * 初始化IndexRequest,並設置數據源。
         * @param student
         * @return IndexRequest
         */
        private IndexRequest initIndexRequest(Student student) {
            // 構建IndexRequest,設置索引名稱,索引類型,索引id
            IndexRequest indexRequest = new IndexRequest(Constants.INDEX_STUDENT);
            // 可以不設置,默認就是'_doc'
            indexRequest.type(Constants.DOC_TYPE);
            // 設置索引id為studentId
            indexRequest.id(String.valueOf(student.getId()));
            // 設置數據源
            String studentJson = gson.toJson(student);
            indexRequest.source(studentJson, XContentType.JSON);
            return indexRequest;
        }
    
        /**
         * 異步索引的回調監聽器,根據不同的結果做出不同的處理
         * @param builder
         * @return ActionListener<IndexResponse>
         */
        private ActionListener<IndexResponse> actionListener(StringBuilder builder) {
            return new ActionListener<IndexResponse>() {
                // 當索引數據到es服務器時,返回不同的狀態
                @Override
                public void onResponse(IndexResponse indexResponse) {
                    String statusName = indexResponse.status().name();
                    int statusCode = indexResponse.status().getStatus();
                    builder.append(statusName).append(Constants.CONNECTOR).append(statusCode);
                }
    
                // 當索引數據時出現異常
                @Override
                public void onFailure(Exception e) {
                    builder.append("Fail").append(Constants.CONNECTOR).append(e.getMessage());
                }
            };
        }
    }
    

    上面的內容很簡單,就是將Student對象格式化為Json字符串,然後存到es服務器中,大家只要遵守一個規則就好,就是操作es服務器,不管是什麼操作都是用RestHighLevelClient這個類去操作,上面的就是student對象索引的es服務器中,使用restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT),首先就是構建indexRequest對象,這個對象就是索引請求對象,具體幹了什麼看代碼上的註釋。這裏還有個restHighLevelClient.indexAsync()這個方法,這個方法和上面的index方法一樣的效果,只不過是異步調用。

    接下來我們測試一下這個代碼,請看:

    @Test
        public void indexTest() {
            List<Student> list = studentMapper.findAll();
            for (Student student : list) {
                String message = indexService.index(student);
                System.out.println(message);
            }
        }
    

    我們使用ElasticVue插件連接es服務器即可看到有一個索引庫:

    當我們點擊到show按鈕的時候,可以看到student_info索引庫中有幾條記錄:

    索引數據到數據庫成功了。

  • 2.3.4 獲取Es服務器數據

    獲取數據,是es提供給我們的API,這個Api只能獲取某個索引的某一條文檔,示例如下:

    GetServiceImpl.java

    	@Override
        public Student get(String id) {
            Student student = new Student();
            GetRequest getRequest = new GetRequest(Constants.INDEX_STUDENT, id);
            try {
                GetResponse getResponse = restHighLevelClient.get(getRequest, RequestOptions.DEFAULT);
                String source = getResponse.getSourceAsString();
                student = gson.fromJson(source, Student.class);
            } catch (IOException e) {
                e.printStackTrace();
            }
            return student;
        }
    

    接着我們在測試類中,調用這個方法然後打印一下結果:

    GetServiceTest.java

    	@Test
        public void getTest() {
            Student student = getService.get("1");
            System.out.println(student);
        }
    

    結果如下:

    更新數據文檔和刪除數據文檔我就不演示了,都是大同小異,大家可以拉下我的代碼,好好研究一下,都有詳細的註釋,覺得可以的話,給我點下star也是極好的。下面演示一下searchApi,這個Api是我們經常需要使用的,特別重要。

  • 2.3.5 搜索Es服務器數據

    ES的搜索API包含很多,比如說組合搜索,區間搜索,高亮显示,分詞搜索等等。我先給大家演示一下組合搜索,區間搜索其實也是組合搜索的一個子條件,其他的搜索其實也都是,代碼如下:

    SearchServiceImpl.java

    	@Override
        public List<Student> searchRange(Object from, Object to, String field, String index) {
            List<Student> list = new ArrayList<>();
            BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
            // 需要搜索的區間字段field
            RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery(field);
            // 左區間
            if (from != null) {
                rangeQueryBuilder.from(from, true);
            }
            // 右區間
            if (to != null) {
                rangeQueryBuilder.to(to, true);
            }
            boolQueryBuilder.must();
            SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
            searchSourceBuilder.query(boolQueryBuilder);
            SearchRequest searchRequest = new SearchRequest(index);
            searchRequest.source(searchSourceBuilder);
            try {
                SearchResponse search = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
                for (SearchHit hit : search.getHits()) {
                    String source = hit.getSourceAsString();
                    Student student = gson.fromJson(source, Student.class);
                    list.add(student);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            return list;
        }
    

    上面的代碼其實很簡單,就是一個區間查詢構建器,查詢指定字段處於區間的所有數據,rangeQueryBuilder.from(from, true)的第一個參數就是字段的下邊界,第二個參數代表是否包含邊界。SearchResponse就是搜索的響應對象,所有的數據都在SearchHit對象中。

    接下來給大家演示一些組合查詢,這個方法搜索年齡在18到19歲並且班級為’G0305’的學生。記得ES默認是分頁的,如果想不分頁,一定要記得給搜索字段加上.keyword(字符串加,数字不支持)。

    SearchServiceImpl.java

    @Override
        public List<Student> searchBool() {
            List<Student> list = new ArrayList<>();
            BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
            boolQuery.must(QueryBuilders.rangeQuery("age").gte(18).lte(19));
            boolQuery.must(QueryBuilders.termQuery("clazz" + Constants.KEYWORD,"G0305"));
            SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
            searchSourceBuilder.query(boolQuery);
            SearchRequest searchRequest = new SearchRequest(Constants.INDEX_STUDENT);
            searchRequest.source(searchSourceBuilder);
            try {
                SearchResponse search = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
                for (SearchHit hit : search.getHits()) {
                    String source = hit.getSourceAsString();
                    Student student = gson.fromJson(source, Student.class);
                    list.add(student);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            return list;
        }
    

    上面的代碼中的類BoolQueryBuilder就是組合查詢構建器,這個類可以用來構建組合的條件查詢。boolQuery.must()方法就是用來拼接條件的一種方式,使用這個方法代表必須滿足這個條件才會查詢出來,上面的代碼說明必須滿足年齡為18(包含18)到19(包含19)歲,並且班級為’G0305’的學生才會查詢出來。還有其他的一些常見的組合查詢方法,如下:

    • boolQuery.must():必須滿足此條件,相當於=或者&
    • boolQuery.mustNot():必須不滿足此條件,相當於!=
    • boolQuery.should():相當於||或者or
    • boolQuery.filter():過濾。

    然後是聚合查詢,很類似於MySQL中的聚合函數,這個示例我就不再解釋了,代碼註釋很清楚:

    @Override
        public void searchBoolAndAggregation() {
            BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
            boolQuery.must(QueryBuilders.rangeQuery("age").gte(18).lte(19));
            boolQuery.must(QueryBuilders.termQuery("clazz" + Constants.KEYWORD,"G0305"));
            // 聚合分組:按clazz字段分組,並將結果取名為clazz,es默認是分詞的,為了精確配置,需要加上‘.keyword’關鍵詞後綴。
            TermsAggregationBuilder aggregationBuilder = AggregationBuilders.terms("clazz").field("clazz" + Constants.KEYWORD);
            // 聚合求和:求符合查詢條件的學生的年齡的和,並將結果取名為ageSum,因為不是字符串,所以默認是精確匹配,不支持分詞。
            aggregationBuilder.subAggregation(AggregationBuilders.sum("ageSum").field("age"));
            // 聚合求平均:求符合查詢條件的學生的年齡的平均值,並將結果取名為ageAvg,因為不是字符串,所以默認是精確匹配,不支持分詞。
            aggregationBuilder.subAggregation(AggregationBuilders.avg("ageAvg").field("age"));
            // 聚合求數量:按學號查詢符合查詢條件的學生個數,並將結果取名為count,es默認是分詞的,為了精確配置,需要加上‘.keyword’關鍵詞後綴。
            aggregationBuilder.subAggregation(AggregationBuilders.count("count").field("studentNo" + Constants.KEYWORD));
            SearchSourceBuilder builder = new SearchSourceBuilder();
            builder.query(boolQuery);
            builder.aggregation(aggregationBuilder);
            // 按年齡降序排序。
            builder.sort("age", SortOrder.DESC);
            SearchRequest request = new SearchRequest("student_info");
            request.source(builder);
            try {
                SearchResponse search = restHighLevelClient.search(request, RequestOptions.DEFAULT);
                for (SearchHit hit : search.getHits()) {
                    String source = hit.getSourceAsString();
                    Student student = gson.fromJson(source, Student.class);
                    System.out.println(student);
                }
                // 使用Terms對象接收
                Terms clazz = search.getAggregations().get("clazz");
                for (Terms.Bucket bucket : clazz.getBuckets()) {
                    System.out.println(bucket.getDocCount());
    
                    System.out.println("=====================");
                    // 使用ParsedSum對象接收
                    ParsedSum ageCount = bucket.getAggregations().get("ageSum");
                    System.out.println(ageCount.getType());
                    System.out.println(ageCount.getValue());
                    System.out.println(ageCount.getValueAsString());
                    System.out.println(ageCount.getMetaData());
                    System.out.println(ageCount.getName());
    
                    System.out.println("=====================");
                    // 使用ParsedAvg對象接收
                    ParsedAvg ageAvg = bucket.getAggregations().get("ageAvg");
                    System.out.println(ageAvg.getType());
                    System.out.println(ageAvg.getValue());
                    System.out.println(ageAvg.getValueAsString());
                    System.out.println(ageAvg.getMetaData());
                    System.out.println(ageAvg.getName());
    
                    System.out.println("=====================");
                    // 使用ParsedValueCount對象接收
                    ParsedValueCount count = bucket.getAggregations().get("count");
                    System.out.println(count.getType());
                    System.out.println(count.getValue());
                    System.out.println(count.getValueAsString());
                    System.out.println(count.getMetaData());
                    System.out.println(count.getName());
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    

    最後還有分詞查詢,分詞查詢就不加.keyword關鍵字即可。

    @Override
        public List<Student> searchMatch(String matchStudentName) {
            List<Student> list = new ArrayList<>();
            BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
            // 分詞查詢時不加'.keyword'關鍵字
            boolQueryBuilder.must(QueryBuilders.matchQuery("studentName",matchStudentName));
            SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
            searchSourceBuilder.query(boolQueryBuilder);
            SearchRequest searchRequest = new SearchRequest("student_info");
            searchRequest.source(searchSourceBuilder);
            try {
                SearchResponse search = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
                for (SearchHit hit : search.getHits().getHits()) {
                    String source = hit.getSourceAsString();
                    Student student = gson.fromJson(source, Student.class);
                    list.add(student);
                }
    
            } catch (IOException e) {
                e.printStackTrace();
            }
            return list;
        }
    

    請記住,一般的進行分詞都是字符串才進行分詞搜索,数字等類型只能是精準匹配。

    最後,ES功能很強大,作為搜索界的扛把子,ES的功能遠遠不止這些,它還可以高亮搜索,數據分析等等。我在這裏演示的僅僅只是皮毛,甚至都不是皮毛,僅作為初學者的參考。如有大佬覺得我哪裡寫錯了,或者有不同見解,歡迎留言。

3. 項目地址

本項目傳送門:

  • GitHub —> spring-boot-elasticsearch
  • Gitee —> spring-boot-elasticsearch

此教程會一直更新下去,覺得博主寫的可以的話,關注一下,也可以更方便下次來學習。

  • 作者:Butterfly-Tri
  • 出處:Butterfly-Tri個人博客
  • 版權所有,歡迎保留原文鏈接進行轉載

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

【其他文章推薦】

網頁設計最專業,超強功能平台可客製化

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

※回頭車貨運收費標準

※推薦評價好的iphone維修中心

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

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

台中搬家公司費用怎麼算?