Lucene & 全文檢索

目錄結(jié)構(gòu):
1.全文檢索
2.Lucene入門
3.Lucene進(jìn)階

全文檢索

一, 生活中的搜索:
1.Windows系統(tǒng)中的有搜索功能:打開“我的電腦”,按“F3”就可以使用查找的功能性穿,查找指定的文件或文件夾泉哈。搜索的范圍是整個(gè)電腦中的文件資源阎抒。

2.Eclipse中的幫助子系統(tǒng):點(diǎn)擊Help?Help Contents,可以查找出相關(guān)的幫助信息糯景。搜索的范圍是Eclipse的所有幫助文件。
搜索引擎,如Baidu或Google等暇榴,可以查詢到互聯(lián)網(wǎng)中的網(wǎng)頁(yè)、PDF蕉世、DOC蔼紧、PPT、圖片狠轻、音樂奸例、視頻等。
3.Mac中的Spotlight搜索
4.數(shù)據(jù)庫(kù)中檢索檢查某一個(gè)關(guān)鍵字的例子向楼。
select * from topic where content like '%java%'
文本檢索,會(huì)使索引失效

存在問題:
1.搜索速度慢
2.搜索效果不好.
3.沒有相關(guān)度排序

二, 什么是全文檢索查吊?

全文檢索是指計(jì)算機(jī)索引程序通過掃描文章中的每一個(gè)詞,對(duì)每一個(gè)詞建立一個(gè)索引蜜自,指明該詞在文章中出現(xiàn)的次數(shù)和位置菩貌,當(dāng)用戶查詢時(shí),檢索程序就根據(jù)事先建立的索引進(jìn)行查找重荠,并將查找的結(jié)果反饋給用戶的檢索方式箭阶。這個(gè)過程類似于通過字典中的檢索字表查字的過程。

在說全文檢索之前我們先來了解一下數(shù)據(jù)分類

結(jié)構(gòu)化數(shù)據(jù):指具有固定格式或有限長(zhǎng)度的數(shù)據(jù)戈鲁,如數(shù)據(jù)庫(kù)仇参,元數(shù)據(jù)等;
半結(jié)構(gòu)化數(shù)據(jù):半結(jié)構(gòu)化數(shù)據(jù)
非結(jié)構(gòu)化數(shù)據(jù):指不定長(zhǎng)或無固定格式的數(shù)據(jù),如郵件婆殿,word文檔等;
非結(jié)構(gòu)化數(shù)據(jù)又一種叫法叫全文數(shù)據(jù)诈乒。從全文數(shù)據(jù)中進(jìn)行檢索就叫全文檢索。
特點(diǎn):只關(guān)注文本不考慮語(yǔ)義

三, 為什么使用 ?
搜索速度:將數(shù)據(jù)源中的數(shù)據(jù)都通過全文索引

匹配效果:過詞元(term)進(jìn)行匹配婆芦,通過語(yǔ)言分析接口的實(shí)現(xiàn)怕磨,可以實(shí)現(xiàn)對(duì)中文等非英語(yǔ)的支持喂饥。

相關(guān)度:有匹配度算法,將匹配程度(相似度)比較高的結(jié)果排在前面肠鲫。

適用場(chǎng)景:關(guān)系數(shù)據(jù)庫(kù)中進(jìn)行模糊查詢時(shí)员帮,數(shù)據(jù)庫(kù)自帶的索引將不起作用,此時(shí)需要通過全文檢索來提高速度导饲;比如:
網(wǎng)站系統(tǒng)中針對(duì)內(nèi)容的模糊查詢捞高;
select * from article where content like '%上海平安%'
ERP系統(tǒng)中產(chǎn)品等數(shù)據(jù)的模糊查詢,BBS渣锦、BLOG中的文章搜索等硝岗;
各種搜索引擎運(yùn)行依賴于全文檢索;
只對(duì)指定領(lǐng)域的網(wǎng)站進(jìn)行索引與搜索(即垂直搜索袋毙,如“818工作搜索”型檀、“有道購(gòu)物搜索”)
要在word、pdf等各種各樣的數(shù)據(jù)格式中檢索內(nèi)容娄猫;
其它場(chǎng)合:比如搜狐拼音輸入法贱除、Google輸入法等。

四, 工作原理

1.如何查詢?nèi)臄?shù)據(jù)?

順序掃描法(Serial Scanning):所謂順序掃描媳溺,比如要找內(nèi)容包含某一個(gè)字符串的文件月幌,就是一個(gè)文檔一個(gè)文檔的看,對(duì)于每一個(gè)文檔悬蔽,從頭看到尾扯躺,如果此文檔包含此字符串,則此文檔為我們要找的文件蝎困,接著看下一個(gè)文件录语,直到掃描完所有的文件。比如Window自帶的搜索禾乘。
如何提升全文檢索的速度?

對(duì)非結(jié)構(gòu)化數(shù)據(jù)順序掃描很慢澎埠,對(duì)結(jié)構(gòu)化數(shù)據(jù)的搜索卻相對(duì)較快(由于結(jié)構(gòu)化數(shù)據(jù)有一定的結(jié)構(gòu)可以采取一定的搜索算法加快速度),那么把我們的非結(jié)構(gòu)化數(shù)據(jù)想辦法弄得有一定結(jié)構(gòu)不就行了嗎始藕?關(guān)系數(shù)據(jù)庫(kù)中存儲(chǔ)的都是結(jié)構(gòu)化數(shù)據(jù)蒲稳,因此很檢索都比較快。
從非結(jié)構(gòu)化數(shù)據(jù)中提取出的然后重新組織的信息伍派,我們稱之索引。
字典及圖書目錄的原理。

2.全文檢索的過程

索引創(chuàng)建:將現(xiàn)實(shí)世界中所有的結(jié)構(gòu)化和非結(jié)構(gòu)化數(shù)據(jù)提取信息晾腔,創(chuàng)建索引的過程舌稀。
搜索索引:就是得到用戶的查詢請(qǐng)求啊犬,搜索創(chuàng)建的索引,然后返回結(jié)果的過程扩借。

3.案例分析

