1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.internal.util; 18 19 import android.annotation.NonNull; 20 import android.util.CharsetUtils; 21 22 import dalvik.system.VMRuntime; 23 24 import java.io.BufferedInputStream; 25 import java.io.Closeable; 26 import java.io.DataInput; 27 import java.io.DataInputStream; 28 import java.io.EOFException; 29 import java.io.IOException; 30 import java.io.InputStream; 31 import java.util.Arrays; 32 import java.util.Objects; 33 34 /** 35 * Optimized implementation of {@link DataInput} which buffers data in memory 36 * from the underlying {@link InputStream}. 37 * <p> 38 * Benchmarks have demonstrated this class is 3x more efficient than using a 39 * {@link DataInputStream} with a {@link BufferedInputStream}. 40 */ 41 public class FastDataInput implements DataInput, Closeable { 42 private static final int MAX_UNSIGNED_SHORT = 65_535; 43 44 private final VMRuntime mRuntime; 45 private final InputStream mIn; 46 47 private final byte[] mBuffer; 48 private final long mBufferPtr; 49 private final int mBufferCap; 50 51 private int mBufferPos; 52 private int mBufferLim; 53 54 /** 55 * Values that have been "interned" by {@link #readInternedUTF()}. 56 */ 57 private int mStringRefCount = 0; 58 private String[] mStringRefs = new String[32]; 59 FastDataInput(@onNull InputStream in, int bufferSize)60 public FastDataInput(@NonNull InputStream in, int bufferSize) { 61 mRuntime = VMRuntime.getRuntime(); 62 mIn = Objects.requireNonNull(in); 63 if (bufferSize < 8) { 64 throw new IllegalArgumentException(); 65 } 66 67 mBuffer = (byte[]) mRuntime.newNonMovableArray(byte.class, bufferSize); 68 mBufferPtr = mRuntime.addressOf(mBuffer); 69 mBufferCap = mBuffer.length; 70 } 71 fill(int need)72 private void fill(int need) throws IOException { 73 final int remain = mBufferLim - mBufferPos; 74 System.arraycopy(mBuffer, mBufferPos, mBuffer, 0, remain); 75 mBufferPos = 0; 76 mBufferLim = remain; 77 need -= remain; 78 79 while (need > 0) { 80 int c = mIn.read(mBuffer, mBufferLim, mBufferCap - mBufferLim); 81 if (c == -1) { 82 throw new EOFException(); 83 } else { 84 mBufferLim += c; 85 need -= c; 86 } 87 } 88 } 89 90 @Override close()91 public void close() throws IOException { 92 mIn.close(); 93 } 94 95 @Override readFully(byte[] b)96 public void readFully(byte[] b) throws IOException { 97 readFully(b, 0, b.length); 98 } 99 100 @Override readFully(byte[] b, int off, int len)101 public void readFully(byte[] b, int off, int len) throws IOException { 102 // Attempt to read directly from buffer space if there's enough room, 103 // otherwise fall back to chunking into place 104 if (mBufferCap >= len) { 105 if (mBufferLim - mBufferPos < len) fill(len); 106 System.arraycopy(mBuffer, mBufferPos, b, off, len); 107 mBufferPos += len; 108 } else { 109 final int remain = mBufferLim - mBufferPos; 110 System.arraycopy(mBuffer, mBufferPos, b, off, remain); 111 mBufferPos += remain; 112 off += remain; 113 len -= remain; 114 115 while (len > 0) { 116 int c = mIn.read(b, off, len); 117 if (c == -1) { 118 throw new EOFException(); 119 } else { 120 off += c; 121 len -= c; 122 } 123 } 124 } 125 } 126 127 @Override readUTF()128 public String readUTF() throws IOException { 129 // Attempt to read directly from buffer space if there's enough room, 130 // otherwise fall back to chunking into place 131 final int len = readUnsignedShort(); 132 if (mBufferCap > len) { 133 if (mBufferLim - mBufferPos < len) fill(len); 134 final String res = CharsetUtils.fromModifiedUtf8Bytes(mBufferPtr, mBufferPos, len); 135 mBufferPos += len; 136 return res; 137 } else { 138 final byte[] tmp = (byte[]) mRuntime.newNonMovableArray(byte.class, len + 1); 139 readFully(tmp, 0, len); 140 return CharsetUtils.fromModifiedUtf8Bytes(mRuntime.addressOf(tmp), 0, len); 141 } 142 } 143 144 /** 145 * Read a {@link String} value with the additional signal that the given 146 * value is a candidate for being canonicalized, similar to 147 * {@link String#intern()}. 148 * <p> 149 * Canonicalization is implemented by writing each unique string value once 150 * the first time it appears, and then writing a lightweight {@code short} 151 * reference when that string is written again in the future. 152 * 153 * @see FastDataOutput#writeInternedUTF(String) 154 */ readInternedUTF()155 public @NonNull String readInternedUTF() throws IOException { 156 final int ref = readUnsignedShort(); 157 if (ref == MAX_UNSIGNED_SHORT) { 158 final String s = readUTF(); 159 160 // We can only safely intern when we have remaining values; if we're 161 // full we at least sent the string value above 162 if (mStringRefCount < MAX_UNSIGNED_SHORT) { 163 if (mStringRefCount == mStringRefs.length) { 164 mStringRefs = Arrays.copyOf(mStringRefs, 165 mStringRefCount + (mStringRefCount >> 1)); 166 } 167 mStringRefs[mStringRefCount++] = s; 168 } 169 170 return s; 171 } else { 172 return mStringRefs[ref]; 173 } 174 } 175 176 @Override readBoolean()177 public boolean readBoolean() throws IOException { 178 return readByte() != 0; 179 } 180 181 /** 182 * Returns the same decoded value as {@link #readByte()} but without 183 * actually consuming the underlying data. 184 */ peekByte()185 public byte peekByte() throws IOException { 186 if (mBufferLim - mBufferPos < 1) fill(1); 187 return mBuffer[mBufferPos]; 188 } 189 190 @Override readByte()191 public byte readByte() throws IOException { 192 if (mBufferLim - mBufferPos < 1) fill(1); 193 return mBuffer[mBufferPos++]; 194 } 195 196 @Override readUnsignedByte()197 public int readUnsignedByte() throws IOException { 198 return Byte.toUnsignedInt(readByte()); 199 } 200 201 @Override readShort()202 public short readShort() throws IOException { 203 if (mBufferLim - mBufferPos < 2) fill(2); 204 return (short) (((mBuffer[mBufferPos++] & 0xff) << 8) | 205 ((mBuffer[mBufferPos++] & 0xff) << 0)); 206 } 207 208 @Override readUnsignedShort()209 public int readUnsignedShort() throws IOException { 210 return Short.toUnsignedInt((short) readShort()); 211 } 212 213 @Override readChar()214 public char readChar() throws IOException { 215 return (char) readShort(); 216 } 217 218 @Override readInt()219 public int readInt() throws IOException { 220 if (mBufferLim - mBufferPos < 4) fill(4); 221 return (((mBuffer[mBufferPos++] & 0xff) << 24) | 222 ((mBuffer[mBufferPos++] & 0xff) << 16) | 223 ((mBuffer[mBufferPos++] & 0xff) << 8) | 224 ((mBuffer[mBufferPos++] & 0xff) << 0)); 225 } 226 227 @Override readLong()228 public long readLong() throws IOException { 229 if (mBufferLim - mBufferPos < 8) fill(8); 230 int h = ((mBuffer[mBufferPos++] & 0xff) << 24) | 231 ((mBuffer[mBufferPos++] & 0xff) << 16) | 232 ((mBuffer[mBufferPos++] & 0xff) << 8) | 233 ((mBuffer[mBufferPos++] & 0xff) << 0); 234 int l = ((mBuffer[mBufferPos++] & 0xff) << 24) | 235 ((mBuffer[mBufferPos++] & 0xff) << 16) | 236 ((mBuffer[mBufferPos++] & 0xff) << 8) | 237 ((mBuffer[mBufferPos++] & 0xff) << 0); 238 return (((long) h) << 32L) | ((long) l) & 0xffffffffL; 239 } 240 241 @Override readFloat()242 public float readFloat() throws IOException { 243 return Float.intBitsToFloat(readInt()); 244 } 245 246 @Override readDouble()247 public double readDouble() throws IOException { 248 return Double.longBitsToDouble(readLong()); 249 } 250 251 @Override skipBytes(int n)252 public int skipBytes(int n) throws IOException { 253 // Callers should read data piecemeal 254 throw new UnsupportedOperationException(); 255 } 256 257 @Override readLine()258 public String readLine() throws IOException { 259 // Callers should read data piecemeal 260 throw new UnsupportedOperationException(); 261 } 262 } 263