본문으로 바로가기
반응형

이번에는 특정 폴더 내부의 파일내용을 검색하는 방법을 알아봅니다.

https://tourspace.tistory.com/237 이 글에서는 하나의 파일의 내용을 읽어 검색하는 방법을 알아 봤습니다.


먼저 예제로 사용될 target Folder에는 세개의 파일이 존재합니다.

이 세개의 파일이 검색대상이 되며, 각 파일의 내용은 아래와 같습니다.


파일명: text1

Lucene Core, our flagship sub-project, provides Java-based indexing and search technology, as well as spellchecking, hit highlighting and advanced analysis/tokenization capabilities.

SolrTM is a high performance search server built using Lucene Core, with XML/HTTP and JSON/Python/Ruby APIs, hit highlighting, faceted search, caching, replication, and a web admin interface.

PyLucene is a Python wrapper around the Core project.


파일명: text2

The Lucene project has added two new announce mailing lists, issues@lucene.apache.org and builds@lucene.apache.org. High-volume automated emails from our bug tracker, JIRA and GitHub will be moved from the dev@ list to issues@ and automated emails from our Jenkins CI build servers will be moved from the dev@ list to builds@.

This is an effort to reduce the sometimes overwhelming email volume on our main development mailing list and thus make it easier for the community to follow important discussions by humans on the dev@lucene.apache.org list.

Everyone who wants to continue receiving these automated emails should sign up for one or both of the two new lists. Sign-up instructions can be found on the Lucene-java and Solr web sites.


파일명: text3

New Features

New XYShape Field and Queries for indexing and querying general cartesian geometries.

Snowball stemmer/analyzer for the Estonian language.

Provide a FeatureSortfield to allow sorting search hits by descending value of a feature.

Add new KoreanNumberFilter that can change Hangul character to number and process decimal point.

Add doc-value support to range fields.

Add monitor subproject (previously Luwak monitoring library) that allows a stream of documents to be matched against a set of registered queriesin an efficient manner.

Add a numeric range query in sandbox that takes advantage of index sorting.Add a numeric range query in sandbox that takes advantage of index sorting.

위 파일은 모두 apache lucene 공식 페이지의 첫 문장부터 3개의 단락 정도를 복사해 왔습니다.

 

먼저 index를 생성하기 위해 필요한 util 성격의 helper class를 만듭니다.

Write Helper class 준비

import org.apache.lucene.analysis.standard.StandardAnalyzer
import org.apache.lucene.document.*
import org.apache.lucene.index.IndexWriter
import org.apache.lucene.index.IndexWriterConfig
import org.apache.lucene.store.FSDirectory
import java.io.File
import java.nio.file.Path
import java.nio.file.Paths
import java.nio.file.attribute.BasicFileAttributes


class DocumentWriter2 {
    companion object {
        const val INDEX_PATH = "/Users/myhome/workspace/LuceneTest/index2"
    }

    val indexWriter = makeIndexWriter()

    fun createDoc(path: Path, attrs: BasicFileAttributes): Document {
        return Document().apply {
            add(StringField("path", path.toString(), Field.Store.YES))
            add(TextField("content", File(path.toString()).readText(), Field.Store.YES))
            add(LongPoint("size", attrs.size()))
            add(StringField("size", attrs.size().toString(), Field.Store.YES))
        }
    }

    private fun makeIndexWriter(): IndexWriter {
        //Writer 생성
        val dir = FSDirectory.open(Paths.get(INDEX_PATH))
        val config = IndexWriterConfig(StandardAnalyzer())
        return IndexWriter(dir, config)
    }
}


makeIndexWriter()를 통해 indexWriter를 만드는건 이전 예제와 동일 합니다.

createDoc() 에서는 java.nio.file 패키지의 Path 클래스와 BasicFileAttributes 클래스를 param으로 넘겨받아 필요한 field를 저장합니다.

파일의 내용을 검색해야 하므로 TextField로 파일 내용 전체를 저장합니다.

TextField로 저장했으니, 내용들이 전부 tokenize 되서 indexing 되겠죠?


또한 size를 exact match로 검색하기 위하여 LongPoint Filed로 저장합니다.

size값 역시 나중에 검색 결과로 사용하고 싶으므로 StringField로도 실제 값도 저장합니다. 


물론 StringField로만 저장해도 size값을 검색할수도 있으나, 아래 검색쪽 코드에서 LongPoint를 이용하여 query를 생성하는 예제를 보여주기 위하여 LongPoint field를 추가하였습니다.

혹, Filed의 대한 이해가 아직 부족하다면 하기 link의 글을 먼저 읽기를 추천드립니다.

https://tourspace.tistory.com/239


Write class 생성

index를 write하는 main 함수를 작성합니다.

import org.apache.lucene.index.Term
import java.io.IOException
import java.nio.file.*
import java.nio.file.attribute.BasicFileAttributes

