用Java處理大文件

最近,我不得不處理一組包含逐筆歷史匯率市場數據的文件,并很快意識到使用傳統的InputStream都無法將它們讀取到內存中,因為每個文件的大小都超過4 GB。 Emacs甚至無法打開它們。

在這種特殊情況下,我可以編寫一個簡單的bash腳本,將文件分成小塊,然后像往常一樣讀取它們。 但是我不希望這樣,因為二進制格式會使這種方法無效。

因此,正確處理此問題的方法是使用內存映射文件逐步處理數據區域。 內存映射文件的優點在于它們不消耗虛擬內存或分頁空間,因為它們由磁盤上的文件數據支持。

Okey,讓我們看一下這些文件并提取一些數據。 似乎它們包含帶有逗號分隔字段的ASCII文本行。

格式: [currency-pair],[timestamp],[bid-price],[ask-price]

例如: EUR/USD,20120102 00:01:30.420,1.29451,1.2949

公平地說,我可以為該格式編寫程序。 但是讀取和解析文件是正交的概念。 因此,讓我們退后一步,考慮一下可以在將來遇到類似問題時可以重用的通用設計。

問題歸結為對一組以無限長字節數組編碼的條目進行增量解碼,而不會耗盡內存。 示例格式以逗號/行分隔的文本編碼的事實與一般解決方案無關,因此很明顯,需要解碼器接口才能處理不同的格式。

同樣,在處理完整個文件之前,無法解析每個條目并將其保留在內存中,因此我們需要一種方法來逐步移交可以在其他位置(磁盤或網絡)寫入的條目塊,然后再進行垃圾回收。 迭代器是處理此要求的很好的抽象方法,因為它們的行為就像游標一樣,這正是重點。 每次迭代都會轉發文件指針,然后讓我們對數據進行處理。

所以首先是Decoder接口。 這個想法是從MappedByteBuffer增量解碼對象,或者如果緩沖區中沒有對象,則返回null。

public interface Decoder<T> {public T decode(ByteBuffer buffer);
}

然后是實現IterableFileReader 。 每次迭代將處理下一個4096字節的數據,并使用Decoder將其Decoder為對象列表。 請注意, FileReader接受文件列表,這很不錯,因為它允許遍歷數據而無需擔心跨文件聚合。 順便說一下,對于較大的文件,4096個字節的塊可能會有點小。