索引文件中應(yīng)該存放什么椒惨?
索引文件中只需要存放單詞及文檔編號(hào)即可
要查出即包含is,又包括 shanghai及pingan的文檔潮罪,先獲得包含is的文檔列表,再獲得包含shanghai及pingan的文檔列表领斥,最合做一個(gè)集合并運(yùn)算嫉到,就得出文檔1及文檔3。

文檔0
What is your name?
文檔1
My name is shanghai pingan!
文檔2
What is that?
文檔3
It is shanghai pingan, ShangHai Pingan

首先將我們非結(jié)構(gòu)化數(shù)據(jù)存儲(chǔ)到文檔區(qū)

文檔編號(hào) 內(nèi)容
0 What is your name?
1 My name is shanghai pingan!
2 What is that?
3 It is shanghai pingan, ShangHai Pingan

如何建立索引月洛?
第一步:分詞組件(Tokenizer)對(duì)文檔進(jìn)行處理,此過程稱為Tokenize何恶。

  1. 將文檔分成一個(gè)一個(gè)單獨(dú)的單詞。(用空格分開)
  2. 去除標(biāo)點(diǎn)符號(hào)嚼黔。
  3. 去除停詞(Stop word)细层。大量出現(xiàn)的助詞,比如is,it等。中文:的唬涧,了疫赎,呢
    經(jīng)過分詞(Tokenizer)后得到的結(jié)果稱為詞元(Token)。詞元(Token)如下:
    shanghai,ShangHai,pingan,My,name,What,your,pingan

第二步:將得到的詞元(Token)傳給語(yǔ)言處理組件(Linguistic Processor)碎节,對(duì)于英語(yǔ)捧搞,處理大致如下:

  1. 變?yōu)樾?Lowercase)。
  2. 將單詞縮減為詞根形式狮荔,如“cars”到“car”等胎撇。這種操作稱為:stemming。
  3. 將單詞轉(zhuǎn)變?yōu)樵~根形式殖氏,如“drove”到“drive”等晚树。這種操作稱為:lemmatization。
    語(yǔ)言處理組件(linguistic processor)的結(jié)果稱為詞(Term)雅采。結(jié)果如下:
    shanghai,pingan,my,name,what,your

第三步:把得到的詞Term傳給索引組件(Indexer)處理,處理過程如下:
1爵憎、把得到的詞創(chuàng)建一個(gè)字典表

詞term 文檔Document
what 0
name 0
My 1
name 1
shanghai 1
pingan 1
what 2
that 2
shanghai 3
pingan 3
shanghai 3
pingan 3

2、對(duì)字典按字母順序進(jìn)行排序

詞term 文檔Document
shanghai 1
shanghai 3
shanghai 3
pingan 1
pingan 3
pingan 3
my 1
name 0
name 1
what 0
what 2
your 0

3总滩、合并相同的詞(Term)成為文檔倒排(Posting List)鏈表纲堵。

詞term 出現(xiàn)次數(shù) 文檔 Frequency 文檔 Frequency
shanghai 3 1 1 3 2
pingan 3 1 1 3 2
my 1 1 1 ~ ~
name 2 0 1 1 1
what 2 0 1 2 1
your 1 0 1 ~ ~

最終會(huì)存儲(chǔ)兩部分一個(gè)文檔區(qū)和一個(gè)索引區(qū)

詞元 文檔編號(hào)
what 0,2
your 0
name 0,1
my 1
shanghai 1,3,3
pingan 1,3,3
that 2

搜索處理的大致流程:
1、接收用戶輸入的搜索詞及關(guān)鍵字并作簡(jiǎn)單處理闰渔;
2席函、對(duì)查詢語(yǔ)句進(jìn)行詞法分析,語(yǔ)法分析冈涧,及語(yǔ)言處理茂附;
3正蛙、查詢到包含輸出詞的文檔列表,并進(jìn)行相關(guān)邏輯運(yùn)算营曼;
4乒验、根據(jù)文檔的相關(guān)性進(jìn)行排序,把相關(guān)性最高的文檔返回出來蒂阱。

4.文檔相關(guān)性

計(jì)算詞的權(quán)重:
1锻全、找出詞(Term)對(duì)文檔的重要性的過程稱為計(jì)算詞的權(quán)重(Term weight)的過程。主要有兩個(gè)因素:
A录煤、Term Frequency (tf):即此Term在此文檔中出現(xiàn)了多少次鳄厌。tf 越大說明越重要。
B妈踊、 Document Frequency (df):即有多少文檔包含該Term了嚎。df 越大說明越不重要。

2廊营、判斷Term之間的關(guān)系從而得到文檔相關(guān)性的過程歪泳,也即向量空間模型的算法(VSM)。
實(shí)現(xiàn)方式:把文檔看作一系列詞(Term)露筒,每一個(gè)詞(Term)都有一個(gè)權(quán)重(Term weight)呐伞,不同的詞(Term)根據(jù)自己在文檔中的權(quán)重來影響文檔相關(guān)性的打分計(jì)算

5.全文檢索應(yīng)用架構(gòu)
6.全文檢索的流程對(duì)應(yīng)的Lucene 實(shí)現(xiàn)的包結(jié)構(gòu)

Lucene 的analysis 模塊主要負(fù)責(zé)詞法分析及語(yǔ)言處理而形成Term。
Lucene的index模塊主要負(fù)責(zé)索引的創(chuàng)建邀窃,里面有IndexWriter荸哟。
Lucene的store模塊主要負(fù)責(zé)索引的讀寫。
Lucene 的QueryParser主要負(fù)責(zé)語(yǔ)法分析瞬捕。
Lucene的search模塊主要負(fù)責(zé)對(duì)索引的搜索鞍历。

Lucene入門

Lucene是什么?

Lucene是一個(gè)用Java寫的高性能肪虎、可伸縮的全文檢索引擎工具包劣砍,它可以方便的嵌入到各種應(yīng)用中實(shí)現(xiàn)針對(duì)應(yīng)用的全文索引/檢索功能。Lucene的目標(biāo)是為各種中小型應(yīng)用程序加入全文檢索功能扇救。

開發(fā)步驟

