|
15 | 15 |
|
16 | 16 | import io.netty.buffer.ByteBuf;
|
17 | 17 | import io.netty.buffer.Unpooled;
|
| 18 | +import io.netty.util.CharsetUtil; |
18 | 19 |
|
| 20 | +import java.nio.ByteBuffer; |
| 21 | +import java.nio.CharBuffer; |
| 22 | +import java.nio.charset.CharacterCodingException; |
19 | 23 | import java.nio.charset.Charset;
|
| 24 | +import java.nio.charset.CharsetDecoder; |
| 25 | +import java.nio.charset.CoderResult; |
20 | 26 |
|
21 |
| -import static java.nio.charset.StandardCharsets.*; |
| 27 | +import static java.nio.charset.StandardCharsets.US_ASCII; |
| 28 | +import static java.nio.charset.StandardCharsets.UTF_8; |
| 29 | +import static org.asynchttpclient.netty.util.Utf8ByteBufCharsetDecoder.*; |
22 | 30 |
|
23 | 31 | public final class ByteBufUtils {
|
24 | 32 |
|
| 33 | + private static final char[] EMPTY_CHARS = new char[0]; |
| 34 | + private static final ThreadLocal<CharBuffer> CHAR_BUFFERS = ThreadLocal.withInitial(() -> CharBuffer.allocate(1024)); |
| 35 | + |
25 | 36 | private ByteBufUtils() {
|
26 | 37 | }
|
27 | 38 |
|
28 |
| - public static boolean isUtf8OrUsAscii(Charset charset) { |
| 39 | + public static byte[] byteBuf2Bytes(ByteBuf buf) { |
| 40 | + int readable = buf.readableBytes(); |
| 41 | + int readerIndex = buf.readerIndex(); |
| 42 | + if (buf.hasArray()) { |
| 43 | + byte[] array = buf.array(); |
| 44 | + if (buf.arrayOffset() == 0 && readerIndex == 0 && array.length == readable) { |
| 45 | + return array; |
| 46 | + } |
| 47 | + } |
| 48 | + byte[] array = new byte[readable]; |
| 49 | + buf.getBytes(readerIndex, array); |
| 50 | + return array; |
| 51 | + } |
| 52 | + |
| 53 | + public static String byteBuf2String(Charset charset, ByteBuf buf) { |
| 54 | + return isUtf8OrUsAscii(charset) ? decodeUtf8(buf) : buf.toString(charset); |
| 55 | + } |
| 56 | + |
| 57 | + public static String byteBuf2String(Charset charset, ByteBuf... bufs) { |
| 58 | + return isUtf8OrUsAscii(charset) ? decodeUtf8(bufs) : byteBuf2String0(charset, bufs); |
| 59 | + } |
| 60 | + |
| 61 | + public static char[] byteBuf2Chars(Charset charset, ByteBuf buf) { |
| 62 | + return isUtf8OrUsAscii(charset) ? decodeUtf8Chars(buf) : decodeChars(buf, charset); |
| 63 | + } |
| 64 | + |
| 65 | + public static char[] byteBuf2Chars(Charset charset, ByteBuf... bufs) { |
| 66 | + return isUtf8OrUsAscii(charset) ? decodeUtf8Chars(bufs) : byteBuf2Chars0(charset, bufs); |
| 67 | + } |
| 68 | + |
| 69 | + private static boolean isUtf8OrUsAscii(Charset charset) { |
29 | 70 | return charset.equals(UTF_8) || charset.equals(US_ASCII);
|
30 | 71 | }
|
31 | 72 |
|
32 |
| - public static String byteBuf2StringDefault(Charset charset, ByteBuf... bufs) { |
| 73 | + private static char[] decodeChars(ByteBuf src, Charset charset) { |
| 74 | + int readerIndex = src.readerIndex(); |
| 75 | + int len = src.readableBytes(); |
33 | 76 |
|
34 |
| - if (bufs.length == 1) { |
35 |
| - return bufs[0].toString(charset); |
| 77 | + if (len == 0) { |
| 78 | + return EMPTY_CHARS; |
36 | 79 | }
|
37 |
| - |
38 |
| - for (ByteBuf buf : bufs) { |
39 |
| - buf.retain(); |
| 80 | + final CharsetDecoder decoder = CharsetUtil.decoder(charset); |
| 81 | + final int maxLength = (int) ((double) len * decoder.maxCharsPerByte()); |
| 82 | + CharBuffer dst = CHAR_BUFFERS.get(); |
| 83 | + if (dst.length() < maxLength) { |
| 84 | + dst = CharBuffer.allocate(maxLength); |
| 85 | + CHAR_BUFFERS.set(dst); |
| 86 | + } else { |
| 87 | + dst.clear(); |
40 | 88 | }
|
| 89 | + if (src.nioBufferCount() == 1) { |
| 90 | + // Use internalNioBuffer(...) to reduce object creation. |
| 91 | + decode(decoder, src.internalNioBuffer(readerIndex, len), dst); |
| 92 | + } else { |
| 93 | + // We use a heap buffer as CharsetDecoder is most likely able to use a fast-path if src and dst buffers |
| 94 | + // are both backed by a byte array. |
| 95 | + ByteBuf buffer = src.alloc().heapBuffer(len); |
| 96 | + try { |
| 97 | + buffer.writeBytes(src, readerIndex, len); |
| 98 | + // Use internalNioBuffer(...) to reduce object creation. |
| 99 | + decode(decoder, buffer.internalNioBuffer(buffer.readerIndex(), len), dst); |
| 100 | + } finally { |
| 101 | + // Release the temporary buffer again. |
| 102 | + buffer.release(); |
| 103 | + } |
| 104 | + } |
| 105 | + dst.flip(); |
| 106 | + return toCharArray(dst); |
| 107 | + } |
41 | 108 |
|
42 |
| - ByteBuf composite = Unpooled.wrappedBuffer(bufs); |
43 |
| - |
| 109 | + static String byteBuf2String0(Charset charset, ByteBuf... bufs) { |
| 110 | + if (bufs.length == 1) { |
| 111 | + return bufs[0].toString(charset); |
| 112 | + } |
| 113 | + ByteBuf composite = composite(bufs); |
44 | 114 | try {
|
45 | 115 | return composite.toString(charset);
|
46 | 116 | } finally {
|
47 | 117 | composite.release();
|
48 | 118 | }
|
49 | 119 | }
|
50 | 120 |
|
51 |
| - public static String byteBuf2String(Charset charset, ByteBuf buf) { |
52 |
| - return isUtf8OrUsAscii(charset) ? Utf8ByteBufCharsetDecoder.decodeUtf8(buf) : buf.toString(charset); |
| 121 | + static char[] byteBuf2Chars0(Charset charset, ByteBuf... bufs) { |
| 122 | + if (bufs.length == 1) { |
| 123 | + return decodeChars(bufs[0], charset); |
| 124 | + } |
| 125 | + ByteBuf composite = composite(bufs); |
| 126 | + try { |
| 127 | + return decodeChars(composite, charset); |
| 128 | + } finally { |
| 129 | + composite.release(); |
| 130 | + } |
53 | 131 | }
|
54 | 132 |
|
55 |
| - public static String byteBuf2String(Charset charset, ByteBuf... bufs) { |
56 |
| - return isUtf8OrUsAscii(charset) ? Utf8ByteBufCharsetDecoder.decodeUtf8(bufs) : byteBuf2StringDefault(charset, bufs); |
| 133 | + private static ByteBuf composite(ByteBuf[] bufs) { |
| 134 | + for (ByteBuf buf : bufs) { |
| 135 | + buf.retain(); |
| 136 | + } |
| 137 | + return Unpooled.wrappedBuffer(bufs); |
57 | 138 | }
|
58 | 139 |
|
59 |
| - public static byte[] byteBuf2Bytes(ByteBuf buf) { |
60 |
| - int readable = buf.readableBytes(); |
61 |
| - int readerIndex = buf.readerIndex(); |
62 |
| - if (buf.hasArray()) { |
63 |
| - byte[] array = buf.array(); |
64 |
| - if (buf.arrayOffset() == 0 && readerIndex == 0 && array.length == readable) { |
65 |
| - return array; |
| 140 | + private static void decode(CharsetDecoder decoder, ByteBuffer src, CharBuffer dst) { |
| 141 | + try { |
| 142 | + CoderResult cr = decoder.decode(src, dst, true); |
| 143 | + if (!cr.isUnderflow()) { |
| 144 | + cr.throwException(); |
| 145 | + } |
| 146 | + cr = decoder.flush(dst); |
| 147 | + if (!cr.isUnderflow()) { |
| 148 | + cr.throwException(); |
66 | 149 | }
|
| 150 | + } catch (CharacterCodingException x) { |
| 151 | + throw new IllegalStateException(x); |
67 | 152 | }
|
68 |
| - byte[] array = new byte[readable]; |
69 |
| - buf.getBytes(readerIndex, array); |
70 |
| - return array; |
| 153 | + } |
| 154 | + |
| 155 | + static char[] toCharArray(CharBuffer charBuffer) { |
| 156 | + char[] chars = new char[charBuffer.remaining()]; |
| 157 | + charBuffer.get(chars); |
| 158 | + return chars; |
71 | 159 | }
|
72 | 160 | }
|
0 commit comments