diff --git a/src/it/unimi/dsi/fastutil/io/FastByteArrayInputStream.java b/src/it/unimi/dsi/fastutil/io/FastByteArrayInputStream.java index 4771af02..cea1a45b 100644 --- a/src/it/unimi/dsi/fastutil/io/FastByteArrayInputStream.java +++ b/src/it/unimi/dsi/fastutil/io/FastByteArrayInputStream.java @@ -13,9 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package it.unimi.dsi.fastutil.io; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectInputStream; +import java.io.UTFDataFormatException; +import java.io.UncheckedIOException; + /** Simple, fast and repositionable byte-array input stream. * *
Warning: this class implements the correct semantics @@ -25,8 +31,7 @@ * * @author Sebastiano Vigna */ - -public class FastByteArrayInputStream extends MeasurableInputStream implements RepositionableStream { +public class FastByteArrayInputStream extends MeasurableInputStream implements RepositionableStream, ObjectInput { /** The array backing the input stream. */ public byte[] array; @@ -38,7 +43,7 @@ public class FastByteArrayInputStream extends MeasurableInputStream implements R public int length; /** The current position as a distance from {@link #offset}. */ - private int position; + protected int position; /** The current mark as a position, or -1 if no mark exists. */ private int mark; @@ -52,7 +57,7 @@ public class FastByteArrayInputStream extends MeasurableInputStream implements R public FastByteArrayInputStream(final byte[] array, final int offset, final int length) { this.array = array; this.offset = offset; - this.length = length; + this.length = Math.min(length, array.length - offset); } /** Creates a new array input stream using a given array. @@ -111,7 +116,7 @@ public int read() { */ @Override - public int read(final byte b[], final int offset, final int length) { + public int read(final byte[] b, final int offset, final int length) { if (this.length == this.position) return length == 0 ? 0 : -1; final int n = Math.min(length, this.length - this.position); System.arraycopy(array, this.offset + this.position, b, offset, n); @@ -133,4 +138,147 @@ public void position(final long newPosition) { public long length() { return length; } -} + + @Override + public byte[] readAllBytes() { + return readNBytes(available()); + } + + @Override + public int readNBytes(byte[] b, int off, int len) { + int n = read(b, off, len); + return n == -1 ? 0 : n; + } + + @Override + public int read (byte[] b) { + return read(b, 0, b.length); + } + + @Override + public byte[] readNBytes (int len) { + int n = Math.min(len, available()); + byte[] result = new byte[n]; + read(result); + return result; + } + + @Override + public void skipNBytes (long n) { + skip(n); + } + + public int peek() { + if (length <= position()) return -1; + return array[(int)(offset + position())] & 0xFF; + } + + + @Override + public void readFully (byte[] b) { + read(b); + } + + @Override + public void readFully (byte[] b, int off, int len) { + read(b, off, len); + } + + @Override + public int skipBytes (int n) { + return (int) skip(n); + } + + @Override + public boolean readBoolean () { + return read() != 0; + } + + @Override + public byte readByte () { + return (byte) read(); + } + + @Override + public int readUnsignedByte () { + return read() & 0xFF; + } + + @Override + public short readShort() { + return (short)((read() << 8)|(read() & 0xFF)); + } + + @Override + public int readUnsignedShort() { + return ((read() & 0xFF) << 8)|(read() & 0xFF); + } + + @Override + public char readChar() { + return (char)(((read() & 0xFF) << 8)|(read() & 0xFF)); + } + + @Override + public int readInt() { + return read() << 24 | ((read() & 0xFF) << 16) | ((read() & 0xFF) << 8) | (read() & 0xFF); + } + + @Override + public long readLong () { + return (long) readInt() << 32 | (readInt() & 0xFFFF_FFFFL); + } + + @Override + public float readFloat () { + return Float.intBitsToFloat(readInt()); + } + + @Override + public double readDouble () { + return Double.longBitsToDouble(readLong()); + } + + @Override @Deprecated + public String readLine () { + StringBuilder sb = new StringBuilder(99); +loop: + for (int c;;){ + switch (c = read()){ + case -1: + break loop;// eof + + case '\n': + return sb.toString(); + case '\r': + if (peek() == '\n'){ + read(); + } + return sb.toString(); + + default: + sb.append((char) c); + } + } + return sb.isEmpty() ? null : sb.toString(); + } + + @Override + public String readUTF () throws UTFDataFormatException { + try { + return available() > 0 ? DataInputStream.readUTF(this) : null; + } catch (UTFDataFormatException badBinaryFormatting){ + throw badBinaryFormatting; + } catch (IOException e){ + throw new UncheckedIOException("readUTF @ "+this, e); + } + } + + /// not efficient! Only added to support custom {@link java.io.Externalizable} + @Override + public Object readObject () throws ClassNotFoundException, IOException { + try (ObjectInputStream ois = new ObjectInputStream(this)){ + return ois.readObject(); + } + } +} \ No newline at end of file diff --git a/src/it/unimi/dsi/fastutil/io/FastByteArrayOutputStream.java b/src/it/unimi/dsi/fastutil/io/FastByteArrayOutputStream.java index 04f043ef..d418d046 100644 --- a/src/it/unimi/dsi/fastutil/io/FastByteArrayOutputStream.java +++ b/src/it/unimi/dsi/fastutil/io/FastByteArrayOutputStream.java @@ -13,11 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package it.unimi.dsi.fastutil.io; import it.unimi.dsi.fastutil.bytes.ByteArrays; +import java.io.DataOutput; +import java.io.IOException; +import java.io.ObjectOutput; +import java.io.ObjectOutputStream; +import java.io.OutputStream; +import java.nio.charset.Charset; + /** Simple, fast byte-array output stream that exposes the backing array. * *
{@link java.io.ByteArrayOutputStream} is nice, but to get its content you
@@ -30,7 +36,7 @@
*
* @author Sebastiano Vigna
*/
-public class FastByteArrayOutputStream extends MeasurableOutputStream implements RepositionableStream {
+public class FastByteArrayOutputStream extends MeasurableOutputStream implements RepositionableStream, ObjectOutput {
/** The array backing the output stream. */
public static final int DEFAULT_INITIAL_CAPACITY = 16;
@@ -42,7 +48,7 @@ public class FastByteArrayOutputStream extends MeasurableOutputStream implements
public int length;
/** The current writing position. */
- private int position;
+ protected int position;
/** Creates a new array output stream with an initial capacity of {@link #DEFAULT_INITIAL_CAPACITY} bytes. */
public FastByteArrayOutputStream() {
@@ -122,4 +128,122 @@ public void write(final byte[] b) {
// Only to force no exception
write(b, 0, b.length);
}
+
+ /** @see java.io.ByteArrayOutputStream#toString(Charset) */
+ public String toString(Charset charset) {
+ return new String(array, 0, length, charset);
+ }
+
+ /** @see java.io.ByteArrayOutputStream#writeTo(OutputStream) */
+ public synchronized void writeTo(OutputStream out) throws IOException {
+ out.write(array, 0, length);
+ }
+
+
+ @Override
+ public void writeBoolean(boolean v) {
+ write(v?1:0);
+ }
+
+ @Override
+ public void writeByte(int v) {
+ write(v);
+ }
+
+ @Override
+ public void writeShort(int v) {
+ write(v >> 8);
+ write(v);
+ }
+
+ @Override
+ public void writeChar(int v) {
+ write(v >> 8);
+ write(v);
+ }
+
+ @Override
+ public void writeInt(int v) {
+ write(v >> 24);
+ write(v >> 16);
+ write(v >> 8);
+ write(v);
+ }
+
+ @Override
+ public void writeLong(long v) {
+ writeInt((int)(v >> 32));
+ writeInt((int) v);
+ }
+
+ @Override
+ public void writeFloat(float v) {
+ writeInt(Float.floatToIntBits(v));
+ }
+
+ @Override
+ public void writeDouble(double v) {
+ writeLong(Double.doubleToLongBits(v));
+ }
+
+ /**
+ * @deprecated This method is dangerous as it discards the high byte of every character. For UTF-8, use {@link #writeUTF(String)} or {@link #write(byte[]) @code write(s.getBytes(UTF_8))}.
+ * @see java.io.DataOutputStream#writeBytes(String)
+ */
+ @Override
+ public void writeBytes(String s) {
+ for (int i = 0, len = s.length(); i < len; i++){
+ write((byte)s.charAt(i));
+ }
+ }
+
+ @Override
+ public void writeChars(String s) {
+ for (int i = 0, len = s.length(); i < len; i++){
+ int v = s.charAt(i);
+ writeChar(v);
+ }
+ }
+
+ @Override
+ public void writeUTF (String s) {
+ int savePos = position;
+ writeShort(0);// len place holder
+ for (int i = 0, len = s.length(); i < len; i++){
+ writeUtf8Char(s.charAt(i));
+ if (position - savePos > 0xFF_FF + 2){
+ length = position = savePos;// rollback
+ throw new IllegalArgumentException("UTF encoded string too long: %d: %s".formatted(s.length(), s.substring(0, 99)));
+ }
+ }
+ int len = position - savePos - 2;
+ array[savePos] = (byte)(len >> 8);
+ array[savePos+1] = (byte)len;
+ }
+
+ /** @see java.io.DataOutputStream#writeUTF(String,DataOutput) */
+ public int writeUtf8Char(char c) {
+ if (c != 0 && c < 0x80){
+ write(c);
+ return 1;
+ } else if (c >= 0x800){
+ write(0xE0 | c >> 12 & 0x0F);
+ write(0x80 | c >> 6 & 0x3F);
+ write(0x80 | c & 0x3F);
+ return 3;
+ } else {
+ write(0xC0 | c >> 6 & 0x1F);
+ write(0x80 | c & 0x3F);
+ return 2;
+ }
+ }
+
+ /// not efficient! Only added to support custom {@link java.io.Externalizable}
+ @Override
+ public void writeObject(Object obj) throws IOException {
+ try (ObjectOutputStream oout = new ObjectOutputStream(this)){
+ oout.writeObject(obj);
+ oout.flush();
+ }
+ }
}
\ No newline at end of file
diff --git a/test/it/unimi/dsi/fastutil/io/FastByteArrayStreamsTest.java b/test/it/unimi/dsi/fastutil/io/FastByteArrayStreamsTest.java
new file mode 100644
index 00000000..d23546a9
--- /dev/null
+++ b/test/it/unimi/dsi/fastutil/io/FastByteArrayStreamsTest.java
@@ -0,0 +1,517 @@
+package it.unimi.dsi.fastutil.io;
+
+import it.unimi.dsi.fastutil.bytes.ByteArrays;
+import org.junit.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInput;
+import java.io.DataInputStream;
+import java.io.DataOutput;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.Serializable;
+import java.io.UTFDataFormatException;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ @see FastByteArrayOutputStream
+ @see FastByteArrayInputStream
+ @see DataOutputStream
+ @see ByteArrayOutputStream
+ @see ByteArrayInputStream
+ */
+@SuppressWarnings({"resource", "IOResourceOpenedButNotSafelyClosed"})
+public class FastByteArrayStreamsTest {
+ /// todo put into {@link it.unimi.dsi.fastutil.bytes.ByteArrays} ?
+ /// @see java.util.HexFormat
+ private static final short[] HEX_TABLE = new short[256];
+ static {
+ final byte[] hex = "0123456789abcdef".getBytes(StandardCharsets.ISO_8859_1);
+ for (int i = 0; i < 256; i ++){
+ HEX_TABLE[i] = (short)((hex[i >>> 4 & 0xF] << 8) | (hex[i & 0xF] & 0xFF));
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ public static String toHexString (byte[] buffer, int fromIndex, int length) {
+ if (length <= 0){ return ""; }
+ length = Math.min(length, buffer.length - fromIndex);
+
+ byte[] buf = new byte[length << 1];// buffer.length * 2
+
+ for (int dstIdx = 0, endIndex = fromIndex + length; fromIndex < endIndex; fromIndex++){
+ int nextByte = buffer[fromIndex] & 0xFF;
+ short byteAsTwoHex = HEX_TABLE[nextByte];
+ buf[dstIdx++] = (byte)(byteAsTwoHex >> 8);
+ buf[dstIdx++] = (byte)(byteAsTwoHex & 0xFF);
+ }
+
+ return new String(buf, 0/*hi*/, 0, buf.length);
+ }
+
+ public static String toHexString (byte[] buffer) {
+ return toHexString(buffer, 0, buffer.length);
+ }
+
+ @Test
+ public void testHex () {
+ assertEquals("", toHexString(null, -1, -1));
+ assertEquals("", toHexString(new byte[]{0, 1, 2, -1}, -1, -1));
+ assertEquals("000102ff", toHexString(new byte[]{0, 1, 2, -1}, 0, 100));
+ assertEquals("0102ff", toHexString(new byte[]{0, 1, 2, -1}, 1, 100));
+ assertEquals("0102", toHexString(new byte[]{0, 1, 2, -1}, 1, 2));
+ }
+
+ @FunctionalInterface
+ interface ThrowingConsumer