建立索引文件

1,創(chuàng)建一個(gè)測(cè)試類LuceneTest
2,導(dǎo)入jar包
lucene-core-4.10.4.jar 核心包
lucene-analyzers-common-4.10.4.jar 分詞器包
3,創(chuàng)建索引寫入器IndexWriter 傳入對(duì)應(yīng)的參數(shù):索引需要存放的位置,索引寫入器配置對(duì)象(配置版本,分詞器)
4.內(nèi)容寫入之后,寫入到二進(jìn)制文件中不方便查看,使用工具(lukeall-4.10.0.jar)查看索引庫(kù)

public class LuceneTest {
    String content1 = "hello world";
    String content2 = "hello java world";
    String content3 = "hello lucene world";
    String indexPath = "hello";
    Analyzer analyzer = new StandardAnalyzer();//分詞器

    @Test
    public void testCreateIndex() throws Exception {
        //1.創(chuàng)建索引寫入器
        Directory d = FSDirectory.open(new File(indexPath));//索引需要存放的位置
        //創(chuàng)建索引寫入器配置對(duì)象
        IndexWriterConfig conf = new IndexWriterConfig(Version.LUCENE_4_10_4, analyzer);
        IndexWriter writer = new IndexWriter(d, conf);
        //2.寫入文檔信息
        //添加文檔 定義字段的存儲(chǔ)規(guī)則
        FieldType type = new FieldType();
        type.setIndexed(true);//是否要索引
        type.setStored(true);//是否需要存儲(chǔ)
        Document document1 = new Document();//數(shù)據(jù)庫(kù)中的一條數(shù)據(jù)
        //new Field("字段名","字段內(nèi)容","字段的配置屬性")
        document1.add(new Field("title", "doc1", type));//該條記錄中的字段 title:doc1
        document1.add(new Field("content", content1, type));//content: hello world
        writer.addDocument(document1);

        Document document2 = new Document();
        document2.add(new Field("title", "doc2", type));
        document2.add(new Field("content", content2, type));
        writer.addDocument(document2);

        Document document3 = new Document();
        document3.add(new Field("title", "doc3", type));
        document3.add(new Field("content", content3, type));
        writer.addDocument(document3);

        //需要把添加的記錄保存
        writer.commit();
        writer.close();
    }
}

運(yùn)行測(cè)試類會(huì)在該項(xiàng)目目錄下生成一個(gè)hello文件夾

打開_0.xfs文件,這時(shí)我們看不出一個(gè)所以然

使用工具(lukeall-4.10.0.jar)查看索引庫(kù)
只需在終端通過命令行 java -jar lukeall-4.10.0.jar 即可

需要在Path路徑上找到hello索引庫(kù)的絕對(duì)路徑


點(diǎn)擊OK即可看到索引庫(kù)


查詢索引庫(kù)

0.導(dǎo)入jar包lucene-queryparser-4.10.4.jar(將字符串變成Query對(duì)象)
1.創(chuàng)建測(cè)試方法searchIndex()
2.創(chuàng)建索引查詢對(duì)象IndexSearcher
3.根據(jù)查詢的文本內(nèi)容解析成Query查詢對(duì)象(導(dǎo)入jar包lucene-queryparser-4.10.4.jar)設(shè)置查詢字段,分詞器
4.根據(jù)查詢器查詢到文檔編號(hào)
5.通過文檔編號(hào)查詢對(duì)應(yīng)的文檔內(nèi)容

//索引查詢過程
@Test
 public void searchIndex() throws Exception {
    //1.創(chuàng)建索引寫入器
    Directory d = FSDirectory.open(new File(indexPath));
    //創(chuàng)建分詞器
    Analyzer analyzer = new StandardAnalyzer();
    //打開索引目錄
    IndexReader r = DirectoryReader.open(d);
    //創(chuàng)建索引查詢對(duì)象
    IndexSearcher searcher = new IndexSearcher(r);
    QueryParser parser = new QueryParser("content", analyzer);

    Query query = parser.parse("hello");//查詢hello
    //search(查詢對(duì)象,符合條件的前n條記錄)
    TopDocs search = searcher.search(query, 10000);//n:前幾個(gè)結(jié)果
    System.out.println("符合條件的記錄有多少個(gè):" + search.totalHits);

    ScoreDoc[] scoreDocs = search.scoreDocs;
    for (int i = 0; i < scoreDocs.length; i++) {
        System.out.println("*******************************");
        System.out.println("分?jǐn)?shù):" + scoreDocs[i].score);//相關(guān)度的排序
        int docId = scoreDocs[i].doc;//文檔編號(hào)
        Document document = searcher.doc(docId);
        System.out.println("文檔編號(hào) docId--->" + docId);
        System.out.println("標(biāo)題內(nèi)容 title:--->" + document.get("content"));
    }
}
    

打印結(jié)果:

常用API

Directory:索引目錄用于存放lucene索引文件
Directory是一個(gè)對(duì)索引目錄的一個(gè)抽象刑枝,索引目錄可以存放在普通的文件中,也可以位于數(shù)據(jù)庫(kù)迅腔,或其它的遠(yuǎn)程服務(wù)中装畅;一般情況下均使用文件來索引目錄,這時(shí)一個(gè)Directory就相當(dāng)于一個(gè)文件夾沧烈。
SimpleFSDirectory:直接使用java.io.RandomAccessFile類來操作索引文件掠兄,在普通的Lucene應(yīng)用中,可以直接使用SimpleFSDirectory。

SimpleFSDirectory類:直接使用java.io.RandomAccessFile類來操作索引文件蚂夕,在普通的Lucene應(yīng)用中迅诬,這是最簡(jiǎn)單的用法。
構(gòu)造函數(shù):
SimpleFSDirectory(File path) :直接根據(jù)一個(gè)文件夾地址來創(chuàng)建索引目錄婿牍;
MMapDirectory(File path) :讓OS把整個(gè)索引文件映射到虛擬地址空間侈贷,這樣Lucene就會(huì)覺得索引在內(nèi)存中。

