Skip to content

Commit 00f284b

Browse files
committed
Only read the relevant portion of a file
- AbstractOrigin.FileOrigin.getByteArray(long, int) - AbstractOrigin.PathOrigin.getByteArray(long, int) - Above implemented using the new RandomAccessFiles
1 parent e5a34ca commit 00f284b

5 files changed

Lines changed: 192 additions & 28 deletions

File tree

src/changes/changes.xml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,12 @@ The <action> type attribute can be add,update,fix,remove.
6161
<action issue="IO-796" dev="ggregory" type="fix" due-to="Giacomo Boccardo, Gary Gregory">
6262
FileAlreadyExistsException in PathUtils.createParentDirectories(Path, LinkOption, FileAttribute...).
6363
</action>
64+
<action dev="ggregory" type="fix" due-to="Gary Gregory">
65+
Only read the relevant portion of a file in AbstractOrigin.FileOrigin.getByteArray(long, int)
66+
</action>
67+
<action dev="ggregory" type="fix" due-to="Gary Gregory">
68+
Only read the relevant portion of a file in AbstractOrigin.PathOrigin.getByteArray(long, int)
69+
</action>
6470
<!-- ADD -->
6571
<action dev="ggregory" type="add" due-to="Gary Gregory">
6672
Add CharSequenceInputStream.Builder.
@@ -77,6 +83,9 @@ The <action> type attribute can be add,update,fix,remove.
7783
<action dev="ggregory" type="add" due-to="Gary Gregory">
7884
Add AbstractOrigin.getByteArray(long, int).
7985
</action>
86+
<action dev="ggregory" type="add" due-to="Gary Gregory">
87+
Add and use RandomAccessFiles.
88+
</action>
8089
<!-- UPDATE -->
8190
<action dev="ggregory" type="update" due-to="Gary Gregory, Dependabot">
8291
Bump commons-parent from 57 to 58.

src/main/java/org/apache/commons/io/IOUtils.java

Lines changed: 57 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757

