This is an automated email from the ASF dual-hosted git repository.

jt2594838 pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/tsfile.git


The following commit(s) were added to refs/heads/develop by this push:
     new b8825b18c Add buffered TsFile input (#868)
b8825b18c is described below

commit b8825b18ce296b6af31aabd1c7727f9007b72652
Author: Jiang Tian <[email protected]>
AuthorDate: Mon Jul 20 12:26:54 2026 +0800

    Add buffered TsFile input (#868)
    
    * Add buffered TsFile input
    
    * add a Constructor for PublicBAOS
---
 .../tsfile/read/reader/BufferedTsFileInput.java    | 203 +++++++++++++
 .../tsfile/read/reader/LocalTsFileInput.java       |   4 +
 .../java/org/apache/tsfile/utils/PublicBAOS.java   |   4 +
 .../read/TsFileSequenceReaderPerformanceTest.java  | 331 +++++++++++++++++++++
 .../read/reader/BufferedTsFileInputTest.java       | 145 +++++++++
 5 files changed, 687 insertions(+)

diff --git 
a/java/tsfile/src/main/java/org/apache/tsfile/read/reader/BufferedTsFileInput.java
 
b/java/tsfile/src/main/java/org/apache/tsfile/read/reader/BufferedTsFileInput.java
new file mode 100644
index 000000000..bea94fed6
--- /dev/null
+++ 
b/java/tsfile/src/main/java/org/apache/tsfile/read/reader/BufferedTsFileInput.java
@@ -0,0 +1,203 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.tsfile.read.reader;
+
+import org.apache.tsfile.i18n.Messages;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.ClosedChannelException;
+import java.nio.file.Path;
+import java.util.Objects;
+
+/** A local TsFile input that caches file read results in a {@link 
ByteBuffer}. */
+public class BufferedTsFileInput extends LocalTsFileInput {
+
+  private static final int DEFAULT_BUFFER_SIZE = 8 * 1024;
+
+  private final ByteBuffer buffer;
+  private long bufferStartPosition = -1;
+  private long logicalPosition;
+
+  public BufferedTsFileInput(Path file) throws IOException {
+    this(file, DEFAULT_BUFFER_SIZE);
+  }
+
+  public BufferedTsFileInput(Path file, int bufferSize) throws IOException {
+    super(validateBufferSize(file, bufferSize));
+    buffer = ByteBuffer.allocate(bufferSize);
+    buffer.limit(0);
+  }
+
+  private static Path validateBufferSize(Path file, int bufferSize) {
+    if (bufferSize <= 0) {
+      throw new IllegalArgumentException(
+          Messages.get("error.utils.buffer_size_not_positive_input"));
+    }
+    return file;
+  }
+
+  @Override
+  public long position() throws IOException {
+    ensureOpen();
+    return logicalPosition;
+  }
+
+  @Override
+  public BufferedTsFileInput position(long newPosition) throws IOException {
+    ensureOpen();
+    if (newPosition < 0) {
+      throw new IllegalArgumentException();
+    }
+    logicalPosition = newPosition;
+    return this;
+  }
+
+  @Override
+  public int read(ByteBuffer dst) throws IOException {
+    int readSize = read(dst, logicalPosition);
+    if (readSize > 0) {
+      logicalPosition += readSize;
+    }
+    return readSize;
+  }
+
+  @Override
+  public int read(ByteBuffer dst, long position) throws IOException {
+    Objects.requireNonNull(dst);
+    ensureOpen();
+    if (position < 0) {
+      throw new IllegalArgumentException();
+    }
+    if (!dst.hasRemaining()) {
+      return 0;
+    }
+
+    int totalReadSize = 0;
+    long currentPosition = position;
+    while (dst.hasRemaining()) {
+      if (isInBuffer(currentPosition)) {
+        int copiedSize = copyFromBuffer(dst, currentPosition);
+        totalReadSize += copiedSize;
+        currentPosition += copiedSize;
+        continue;
+      }
+
+      if (dst.remaining() >= buffer.capacity()) {
+        int readSize = super.read(dst, currentPosition);
+        if (readSize <= 0) {
+          return totalReadSize == 0 ? readSize : totalReadSize;
+        }
+        return totalReadSize + readSize;
+      }
+
+      int readSize = fillBuffer(currentPosition);
+      if (readSize <= 0) {
+        return totalReadSize == 0 ? readSize : totalReadSize;
+      }
+    }
+    return totalReadSize;
+  }
+
+  private boolean isInBuffer(long position) {
+    return position >= bufferStartPosition && position - bufferStartPosition < 
buffer.limit();
+  }
+
+  private int copyFromBuffer(ByteBuffer dst, long position) {
+    int bufferOffset = (int) (position - bufferStartPosition);
+    int copiedSize = Math.min(dst.remaining(), buffer.limit() - bufferOffset);
+    ByteBuffer source = buffer.asReadOnlyBuffer();
+    source.position(bufferOffset);
+    source.limit(bufferOffset + copiedSize);
+    dst.put(source);
+    return copiedSize;
+  }
+
+  private int fillBuffer(long position) throws IOException {
+    buffer.clear();
+    int readSize = super.read(buffer, position);
+    buffer.flip();
+    bufferStartPosition = position;
+    return readSize;
+  }
+
+  @Override
+  public InputStream wrapAsInputStream() {
+    return new BufferedTsFileInputStream();
+  }
+
+  @Override
+  public void close() throws IOException {
+    buffer.limit(0);
+    super.close();
+  }
+
+  private void ensureOpen() throws ClosedChannelException {
+    if (!isOpen()) {
+      throw new ClosedChannelException();
+    }
+  }
+
+  private class BufferedTsFileInputStream extends InputStream {
+
+    private final ByteBuffer oneByteBuffer = ByteBuffer.allocate(Byte.BYTES);
+
+    @Override
+    public int read() throws IOException {
+      oneByteBuffer.clear();
+      int readSize = BufferedTsFileInput.this.read(oneByteBuffer);
+      if (readSize < 0) {
+        return -1;
+      }
+      oneByteBuffer.flip();
+      return oneByteBuffer.get() & 0xFF;
+    }
+
+    @Override
+    public int read(byte[] bytes, int offset, int length) throws IOException {
+      return BufferedTsFileInput.this.read(ByteBuffer.wrap(bytes, offset, 
length));
+    }
+
+    @Override
+    public long skip(long skippedBytes) throws IOException {
+      if (skippedBytes <= 0) {
+        return 0;
+      }
+      long currentPosition = BufferedTsFileInput.this.position();
+      long remainingSize = Math.max(0, BufferedTsFileInput.this.size() - 
currentPosition);
+      long actualSkippedBytes = Math.min(skippedBytes, remainingSize);
+      BufferedTsFileInput.this.position(currentPosition + actualSkippedBytes);
+      return actualSkippedBytes;
+    }
+
+    @Override
+    public int available() throws IOException {
+      long remainingSize =
+          Math.max(0, BufferedTsFileInput.this.size() - 
BufferedTsFileInput.this.position());
+      return (int) Math.min(remainingSize, Integer.MAX_VALUE);
+    }
+
+    @Override
+    public void close() throws IOException {
+      BufferedTsFileInput.this.close();
+    }
+  }
+}
diff --git 
a/java/tsfile/src/main/java/org/apache/tsfile/read/reader/LocalTsFileInput.java 
b/java/tsfile/src/main/java/org/apache/tsfile/read/reader/LocalTsFileInput.java
index 9692c28cf..8ed39e3e9 100644
--- 
a/java/tsfile/src/main/java/org/apache/tsfile/read/reader/LocalTsFileInput.java
+++ 
b/java/tsfile/src/main/java/org/apache/tsfile/read/reader/LocalTsFileInput.java
@@ -45,6 +45,10 @@ public class LocalTsFileInput implements TsFileInput {
     filePath = file.toString();
   }
 
+  protected boolean isOpen() {
+    return channel.isOpen();
+  }
+
   @Override
   public long size() throws IOException {
     try {
diff --git a/java/tsfile/src/main/java/org/apache/tsfile/utils/PublicBAOS.java 
b/java/tsfile/src/main/java/org/apache/tsfile/utils/PublicBAOS.java
index fc3928b52..3d97cb0e7 100644
--- a/java/tsfile/src/main/java/org/apache/tsfile/utils/PublicBAOS.java
+++ b/java/tsfile/src/main/java/org/apache/tsfile/utils/PublicBAOS.java
@@ -43,6 +43,10 @@ public class PublicBAOS extends ByteArrayOutputStream {
     super(size);
   }
 
+  public PublicBAOS(byte[] buf) {
+    this.buf = buf;
+  }
+
   private void ensureCapacity(int minCapacity) {
     // overflow-conscious code
     if (minCapacity - buf.length > 0) {
diff --git 
a/java/tsfile/src/test/java/org/apache/tsfile/read/TsFileSequenceReaderPerformanceTest.java
 
b/java/tsfile/src/test/java/org/apache/tsfile/read/TsFileSequenceReaderPerformanceTest.java
new file mode 100644
index 000000000..a213daa9d
--- /dev/null
+++ 
b/java/tsfile/src/test/java/org/apache/tsfile/read/TsFileSequenceReaderPerformanceTest.java
@@ -0,0 +1,331 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.tsfile.read;
+
+import org.apache.tsfile.common.conf.TSFileConfig;
+import org.apache.tsfile.common.conf.TSFileDescriptor;
+import org.apache.tsfile.encoding.decoder.Decoder;
+import org.apache.tsfile.enums.TSDataType;
+import org.apache.tsfile.external.commons.io.FileUtils;
+import org.apache.tsfile.file.MetaMarker;
+import org.apache.tsfile.file.header.ChunkHeader;
+import org.apache.tsfile.file.header.PageHeader;
+import org.apache.tsfile.file.metadata.ChunkMetadata;
+import org.apache.tsfile.file.metadata.IDeviceID;
+import org.apache.tsfile.file.metadata.enums.CompressionType;
+import org.apache.tsfile.file.metadata.enums.TSEncoding;
+import org.apache.tsfile.read.common.BatchData;
+import org.apache.tsfile.read.reader.BufferedTsFileInput;
+import org.apache.tsfile.read.reader.LocalTsFileInput;
+import org.apache.tsfile.read.reader.TsFileInput;
+import org.apache.tsfile.read.reader.page.PageReader;
+import org.apache.tsfile.write.TsFileWriter;
+import org.apache.tsfile.write.record.Tablet;
+import org.apache.tsfile.write.schema.IMeasurementSchema;
+import org.apache.tsfile.write.schema.MeasurementSchema;
+
+import org.junit.Assume;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertEquals;
+
+public class TsFileSequenceReaderPerformanceTest {
+
+  private static final int FILE_COUNT = 100;
+  private static final int DEVICE_COUNT = 100;
+  private static final int POINTS_PER_DEVICE = 100_000;
+  private static final int[] POINT_COUNTS_PER_CHUNK = {100, 1_000, 10_000};
+  private static final int LOCAL_BUFFER_SIZE = 0;
+  private static final int[] BUFFER_SIZES = {4 * 1024, 8 * 1024, 16 * 1024, 32 
* 1024, 64 * 1024};
+  private static final String RUN_PERFORMANCE_TEST_PROPERTY = 
"tsfile.runPerformanceTests";
+  private static final File TEST_DIRECTORY =
+      new File("target", "tsfile-sequence-reader-performance");
+
+  @Test
+  public void testReadTsFilesWithDifferentBufferAndChunkSizes() throws 
Exception {
+    Assume.assumeTrue(
+        "Set -Dtsfile.runPerformanceTests=true to run the performance test",
+        Boolean.getBoolean(RUN_PERFORMANCE_TEST_PROPERTY));
+    TSFileConfig config = TSFileDescriptor.getInstance().getConfig();
+    int previousMaxPointsInPage = config.getMaxNumberOfPointsInPage();
+
+    try {
+      for (int pointsPerChunk : POINT_COUNTS_PER_CHUNK) {
+        assertEquals(0, POINTS_PER_DEVICE % pointsPerChunk);
+        int measurementCount = POINTS_PER_DEVICE / pointsPerChunk;
+        config.setMaxNumberOfPointsInPage(pointsPerChunk);
+        List<File> files = generateTsFiles(measurementCount, pointsPerChunk);
+
+        List<ReadConfiguration> configurations = createReadConfigurations();
+        for (int fileIndex = 0; fileIndex < files.size(); fileIndex++) {
+          File file = files.get(fileIndex);
+          int firstConfigurationIndex = fileIndex % configurations.size();
+          for (int offset = 0; offset < configurations.size(); offset++) {
+            ReadConfiguration configuration =
+                configurations.get((firstConfigurationIndex + offset) % 
configurations.size());
+            configuration.elapsedNanos +=
+                readTsFile(
+                    file,
+                    configuration.bufferSize,
+                    configuration.statistics,
+                    measurementCount,
+                    pointsPerChunk);
+          }
+        }
+
+        long expectedDeviceCount = (long) FILE_COUNT * DEVICE_COUNT;
+        long expectedChunkCount = expectedDeviceCount * measurementCount;
+        long expectedPointCount = expectedChunkCount * pointsPerChunk;
+        System.out.printf(
+            "Chunk scenario: points per chunk=%,d, measurements per 
device=%,d%n",
+            pointsPerChunk, measurementCount);
+        for (ReadConfiguration configuration : configurations) {
+          assertStatistics(
+              configuration.statistics,
+              expectedDeviceCount,
+              expectedChunkCount,
+              expectedPointCount);
+          printStatistics(
+              configuration.inputName, configuration.statistics, 
configuration.elapsedNanos);
+        }
+
+        ReadConfiguration localConfiguration = configurations.get(0);
+        for (int configurationIndex = 1;
+            configurationIndex < configurations.size();
+            configurationIndex++) {
+          ReadConfiguration bufferedConfiguration = 
configurations.get(configurationIndex);
+          System.out.printf(
+              "%s vs LocalTsFileInput: speedup=%.3fx, time 
reduction=%+.2f%%%n",
+              bufferedConfiguration.inputName,
+              localConfiguration.elapsedNanos / (double) 
bufferedConfiguration.elapsedNanos,
+              (localConfiguration.elapsedNanos - 
bufferedConfiguration.elapsedNanos)
+                  * 100.0
+                  / localConfiguration.elapsedNanos);
+        }
+      }
+    } finally {
+      config.setMaxNumberOfPointsInPage(previousMaxPointsInPage);
+      FileUtils.deleteDirectory(TEST_DIRECTORY);
+    }
+  }
+
+  private List<File> generateTsFiles(int measurementCount, int pointsPerChunk) 
throws Exception {
+    FileUtils.deleteDirectory(TEST_DIRECTORY);
+    Files.createDirectories(TEST_DIRECTORY.toPath());
+
+    List<IMeasurementSchema> schemas = 
createMeasurementSchemas(measurementCount);
+    Tablet tablet = createTablet(schemas, pointsPerChunk);
+    List<File> files = new ArrayList<>(FILE_COUNT);
+    for (int fileIndex = 0; fileIndex < FILE_COUNT; fileIndex++) {
+      File file = new File(TEST_DIRECTORY, "sequence-reader-" + fileIndex + 
".tsfile");
+      generateTsFile(file, schemas, tablet);
+      files.add(file);
+    }
+    return files;
+  }
+
+  private List<ReadConfiguration> createReadConfigurations() {
+    List<ReadConfiguration> configurations = new 
ArrayList<>(BUFFER_SIZES.length + 1);
+    configurations.add(new ReadConfiguration("LocalTsFileInput", 
LOCAL_BUFFER_SIZE));
+    for (int bufferSize : BUFFER_SIZES) {
+      configurations.add(
+          new ReadConfiguration(
+              String.format("BufferedTsFileInput(%d KiB)", bufferSize / 1024), 
bufferSize));
+    }
+    return configurations;
+  }
+
+  private List<IMeasurementSchema> createMeasurementSchemas(int 
measurementCount) {
+    List<IMeasurementSchema> schemas = new ArrayList<>(measurementCount);
+    for (int measurementIndex = 0; measurementIndex < measurementCount; 
measurementIndex++) {
+      schemas.add(
+          new MeasurementSchema(
+              "s" + measurementIndex, TSDataType.INT64, TSEncoding.TS_2DIFF, 
CompressionType.LZ4));
+    }
+    return schemas;
+  }
+
+  private Tablet createTablet(List<IMeasurementSchema> schemas, int 
pointsPerChunk) {
+    Tablet tablet = new Tablet(null, schemas, pointsPerChunk);
+    for (int pointIndex = 0; pointIndex < pointsPerChunk; pointIndex++) {
+      tablet.addTimestamp(pointIndex, pointIndex);
+    }
+    for (int measurementIndex = 0; measurementIndex < schemas.size(); 
measurementIndex++) {
+      for (int pointIndex = 0; pointIndex < pointsPerChunk; pointIndex++) {
+        tablet.addValue(pointIndex, measurementIndex, (long) pointIndex);
+      }
+    }
+    return tablet;
+  }
+
+  private void generateTsFile(File file, List<IMeasurementSchema> schemas, 
Tablet tablet)
+      throws Exception {
+    try (TsFileWriter writer = new TsFileWriter(file)) {
+      for (int deviceIndex = 0; deviceIndex < DEVICE_COUNT; deviceIndex++) {
+        String device = "root.performance.d" + deviceIndex;
+        IDeviceID deviceID = IDeviceID.Factory.DEFAULT_FACTORY.create(device);
+        for (IMeasurementSchema schema : schemas) {
+          writer.registerTimeseries(deviceID, schema);
+        }
+        tablet.setDeviceId(device);
+        writer.writeTree(tablet);
+      }
+    }
+  }
+
+  private long readTsFile(
+      File file,
+      int bufferSize,
+      ReadStatistics statistics,
+      int measurementCount,
+      int pointsPerChunk)
+      throws IOException {
+    long startTime = System.nanoTime();
+    TsFileInput input =
+        bufferSize == LOCAL_BUFFER_SIZE
+            ? new LocalTsFileInput(file.toPath())
+            : new BufferedTsFileInput(file.toPath(), bufferSize);
+    try (TsFileSequenceReader reader = new TsFileSequenceReader(input)) {
+      reader.position(TSFileConfig.MAGIC_STRING.getBytes().length + 1L);
+      byte marker;
+      while ((marker = reader.readMarker()) != MetaMarker.SEPARATOR) {
+        switch (marker) {
+          case MetaMarker.CHUNK_HEADER:
+          case MetaMarker.TIME_CHUNK_HEADER:
+          case MetaMarker.VALUE_CHUNK_HEADER:
+          case MetaMarker.ONLY_ONE_PAGE_CHUNK_HEADER:
+          case MetaMarker.ONLY_ONE_PAGE_TIME_CHUNK_HEADER:
+          case MetaMarker.ONLY_ONE_PAGE_VALUE_CHUNK_HEADER:
+            readChunk(reader, marker, statistics, pointsPerChunk);
+            break;
+          case MetaMarker.CHUNK_GROUP_HEADER:
+            reader.readChunkGroupHeader();
+            break;
+          case MetaMarker.OPERATION_INDEX_RANGE:
+            reader.readPlanIndex();
+            break;
+          default:
+            MetaMarker.handleUnexpectedMarker(marker);
+        }
+      }
+      verifyMetadata(reader, statistics, measurementCount, pointsPerChunk);
+    }
+    return System.nanoTime() - startTime;
+  }
+
+  private void assertStatistics(
+      ReadStatistics statistics,
+      long expectedDeviceCount,
+      long expectedChunkCount,
+      long expectedPointCount) {
+    assertEquals(expectedDeviceCount, statistics.deviceCount);
+    assertEquals(expectedChunkCount, statistics.chunkCount);
+    assertEquals(expectedPointCount, statistics.pointCount);
+  }
+
+  private void printStatistics(String inputName, ReadStatistics statistics, 
long elapsedNanos) {
+    System.out.printf(
+        "%s sequentially read %d TsFiles: devices=%,d, chunks=%,d, points=%,d, 
total time=%d ms (%.3f s)%n",
+        inputName,
+        FILE_COUNT,
+        statistics.deviceCount,
+        statistics.chunkCount,
+        statistics.pointCount,
+        TimeUnit.NANOSECONDS.toMillis(elapsedNanos),
+        elapsedNanos / 1_000_000_000.0);
+  }
+
+  private void readChunk(
+      TsFileSequenceReader reader, byte marker, ReadStatistics statistics, int 
pointsPerChunk)
+      throws IOException {
+    ChunkHeader chunkHeader = reader.readChunkHeader(marker);
+    int remainingDataSize = chunkHeader.getDataSize();
+    long pointsInChunk = 0;
+    while (remainingDataSize > 0) {
+      boolean hasStatistics = (chunkHeader.getChunkType() & 0x3F) == 
MetaMarker.CHUNK_HEADER;
+      PageHeader pageHeader = reader.readPageHeader(chunkHeader.getDataType(), 
hasStatistics);
+      ByteBuffer pageData = reader.readPage(pageHeader, 
chunkHeader.getCompressionType());
+      Decoder valueDecoder =
+          Decoder.getDecoderByType(chunkHeader.getEncodingType(), 
chunkHeader.getDataType());
+      Decoder timeDecoder =
+          Decoder.getDecoderByType(
+              
TSEncoding.valueOf(TSFileDescriptor.getInstance().getConfig().getTimeEncoder()),
+              TSDataType.INT64);
+      BatchData batchData =
+          new PageReader(pageData, chunkHeader.getDataType(), valueDecoder, 
timeDecoder)
+              .getAllSatisfiedPageData();
+      pointsInChunk += batchData.length();
+      remainingDataSize -= pageHeader.getSerializedPageSize();
+    }
+
+    assertEquals(pointsPerChunk, pointsInChunk);
+    statistics.chunkCount++;
+    statistics.pointCount += pointsInChunk;
+  }
+
+  private void verifyMetadata(
+      TsFileSequenceReader reader,
+      ReadStatistics statistics,
+      int measurementCount,
+      int pointsPerChunk)
+      throws IOException {
+    List<IDeviceID> devices = reader.getAllDevices();
+    assertEquals(DEVICE_COUNT, devices.size());
+    for (IDeviceID device : devices) {
+      Map<String, List<ChunkMetadata>> metadataByMeasurement =
+          reader.readChunkMetadataInDevice(device);
+      assertEquals(measurementCount, metadataByMeasurement.size());
+      for (List<ChunkMetadata> chunkMetadata : metadataByMeasurement.values()) 
{
+        assertEquals(1, chunkMetadata.size());
+        assertEquals(pointsPerChunk, chunkMetadata.get(0).getNumOfPoints());
+      }
+    }
+    statistics.deviceCount += devices.size();
+  }
+
+  private static class ReadStatistics {
+
+    private long deviceCount;
+    private long chunkCount;
+    private long pointCount;
+  }
+
+  private static class ReadConfiguration {
+
+    private final String inputName;
+    private final int bufferSize;
+    private final ReadStatistics statistics = new ReadStatistics();
+    private long elapsedNanos;
+
+    private ReadConfiguration(String inputName, int bufferSize) {
+      this.inputName = inputName;
+      this.bufferSize = bufferSize;
+    }
+  }
+}
diff --git 
a/java/tsfile/src/test/java/org/apache/tsfile/read/reader/BufferedTsFileInputTest.java
 
b/java/tsfile/src/test/java/org/apache/tsfile/read/reader/BufferedTsFileInputTest.java
new file mode 100644
index 000000000..679e10a91
--- /dev/null
+++ 
b/java/tsfile/src/test/java/org/apache/tsfile/read/reader/BufferedTsFileInputTest.java
@@ -0,0 +1,145 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.tsfile.read.reader;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.File;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.ClosedChannelException;
+import java.nio.file.Files;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+public class BufferedTsFileInputTest {
+
+  @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+  private File file;
+  private byte[] data;
+
+  @Before
+  public void setUp() throws Exception {
+    file = temporaryFolder.newFile("buffered-input.tsfile");
+    data = new byte[32];
+    for (int i = 0; i < data.length; i++) {
+      data[i] = (byte) i;
+    }
+    Files.write(file.toPath(), data);
+  }
+
+  @Test
+  public void testSequentialAndPositionedRead() throws Exception {
+    BufferedTsFileInput input = new BufferedTsFileInput(file.toPath(), 8);
+    try {
+      assertEquals(data.length, input.size());
+      assertEquals(file.toPath().toString(), input.getFilePath());
+      assertEquals(0, input.position());
+
+      ByteBuffer firstRead = ByteBuffer.allocate(5);
+      assertEquals(5, input.read(firstRead));
+      assertBufferEquals(new byte[] {0, 1, 2, 3, 4}, firstRead);
+      assertEquals(5, input.position());
+
+      ByteBuffer positionedRead = ByteBuffer.allocate(6);
+      assertEquals(6, input.read(positionedRead, 2));
+      assertBufferEquals(new byte[] {2, 3, 4, 5, 6, 7}, positionedRead);
+      assertEquals(5, input.position());
+
+      ByteBuffer crossBufferRead = ByteBuffer.allocate(10);
+      assertEquals(10, input.read(crossBufferRead));
+      assertBufferEquals(new byte[] {5, 6, 7, 8, 9, 10, 11, 12, 13, 14}, 
crossBufferRead);
+      assertEquals(15, input.position());
+
+      input.position(29);
+      ByteBuffer endRead = ByteBuffer.allocate(8);
+      assertEquals(3, input.read(endRead));
+      assertBufferEquals(new byte[] {29, 30, 31}, endRead);
+      assertEquals(32, input.position());
+      assertEquals(-1, input.read(ByteBuffer.allocate(1)));
+      assertEquals(0, input.read(ByteBuffer.allocate(0)));
+    } finally {
+      input.close();
+    }
+  }
+
+  @Test
+  public void testReadLargerThanBuffer() throws Exception {
+    BufferedTsFileInput input = new BufferedTsFileInput(file.toPath(), 4);
+    try {
+      ByteBuffer destination = ByteBuffer.allocate(20);
+      assertEquals(20, input.read(destination, 3));
+      byte[] expected = new byte[20];
+      System.arraycopy(data, 3, expected, 0, expected.length);
+      assertBufferEquals(expected, destination);
+      assertEquals(0, input.position());
+    } finally {
+      input.close();
+    }
+  }
+
+  @Test
+  public void testInputStreamSharesPosition() throws Exception {
+    BufferedTsFileInput input = new BufferedTsFileInput(file.toPath(), 4);
+    input.position(3);
+    try (InputStream stream = input.wrapAsInputStream()) {
+      assertEquals(3, stream.read());
+      assertEquals(4, input.position());
+
+      byte[] bytes = new byte[7];
+      assertEquals(7, stream.read(bytes));
+      assertArrayEquals(new byte[] {4, 5, 6, 7, 8, 9, 10}, bytes);
+      assertEquals(11, input.position());
+
+      assertEquals(5, stream.skip(5));
+      assertEquals(16, input.position());
+      assertEquals(16, stream.available());
+      assertEquals(16, stream.read());
+    }
+    assertThrows(ClosedChannelException.class, input::position);
+  }
+
+  @Test
+  public void testInvalidArguments() throws Exception {
+    assertThrows(IllegalArgumentException.class, () -> new 
BufferedTsFileInput(file.toPath(), 0));
+    assertThrows(IllegalArgumentException.class, () -> new 
BufferedTsFileInput(file.toPath(), -1));
+
+    BufferedTsFileInput input = new BufferedTsFileInput(file.toPath());
+    try {
+      assertThrows(IllegalArgumentException.class, () -> input.position(-1));
+      assertThrows(IllegalArgumentException.class, () -> 
input.read(ByteBuffer.allocate(1), -1));
+    } finally {
+      input.close();
+    }
+  }
+
+  private void assertBufferEquals(byte[] expected, ByteBuffer actual) {
+    actual.flip();
+    byte[] actualBytes = new byte[actual.remaining()];
+    actual.get(actualBytes);
+    assertArrayEquals(expected, actualBytes);
+  }
+}

Reply via email to