Document:當(dāng)往索引中加入內(nèi)容的時(shí)候等脂,每一條信息用一個(gè)子Document來表示,Document的意思表示文檔俏蛮,也可以理解成記錄,與關(guān)系數(shù)據(jù)表中的一行數(shù)據(jù)記錄類似慎菲;
在Document創(chuàng)建完以后嫁蛇,直接調(diào)用其提供的字段操作方法來操作其中的字段對(duì)象。
Document提供的方法主要包括:
字段添加:add(Field field)
字段刪除:removeField露该、removeFields
獲取字段或值:get、getBinaryValue第煮、getField解幼、getFields等

Field:Field代表Document中的一行數(shù)據(jù),相當(dāng)于一條Lucene記錄中的一列包警。
Lucene提供了一個(gè)接口Fieldable撵摆,其它的API大多針對(duì)這個(gè)接口編程,因此Lucene中的列對(duì)象實(shí)際上是由Fieldable來定義害晦,實(shí)現(xiàn)該接口的除了Field類特铝,還包括NumericField等。在實(shí)際開發(fā)中壹瘟,主要使用的是Field類鲫剿。
Field類提供的常用構(gòu)造方法:
1、Field(String name, String value, Field.Store store, Field.Index index) -通過字段名稱稻轨,字段值灵莲,存儲(chǔ)類型及索引方式來創(chuàng)建一個(gè)字段;
2殴俱、Field(String name, byte[] value, Field.Store store) -通過字段名稱政冻、字段值(字節(jié)碼)及字段存儲(chǔ)方式創(chuàng)建字段對(duì)象;
3线欲、Field(String name, Reader reader) -根據(jù)字段名稱及Reader對(duì)象創(chuàng)建字段對(duì)象明场;
4、其它構(gòu)造方法李丰,詳情查看API苦锨。
new Field("title", "中國(guó)太平", Store.NO, Index.ANALYZED);
new Field("content", "比較好的保險(xiǎn)公司", Store.YES, Index.ANALYZED);

FieldType:Lucene中,在創(chuàng)建Field的時(shí)候,可以指定Field的store及index屬性逆屡;
store屬性:表示字段值是否存儲(chǔ)圾旨,True表示要存儲(chǔ),而False則表示不存儲(chǔ)魏蔗;
type.setStored(true);//是否需要存儲(chǔ)在文檔區(qū)中
indexed屬性:表示字段的是否需要建立索引砍的,即是否支持搜索。tokenized屬性:表示字段是否需要根據(jù)Analyzer規(guī)則進(jìn)行分詞

創(chuàng)建FieldTest測(cè)試類(復(fù)制上面的類修改類名)
定義字段的存儲(chǔ)規(guī)則

  FieldType type2 = new FieldType();
  type2.setIndexed(true);//該字段是否要索引
  type2.setStored(true);//是否需要存儲(chǔ)在文檔區(qū)中
  type2.setTokenized(false);//字段是否分詞
  type2.setTokenized(false);//字段是否分詞

設(shè)置所有的字段的配置屬性為type2

document1.add(new Field("content", content1, type2));
document2.add(new Field("content", content2, type2));
document3.add(new Field("content", content3, type2));
public class FieldTest {
    String content1 = "hello world";
    String content2 = "hello java world";
    String content3 = "hello lucene world";
    String indexPath = "fieldType";
    Analyzer analyzer = new StandardAnalyzer();//分詞器

    //創(chuàng)建索引
    @Test
    public void testCreateIndex() throws Exception {
        //1.創(chuàng)建索引寫入器
        Directory d = FSDirectory.open(new File(indexPath));//索引需要存放的位置
        //創(chuàng)建索引寫入器配置對(duì)象
        IndexWriterConfig confg = new IndexWriterConfig(Version.LUCENE_4_10_4, analyzer);
        confg.setOpenMode(IndexWriterConfig.OpenMode.CREATE);//索引每次重新創(chuàng)建
        IndexWriter writer = new IndexWriter(d, confg);
        //2.寫入文檔信息
        //添加文檔 定義字段的存儲(chǔ)規(guī)則
        FieldType type = new FieldType();
        type.setIndexed(true);//該字段是否要索引
        type.setStored(true);//是否需要存儲(chǔ)
        type.setTokenized(true);

        FieldType type2 = new FieldType();
        type2.setIndexed(true);//該字段是否要索引
        type2.setStored(true);//是否需要存儲(chǔ)
        type2.setTokenized(false);//字段是否分詞

        Document document1 = new Document();//數(shù)據(jù)庫(kù)中的一條數(shù)據(jù)
        //new Field("字段名","字段內(nèi)容","字段的配置屬性")
        document1.add(new Field("title", "doc1", type));//該條記錄中的字段 title:doc1
        document1.add(new Field("content", content1, type2));//content: hello world
        writer.addDocument(document1);

        Document document2 = new Document();
        document2.add(new Field("title", "doc2", type));
        document2.add(new Field("content", content2, type2));
        writer.addDocument(document2);

        Document document3 = new Document();
        document3.add(new Field("title", "doc3", type));
        document3.add(new Field("content", content3, type2));
        writer.addDocument(document3);

        //需要把添加的記錄保存
        writer.commit();
        writer.close();
    }
}

運(yùn)行測(cè)試類


查看索引庫(kù)


當(dāng)我們搜索用戶名或者地名希望是完整的詞元,不希望被分割,此時(shí)就可以設(shè)置該字段的tokenize屬性為false,設(shè)置不進(jìn)行分詞
在索引庫(kù)中:
1.標(biāo)題和內(nèi)容都通過分詞器進(jìn)行索引了.
2.標(biāo)題是完整儲(chǔ)存在文檔區(qū)中,內(nèi)容值截取前30個(gè)字符存儲(chǔ)在存儲(chǔ)區(qū)
3.文章ID只是存儲(chǔ)在文檔區(qū)但是沒有進(jìn)行分詞
4.時(shí)間,作者,閱讀量,評(píng)論數(shù),來源是沒索引也沒存儲(chǔ)的

Analyzer(詞法分析器)

創(chuàng)建一個(gè)測(cè)試類AnalyzerTest
封裝一個(gè)測(cè)試各個(gè)分詞器的方法analyzerMethod(Analyzer analyzer, String content);

