2017-02-21 118 views
1

我正在使用RandomAccessFile從大文件讀取一些信息。 RandomAccessFile有一個方法seek,它將光標指向我想要讀取整行的文件的特定部分。要閱讀這一行,我使用readLine()方法。讀取文件中最後一行的最快方法

我之前閱讀了這個整個文件,然後創建了一個索引,允許我使用seek方法訪問任何行的開頭。這個指數工作正常。 我創建了一個基於這個答案此指數:https://stackoverflow.com/a/42077860/763368

因爲我必須做很多在這個文件的訪問,性能十分照顧,於是我找了其他的選擇來讀取文件將涉及的重要問題特定的線路並獲得整條線路。

我看過FileChannelMappedByteBuffer是一個很好的選擇來快速讀取文件,但我沒有看到任何解決方案,做我想做的。

P.S .:線條有不同的長度,我不知道這個長度。

有沒有人有一個很好的解決方案?

編輯

文件我要讀有遵循格式:關鍵\t

該指數是與該文件已經鍵的所有鍵和值一個HashMap是字節位置(Long)。

讓我們假設我想進入該關鍵「富」行,那我一定設法值位置,就像這樣:

raf.seek(index.get("foo")) 

如果我使用raf.readLine()回報率將是整個與鑰匙「foo」一致。

但我不想使用RandomAccessFile進行這項工作,因爲它太慢了。

這是我在斯卡拉現在做的方式:

val raf = new RandomAccessFile(file,"r") 
raf.seek(position.get(key)) 
println(raf.readLine) 
raf.close 
+2

是不是訪問到不同的文件?如果沒有,爲什麼你關閉文件訪問?如果保持文件訪問權限不變,則不必等待操作系統授予您讀取權限。 – Tschallacka

+0

@Tschallacka我只是在所有閱讀結束時才結束,這只是一個例子。但是我的問題在於讀取文件的方式。 –

+0

您能否提供索引閱讀的代碼以及如何將其翻譯爲查找位置?由於您已經走上了一條良好的道路,您的索引查找可能會從一些優化中受益,但是如果沒有完整的代碼和示例數據,則很難提供幫助。 – Tschallacka

回答

1

如果您已經有通過文件讀取一次找到鑰匙的指數,絕對速度最快的解決辦法是讀線並記住它們。如果由於某些原因(例如內存限制)不起作用,使用緩衝區確實是一個很好的選擇。這是代碼大綱:

FileChannel channel = new RandomAccessFile("/some/file", "r").getChannel(); 

long pageSize = ...; // e.g. "3 GB or file size": max(channel.size(), THREE_GB); 
long position = 0; 
ByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, position, pageSize); 

ByteBuffer slice; 
int maxLineLength = 30; 
byte[] lineBuffer = new byte[maxLineLength]; 

// Read line at indices 20 - 25 
buffer.position(20); 
slice = buffer.slice(); 
slice.get(lineBuffer, 0, 6); 
System.out.println("Starting at 20:" + new String(lineBuffer, Charset.forName("UTF8"))); 

// Read line at indices 0 - 10 
buffer.position(0); 
slice = buffer.slice(); 
slice.get(lineBuffer, 0, 11); 
System.out.println("Starting at 0:" + new String(lineBuffer, Charset.forName("UTF8"))); 

此代碼也可以用於非常大的文件。只需撥打channel.map找到「頁」裏你的關鍵所在位置:position = keyIndex/pageSize * pageSize,然後調用buffer.position從指數:keyIndex - position

如果你真的沒有任何辦法組獲得一「頁」在一起,那麼你不需要slice。業績不會好,但是這可以讓你簡化代碼進一步:

byte[] lineBuffer = new byte[maxLineLength]; 
// ... 
ByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, keyIndex, lineLength); 
buffer .get(lineBuffer, 0, lineLength); 
System.out.println(new String(lineBuffer, Charset.forName("UTF8"))); 

注意,ByteBuffer未在JVM堆上創建,但實際上是在操作系統級別內存映射文件。 (從Java 8開始,您可以通過查看源代碼並在實現中搜索sun.nio.ch.DirectBuffer來驗證這一點)。

線尺寸:獲得行大小的最好方法是儲存它,當你通過文件掃描,即使用Map[String, (Long, Int)],而不是現在使用的是什麼index。如果不爲你工作,你應該運行一些測試,以找出什麼是快:

  • 只存儲最大行的大小,然後搜索這個最大長度的字符串中的換行。在這種情況下,請注意您覆蓋了在單元測試中訪問文件的末尾。
  • ByteBuffer.get繼續掃描,直至遇到\n。如果您有真正的Unicode文件,那麼這可能不是一種選擇,因爲換行的ASCII碼(0x0A)可以出現在其他地方,例如UTF-16編碼的韓文音節中帶有字符代碼0xAC0A。

這將是第二個方法的Scala代碼:

// this happens once 
val maxLineLength: Long = 2000 // find this in your initial sequential scan 
val lineBuffer = new Array[Byte](maxLineLength.asInstanceOf[Int]) 

// this is how you read a key 
val bufferLength = maxLineLength min (channel.size() - index("key")) 
val buffer = channel.map(FileChannel.MapMode.READ_ONLY, index("key"), bufferLength) 
var lineLength = 0 // or minLineLength 
while (buffer.get(lineLength) != '\n') { 
    lineLength += 1 
} 
buffer.get(lineBuffer, 0, lineLength - 1) 
println(new String(lineBuffer, Charset.forName("UTF8"))) 
+0

我有一個索引,所以我可以訪問一行的開頭。我訪問這個索引,然後在那裏尋找。有了與RandomAccessFile不同的其他選項,我也想找到這個位置,索引也會被使用。 –

+0

我已經閱讀過所有文件,然後創建了一個索引。我把這個索引放在內存中,這樣我就可以訪問它,並通過查找方法進入一行的開頭。使用與RandomAccessFile不同的其他選項,我也想尋求這個位置,索引也會被使用。 –

+0

不,我不能把這個文件放在內存中,它大於100GB。我的解決方案有效,但速度很慢,這是我的問題。 –

相關問題