diff --git a/src/java.base/share/classes/java/util/HexFormat.java b/src/java.base/share/classes/java/util/HexFormat.java index 8cc1a23b81a..107c362cbc2 100644 --- a/src/java.base/share/classes/java/util/HexFormat.java +++ b/src/java.base/share/classes/java/util/HexFormat.java @@ -140,14 +140,6 @@ public final class HexFormat { // Access to create strings from a byte array. private static final JavaLangAccess jla = SharedSecrets.getJavaLangAccess(); - private static final byte[] UPPERCASE_DIGITS = { - '0', '1', '2', '3', '4', '5', '6', '7', - '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', - }; - private static final byte[] LOWERCASE_DIGITS = { - '0', '1', '2', '3', '4', '5', '6', '7', - '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', - }; // Analysis has shown that generating the whole array allows the JIT to generate // better code compared to a slimmed down array, such as one cutting off after 'f' private static final byte[] DIGITS = { @@ -173,14 +165,22 @@ public final class HexFormat { * The hexadecimal characters are from lowercase alpha digits. */ private static final HexFormat HEX_FORMAT = - new HexFormat("", "", "", LOWERCASE_DIGITS); + new HexFormat("", "", "", Case.LOWERCASE); + + private static final HexFormat HEX_UPPER_FORMAT = + new HexFormat("", "", "", Case.UPPERCASE); private static final byte[] EMPTY_BYTES = {}; private final String delimiter; private final String prefix; private final String suffix; - private final byte[] digits; + private final Case digitCase; + + private enum Case { + LOWERCASE, + UPPERCASE + } /** * Returns a HexFormat with a delimiter, prefix, suffix, and array of digits. @@ -188,14 +188,14 @@ public final class HexFormat { * @param delimiter a delimiter, non-null * @param prefix a prefix, non-null * @param suffix a suffix, non-null - * @param digits byte array of digits indexed by low nibble, non-null + * @param digitCase enum indicating how to case digits * @throws NullPointerException if any argument is null */ - private HexFormat(String delimiter, String prefix, String suffix, byte[] digits) { + private HexFormat(String delimiter, String prefix, String suffix, Case digitCase) { this.delimiter = Objects.requireNonNull(delimiter, "delimiter"); this.prefix = Objects.requireNonNull(prefix, "prefix"); this.suffix = Objects.requireNonNull(suffix, "suffix"); - this.digits = digits; + this.digitCase = digitCase; } /** @@ -224,7 +224,7 @@ public final class HexFormat { * @return a {@link HexFormat} with the delimiter and lowercase characters */ public static HexFormat ofDelimiter(String delimiter) { - return new HexFormat(delimiter, "", "", LOWERCASE_DIGITS); + return new HexFormat(delimiter, "", "", Case.LOWERCASE); } /** @@ -233,7 +233,7 @@ public final class HexFormat { * @return a copy of this {@code HexFormat} with the delimiter */ public HexFormat withDelimiter(String delimiter) { - return new HexFormat(delimiter, this.prefix, this.suffix, this.digits); + return new HexFormat(delimiter, this.prefix, this.suffix, this.digitCase); } /** @@ -243,7 +243,7 @@ public final class HexFormat { * @return a copy of this {@code HexFormat} with the prefix */ public HexFormat withPrefix(String prefix) { - return new HexFormat(this.delimiter, prefix, this.suffix, this.digits); + return new HexFormat(this.delimiter, prefix, this.suffix, this.digitCase); } /** @@ -253,7 +253,7 @@ public final class HexFormat { * @return a copy of this {@code HexFormat} with the suffix */ public HexFormat withSuffix(String suffix) { - return new HexFormat(this.delimiter, this.prefix, suffix, this.digits); + return new HexFormat(this.delimiter, this.prefix, suffix, this.digitCase); } /** @@ -263,7 +263,9 @@ public final class HexFormat { * @return a copy of this {@code HexFormat} with uppercase hexadecimal characters */ public HexFormat withUpperCase() { - return new HexFormat(this.delimiter, this.prefix, this.suffix, UPPERCASE_DIGITS); + if (this == HEX_FORMAT) + return HEX_UPPER_FORMAT; + return new HexFormat(this.delimiter, this.prefix, this.suffix, Case.UPPERCASE); } /** @@ -273,7 +275,7 @@ public final class HexFormat { * @return a copy of this {@code HexFormat} with lowercase hexadecimal characters */ public HexFormat withLowerCase() { - return new HexFormat(this.delimiter, this.prefix, this.suffix, LOWERCASE_DIGITS); + return new HexFormat(this.delimiter, this.prefix, this.suffix, Case.LOWERCASE); } /** @@ -311,7 +313,7 @@ public final class HexFormat { * otherwise {@code false} */ public boolean isUpperCase() { - return Arrays.equals(digits, UPPERCASE_DIGITS); + return digitCase == Case.UPPERCASE; } /** @@ -401,16 +403,17 @@ public final class HexFormat { int length = toIndex - fromIndex; if (length > 0) { try { - String between = suffix + delimiter + prefix; out.append(prefix); toHexDigits(out, bytes[fromIndex]); - if (between.isEmpty()) { + if (suffix.isEmpty() && delimiter.isEmpty() && prefix.isEmpty()) { for (int i = 1; i < length; i++) { toHexDigits(out, bytes[fromIndex + i]); } } else { for (int i = 1; i < length; i++) { - out.append(between); + out.append(suffix); + out.append(delimiter); + out.append(prefix); toHexDigits(out, bytes[fromIndex + i]); } } @@ -631,7 +634,14 @@ public final class HexFormat { * @return the hexadecimal character for the low 4 bits {@code 0-3} of the value */ public char toLowHexDigit(int value) { - return (char)digits[value & 0xf]; + value = value & 0xf; + if (value < 10) { + return (char)('0' + value); + } + if (digitCase == Case.LOWERCASE) { + return (char)('a' - 10 + value); + } + return (char)('A' - 10 + value); } /** @@ -645,7 +655,14 @@ public final class HexFormat { * @return the hexadecimal character for the bits {@code 4-7} of the value */ public char toHighHexDigit(int value) { - return (char)digits[(value >> 4) & 0xf]; + value = (value >> 4) & 0xf; + if (value < 10) { + return (char)('0' + value); + } + if (digitCase == Case.LOWERCASE) { + return (char)('a' - 10 + value); + } + return (char)('A' - 10 + value); } /** @@ -1052,7 +1069,7 @@ public final class HexFormat { if (o == null || getClass() != o.getClass()) return false; HexFormat otherHex = (HexFormat) o; - return Arrays.equals(digits, otherHex.digits) && + return digitCase == otherHex.digitCase && delimiter.equals(otherHex.delimiter) && prefix.equals(otherHex.prefix) && suffix.equals(otherHex.suffix); @@ -1066,7 +1083,7 @@ public final class HexFormat { @Override public int hashCode() { int result = Objects.hash(delimiter, prefix, suffix); - result = 31 * result + Boolean.hashCode(Arrays.equals(digits, UPPERCASE_DIGITS)); + result = 31 * result + Boolean.hashCode(digitCase == Case.UPPERCASE); return result; } @@ -1078,7 +1095,7 @@ public final class HexFormat { */ @Override public String toString() { - return escapeNL("uppercase: " + Arrays.equals(digits, UPPERCASE_DIGITS) + + return escapeNL("uppercase: " + (digitCase == Case.UPPERCASE) + ", delimiter: \"" + delimiter + "\", prefix: \"" + prefix + "\", suffix: \"" + suffix + "\""); diff --git a/test/micro/org/openjdk/bench/java/util/HexFormatBench.java b/test/micro/org/openjdk/bench/java/util/HexFormatBench.java new file mode 100644 index 00000000000..c554f70c351 --- /dev/null +++ b/test/micro/org/openjdk/bench/java/util/HexFormatBench.java @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.java.util; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; + +import java.util.HexFormat; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +/** + * Tests java.net.URLEncoder.encode and Decoder.decode. + */ +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.MICROSECONDS) +@State(Scope.Thread) +@Warmup(iterations = 5, time = 1) +@Measurement(iterations = 5, time = 1) +@Fork(value = 3) +public class HexFormatBench { + + @Param({"512"}) + public int size; + + public byte[] bytes; + + public StringBuilder builder = new StringBuilder(size * 2); + + HexFormat LOWER_FORMATTER = HexFormat.of(); + HexFormat UPPER_FORMATTER = HexFormat.of().withUpperCase(); + + @Setup + public void setupStrings() { + Random random = new Random(3); + bytes = new byte[size]; + for (int i = 0; i < size; i++) { + bytes[i] = (byte)random.nextInt(16); + } + } + + @Benchmark + public StringBuilder appenderLower() { + builder.setLength(0); + return HexFormat.of().formatHex(builder, bytes); + } + + @Benchmark + public StringBuilder appenderUpper() { + builder.setLength(0); + return HexFormat.of().withUpperCase().formatHex(builder, bytes); + } + + @Benchmark + public StringBuilder appenderLowerCached() { + builder.setLength(0); + return LOWER_FORMATTER.formatHex(builder, bytes); + } + + @Benchmark + public StringBuilder appenderUpperCached() { + builder.setLength(0); + return UPPER_FORMATTER.formatHex(builder, bytes); + } + + + @Benchmark + public void toHexLower(Blackhole bh) { + for (byte b : bytes) { + bh.consume(HexFormat.of().toHighHexDigit(b)); + bh.consume(HexFormat.of().toLowHexDigit(b)); + } + } + + @Benchmark + public void toHexUpper(Blackhole bh) { + for (byte b : bytes) { + bh.consume(HexFormat.of().withUpperCase().toHighHexDigit(b)); + bh.consume(HexFormat.of().withUpperCase().toLowHexDigit(b)); + } + } + + @Benchmark + public void toHexLowerCached(Blackhole bh) { + for (byte b : bytes) { + bh.consume(LOWER_FORMATTER.toHighHexDigit(b)); + bh.consume(LOWER_FORMATTER.toLowHexDigit(b)); + } + } + + @Benchmark + public void toHexUpperCached(Blackhole bh) { + for (byte b : bytes) { + bh.consume(UPPER_FORMATTER.toHighHexDigit(b)); + bh.consume(UPPER_FORMATTER.toLowHexDigit(b)); + } + } + + @Benchmark + public void toHexDigitsByte(Blackhole bh) { + for (byte b : bytes) { + bh.consume(LOWER_FORMATTER.toHexDigits(b)); + } + } + + @Benchmark + public void toHexDigitsShort(Blackhole bh) { + for (short b : bytes) { + bh.consume(LOWER_FORMATTER.toHexDigits(b)); + } + } + + @Benchmark + public void toHexDigitsInt(Blackhole bh) { + for (int b : bytes) { + bh.consume(LOWER_FORMATTER.toHexDigits(b)); + } + } + + @Benchmark + public void toHexDigitsLong(Blackhole bh) { + for (long b : bytes) { + bh.consume(LOWER_FORMATTER.toHexDigits(b)); + } + } +}