public class AnalyzerTest {
    String en = "good morning boy";
    String ch = "你好 恭喜發(fā)財(cái) 東方明珠三生三世十里桃花";

    @Test
    public void analyzerMethod(Analyzer analyzer, String content) throws Exception {

        TokenStream tokenStream = analyzer.tokenStream("content", content);
        tokenStream.reset();
        while (tokenStream.incrementToken()) {
            System.out.println(tokenStream);
        }
    }

    //英文分詞器SimpleAnalyzer測(cè)試
    @Test
    public void testSimpleAnalyzer() throws Exception {
        analyzerMethod(new SimpleAnalyzer(), en);
    }
}

英文分詞:
SimpleAnalyzer:最簡(jiǎn)單的詞法分析器莺治,按英文單詞建立索引廓鞠,以空格為分隔符;

  //英文分詞器SimpleAnalyzer測(cè)試
    @Test
    public void testSimpleAnalyzer() throws Exception {
        analyzerMethod(new SimpleAnalyzer(), en);
    }

StandardAnalyzer:按英文單詞及中文字符來進(jìn)行分析谣旁。

 //英文分詞器StandardAnalyzer測(cè)試
    @Test
    public void testStandardAnalyzer() throws Exception {
        analyzerMethod(new StandardAnalyzer(), en);
    }

對(duì)于英文StandardAnalyzer也是采取空格進(jìn)行分詞
下面對(duì)中文進(jìn)行分詞測(cè)試(對(duì)于中文他是單字分詞)

 //英文分詞器StandardAnalyzer測(cè)試
    @Test
    public void testStandardAnalyzer() throws Exception {
        analyzerMethod(new StandardAnalyzer(), ch);
    }

PerFieldAnalyzerWrapper:

public void testPerFieldAnalyzerWrapper() throws Exception {
  Map<String, Analyzer> analyzerMap = new HashMap<>();
  analyzerMap.put("en", new SimpleAnalyzer());//使用SimpleAnalyzer分詞器
  analyzerMap.put("ch", new StandardAnalyzer());//使用StandardAnalyzer
  //設(shè)置默認(rèn)分詞器
  PerFieldAnalyzerWrapper wrapper = new PerFieldAnalyzerWrapper(new SimpleAnalyzer(), analyzerMap);
   //會(huì)根據(jù)傳入的字段名在PerFieldAnalyzerWrapper找到這個(gè)字段對(duì)應(yīng)的分詞器
   //如果PerFieldAnalyzerWrapper沒有該字段對(duì)應(yīng)的分詞器就會(huì)應(yīng)用默認(rèn)的的分詞器
   //tokenStream("content", xxxxxxxxx);根據(jù)xxxxxx來判斷選擇的分詞器
   TokenStream tokenStream = wrapper.tokenStream("content", ch);
   tokenStream.reset();
   while (tokenStream.incrementToken()) {
     System.out.println(tokenStream);
   }
 }

中文分詞:
StandardAnalyzer:單字分詞床佳,把每一個(gè)字當(dāng)成一個(gè)詞

//中文分詞器StandardAnalyzer測(cè)試
@Test
public void testStandardAnalyzer() throws Exception {
   analyzerMethod(new StandardAnalyzer(), ch);
 }

CJKAnalyzer:二分法分詞,把相臨的兩個(gè)字當(dāng)成一個(gè)詞榄审,比如我們是中國(guó)人砌们;我們,們是搁进,是中浪感,中國(guó),國(guó)人等

//中文分詞器CJKAnalyzer測(cè)試
@Test
public void testCJKAnalyzer() throws Exception {
analyzerMethod(new CJKAnalyzer(), ch);
}

SmartChineseAnalyzer:字典分詞饼问,也叫詞庫(kù)分詞影兽;把中文的詞全部放置到一個(gè)詞庫(kù)中,按某種算法來維護(hù)詞庫(kù)內(nèi)容莱革;如果匹配到就切分出來成為詞語(yǔ)辅肾。通常詞庫(kù)分詞被認(rèn)為是最理想的中文分詞算法厕氨。如:“我們是中國(guó)人”挺举,效果為:“我們”询刹、“中國(guó)人”。(可以使用SmartChineseAnalyzer左冬,“極易分詞” MMAnalyzer 桐筏,或者是“庖丁分詞”分詞器、IKAnalyzer拇砰。推薦使用IKAnalyzer )

//中文分詞器SmartChineseAnalyzer測(cè)試
//需要導(dǎo)入jar包lucene-analyzers-smartcn-4.10.4.jar
@Test
public void testSmartChineseAnalyzer() throws Exception {
   analyzerMethod(new SmartChineseAnalyzer(), ch);
  }
}

IKAnalyzer:第三方的
1.導(dǎo)入jar包 IKAnalyzer2012FF_u1.jar(這個(gè)包在中央倉(cāng)庫(kù)是沒有的)支持停詞和自定義拓展詞
2.添加停詞詞典stopword.dic
3.添加拓展詞典ext.dic

//中文分詞器IKAnalyzer測(cè)試
//需要導(dǎo)入jar包IKAnalyzer2012FF_u1.jar
 @Test
 public void testIKAnalyzer() throws Exception {
   analyzerMethod(new IKAnalyzer(), ch);
 }

如果想去掉"的","了","嗎".....的語(yǔ)氣詞我們可以加入配置文件
IKAnalyzer.cfg.xml和stopword.dic

在stopword.dic文件里添加我們不需要的分詞即可,這樣拆分詞元就不會(huì)把這些停詞作為分詞了

我們?nèi)绻爰尤胍恍┪覀冏约盒枰脑~元?jiǎng)t需要在配置文件IKAnalyzer.cfg.xml中配置一個(gè)額外分詞文件 拓展詞典ext.dic
在拓展詞典ext.dic中設(shè)置我們自定義的詞元


索引庫(kù)的更新
public class CRUDTest {

    String content1 = "hello world";
    String content2 = "hello java world";
    String content3 = "hello lucene world";
    String indexPath = "luncecrud";
    Analyzer analyzer = new StandardAnalyzer();//分詞器

