辞書アプリの改良:部分一致(4)
追記
Indexer.groovy、MetaIndex.groovyを修正(10/27: 01:54)
関連エントリ
StarDictの辞書アプリ - Random Note
辞書アプリの改良 - Random Note
辞書アプリの改良:初期化処理の高速化(1) - Random Note
辞書アプリの改良:初期化処理の高速化(2) - Random Note
辞書アプリの改良:コマンド化 - Random Note
辞書アプリの改良:部分一致(1) - Random Note
辞書アプリの改良:部分一致(2) - Random Note
辞書アプリの改良:部分一致(3) - Random Note
メタインデックス作成ツール修正
辞書アプリの改良:部分一致(2) - Random Noteで作成したメタインデックス作成ツールにはバグがあった。
Index#toMetaIndexListの中の下記の部分は
while (i <val.length()-MetaIndex.INDEX_KEY_CHAR_LENGTH -1) {
こうでないと、辞書インデックスの最後の3文字分がメタインデックス化されない。
while (i <val.length()-MetaIndex.INDEX_KEY_CHAR_LENGTH +1) {
ここを修正すると、今度はテストで使っている辞書データファイルのメタインデックスを作成するのに1GBのヒープメモリでも不足するようになってしまった。
そこで、まずはこのツールを修正する。
修正点は次の3つ。
- 上記のバグフィックス
- メタインデックス作成ツールと辞書アプリでクラスを共有するようになってきているので、共有しているクラスを別ファイルに分割
- メタインデックス作成中に一時ファイルに作成中のメタインデックスを退避し、メモリの使用量を抑える
結果、できたソースは下記の通り。
Indexer.groovy
workDir = new File("work") deleteTmpFile() workDir.mkdirs() def indexes = loadCompleteIndex() List metaList = [] int i=0 for (idx in indexes) { metaList.addAll(idx.toMetaIndexList()) i++ if (i%100 == 0) { makeTmpFile(metaList) metaList = [] println ("parsing:" + (i*100/indexes.size()) + "%") } } makeTmpFile(metaList) makeMetaFile() def makeMetaFile() { File outFile = new File("test.meta") outFile.delete() outFile.createNewFile() def os = new BufferedOutputStream(new FileOutputStream(outFile)) listTmpFiles().each {f -> println ("writing: " + hex2str(f.name) + ":" + f.length() + ":" + (f.length() / MetaIndex.LENGTH)) def buf = f.readBytes() def list = [] def i = 0 while ((i*MetaIndex.LENGTH) < buf.length) { list << new MetaIndex(buf, i*MetaIndex.LENGTH) i++ } list.sort() i = 0 for (m in list) { os.write(m.toBytes()) metaSize += m.toBytes().length i++ } } os.close() } def listTmpFiles() { def list = workDir.listFiles().toList() return list.sort {l,r -> return hex2str(l.name).compareTo(hex2str(r.name)) } } def makeTmpFile(metaList) { metaList.sort() def prev = str2Hex("a") def tmpFile = new BufferedOutputStream(new FileOutputStream(new File(workDir, prev), true)) for (m in metaList) { def first = str2Hex(m.word.substring(0,1)) if (prev != first) { prev = first tmpFile.close() tmpFile = new BufferedOutputStream(new FileOutputStream(new File(workDir, prev), true)) } tmpFile.write(m.toBytes()) } tmpFile.close() } def str2Hex(String val) { byte[] bytes = val.getBytes() def result = "" bytes.each { result += Integer.toHexString(it & 0xff) } return result } def hex2str(val) { byte[] buf = new byte[(val.length() / 2)] int i=0 while (val.length() > 0) { buf[i] = Byte.parseByte(val.substring(0, 1), 16) * 16 + Byte.parseByte(val.substring(1, 2), 16) val = val.substring(2) i++ } return new String(buf, "UTF-8") } def deleteTmpFile() { workDir.listFiles().each { it.delete() } workDir.delete() } def loadCompleteIndex(indexOffset = 0, indexLength = -1) { if (indexOffset < 0) indexOffset = 0 RandomAccessFile randomFile = new RandomAccessFile("test.idx", "r") if (indexLength == -1) indexLength = randomFile.length() def buf = new byte[indexLength] randomFile.seek(indexOffset) randomFile.read(buf) randomFile.close() def i=0 int offset = 0 int count = 0 def indexes = new ArrayList(); while (i<buf.length) { if (buf[i] == 0) { def idx = new Index(buf, offset, i-offset+9) if (idx.word.length() > 0) indexes.add(idx) offset = i+9 i = i+8 count++ } i++ if (i%200000 == 0) println ("loading:" + (i*100/(indexLength)) + "%") } return indexes }
BaseIndex.groovy
class BaseIndex { def word def offset def length def cut(bytes, o, l) { def i=0 def buf = new byte[l] while (i < l) { buf[i] = bytes[i+o] i++ } return buf } def byte2int(byte[] bytes) { return byte2int(bytes, 0, bytes.length) } def byte2int(byte[] bytes, int offset, int length) { def result = 0 int i = 0 while (i < length) { result += (bytes[i+offset] & 0xff) * (int)(Math.pow(256, length - i - 1)) i++ } return result } byte[] int2byte(intVal) { byte[] b = new byte[4]; b[0] = ((intVal & 0xFF000000) /(256 ** 3)) b[1] = ((intVal & 0x00FF0000) /(256 ** 2)) b[2] = ((intVal & 0x0000FF00) /256) b[3] = intVal & 0x000000FF return b } int startsWith(String string) { int i=0 while (i<word.length() && i<string.length()) { if (word.charAt(i) != string.charAt(i)) return word.charAt(i) - string.charAt(i) i++ } return word.length() < string.length() ? -1 : 0 } String toString() { return "{word: ${word},\toffset:${offset},\tlength:${length}}" } String normalize(String value) { return value.toLowerCase() } }
Index.groovy
class Index extends BaseIndex { int indexOffset = 0 int indexLength = 0 def Index(byte[] buf, int o, int l) { word = new String(buf, o, l-9, "UTF-8").trim() offset = byte2int(buf, o+l-8, 4) length = byte2int(buf, o+l-4, 4) indexOffset = o indexLength = l } def toMetaIndexList() { List list = [] def val = normalize(word) int i=0 while (i <val.length()-MetaIndex.KEY_CHAR_LENGTH +1) { list << new MetaIndex(val.substring(i, i+MetaIndex.KEY_CHAR_LENGTH), indexOffset, indexLength, i) i++ } if (val.length() == 1) list << new MetaIndex(val, indexOffset, indexLength, 0) return list } }
MetaIndex.groovy
class MetaIndex extends BaseIndex implements Comparable { public static final int LENGTH = 24 public static final int KEY_LENGTH = 12 public static final int KEY_CHAR_LENGTH = 2 int wordOffset = 0 def MetaIndex(String w, int o, int l, int wo) { word = w offset = o length = l wordOffset = wo } def MetaIndex(byte[] buf) { parse(buf, 0) } def MetaIndex(byte[] buf, int o) { parse(buf, o) } def parse(byte[] buf, int o) { word = new String(buf, o, KEY_LENGTH, "UTF-8").trim() // 余計なバイト(0)をtrimで削除 offset = byte2int(buf, o+KEY_LENGTH, 4) length = byte2int(buf, o+KEY_LENGTH+4, 4) wordOffset = byte2int(buf, o+KEY_LENGTH+8, 4) } int compareTo(MetaIndex target) { if (word != target.word) return word.compareTo(target.word) if (offset != target.offset) return offset <=> target.offset if (wordOffset != target.wordOffset) return wordOffset <=> target.wordOffset return 0 } int compareTo(Object target) { if (target instanceof MetaIndex) return compareTo((MetaIndex)target) if (target instanceof String) return word.compareTo(target) return -1 } byte[] toBytes() { byte[] buf = new byte[LENGTH] Arrays.fill(buf, (byte)0) byte[] w = word.getBytes("UTF-8") byte[] o = int2byte(offset) byte[] l = int2byte(length) byte[] wo = int2byte(wordOffset) int i=0 for (b in w) { buf[i] = b i++ } i = 12 for (b in o) { buf[i] = b i++ } for (b in l) { buf[i] = b i++ } for (b in wo) { buf[i] = b i++ } return buf } String toString() { return "{word: ${word},\toffset:${offset},\tlength:${length},\twordOffset:${wordOffset}}" } }