public class FileReader implements Iterable<List<T>> {private static final long CHUNK_SIZE = 4096;private final Decoder<T> decoder;private Iterator<File> files;private FileReader(Decoder<T> decoder, File... files) {this(decoder, Arrays.asList(files));}private FileReader(Decoder<T> decoder, List<File> files) {this.files = files.iterator();this.decoder = decoder;}public static <T> FileReader<T> create(Decoder<T> decoder, List<File> files) {return new FileReader<T>(decoder, files);}public static <T> FileReader<T> create(Decoder<T> decoder, File... files) {return new FileReader<T>(decoder, files);}@Overridepublic Iterator<List<T>> iterator() {return new Iterator<List<T>>() {private List<T> entries;private long chunkPos = 0;private MappedByteBuffer buffer;private FileChannel channel;@Overridepublic boolean hasNext() {if (buffer == null || !buffer.hasRemaining()) {buffer = nextBuffer(chunkPos);if (buffer == null) {return false;}}T result = null;while ((result = decoder.decode(buffer)) != null) {if (entries == null) {entries = new ArrayList<T>();}entries.add(result);}// set next MappedByteBuffer chunkchunkPos += buffer.position();buffer = null;if (entries != null) {return true;} else {Closeables.closeQuietly(channel);return false;}}private MappedByteBuffer nextBuffer(long position) {try {if (channel == null || channel.size() == position) {if (channel != null) {Closeables.closeQuietly(channel);channel = null;}if (files.hasNext()) {File file = files.next();channel = new RandomAccessFile(file, "r").getChannel();chunkPos = 0;position = 0;} else {return null;}}long chunkSize = CHUNK_SIZE;if (channel.size() - position < chunkSize) {chunkSize = channel.size() - position;}return channel.map(FileChannel.MapMode.READ_ONLY, chunkPos, chunkSize);} catch (IOException e) {Closeables.closeQuietly(channel);throw new RuntimeException(e);}}@Overridepublic List<T> next() {List<T> res = entries;entries = null;return res;}@Overridepublic void remove() {throw new UnsupportedOperationException();}};}
}

下一個任務是編寫一個Decoder ,我決定為任何逗號分隔的文本文件格式實現一個通用的TextRowDecoder ,接受每行的字段數和一個字段定界符,并返回一個字節數組數組。 然后, TextRowDecoder可以由可能處理不同字符集的格式特定的解碼器重用。

public class TextRowDecoder implements Decoder<byte[][]> {private static final byte LF = 10;private final int numFields;private final byte delimiter;public TextRowDecoder(int numFields, byte delimiter) {this.numFields = numFields;this.delimiter = delimiter;}@Overridepublic byte[][] decode(ByteBuffer buffer) {int lineStartPos = buffer.position();int limit = buffer.limit();while (buffer.hasRemaining()) {byte b = buffer.get();if (b == LF) { // reached line feed so parse lineint lineEndPos = buffer.position();// set positions for one row duplicationif (buffer.limit() < lineEndPos + 1) {buffer.position(lineStartPos).limit(lineEndPos);} else {buffer.position(lineStartPos).limit(lineEndPos + 1);}byte[][] entry = parseRow(buffer.duplicate());if (entry != null) {// reset main bufferbuffer.position(lineEndPos);buffer.limit(limit);// set start after LFlineStartPos = lineEndPos;}return entry;}}buffer.position(lineStartPos);return null;}public byte[][] parseRow(ByteBuffer buffer) {int fieldStartPos = buffer.position();int fieldEndPos = 0;int fieldNumber = 0;byte[][] fields = new byte[numFields][];while (buffer.hasRemaining()) {byte b = buffer.get();if (b == delimiter || b == LF) {fieldEndPos = buffer.position();// save limitint limit = buffer.limit();// set positions for one row duplicationbuffer.position(fieldStartPos).limit(fieldEndPos);fields[fieldNumber] = parseField(buffer.duplicate(), fieldNumber, fieldEndPos - fieldStartPos - 1);fieldNumber++;// reset main bufferbuffer.position(fieldEndPos);buffer.limit(limit);// set start after LFfieldStartPos = fieldEndPos;}if (fieldNumber == numFields) {return fields;}}return null;}private byte[] parseField(ByteBuffer buffer, int pos, int length) {byte[] field = new byte[length];for (int i = 0; i < field.length; i++) {field[i] = buffer.get();}return field;}
}

這就是文件的處理方式。 每個列表包含從單個緩沖區解碼的元素,每個元素都是由TextRowDecoder指定的字節數組的數組。

TextRowDecoder decoder = new TextRowDecoder(4, comma);
FileReader<byte[][]> reader = FileReader.create(decoder, file.listFiles());
for (List<byte[][]> chunk : reader) {// do something with each chunk
}

我們可以在這里停下來,但還有其他要求。 每行都包含一個時間戳記,并且必須按時間段而不是按天或按小時對緩沖區進行分組。 我仍然想遍歷每個批次,因此立即的反應是為FileReader創建一個Iterable包裝器,以實現此行為。 另外一個細節是,每個元素必須通過實現PeriodEntries Timestamped接口(此處未顯示)為PeriodEntries提供其時間戳。

public class PeriodEntries<T extends Timestamped> implements Iterable<List<T>> {private final Iterator<List<T extends Timestamped>> entriesIt;private final long interval;private PeriodEntries(Iterable<List<T>> entriesIt, long interval) {this.entriesIt = entriesIt.iterator();this.interval = interval;}public static <T extends Timestamped> PeriodEntries<T> create(Iterable<List<T>> entriesIt, long interval) {return new PeriodEntries<T>(entriesIt, interval);}@Overridepublic Iterator<List<T extends Timestamped>> iterator() {return new Iterator<List<T>>() {private Queue<List<T>> queue = new LinkedList<List<T>>();private long previous;private Iterator<T> entryIt;@Overridepublic boolean hasNext() {if (!advanceEntries()) {return false;}T entry =  entryIt.next();long time = normalizeInterval(entry);if (previous == 0) {previous = time;}if (queue.peek() == null) {List<T> group = new ArrayList<T>();queue.add(group);}while (previous == time) {queue.peek().add(entry);if (!advanceEntries()) {break;}entry = entryIt.next();time = normalizeInterval(entry);}previous = time;List<T> result = queue.peek();if (result == null || result.isEmpty()) {return false;}return true;}private boolean advanceEntries() {// if there are no rows leftif (entryIt == null || !entryIt.hasNext()) {// try get more rows if possibleif (entriesIt.hasNext()) {entryIt = entriesIt.next().iterator();return true;} else {// no more rowsreturn false;}}return true;}private long normalizeInterval(Timestamped entry) {long time = entry.getTime();int utcOffset = TimeZone.getDefault().getOffset(time);long utcTime = time + utcOffset;long elapsed = utcTime % interval;return time - elapsed;}@Overridepublic List<T> next() {return queue.poll();}@Overridepublic void remove() {throw new UnsupportedOperationException();}};}
}

引入此功能后,最終處理代碼并沒有太大變化,只有一個干凈緊湊的for循環,不必關心跨文件,緩沖區和句點對元素進行分組。 PeriodEntries也足夠靈活,可以管理間隔上的任何長度。

TrueFxDecoder decoder = new TrueFxDecoder();
FileReader<TrueFxData> reader = FileReader.create(decoder, file.listFiles());
long periodLength = TimeUnit.DAYS.toMillis(1);
PeriodEntries<TrueFxData> periods = PeriodEntries.create(reader, periodLength);for (List<TrueFxData> entries : periods) {// data for each dayfor (TrueFxData entry : entries) {// process each entry}
}

正如您可能意識到的那樣,不可能用集合來解決這個問題。 選擇迭代器是一項關鍵的設計決策,它能夠解析TB級的數據而不會占用太多的堆空間。

參考: Deephacks博客上的JCG合作伙伴 Kristoffer Sjogren 使用Java處理大型文件 。

翻譯自: https://www.javacodegeeks.com/2013/01/processing-huge-files-with-java.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/370070.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/370070.shtml
英文地址,請注明出處:http://en.pswp.cn/news/370070.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

java IO(一):File類

1.File類簡介 File類位于java.io包中。它面向文件層次級別操作、查看文件&#xff0c;而字節流、字符流操作數據時顯然比之更底層。 學習File類包括以下幾個重點&#xff1a;文件路徑、文件分隔符、創建文件(目錄)、刪除文件(目錄)、查看文件內容(輸出目錄內文件)、判斷文件(是…

android listview 開發,android開發之ListView實現

今天又初步學習了一下ListView控件&#xff0c;看看效果如下&#xff1a;LisViewActivity.java源碼&#xff1a;package com.jinhoward.UI_listview;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;import android.os.Bundl…

input ios問題 小程序_微信小程序開發常見問題匯總

原標題&#xff1a;微信小程序開發常見問題匯總1、域名必須是https非https的域名不被微信小程序允許。2、input組件placeholder字體顏色卸載placeholder-class里面的color并不生效&#xff0c;需要寫在placeholder-style里面就可以了。3、wx.navigateTo無法跳轉到帶tabbar的頁面…

https://github.com/

https://github.com/ qq郵箱 轉載于:https://www.cnblogs.com/chang1/p/7133251.html

Less 的用法

1. node.js node.js是一個前端的框架 自帶一個包管理工具npm node.js 的安裝 官網&#xff1a;http://nodejs.cn/ 在命令行檢驗是否安裝成功 切換到項目目錄&#xff0c;初始化了一個package.json文件 安裝與卸載jQuery包&#xff08;例子&#xff09; 安裝 卸載 安裝淘寶…

淺談springboot整合ganymed-ssh2遠程訪問linux

環境介紹 技術棧 springbootmybatis-plusmysqlganymed-ssh2 軟件 版本 mysql 8 IDEA IntelliJ IDEA 2022.2.1 JDK 1.8 Spring Boot 2.7.13 mybatis-plus 3.5.3.2 SSH(遠程連接工具)連接原理&#xff1a;ssh服務是一個守護進程(demon)&#xff0c;系統后臺監聽客戶…

優化Neo4j Cypher查詢

上周&#xff0c;我花了很多時間嘗試使用實時系統中的數據來優化大約20個執行失敗的Cypher查詢&#xff08;36866ms至155575ms&#xff09;。 經過一番嘗試和錯誤&#xff0c;以及來自Michael的大量投入&#xff0c;我能夠大致確定對查詢進行哪些操作才能使它們性能更好-最后&a…

python 多文件知識

對于一個大型的項目&#xff0c;會存在很多個py文件&#xff0c;本文記錄與多文件有關的內容。 1. python 如何在一個.py文件中調用另一個.py文件的類 如果是在同一個 module中(也就是同一個py 文件里),直接用就可以如果在不同的module里,例如a.py里有 class A:b.py 里有 class…

android pick file,LFilePicker---文件選擇利器,各種樣式有它就夠了

LFilePicker在 Android 開發中如果需要選擇某個文件&#xff0c;可以直接調取系統的文件管理器進行選擇&#xff0c;但是無法保證各個廠商的手機界面一致&#xff0c;而且解析Uri 還比較繁瑣&#xff0c;如果還需要多選呢&#xff1f;需要文件類型過濾呢&#xff1f;老板說界面…

老筆記整理二:網頁小問題匯總

最近有一些小問題。想在這里寫出來。一是方便大家排錯&#xff0c;再是自己也整理一下。 1。很傻的小問題。。。參數提交方式有一個應該是form而不是from。&#xff08;英語老師&#xff0c;我對不起你。。。&#xff09; 2。用超鏈接傳參數&#xff0c;在&#xff1f;后面不能…

在JVM之下–類加載器

在許多開發人員中&#xff0c;類加載器是Java語言的底層&#xff0c;并且經常被忽略。 在ZeroTurnaround上 &#xff0c;我們的開發人員必須生活&#xff0c;呼吸&#xff0c;飲食&#xff0c;喝酒&#xff0c;并且幾乎與類加載器保持親密關系&#xff0c;才能生產JRebel技術&a…

matplotlib繪制餅狀圖

源自http://blog.csdn.net/skyli114/article/details/77508430?ticketST-41707-PzNbUDGt6R5KYl3TkWDg-passport.csdn.net pyplot使用plt.pie()來繪制餅圖 1 import matplotlib.pyplot as plt 2 labels frogs, hogs, dogs, logs 3 sizes 15, 20, 45, 10 # [15,20,45,10…

自適應寬度元素單行文本省略用法探究

單行文本省略是現代網頁設計中非常常用的技術&#xff0c;幾乎每個站點都會用到。單行文本省略適用于顯示摘要信息的場景&#xff0c;如列表標題、文章摘要等。在響應式開發中&#xff0c;自適應寬度元素單行文本省略容易失效不起作用&#xff0c;對網頁開發這造成困擾。因此&a…

P3390 【模板】矩陣快速冪

題目背景 矩陣快速冪 題目描述 給定n*n的矩陣A&#xff0c;求A^k 輸入輸出格式 輸入格式&#xff1a; 第一行&#xff0c;n,k 第2至n1行&#xff0c;每行n個數&#xff0c;第i1行第j個數表示矩陣第i行第j列的元素 輸出格式&#xff1a; 輸出A^k 共n行&#xff0c;每行n個數&…

c#精彩編程200例百度云_永安市教育局被授予“人工智能編程教育試驗區”

11月28日&#xff0c;“第二屆人工智能與機器人教育大會青少年人工智能與編程教育主題論壇”在廈門召開。永安市教育局被中國教育發展戰略學會人工智能與機器人教育專委會授予“人工智能編程教育試驗區”牌匾&#xff0c;巴溪灣小學、西門小學、三中、一中附屬學校、實驗小學等…

python中+=和=+的區別

本文原創&#xff0c;版權屬作者個人所有&#xff0c;如需轉載請聯系作者本人。Q&微&#xff1a;155122733 -------------------------------------------------------------------------------------------------------- a1 代表在原值上更改 aa1相當于先定義一個變量&…

Spring Data JPA和分頁

讓我們從支持分頁的經典JPA方法開始。 考慮一個簡單的域類–一個具有名字&#xff0c;姓氏的“成員”。 為了支持在成員列表上進行分頁&#xff0c;JPA方法是支持一種查找器&#xff0c;該查找器將獲取第一個結果&#xff08;firstResult&#xff09;的偏移量和要檢索的結果&am…

南陽理工 題目63 小猴子下落

小猴子下落 時間限制&#xff1a;3000 ms | 內存限制&#xff1a;65535 KB 難度&#xff1a;3 描述 有一顆二叉樹&#xff0c;最大深度為D,且所有葉子的深度都相同。所有結點從左到右從上到下的編號為1,2,3&#xff0c;&#xff0c;2的D次方減1。在結點1處放一個小猴子&#…

vue 方法獲取返回值_vue.js - vuex異步提交,怎么獲取返回數據

問 題 做登錄頁面時,在vuex中的action異步提交后獲取的數據在mutations中存儲在state里面,但是總感覺不對,沒有返回數據,我前端頁面怎么獲取數據,用mapgetter獲取不到數據,是不是他不是實時更新的,而且輸出的mapgetter輸出的數據還在action的前面。下面是我前端部分代碼…

Windows環境下安裝、卸載Apache

安裝Apache 服務 打開 Apcahe的目錄 &#xff0c;打開bin目錄&#xff0c; 如&#xff1a;E:\wamp\Apache24\bin &#xff0c;打開目錄&#xff0c;Shift鍵 鼠標右鍵 &#xff0c; 點擊 在此處打開命令窗口或者W快捷鍵直接到此處&#xff0c; 也可以Window鍵r&#xff0c;輸入…