fun main(args: Array) {

    // SourceFile 경로
    val TARGET_PATH = "/Users/myhome/workspace/LuceneTest/searchTarget"

    val docWriter = DocumentWriter2()
    val path = Paths.get(TARGET_PATH)

    // 일단 초기화
    docWriter.indexWriter.deleteAll()

    // nio 패키지를 이용란 파일 전체 순회 처리
    Files.walkFileTree(path, object : SimpleFileVisitor() {
        @Throws(IOException::class)
        override fun visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult {
            try {
                //document 생성.
                val doc = docWriter.createDoc(file, attrs)
                //index 생성 -> path가 같을 경우 덮어쓰기.
                docWriter.indexWriter.updateDocument(Term("path", file.toString()), doc)
            } catch (e: IOException) {
                e.printStackTrace()
            }

            return FileVisitResult.CONTINUE
        }
    })

    docWriter.indexWriter.close()
}


이 역시 이전 예제와 거의 동일합니다.

다만 검색할 대상이 되는 searchTarget 폴더를 순차적으로 전부 탐색하기 위해 java.nio.file package를 사용합니다.

walkFileTree()를 호출하면 폴더 내부를 전부 순회하며 탐색하게 됩니다.

이때 넘겨주는 SimpleFileVisitor()를 통해서 폴더나, 파일을 만났을때의 동작을 지정할 수 있습니다.

여기서는 파일일때만 index를 추가하면 되므로 visitFile() 함수만 override 하여 사용하였습니다.


이전 예제에서는 document 생성후 이를 index로 write 할때 addDocument() 함수를 이용하였습니다.

여기서는 updateDocument() 함수를 이용하며, 기준이 되는 "path"값이 있으면 기존꺼를 update하고, 없으면 새로 생성합니다.


Search class 생성

import org.apache.lucene.analysis.standard.StandardAnalyzer
import org.apache.lucene.document.LongPoint
import org.apache.lucene.index.DirectoryReader
import org.apache.lucene.queryparser.classic.QueryParser
import org.apache.lucene.search.IndexSearcher
import org.apache.lucene.search.Query
import org.apache.lucene.search.TopDocs
import org.apache.lucene.store.FSDirectory
import java.nio.file.Paths

class FileSearch {
    companion object {
        const val INDEX_PATH = "/Users/1111336/workspace/LuceneTest/index2"
    }

    private val searcher = getIndexSearcher()

    //Search 생성
    private fun getIndexSearcher(): IndexSearcher {
        val dir = FSDirectory.open(Paths.get(INDEX_PATH))
        val reader = DirectoryReader.open(dir)
        return IndexSearcher(reader)
    }

    fun search(column: String, text: String): TopDocs {
        val qp = QueryParser(column, StandardAnalyzer())
        val query = qp.parse(text)
        return search(query)
    }

    fun search(query: Query) = searcher.search(query, 10)

    fun getDoc(docid: Int) = searcher.doc(docid)
}

fun main(args: Array) {
    val fileSearcher = FileSearch()

    // content에서 lucene 검색
    val topDocs1 = fileSearcher.search("content", "lucene")

    println("searchCount = ${topDocs1.totalHits}")
    topDocs1.scoreDocs.forEach {
        val document = fileSearcher.getDoc(it.doc)
        println("file: ${document.get("path")} | " +
                "size:${document.get("size")}")
    }

    // size가 428 컨텐츠를 검색
    val query = LongPoint.newExactQuery("size", 428)
    val topDocs2 = fileSearcher.search(query)

    println("searchCount = ${topDocs2.totalHits}")
    topDocs2.scoreDocs.forEach {
        val document = fileSearcher.getDoc(it.doc)
        println("file: ${document.get("path")} | " +
                "size:${document.get("size")}")
    }
}


먼저 FileSearch class를 만들고 util 성격의 함수를 추가합니다.

이전 예제와 동일하나, search 함수를 두개 만들었습니다.

하나는 검색할 column과 value를 넣는 함수와 하나는 직접 query를 받아서 검색하는 함수 입니다.


main() 함수에서 먼저 content로 "lucene"을 검색합니다.

첫번째 결과로 아래의 값들이 출력됩니다.

searchCount = 2 hits

file: /Users/myhome/workspace/LuceneTest/searchTarget/text1 | size:428

file: /Users/myhome/workspace/LuceneTest/searchTarget/text2 | size:740

 lucene은 문서 두개에 속하며, 해당 문서의 크기를 나타냅니다.


두번째로 LongPoint로 저장한 file size를 검색해 봅니다.

이때는 LongPoint에서 제공하는 newExactQuery() 함수를 이용합니다.

searchCount = 1 hits

file: /Users/1111336/workspace/LuceneTest/searchTarget/text1 | size:428

이때는 정확하게 요청한 428 사이즈를 같는 text1만 검색됩니다.

반응형