    //創(chuàng)建索引
    @Test
    public void testCreateIndex() throws Exception {
        //1.創(chuàng)建索引寫入器
        Directory d = FSDirectory.open(new File(indexPath));//索引需要存放的位置
        //創(chuàng)建索引寫入器配置對(duì)象
        IndexWriterConfig conf = new IndexWriterConfig(Version.LUCENE_4_10_4, analyzer);
        IndexWriter writer = new IndexWriter(d, conf);
        //2.寫入文檔信息
        //添加文檔 定義字段的存儲(chǔ)規(guī)則
        FieldType type = new FieldType();
        type.setIndexed(true);//是否要索引
        type.setStored(true);//是否需要存儲(chǔ)
        Document document1 = new Document();//數(shù)據(jù)庫(kù)中的一條數(shù)據(jù)
        //new Field("字段名","字段內(nèi)容","字段的配置屬性")
        document1.add(new Field("title", "doc1", type));//該條記錄中的字段 title:doc1
        document1.add(new Field("content", content1, type));//content: hello world
        writer.addDocument(document1);

        Document document2 = new Document();
        document2.add(new Field("title", "doc2", type));
        document2.add(new Field("content", content2, type));
        writer.addDocument(document2);

        Document document3 = new Document();
        document3.add(new Field("title", "doc3", type));
        document3.add(new Field("content", content3, type));
        writer.addDocument(document3);

        //需要把添加的記錄保存
        writer.commit();
        writer.close();
        testSearch();
    }

    @Test
    public void testUpdate() throws Exception {
        //創(chuàng)建索引寫入器
        Directory d = FSDirectory.open(new File(indexPath));
        IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_4_10_4, analyzer);
        IndexWriter writer = new IndexWriter(d, config);
        //更新對(duì)象
        Term term = new Term("title", "doc2");//更新的條件
        Document updateDoc = new Document();//更新之后的文檔對(duì)象
        FieldType type = new FieldType();
        type.setIndexed(true);
        type.setStored(true);
        updateDoc.add(new Field("title", "doc2", type));
        updateDoc.add(new Field("content", "hello黃河之水天上來吧我要更新內(nèi)容啦", type));
        writer.updateDocument(term, updateDoc);
        //提交更新內(nèi)容 釋放資源
        writer.commit();
        writer.close();
        testSearch();
    }

    //索引查詢過程
    @Test
    public void testSearch() throws Exception {
        //1.創(chuàng)建索引寫入器
        Directory d = FSDirectory.open(new File(indexPath));

        //打開索引目錄
        IndexReader r = DirectoryReader.open(d);
        IndexSearcher searcher = new IndexSearcher(r);
        QueryParser parser = new QueryParser("content", analyzer);

        Query query = parser.parse("hello");//查詢hello
        //search(查詢對(duì)象,符合條件的前n條記錄)
        TopDocs search = searcher.search(query, 10000);//n:前幾個(gè)結(jié)果
        System.out.println("符合條件的記錄有多少個(gè):" + search.totalHits);
        ScoreDoc[] scoreDocs = search.scoreDocs;
        Document doc = null;
        for (int i = 0; i < scoreDocs.length; i++) {
            System.out.println("*******************************");
            System.out.println("分?jǐn)?shù):" + scoreDocs[i].score);//相關(guān)度的排序
            int docId = scoreDocs[i].doc;//文檔編號(hào)
            Document document = searcher.doc(docId);
            System.out.println("文檔編號(hào) docId--->" + docId);
            System.out.println("標(biāo)題內(nèi)容 title:--->" + document.get("title"));
            System.out.println("正文內(nèi)容 content:--->" + document.get("content"));
        }
    }
}

先創(chuàng)建一個(gè)創(chuàng)建索引的方法testCreateIndex()和索引查詢的方法testSearch()然后創(chuàng)建一個(gè)索引更新的方法testUpdate();
先執(zhí)行testCreateIndex()

在執(zhí)行testUpdate();

把文檔標(biāo)題為doc2 的內(nèi)容更新為新的內(nèi)容,同時(shí)文檔編號(hào)發(fā)生變化,文檔編號(hào)為1的被刪除,增加類文檔編號(hào)3.說明更新的操作是先刪除后添加

刪除索引庫(kù)
 @Test
    public void testDelete()throws Exception{
        //創(chuàng)建索引寫入器
        Directory d = FSDirectory.open(new File(indexPath));
        IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_4_10_4, analyzer);
        IndexWriter writer = new IndexWriter(d, config);
        //刪除記錄
        /**
         * 方式一
         Term term=new Term("title","doc2");
         writer.deleteDocuments(term);
         */
        //方式二
        QueryParser parser = new QueryParser("title", analyzer);
        Query query = parser.parse("doc3");
        writer.deleteDocuments(query);

        //將刪除操作提交
        writer.commit();
        writer.close();
        testSearch();
    }

Lucene進(jìn)階

查詢所有
//索引查詢過程1
public void search1(String content) throws Exception {
  //1.創(chuàng)建索引寫入器
  Directory d = FSDirectory.open(new File(indexPath));
  //創(chuàng)建分詞器
  Analyzer analyzer = new StandardAnalyzer();
  //打開索引目錄
  IndexReader r = DirectoryReader.open(d);
  IndexSearcher searcher = new IndexSearcher(r);
  QueryParser parser = new QueryParser("content", analyzer);

  Query query = parser.parse(content);//查詢hello
  //search(查詢對(duì)象,符合條件的前n條記錄)
  TopDocs search = searcher.search(query, 10000);//n:前幾個(gè)結(jié)果
  System.out.println("符合條件的記錄有多少個(gè):" + search.totalHits);

  ScoreDoc[] scoreDocs = search.scoreDocs;
    for (int i = 0; i < scoreDocs.length; i++) {
    System.out.println("*******************************");
    System.out.println("分?jǐn)?shù):" + scoreDocs[i].score);//相關(guān)度的排序
    int docId = scoreDocs[i].doc;//文檔編號(hào)
    Document document = searcher.doc(docId);
    System.out.println("文檔編號(hào) docId--->" + docId);
    System.out.println("標(biāo)題內(nèi)容 title:--->" + document.get("title"));
    System.out.println("正文內(nèi)容 content:--->" + document.get("content"));
        }
    }


