辞書アプリの改良:初期化処理の高速化(1)

初期化処理の高速化

StarDictの辞書アプリで作った辞書アプリは、インデックスを最初に読み込んでいるため、インデックスファイルが大きいと初期化処理が重くなる。そこで、インデックスファイルをメモリ上に読み込むのはやめ、検索実行の際に直接インデックスファイルを読むようにする。

StarDictの辞書形式は1つのインデックスのサイズが不定のため、ファイルを頭から読み込まないとどこに目的のインデックスがあるのか分からない。そのため、インデックスファイルのインデックス、メタインデックスを以下のような固定長形式のファイルとして用意し、検索の際はまずそれを利用する。

1インデックスごとの形式

  • 1-12ビット:インデックスキー文字列の最初の2文字(UTF-8)
  • 13-16ビット:インデックスファイル中の位置(offset)
  • 17-20ビット:インデックスキー文字列の長さ(バイト数)

メタインデックス作成ツール

まず最初に必要なのは上記のメタインデックスファイルを作成するツールだ。
メタインデックス作成ツールの要件は下記の通り。

  • インデックスファイル名を引数に取る
  • 引数で指定されたインデックスファイルの全てのインデックスを上記形式に変換したメタインデックスファイルを作成する
  • メタインデックスファイル名は、インデックスファイル名の拡張子をmetaにしたもの

メタインデックス作成ツールのソース

とりあえず、こんな感じでいいかな。

def indexFileName = args[0]
def fileName = indexFileName
if (indexFileName.indexOf(".") > 0) fileName = indexFileName.substring(0, indexFileName.indexOf("."))
fileName += ".meta"

def indexes = loadIndex(indexFileName, fileName)

metaOut = new BufferedOutputStream(new FileOutputStream(fileName))
for (idx in indexes) {
	metaOut.write(idx.toByteArray())
}
metaOut.close()

def loadIndex(indexFileName, metaFileName, indexSize = 2, indexOffset = 0, indexLength = -1) {
	if (indexOffset < 0) indexOffset = 0
	def indexFile = new File(indexFileName)
	if (indexLength == -1) indexLength = indexFile.length()
	def buf = new byte[indexLength]
	def fileInputStream = new BufferedInputStream(new FileInputStream(indexFile))
	fileInputStream.skip(indexOffset)
	fileInputStream.read(buf)
	fileInputStream.close()

	def i=0
	int offset = 0
	def indexes = new ArrayList();
	def wd = ""
	def prev = ""
	while (i<buf.length) {
		if (buf[i] == 0) {
			wd = new String(buf, offset, i-offset, "UTF-8")
			if (indexSize > 0) {
				wd = wd.substring(0, indexSize>wd.length() ? wd.length() : indexSize)
			}
			if (prev != wd) {
				indexes.add(new Index(
					word:wd
					, offset:offset
					, length:i-offset+9))
			} else {
				def index = indexes.get(indexes.size()-1)
				index.addLength(i-offset+9)
			}
			prev = wd
			offset = i+9
			i = i+8
		}

		i++
	}
	return indexes
}
def parseInt(byte b1, byte b2, byte b3, byte b4) {
	return (b1 & 0xff) * 256*256*256 + (b2 & 0xff) * 256*256 + (b3 & 0xff) * 256 + (b4 & 0xff)
}

class Index {
	def word
	def offset
	def length

	def addLength(l) {length += l}
		
	def parseByteArray(intVal) {
		byte[] b = new byte[4];
		b[0] = ((intVal & 0xFF000000) /(256*256*256))
		b[1] = ((intVal & 0x00FF0000) /(256*256))
		b[2] = ((intVal & 0x0000FF00) /256)
		b[3] = intVal & 0x000000FF
		return b
	}
	String toString() {
		return "{word: ${word},\toffset:${offset},\tlength:${length},\t${toByteArray()}}"
	}
	byte[] toByteArray() {
		byte[] result = new byte[20]
		Arrays.fill(result, (byte)0)
		
		byte[] w = word.getBytes("UTF-8")
		byte[] o = parseByteArray(offset)
		byte[] l = parseByteArray(length)
		
		int i=0
		for (b in w) {
			result[i] = b
			i++
		}
		i = 12
		for (b in o) {
			result[i] = b
			i++
		}
		for (b in l) {
			result[i] = b
			i++
		}
		return result
	}
}