From 05c10fae051163b3b3925ac1bd9b179fc3e3bb00 Mon Sep 17 00:00:00 2001 From: Norman Maurer Date: Tue, 3 Dec 2013 12:04:07 +0100 Subject: [PATCH] Replace usage of StringBuilder by AppendableCharSequence for performance reasons --- .../handler/codec/http/HttpObjectDecoder.java | 25 ++-- .../util/internal/AppendableCharSequence.java | 137 ++++++++++++++++++ .../internal/AppendableCharSequenceTest.java | 94 ++++++++++++ 3 files changed, 244 insertions(+), 12 deletions(-) create mode 100644 common/src/main/java/io/netty/util/internal/AppendableCharSequence.java create mode 100644 common/src/test/java/io/netty/util/internal/AppendableCharSequenceTest.java diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java index 0f98fc2857..5648b260a9 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java @@ -22,6 +22,7 @@ import io.netty.channel.ChannelPipeline; import io.netty.handler.codec.DecoderResult; import io.netty.handler.codec.ReplayingDecoder; import io.netty.handler.codec.TooLongFrameException; +import io.netty.util.internal.AppendableCharSequence; import java.util.List; @@ -111,7 +112,7 @@ public abstract class HttpObjectDecoder extends ReplayingDecoder 0) { @@ -624,7 +625,7 @@ public abstract class HttpObjectDecoder extends ReplayingDecoder 0) { LastHttpContent trailer = new DefaultLastHttpContent(Unpooled.EMPTY_BUFFER, validateHeaders); @@ -659,9 +660,9 @@ public abstract class HttpObjectDecoder extends ReplayingDecoder pos) { + throw new IndexOutOfBoundsException(); + } + return chars[index]; + } + + @Override + public AppendableCharSequence subSequence(int start, int end) { + return new AppendableCharSequence(Arrays.copyOfRange(chars, start, end)); + } + + @Override + public AppendableCharSequence append(char c) { + if (pos == chars.length) { + char[] old = chars; + // double it + int len = old.length << 1; + if (len < 0) { + throw new IllegalStateException(); + } + chars = new char[len]; + System.arraycopy(old, 0, chars, 0, old.length); + } + chars[pos++] = c; + return this; + } + + @Override + public AppendableCharSequence append(CharSequence csq) { + return append(csq, 0, csq.length()); + } + + @Override + public AppendableCharSequence append(CharSequence csq, int start, int end) { + if (csq.length() < end) { + throw new IndexOutOfBoundsException(); + } + int length = end - start; + if (length > chars.length - pos) { + chars = expand(chars, pos + length, pos); + } + if (csq instanceof AppendableCharSequence) { + // Optimize append operations via array copy + AppendableCharSequence seq = (AppendableCharSequence) csq; + char[] src = seq.chars; + System.arraycopy(src, start, chars, pos, length); + pos += length; + return this; + } + for (int i = start; i < end; i++) { + chars[pos++] = csq.charAt(i); + } + + return this; + } + + /** + * Reset the {@link AppendableCharSequence}. Be aware this will only reset the current internal position and not + * shrink the internal char array. + */ + public void reset() { + pos = 0; + } + + @Override + public String toString() { + return new String(chars, 0, pos); + } + + /** + * Create a new {@link String} from the given start to end. + */ + public String substring(int start, int end) { + int length = end - start; + if (start > pos || length > pos) { + throw new IndexOutOfBoundsException(); + } + return new String(chars, start, length); + } + + private static char[] expand(char[] array, int neededSpace, int size) { + int newCapacity = array.length; + do { + // double capacity until it is big enough + newCapacity <<= 1; + + if (newCapacity < 0) { + throw new IllegalStateException(); + } + + } while (neededSpace > newCapacity); + + char[] newArray = new char[newCapacity]; + System.arraycopy(array, 0, newArray, 0, size); + + return newArray; + } +} diff --git a/common/src/test/java/io/netty/util/internal/AppendableCharSequenceTest.java b/common/src/test/java/io/netty/util/internal/AppendableCharSequenceTest.java new file mode 100644 index 0000000000..3fca3e1569 --- /dev/null +++ b/common/src/test/java/io/netty/util/internal/AppendableCharSequenceTest.java @@ -0,0 +1,94 @@ +/* + * Copyright 2013 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.util.internal; + + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class AppendableCharSequenceTest { + + @Test + public void testSimpleAppend() { + testSimpleAppend0(new AppendableCharSequence(128)); + } + + @Test + public void testAppendString() { + testAppendString0(new AppendableCharSequence(128)); + } + + @Test + public void testAppendAppendableCharSequence() { + AppendableCharSequence seq = new AppendableCharSequence(128); + + String text = "testdata"; + AppendableCharSequence seq2 = new AppendableCharSequence(128); + seq2.append(text); + seq.append(seq2); + + assertEquals(text, seq.toString()); + assertEquals(text.substring(1, text.length() - 2), seq.substring(1, text.length() - 2)); + + assertEqualsChars(text, seq); + } + + @Test + public void testSimpleAppendWithExpand() { + testSimpleAppend0(new AppendableCharSequence(2)); + } + + @Test + public void testAppendStringWithExpand() { + testAppendString0(new AppendableCharSequence(2)); + } + + private static void testSimpleAppend0(AppendableCharSequence seq) { + String text = "testdata"; + for (int i = 0; i < text.length(); i++) { + seq.append(text.charAt(i)); + } + + assertEquals(text, seq.toString()); + assertEquals(text.substring(1, text.length() - 2), seq.substring(1, text.length() - 2)); + + assertEqualsChars(text, seq); + + seq.reset(); + assertEquals(0, seq.length()); + } + + private static void testAppendString0(AppendableCharSequence seq) { + String text = "testdata"; + seq.append(text); + + assertEquals(text, seq.toString()); + assertEquals(text.substring(1, text.length() - 2), seq.substring(1, text.length() - 2)); + + assertEqualsChars(text, seq); + + seq.reset(); + assertEquals(0, seq.length()); + } + + private static void assertEqualsChars(CharSequence seq1, CharSequence seq2) { + assertEquals(seq1.length(), seq2.length()); + for (int i = 0; i < seq1.length(); i++) { + assertEquals(seq1.charAt(i), seq2.charAt(i)); + } + } +}