//索引查詢過程2
 public void search2(Query query) throws Exception {
//1.創(chuàng)建索引寫入器
 Directory d = FSDirectory.open(new File(indexPath));
//創(chuàng)建分詞器
 Analyzer analyzer = new StandardAnalyzer();
  //打開索引目錄
 IndexReader r = DirectoryReader.open(d);
 IndexSearcher searcher = new IndexSearcher(r);
 QueryParser parser = new QueryParser("content", analyzer);
 //search(查詢對(duì)象,符合條件的前n條記錄)
 TopDocs search = searcher.search(query, 10000);//n:前幾個(gè)結(jié)果
 System.out.println("符合條件的記錄有多少個(gè):" + search.totalHits);

 ScoreDoc[] scoreDocs = search.scoreDocs;
 for (int i = 0; i < scoreDocs.length; i++) {
    System.out.println("*******************************");
    System.out.println("分?jǐn)?shù):" + scoreDocs[i].score);//相關(guān)度的排序
    int docId = scoreDocs[i].doc;//文檔編號(hào)
    Document document = searcher.doc(docId);
    System.out.println("文檔編號(hào) docId--->" + docId);
    System.out.println("標(biāo)題內(nèi)容 title:--->" + document.get("title"));
    System.out.println("正文內(nèi)容 content:--->" + document.get("content"));
    }
    }

@Test
public void test1() throws Exception {
 search1("*:*");//查詢所有,匹配所有字段
 search2(new MatchAllDocsQuery());
 }
單詞搜索
 /**
     * 單詞搜索
     *
     * @throws Exception
     */
    @Test
    public void test2() throws Exception {
        //search("title:doc1"); --->public void search(String content)
        search(new TermQuery(new Term("title", "doc1")));//--->search(Query query)
    }
段落查詢
 /**
     * 段落查詢
     * @throws Exception
     */
    @Test
    public void test3() throws Exception {
     // search("content:\"hello world\"");
      PhraseQuery query =new PhraseQuery();
      query.add(new Term("content","hello"));
      query.add(new Term("content","world"));
      search(query);
    }
通配符檢索
/**
 * 通配符檢索
 * @throws Exception
 */
@Test
public void test4() throws Exception {
//查詢所有
//方式1
 search("l*ne");
//方式2
 search("luenc?");
//方式3
 WildcardQuery query = new WildcardQuery(new Term("content","l*ne"));
   search(query);
}

search("l**ne");中的 *表示多個(gè)字符
search("luenc?");中的?表示一個(gè)字符

單詞模糊查詢

Lucene支持單詞容錯(cuò)content:lucenx ~1 表示支持單詞容錯(cuò)一個(gè)字母,content:lucenx~N N最大值為2

@Test
public void test5() throws Exception{
search("content:lxcenX~2");
FuzzyQuery query = new FuzzyQuery(new Term("content","lucenx"),1);
search(query);
}

相似查詢?cè)陉P(guān)鍵字后面使用 ~ ( 波浪線)符號(hào)梅忌,后面可以跟一個(gè)表示相似度的數(shù)字,比如~0.85 , ~ 0.3 , ~1除破,值在0-1之間牧氮,1表示非常相似度最高,默認(rèn)為0.5瑰枫。

@Test
public void test6() throws Exception{
search("lqcenX~1");
FuzzyQuery query = new FuzzyQuery(new Term("content","lqcenX"));
search(query);
}
段落查詢 (臨近查詢)

content:"hello world"~1 表示這個(gè)段落中間可以插入1個(gè)單詞
content:"hello world"~N 表示這個(gè)段落中間可以插入N個(gè)單詞

 /**
  * 段落查詢 (臨近查詢)
  * @throws Exception
  */
 @Test
 public void test7() throws Exception{
  //~1 表示這個(gè)段落中間可以插入一個(gè)單詞
  //content:\"hello world\"~N 表示這個(gè)段落中間可以插入N個(gè)單詞
  //search("content:\"hello world\"~1");
   PhraseQuery query = new PhraseQuery();
   query.add(new Term("content","hello"));
   query.add(new Term("content","world"));
   query.setSlop(1);//設(shè)置中間有一個(gè)停詞
   search(query);
 }
范圍檢索
 /**
  * 范圍檢索
  */
@Test
public void test8() throws Exception {
//  {:左開區(qū)間
//  }:右開區(qū)間
//  [:左閉區(qū)間
//  ]:右閉區(qū)間
//search("inputtime:{20101010 TO 20101012}");
//TermRangeQuery(查詢字段,左邊的值,右邊的值,是否左閉區(qū)間,是否右閉區(qū)間);
  TermRangeQuery query = new TermRangeQuery("inputtime", new BytesRef("20101010"), new BytesRef("20101012"), false, false);
   search(query);
}
組合查詢

AND和&&:目標(biāo)-->查詢出標(biāo)題中包括One及內(nèi)容中包括java的文檔踱葛;
下面兩種情況均可:
title:one && content:java
title:one AND content:java

/**
 * 組合查詢AND和&&
 * @throws Exception
 */
 @Test
 public  void test9() throws Exception {
   //search("content:hello AND inputtime:{20101010 TO 20101012}");
    search("content:hello && inputtime:{20101010 TO 20101012}");
   /*
    BooleanQuery query = new BooleanQuery();
    query.add(new TermQuery(new Term("content","hello")), BooleanClause.Occur.MUST);
    query.add(new TermRangeQuery("inputtime",new BytesRef("20101010"),new BytesRef("20101012"),false,false), BooleanClause.Occur.MUST);
    search(query);
   */
 }

OR和||:查詢出標(biāo)題中包括One但內(nèi)容中不包括java的文檔丹莲;
默認(rèn)情況下分詞組合即為邏輯或(OR)方式。
下面三種情況均可:
title:one || content:java
title:one OR content:java
title:one content:java

/**
 * 組合查詢OR和||
 * @throws Exception
 */