5858
import org.apache.commons.io.function.IOConsumer;
5959
import org.apache.commons.io.function.IOSupplier;
60+
import org.apache.commons.io.function.IOTriFunction;
6061
import org.apache.commons.io.input.QueueInputStream;
6162
import org.apache.commons.io.output.AppendableWriter;
6263
import org.apache.commons.io.output.ByteArrayOutputStream;
@@ -1914,7 +1915,7 @@ public static int read(final InputStream input, final byte[] buffer) throws IOEx
19141915
* as possible before giving up; this may not always be the case for
19151916
* subclasses of {@link InputStream}.
19161917
*
1917-
* @param input where to read input from
1918+
* @param input where to read input
19181919
* @param buffer destination
19191920
* @param offset initial offset into buffer
19201921
* @param length length to read, must be &gt;= 0
@@ -1925,14 +1926,32 @@ public static int read(final InputStream input, final byte[] buffer) throws IOEx
19251926
*/
19261927
public static int read(final InputStream input, final byte[] buffer, final int offset, final int length)
19271928
throws IOException {
1929+
return read(input::read, buffer, offset, length);
1930+
}
1931+
1932+
/**
1933+
* Reads bytes from an input. This implementation guarantees that it will read as many bytes as possible before giving up; this may not always be the case
1934+
* for subclasses of {@link InputStream}.
1935+
*
1936+
* @param input How to read input
1937+
* @param buffer destination
1938+
* @param offset initial offset into buffer
1939+
* @param length length to read, must be &gt;= 0
1940+
* @return actual length read; may be less than requested if EOF was reached
1941+
* @throws IllegalArgumentException if length is negative
1942+
* @throws IOException if a read error occurs
1943+
* @since 2.2
1944+
*/
1945+
static int read(final IOTriFunction<byte[], Integer, Integer, Integer> input, final byte[] buffer, final int offset, final int length)
1946+
throws IOException {
19281947
if (length < 0) {
19291948
throw new IllegalArgumentException("Length must not be negative: " + length);
19301949
}
19311950
int remaining = length;
19321951
while (remaining > 0) {
19331952
final int location = length - remaining;
1934-
final int count = input.read(buffer, offset + location, remaining);
1935-
if (EOF == count) { // EOF
1953+
final int count = input.apply(buffer, offset + location, remaining);
1954+
if (EOF == count) {
19361955
break;
19371956
}
19381957
remaining -= count;
@@ -2640,28 +2659,7 @@ public static byte[] toByteArray(final InputStream inputStream) throws IOExcepti
26402659
* @since 2.1
26412660
*/
26422661
public static byte[] toByteArray(final InputStream input, final int size) throws IOException {
2643-
2644-
if (size < 0) {
2645-
throw new IllegalArgumentException("Size must be equal or greater than zero: " + size);
2646-
}
2647-
2648-
if (size == 0) {
2649-
return EMPTY_BYTE_ARRAY;
2650-
}
2651-
2652-
final byte[] data = byteArray(size);
2653-
int offset = 0;
2654-
int read;
2655-
2656-
while (offset < size && (read = input.read(data, offset, size - offset)) != EOF) {
2657-
offset += read;
2658-
}
2659-
2660-
if (offset != size) {
2661-
throw new IOException("Unexpected read size, current: " + offset + ", expected: " + size);
2662-
}
2663-
2664-
return data;
2662+
return toByteArray(input::read, size);
26652663
}
26662664

26672665
/**
@@ -2687,6 +2685,40 @@ public static byte[] toByteArray(final InputStream input, final long size) throw
26872685
return toByteArray(input, (int) size);
26882686
}
26892687

2688+
/**
2689+
* Gets the contents of an input as a {@code byte[]}.
2690+
*
2691+
* @param input the input to read.
2692+
* @param size the size of the input to read, where 0 &lt; {@code size} &lt;= length of input.
2693+
* @return byte [] of length {@code size}.
2694+
* @throws IOException if an I/O error occurs or input length is smaller than parameter {@code size}.
2695+
* @throws IllegalArgumentException if {@code size} is less than zero.
2696+
*/
2697+
static byte[] toByteArray(final IOTriFunction<byte[], Integer, Integer, Integer> input, final int size) throws IOException {
2698+
2699+
if (size < 0) {
2700+
throw new IllegalArgumentException("Size must be equal or greater than zero: " + size);
2701+
}
2702+
2703+
if (size == 0) {
2704+
return EMPTY_BYTE_ARRAY;
2705+
}
2706+
2707+
final byte[] data = byteArray(size);
2708+
int offset = 0;
2709+
int read;
2710+
2711+
while (offset < size && (read = input.apply(data, offset, size - offset)) != EOF) {
2712+
offset += read;
2713+
}
2714+
2715+
if (offset != size) {
2716+
throw new IOException("Unexpected read size, current: " + offset + ", expected: " + size);
2717+
}
2718+
2719+
return data;
2720+
}
2721+
26902722
/**
26912723
* Gets the contents of a {@link Reader} as a {@code byte[]}
26922724
* using the default character encoding of the platform.
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.commons.io;
19+
20+
import java.io.IOException;
21+
import java.io.RandomAccessFile;
22+
23+
/**
24+
* Works on RandomAccessFile.
25+
*
26+
* @since 2.13.0
27+
*/
28+
public class RandomAccessFiles {
29+
30+
/**
31+
* Reads a byte array starting at "position" for "length" bytes.
32+
*
33+
* @param input The source RandomAccessFile.
34+
* @param position The offset position, measured in bytes from the beginning of the file, at which to set the file pointer.
35+
* @param length How many bytes to read.
36+
* @return a new byte array.
37+
* @throws IOException If the first byte cannot be read for any reason other than end of file, or if the random access file has been closed, or if some
38+
* other I/O error occurs.
39+
*/
40+
public static byte[] read(final RandomAccessFile input, final long position, final int length) throws IOException {
41+
input.seek(position);
42+
return IOUtils.toByteArray(input::read, length);
43+
}
44+
45+
}

src/main/java/org/apache/commons/io/build/AbstractOrigin.java

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.io.InputStreamReader;
2525
import java.io.OutputStream;
2626
import java.io.OutputStreamWriter;
27+
import java.io.RandomAccessFile;
2728
import java.io.Reader;
2829
import java.io.Writer;
2930
import java.net.URI;
@@ -36,6 +37,8 @@
3637
import java.util.Objects;
3738

3839
import org.apache.commons.io.IOUtils;
40+
import org.apache.commons.io.RandomAccessFileMode;
41+
import org.apache.commons.io.RandomAccessFiles;
3942
import org.apache.commons.io.input.ReaderInputStream;
4043
import org.apache.commons.io.output.WriterOutputStream;
4144

@@ -143,6 +146,14 @@ public FileOrigin(final File origin) {
143146
super(origin);
144147
}
145148

149+
150+
@Override
151+
public byte[] getByteArray(final long position, final int length) throws IOException {
152+
try (RandomAccessFile raf = RandomAccessFileMode.READ_ONLY.create(origin)) {
153+
return RandomAccessFiles.read(raf, position, length);
154+
}
155+
}
156+
146157
@Override
147158
public File getFile() {
148159
// No conversion
@@ -237,6 +248,13 @@ public PathOrigin(final Path origin) {
237248
super(origin);
238249
}
239250

251+
@Override
252+
public byte[] getByteArray(final long position, final int length) throws IOException {
253+
try (RandomAccessFile raf = RandomAccessFileMode.READ_ONLY.create(origin)) {
254+
return RandomAccessFiles.read(raf, position, length);
255+
}
256+
}
257+
240258
@Override
241259
public File getFile() {
242260
return get().toFile();
@@ -385,16 +403,16 @@ public byte[] getByteArray() throws IOException {
385403
/**
386404
* Gets this origin as a byte array, if possible.
387405
*
388-
* @param from the initial index of the range to be copied, inclusive.
406+
* @param position the initial index of the range to be copied, inclusive.
389407
* @param length How many bytes to copy.
390408
* @return this origin as a byte array, if possible.
391409
* @throws IOException if an I/O error occurs.
392410
* @throws UnsupportedOperationException if the origin cannot be converted to a Path.
393411
* @since 2.13.0
394412
*/
395-
public byte[] getByteArray(final long from, final int length) throws IOException {
413+
public byte[] getByteArray(final long position, final int length) throws IOException {
396414
final byte[] bytes = getByteArray();
397-
final int start = (int) from;
415+
final int start = (int) position;
398416
// We include a separate check for int overflow.
399417
if (start < 0 || length < 0 || start + length < 0 || start + length > bytes.length) {
400418
throw new IllegalArgumentException("Couldn't read array (start: " + start + ", length: " + length
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.commons.io;
19+
20+
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
21+
import static org.junit.jupiter.api.Assertions.assertEquals;
22+
import static org.junit.jupiter.api.Assertions.assertThrows;
23+
24+
import java.io.IOException;
25+
import java.io.RandomAccessFile;
26+
27+
import org.junit.jupiter.api.Test;
28+
29+
public class RandomAccessFilesTest {
30+
31+
protected static final String FILE_RES_RO = "/org/apache/commons/io/test-file-20byteslength.bin";
32+
protected static final String FILE_NAME_RO = "src/test/resources" + FILE_RES_RO;
33+
34+
@Test
35+
public void testRead() throws IOException {
36+
try (final RandomAccessFile raf = RandomAccessFileMode.READ_ONLY.create(FILE_NAME_RO)) {
37+
final byte[] buffer = RandomAccessFiles.read(raf, 0, 0);
38+
assertArrayEquals(new byte[] {}, buffer);
39+
}
40+
try (final RandomAccessFile raf = RandomAccessFileMode.READ_ONLY.create(FILE_NAME_RO)) {
41+
final byte[] buffer = RandomAccessFiles.read(raf, 1, 0);
42+
assertArrayEquals(new byte[] {}, buffer);
43+
}
44+
try (final RandomAccessFile raf = RandomAccessFileMode.READ_ONLY.create(FILE_NAME_RO)) {
45+
final byte[] buffer = RandomAccessFiles.read(raf, 0, 1);
46+
assertArrayEquals(new byte[] { '1' }, buffer);
47+
}
48+
try (final RandomAccessFile raf = RandomAccessFileMode.READ_ONLY.create(FILE_NAME_RO)) {
49+
final byte[] buffer = RandomAccessFiles.read(raf, 1, 1);
50+
assertArrayEquals(new byte[] { '2' }, buffer);
51+
}
52+
try (final RandomAccessFile raf = RandomAccessFileMode.READ_ONLY.create(FILE_NAME_RO)) {
53+
final byte[] buffer = RandomAccessFiles.read(raf, 0, 20);
54+
assertEquals(20, buffer.length);
55+
}
56+
try (final RandomAccessFile raf = RandomAccessFileMode.READ_ONLY.create(FILE_NAME_RO)) {
57+
assertThrows(IOException.class, () -> RandomAccessFiles.read(raf, 0, 21));
58+
}
59+
}
60+
}

0 commit comments

Comments
 (0)