1 // Protocol Buffers - Google's data interchange format 2 // Copyright 2008 Google Inc. All rights reserved. 3 // 4 // Use of this source code is governed by a BSD-style 5 // license that can be found in the LICENSE file or at 6 // https://developers.google.com/open-source/licenses/bsd 7 8 package com.google.protobuf; 9 10 import static java.lang.Math.max; 11 import static java.lang.Math.min; 12 13 import java.io.IOException; 14 import java.io.OutputStream; 15 import java.lang.ref.SoftReference; 16 import java.lang.reflect.Field; 17 import java.nio.ByteBuffer; 18 import java.nio.channels.WritableByteChannel; 19 20 /** Utility class to provide efficient writing of {@link ByteBuffer}s to {@link OutputStream}s. */ 21 final class ByteBufferWriter { ByteBufferWriter()22 private ByteBufferWriter() {} 23 24 /** 25 * Minimum size for a cached buffer. This prevents us from allocating buffers that are too small 26 * to be easily reused. 27 */ 28 // TODO: tune this property or allow configuration? 29 private static final int MIN_CACHED_BUFFER_SIZE = 1024; 30 31 /** 32 * Maximum size for a cached buffer. If a larger buffer is required, it will be allocated but not 33 * cached. 34 */ 35 // TODO: tune this property or allow configuration? 36 private static final int MAX_CACHED_BUFFER_SIZE = 16 * 1024; 37 38 /** The fraction of the requested buffer size under which the buffer will be reallocated. */ 39 // TODO: tune this property or allow configuration? 40 private static final float BUFFER_REALLOCATION_THRESHOLD = 0.5f; 41 42 /** 43 * Keeping a soft reference to a thread-local buffer. This buffer is used for writing a {@link 44 * ByteBuffer} to an {@link OutputStream} when no zero-copy alternative was available. Using a 45 * "soft" reference since VMs may keep this reference around longer than "weak" (e.g. HotSpot will 46 * maintain soft references until memory pressure warrants collection). 47 */ 48 private static final ThreadLocal<SoftReference<byte[]>> BUFFER = 49 new ThreadLocal<SoftReference<byte[]>>(); 50 51 /** This is a hack for GAE, where {@code FileOutputStream} is unavailable. */ 52 private static final Class<?> FILE_OUTPUT_STREAM_CLASS = safeGetClass("java.io.FileOutputStream"); 53 54 private static final long CHANNEL_FIELD_OFFSET = getChannelFieldOffset(FILE_OUTPUT_STREAM_CLASS); 55 56 /** 57 * For testing purposes only. Clears the cached buffer to force a new allocation on the next 58 * invocation. 59 */ clearCachedBuffer()60 static void clearCachedBuffer() { 61 BUFFER.set(null); 62 } 63 64 /** 65 * Writes the remaining content of the buffer to the given stream. The buffer {@code position} 66 * will remain unchanged by this method. 67 */ write(ByteBuffer buffer, OutputStream output)68 static void write(ByteBuffer buffer, OutputStream output) throws IOException { 69 final int initialPos = buffer.position(); 70 try { 71 if (buffer.hasArray()) { 72 // Optimized write for array-backed buffers. 73 // Note that we're taking the risk that a malicious OutputStream could modify the array. 74 output.write(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining()); 75 } else if (!writeToChannel(buffer, output)) { 76 // Read all of the data from the buffer to an array. 77 // TODO: Consider performance improvements for other "known" stream types. 78 final byte[] array = getOrCreateBuffer(buffer.remaining()); 79 while (buffer.hasRemaining()) { 80 int length = min(buffer.remaining(), array.length); 81 buffer.get(array, 0, length); 82 output.write(array, 0, length); 83 } 84 } 85 } finally { 86 // Restore the initial position. 87 Java8Compatibility.position(buffer, initialPos); 88 } 89 } 90 getOrCreateBuffer(int requestedSize)91 private static byte[] getOrCreateBuffer(int requestedSize) { 92 requestedSize = max(requestedSize, MIN_CACHED_BUFFER_SIZE); 93 94 byte[] buffer = getBuffer(); 95 // Only allocate if we need to. 96 if (buffer == null || needToReallocate(requestedSize, buffer.length)) { 97 buffer = new byte[requestedSize]; 98 99 // Only cache the buffer if it's not too big. 100 if (requestedSize <= MAX_CACHED_BUFFER_SIZE) { 101 setBuffer(buffer); 102 } 103 } 104 return buffer; 105 } 106 needToReallocate(int requestedSize, int bufferLength)107 private static boolean needToReallocate(int requestedSize, int bufferLength) { 108 // First check against just the requested length to avoid the multiply. 109 return bufferLength < requestedSize 110 && bufferLength < requestedSize * BUFFER_REALLOCATION_THRESHOLD; 111 } 112 getBuffer()113 private static byte[] getBuffer() { 114 SoftReference<byte[]> sr = BUFFER.get(); 115 return sr == null ? null : sr.get(); 116 } 117 setBuffer(byte[] value)118 private static void setBuffer(byte[] value) { 119 BUFFER.set(new SoftReference<byte[]>(value)); 120 } 121 writeToChannel(ByteBuffer buffer, OutputStream output)122 private static boolean writeToChannel(ByteBuffer buffer, OutputStream output) throws IOException { 123 if (CHANNEL_FIELD_OFFSET >= 0 && FILE_OUTPUT_STREAM_CLASS.isInstance(output)) { 124 // Use a channel to write out the ByteBuffer. This will automatically empty the buffer. 125 WritableByteChannel channel = null; 126 try { 127 channel = (WritableByteChannel) UnsafeUtil.getObject(output, CHANNEL_FIELD_OFFSET); 128 } catch (ClassCastException e) { 129 // Absorb. 130 } 131 if (channel != null) { 132 channel.write(buffer); 133 return true; 134 } 135 } 136 return false; 137 } 138 safeGetClass(String className)139 private static Class<?> safeGetClass(String className) { 140 try { 141 return Class.forName(className); 142 } catch (ClassNotFoundException e) { 143 return null; 144 } 145 } 146 getChannelFieldOffset(Class<?> clazz)147 private static long getChannelFieldOffset(Class<?> clazz) { 148 try { 149 if (clazz != null && UnsafeUtil.hasUnsafeArrayOperations()) { 150 Field field = clazz.getDeclaredField("channel"); 151 return UnsafeUtil.objectFieldOffset(field); 152 } 153 } catch (Throwable e) { 154 // Absorb 155 } 156 return -1; 157 } 158 } 159