@Test
public  void test10() throws Exception {
//search("content:lucene OR inputtime:{20101010 TO 20101012}");
//search("content:lucene || inputtime:{20101010 TO 20101012}");
  BooleanQuery query = new BooleanQuery();
  query.add(new TermQuery(new Term("content","lucene")), BooleanClause.Occur.SHOULD);
  query.add(new TermRangeQuery("inputtime",new BytesRef("20101010"),new BytesRef("20101012"),false,false), BooleanClause.Occur.SHOULD);
  search(query);
}

Not或!:查詢出標(biāo)題中包括One但內(nèi)容中不包括java的文檔尸诽;
下面兩種情況均可:
title:one ! content:java
title:one NOT content:java

/**
 * 組合查詢OR和||
 * @throws Exception
 */
@Test
public  void test10() throws Exception {
  //search("content:lucene OR inputtime:{20101010 TO 20101012}");
  //search("content:lucene || inputtime:{20101010 TO 20101012}");
    BooleanQuery query = new BooleanQuery();
    query.add(new TermQuery(new Term("content","lucene")), BooleanClause.Occur.SHOULD);
    query.add(new TermRangeQuery("inputtime",new BytesRef("20101010"),new BytesRef("20101012"),false,false), BooleanClause.Occur.SHOULD);
    search(query);
}

必須包括(+)及排除(-):目標(biāo)--->查詢出標(biāo)題中包括One但內(nèi)容中不包括java的文檔甥材;
+title:one -content:title

增加權(quán)重

Luence允許我們?cè)诮M合查詢中,指定某一個(gè)詞的相關(guān)性權(quán)重值性含,從而可以讓得到相關(guān)性高的結(jié)果;
要提升一個(gè)詞的相關(guān)性權(quán)重洲赵,則可以在關(guān)鍵詞的后面添加^n來實(shí)現(xiàn)。
比如查詢jakarta apache商蕴,如果要把jakarta 的相關(guān)性提高叠萍,則可以改為jakarta^4 apache
相關(guān)性權(quán)重也可以用于詞組查詢,比如"jakarta apache"^4 "Apache Lucene" 將把與jakarta apache詞組最相關(guān)的優(yōu)先排列出來绪商;
相關(guān)性權(quán)重值默認(rèn)為1苛谷,一般要提升權(quán)重時(shí)均設(shè)置為大于1的整數(shù);該值也可以為0-1的小數(shù)格郁,但不能為負(fù)數(shù)腹殿。

/**
 *  增加權(quán)重
 * @throws Exception
 */
@Test
public  void test12() throws Exception {
 //search("content:lucene^10 java");
   BooleanQuery query = new BooleanQuery();
   TermQuery termQuery = new TermQuery(new Term("content", "lucene"));
   termQuery.setBoost(10);//該查詢對(duì)象添加權(quán)重
   query.add(termQuery, BooleanClause.Occur.SHOULD);
   query.add(new TermQuery(new Term("content","java")), BooleanClause.Occur.SHOULD);
  search(query);
}
特殊字符

由于| & ! + - ( ) 等符號(hào)在查詢表達(dá)式中被用做關(guān)鍵字,因此要查詢這些字符必須使用\來進(jìn)行轉(zhuǎn)義處理例书。
當(dāng)前Lucene查詢中的特殊字符:+ - && || ! ( ) { } [ ] ^ " ~ * ? :
比如赫蛇,要查詢包括(1+1):2 的文檔,需要使用到如下表達(dá)式:
(1+1):2

分組
使用括號(hào)()對(duì)查詢表示式分組Grouping
Lucene查詢語(yǔ)法中支持通過()來對(duì)查詢表達(dá)式進(jìn)行分組雾叭,從而組合出各種復(fù)雜的查詢。
1落蝙、查詢出標(biāo)題中包括one或two织狐,但內(nèi)容中不包括java的文檔;
Query query=parser.parse("title:(one OR two) NOT content:java");

高亮實(shí)現(xiàn)

1筏勒、高亮的概述:從搜索結(jié)果中截取一部分摘要移迫,并把符合條件的記錄添加高亮顯示;
高亮需要使用jar包lucene-highlighter-4.10.4.jar
2管行、高亮涉及的功能包括兩部分:A厨埋、截取摘要,B捐顷、高亮顯示

Formatter formatter = new SimpleHTMLFormatter("<font color=\"red\">","</font>");
Scorer scorer = new QueryScorer(query);
Highlighter hl = new Highlighter(formatter,scorer);
hl.setMaxDocCharsToAnalyze(20);
String str=hl.getBestFragment(new StandardAnalyzer(), "content",doc.get("content"));
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末荡陷,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子迅涮,更是在濱河造成了極大的恐慌废赞,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件叮姑,死亡現(xiàn)場(chǎng)離奇詭異唉地,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門耘沼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來极颓,“玉大人,你說我怎么就攤上這事群嗤〔ぢ。” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵骚烧,是天一觀的道長(zhǎng)浸赫。 經(jīng)常有香客問我,道長(zhǎng)赃绊,這世上最難降的妖魔是什么既峡? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮碧查,結(jié)果婚禮上运敢,老公的妹妹穿的比我還像新娘。我一直安慰自己忠售,他們只是感情好传惠,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著稻扬,像睡著了一般卦方。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上泰佳,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天盼砍,我揣著相機(jī)與錄音,去河邊找鬼逝她。 笑死浇坐,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的黔宛。 我是一名探鬼主播近刘,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼臀晃!你這毒婦竟也來了觉渴?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤积仗,失蹤者是張志新(化名)和其女友劉穎疆拘,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體寂曹,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡哎迄,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年回右,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片漱挚。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡翔烁,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出旨涝,到底是詐尸還是另有隱情蹬屹,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布白华,位于F島的核電站慨默,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏弧腥。R本人自食惡果不足惜厦取,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望管搪。 院中可真熱鬧虾攻,春花似錦、人聲如沸更鲁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)澡为。三九已至漂坏,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間媒至,已是汗流浹背樊拓。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留塘慕,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓蒂胞,卻偏偏與公主長(zhǎng)得像图呢,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子骗随,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容