簡體   English   中英

將文件列表作為Java 8 Stream讀取

[英]Reading a list of Files as a Java 8 Stream

我有一個(可能很長)二進制文件列表,我想懶惰地閱讀。 將有太多文件加載到內存中。 我目前正在使用FileChannel.map()它們作為MappedByteBuffer讀取,但這可能不是必需的。 我希望方法readBinaryFiles(...)返回Java 8 Stream,這樣我就可以在訪問文件時懶惰加載文件列表。

    public List<FileDataMetaData> readBinaryFiles(
    List<File> files, 
    int numDataPoints, 
    int dataPacketSize )
    throws
    IOException {

    List<FileDataMetaData> fmdList = new ArrayList<FileDataMetaData>();

    IOException lastException = null;
    for (File f: files) {

        try {
            FileDataMetaData fmd = readRawFile(f, numDataPoints, dataPacketSize);
            fmdList.add(fmd);
        } catch (IOException e) {
            logger.error("", e);
            lastException = e;
        }
    }

    if (null != lastException)
        throw lastException;

    return fmdList;
}


//  The List<DataPacket> returned will be in the same order as in the file.
public FileDataMetaData readRawFile(File file, int numDataPoints, int dataPacketSize) throws IOException {

    FileDataMetaData fmd;
    FileChannel fileChannel = null;
    try {
        fileChannel = new RandomAccessFile(file, "r").getChannel();
        long fileSz = fileChannel.size();
        ByteBuffer bbRead = ByteBuffer.allocate((int) fileSz);
        MappedByteBuffer buffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fileSz);

        buffer.get(bbRead.array());
        List<DataPacket> dataPacketList = new ArrayList<DataPacket>();

        while (bbRead.hasRemaining()) {

            int channelId = bbRead.getInt();
            long timestamp = bbRead.getLong();
            int[] data = new int[numDataPoints];
            for (int i=0; i<numDataPoints; i++) 
                data[i] = bbRead.getInt();

            DataPacket dp = new DataPacket(channelId, timestamp, data);
            dataPacketList.add(dp);
        }

        fmd = new FileDataMetaData(file.getCanonicalPath(), fileSz, dataPacketList);

    } catch (IOException e) {
        logger.error("", e);
        throw e;
    } finally {
        if (null != fileChannel) {
            try {
                fileChannel.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    return fmd;
}

readBinaryFiles(...)返回fmdList.Stream()將無法完成此操作,因為文件內容已經被讀入內存,我將無法做到。

將多個文件的內容作為Stream讀取的其他方法依賴於使用Files.lines() ,但我需要讀取二進制文件。

我願意在Scala或Golang中這樣做,如果這些語言比Java更能支持這個用例。

我很感激任何關於如何懶惰地閱讀多個二進制文件內容的指針。

有沒有為你正在閱讀的整個文件構造的一個實例的一個文件讀取懶惰可能FileDataMetaData 您需要對該類進行大量重構才能構建FileDataMetaData實例,而無需讀取整個文件。

但是,在該代碼中有幾個要清理的東西,甚至特定於Java 7而不是Java 8,即您不再需要RandomAccessFile繞道來打開一個通道,並且嘗試使用資源以確保正確關閉。 請進一步注意,使用內存映射毫無意義。 在映射文件后將整個內容復制到堆ByteBuffer ,沒有什么是懶惰的。 當在通道上使用堆ByteBuffer調用read時,情況完全相同,除了JRE可以在read情況下重用緩沖區。

為了允許系統管理頁面,您必須從映射的字節緩沖區中讀取。 根據系統的不同,這可能仍然不會比將小塊重復讀入堆字節緩沖區更好。

public FileDataMetaData readRawFile(
    File file, int numDataPoints, int dataPacketSize) throws IOException {

    try(FileChannel fileChannel=FileChannel.open(file.toPath(), StandardOpenOption.READ)) {
        long fileSz = fileChannel.size();
        MappedByteBuffer bbRead=fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fileSz);
        List<DataPacket> dataPacketList = new ArrayList<>();
        while(bbRead.hasRemaining()) {
            int channelId = bbRead.getInt();
            long timestamp = bbRead.getLong();
            int[] data = new int[numDataPoints];
            for (int i=0; i<numDataPoints; i++) 
                data[i] = bbRead.getInt();
            dataPacketList.add(new DataPacket(channelId, timestamp, data));
        }
        return new FileDataMetaData(file.getCanonicalPath(), fileSz, dataPacketList);
    } catch (IOException e) {
        logger.error("", e);
        throw e;
    }
}

基於此方法構建Stream非常簡單,只需處理已檢查的異常:

public Stream<FileDataMetaData> readBinaryFiles(
    List<File> files, int numDataPoints, int dataPacketSize) throws IOException {
    return files.stream().map(f -> {
        try {
            return readRawFile(f, numDataPoints, dataPacketSize);
        } catch (IOException e) {
            logger.error("", e);
            throw new UncheckedIOException(e);
        }
    });
}

我不知道這是多么java.io.SequenceInputStream ,但你可以使用java.io.SequenceInputStream包裝在DataInputStream 這將有效地將您的文件連接在一起。 如果從每個文件創建一個BufferedInputStream ,那么應該正確緩沖整個事情。

基於VGR的評論 ,我認為他的基本解決方案是:

return files.stream().map(f -> readRawFile(f, numDataPoints, dataPacketSize))

是正確的,因為它將懶惰地處理文件(並且如果從map()操作的結果調用短路終端動作則停止。我還建議利用try資源的readRawFile的實現略有不同和InputStream,它不會將整個文件加載到內存中:

public FileDataMetaData readRawFile(File file, int numDataPoints, int dataPacketSize)
  throws DataPacketReadException { // <- Custom unchecked exception, nested for class

  FileDataMetadata results = null;

  try (FileInputStream fileInput = new FileInputStream(file)) {
    String filePath = file.getCanonicalPath();
    long fileSize = fileInput.getChannel().size()

    DataInputStream dataInput = new DataInputStream(new BufferedInputStream(fileInput);

    results = new FileDataMetadata(
      filePath, 
      fileSize,
      dataPacketsFrom(dataInput, numDataPoints, dataPacketSize, filePath);
  }

  return results;
}

private List<DataPacket> dataPacketsFrom(DataInputStream dataInput, int numDataPoints, int dataPacketSize, String filePath)
    throws DataPacketReadException { 

  List<DataPacket> packets = new 
  while (dataInput.available() > 0) {
    try {
      // Logic to assemble DataPacket
    }
    catch (EOFException e) {
      throw new DataPacketReadException("Unexpected EOF on file: " + filePath, e);
    }
    catch (IOException e) {
      throw new DataPacketReadException("Unexpected I/O exception on file: " + filePath, e);
    }
  }

  return packets;
}

這應該減少代碼量,並確保您的文件在出錯時關閉。

這應該足夠了:

return files.stream().map(f -> readRawFile(f, numDataPoints, dataPacketSize));

...如果,也就是說,您願意從readRawFile方法的簽名中刪除throws IOException 您可以讓該方法在內部捕獲IOException並將其包裝在UncheckedIOException中 (延遲執行的問題是異常也需要延遲。)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM