• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.BufferedOutputStream;
25 import java.io.Closeable;
26 import java.io.DataOutput;
27 import java.io.DataOutputStream;
28 import java.io.Flushable;
29 import java.io.IOException;
30 import java.io.OutputStream;
31 import java.util.HashMap;
32 import java.util.Objects;
33 
34 /**
35  * Optimized implementation of {@link DataOutput} which buffers data in memory
36  * before flushing to the underlying {@link OutputStream}.
37  * <p>
38  * Benchmarks have demonstrated this class is 2x more efficient than using a
39  * {@link DataOutputStream} with a {@link BufferedOutputStream}.
40  */
41 public class FastDataOutput implements DataOutput, Flushable, Closeable {
42     private static final int MAX_UNSIGNED_SHORT = 65_535;
43 
44     private final VMRuntime mRuntime;
45     private final OutputStream mOut;
46 
47     private final byte[] mBuffer;
48     private final long mBufferPtr;
49     private final int mBufferCap;
50 
51     private int mBufferPos;
52 
53     /**
54      * Values that have been "interned" by {@link #writeInternedUTF(String)}.
55      */
56     private HashMap<String, Short> mStringRefs = new HashMap<>();
57 
FastDataOutput(@onNull OutputStream out, int bufferSize)58     public FastDataOutput(@NonNull OutputStream out, int bufferSize) {
59         mRuntime = VMRuntime.getRuntime();
60         mOut = Objects.requireNonNull(out);
61         if (bufferSize < 8) {
62             throw new IllegalArgumentException();
63         }
64 
65         mBuffer = (byte[]) mRuntime.newNonMovableArray(byte.class, bufferSize);
66         mBufferPtr = mRuntime.addressOf(mBuffer);
67         mBufferCap = mBuffer.length;
68     }
69 
drain()70     private void drain() throws IOException {
71         if (mBufferPos > 0) {
72             mOut.write(mBuffer, 0, mBufferPos);
73             mBufferPos = 0;
74         }
75     }
76 
77     @Override
flush()78     public void flush() throws IOException {
79         drain();
80         mOut.flush();
81     }
82 
83     @Override
close()84     public void close() throws IOException {
85         mOut.close();
86     }
87 
88     @Override
write(int b)89     public void write(int b) throws IOException {
90         writeByte(b);
91     }
92 
93     @Override
write(byte[] b)94     public void write(byte[] b) throws IOException {
95         write(b, 0, b.length);
96     }
97 
98     @Override
write(byte[] b, int off, int len)99     public void write(byte[] b, int off, int len) throws IOException {
100         if (mBufferCap < len) {
101             drain();
102             mOut.write(b, off, len);
103         } else {
104             if (mBufferCap - mBufferPos < len) drain();
105             System.arraycopy(b, off, mBuffer, mBufferPos, len);
106             mBufferPos += len;
107         }
108     }
109 
110     @Override
writeUTF(String s)111     public void writeUTF(String s) throws IOException {
112         // Attempt to write directly to buffer space if there's enough room,
113         // otherwise fall back to chunking into place
114         if (mBufferCap - mBufferPos < 2 + s.length()) drain();
115 
116         // Magnitude of this returned value indicates the number of bytes
117         // required to encode the string; sign indicates success/failure
118         int len = CharsetUtils.toModifiedUtf8Bytes(s, mBufferPtr, mBufferPos + 2, mBufferCap);
119         if (Math.abs(len) > MAX_UNSIGNED_SHORT) {
120             throw new IOException("Modified UTF-8 length too large: " + len);
121         }
122 
123         if (len >= 0) {
124             // Positive value indicates the string was encoded into the buffer
125             // successfully, so we only need to prefix with length
126             writeShort(len);
127             mBufferPos += len;
128         } else {
129             // Negative value indicates buffer was too small and we need to
130             // allocate a temporary buffer for encoding
131             len = -len;
132             final byte[] tmp = (byte[]) mRuntime.newNonMovableArray(byte.class, len + 1);
133             CharsetUtils.toModifiedUtf8Bytes(s, mRuntime.addressOf(tmp), 0, tmp.length);
134             writeShort(len);
135             write(tmp, 0, len);
136         }
137     }
138 
139     /**
140      * Write a {@link String} value with the additional signal that the given
141      * value is a candidate for being canonicalized, similar to
142      * {@link String#intern()}.
143      * <p>
144      * Canonicalization is implemented by writing each unique string value once
145      * the first time it appears, and then writing a lightweight {@code short}
146      * reference when that string is written again in the future.
147      *
148      * @see FastDataInput#readInternedUTF()
149      */
writeInternedUTF(@onNull String s)150     public void writeInternedUTF(@NonNull String s) throws IOException {
151         Short ref = mStringRefs.get(s);
152         if (ref != null) {
153             writeShort(ref);
154         } else {
155             writeShort(MAX_UNSIGNED_SHORT);
156             writeUTF(s);
157 
158             // We can only safely intern when we have remaining values; if we're
159             // full we at least sent the string value above
160             ref = (short) mStringRefs.size();
161             if (ref < MAX_UNSIGNED_SHORT) {
162                 mStringRefs.put(s, ref);
163             }
164         }
165     }
166 
167     @Override
writeBoolean(boolean v)168     public void writeBoolean(boolean v) throws IOException {
169         writeByte(v ? 1 : 0);
170     }
171 
172     @Override
writeByte(int v)173     public void writeByte(int v) throws IOException {
174         if (mBufferCap - mBufferPos < 1) drain();
175         mBuffer[mBufferPos++] = (byte) ((v >>  0) & 0xff);
176     }
177 
178     @Override
writeShort(int v)179     public void writeShort(int v) throws IOException {
180         if (mBufferCap - mBufferPos < 2) drain();
181         mBuffer[mBufferPos++] = (byte) ((v >>  8) & 0xff);
182         mBuffer[mBufferPos++] = (byte) ((v >>  0) & 0xff);
183     }
184 
185     @Override
writeChar(int v)186     public void writeChar(int v) throws IOException {
187         writeShort((short) v);
188     }
189 
190     @Override
writeInt(int v)191     public void writeInt(int v) throws IOException {
192         if (mBufferCap - mBufferPos < 4) drain();
193         mBuffer[mBufferPos++] = (byte) ((v >> 24) & 0xff);
194         mBuffer[mBufferPos++] = (byte) ((v >> 16) & 0xff);
195         mBuffer[mBufferPos++] = (byte) ((v >>  8) & 0xff);
196         mBuffer[mBufferPos++] = (byte) ((v >>  0) & 0xff);
197     }
198 
199     @Override
writeLong(long v)200     public void writeLong(long v) throws IOException {
201         if (mBufferCap - mBufferPos < 8) drain();
202         int i = (int) (v >> 32);
203         mBuffer[mBufferPos++] = (byte) ((i >> 24) & 0xff);
204         mBuffer[mBufferPos++] = (byte) ((i >> 16) & 0xff);
205         mBuffer[mBufferPos++] = (byte) ((i >>  8) & 0xff);
206         mBuffer[mBufferPos++] = (byte) ((i >>  0) & 0xff);
207         i = (int) v;
208         mBuffer[mBufferPos++] = (byte) ((i >> 24) & 0xff);
209         mBuffer[mBufferPos++] = (byte) ((i >> 16) & 0xff);
210         mBuffer[mBufferPos++] = (byte) ((i >>  8) & 0xff);
211         mBuffer[mBufferPos++] = (byte) ((i >>  0) & 0xff);
212     }
213 
214     @Override
writeFloat(float v)215     public void writeFloat(float v) throws IOException {
216         writeInt(Float.floatToIntBits(v));
217     }
218 
219     @Override
writeDouble(double v)220     public void writeDouble(double v) throws IOException {
221         writeLong(Double.doubleToLongBits(v));
222     }
223 
224     @Override
writeBytes(String s)225     public void writeBytes(String s) throws IOException {
226         // Callers should use writeUTF()
227         throw new UnsupportedOperationException();
228     }
229 
230     @Override
writeChars(String s)231     public void writeChars(String s) throws IOException {
232         // Callers should use writeUTF()
233         throw new UnsupportedOperationException();
234     }
235 }
236