diff --git a/src/java.base/share/classes/java/util/HexFormat.java b/src/java.base/share/classes/java/util/HexFormat.java index d54d491f3ab..b8c3a06e7ee 100644 --- a/src/java.base/share/classes/java/util/HexFormat.java +++ b/src/java.base/share/classes/java/util/HexFormat.java @@ -27,6 +27,7 @@ package java.util; import jdk.internal.access.JavaLangAccess; import jdk.internal.access.SharedSecrets; +import jdk.internal.util.HexDigits; import java.io.IOException; import java.io.UncheckedIOException; @@ -150,15 +151,7 @@ public final class HexFormat { -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; /** * Format each byte of an array as a pair of hexadecimal digits. @@ -403,21 +396,20 @@ public final class HexFormat { int length = toIndex - fromIndex; if (length > 0) { try { - out.append(prefix); - toHexDigits(out, bytes[fromIndex]); - if (suffix.isEmpty() && delimiter.isEmpty() && prefix.isEmpty()) { - for (int i = 1; i < length; i++) { - toHexDigits(out, bytes[fromIndex + i]); - } + String s = formatOptDelimiter(bytes, fromIndex, toIndex); + if (s != null) { + out.append(s); } else { + out.append(prefix); + toHexDigits(out, bytes[fromIndex]); for (int i = 1; i < length; i++) { out.append(suffix); out.append(delimiter); out.append(prefix); toHexDigits(out, bytes[fromIndex + i]); } + out.append(suffix); } - out.append(suffix); } catch (IOException ioe) { throw new UncheckedIOException(ioe.getMessage(), ioe); } @@ -438,29 +430,36 @@ public final class HexFormat { * or non-empty prefix or suffix */ private String formatOptDelimiter(byte[] bytes, int fromIndex, int toIndex) { + char sep; byte[] rep; if (!prefix.isEmpty() || !suffix.isEmpty()) { return null; } + + boolean ucase = digitCase == Case.UPPERCASE; int length = toIndex - fromIndex; if (delimiter.isEmpty()) { // Allocate the byte array and fill in the hex pairs for each byte rep = new byte[checkMaxArraySize(length * 2L)]; for (int i = 0; i < length; i++) { - rep[i * 2] = (byte)toHighHexDigit(bytes[fromIndex + i]); - rep[i * 2 + 1] = (byte)toLowHexDigit(bytes[fromIndex + i]); + short pair = HexDigits.digitPair(bytes[fromIndex + i], ucase); + int pos = i * 2; + rep[pos] = (byte)pair; + rep[pos + 1] = (byte)(pair >>> 8); } - } else if (delimiter.length() == 1 && delimiter.charAt(0) < 256) { + } else if (delimiter.length() == 1 && (sep = delimiter.charAt(0)) < 256) { // Allocate the byte array and fill in the characters for the first byte // Then insert the delimiter and hexadecimal characters for each of the remaining bytes - char sep = delimiter.charAt(0); rep = new byte[checkMaxArraySize(length * 3L - 1L)]; - rep[0] = (byte) toHighHexDigit(bytes[fromIndex]); - rep[1] = (byte) toLowHexDigit(bytes[fromIndex]); + short pair = HexDigits.digitPair(bytes[fromIndex], ucase); + rep[0] = (byte)pair; + rep[1] = (byte)(pair >>> 8); for (int i = 1; i < length; i++) { - rep[i * 3 - 1] = (byte) sep; - rep[i * 3 ] = (byte) toHighHexDigit(bytes[fromIndex + i]); - rep[i * 3 + 1] = (byte) toLowHexDigit(bytes[fromIndex + i]); + int pos = i * 3; + pair = HexDigits.digitPair(bytes[fromIndex + i], ucase); + rep[pos - 1] = (byte) sep; + rep[pos] = (byte)pair; + rep[pos + 1] = (byte)(pair >>> 8); } } else { // Delimiter formatting not to a single byte @@ -887,7 +886,7 @@ public final class HexFormat { * otherwise {@code false} */ public static boolean isHexDigit(int ch) { - return ((ch >>> 8) == 0 && DIGITS[ch] >= 0); + return ((ch >>> 7) == 0 && DIGITS[ch] >= 0); } /** @@ -905,7 +904,7 @@ public final class HexFormat { */ public static int fromHexDigit(int ch) { int value; - if ((ch >>> 8) == 0 && (value = DIGITS[ch]) >= 0) { + if ((ch >>> 7) == 0 && (value = DIGITS[ch]) >= 0) { return value; } throw new NumberFormatException("not a hexadecimal digit: \"" + (char) ch + "\" = " + ch); diff --git a/src/java.base/share/classes/jdk/internal/util/HexDigits.java b/src/java.base/share/classes/jdk/internal/util/HexDigits.java index 5ffb454ae00..1adda90a65a 100644 --- a/src/java.base/share/classes/jdk/internal/util/HexDigits.java +++ b/src/java.base/share/classes/jdk/internal/util/HexDigits.java @@ -91,6 +91,30 @@ public final class HexDigits implements Digits { private HexDigits() { } + /** + * For values from 0 to 255 return a short encoding a pair of hex ASCII-encoded digit characters in little-endian + * @param i value to convert + * @param ucase true uppper case, false lower case + * @return a short encoding a pair of hex ASCII-encoded digit characters + */ + public static short digitPair(int i, boolean ucase) { + /* + * 0b0100_0000_0100_0000 is a selector that selects letters (1 << 6), + * uppercase or not, and shifting it right by 1 bit incidentally + * becomes a bit offset between cases (1 << 5). + * + * ([0-9] & 0b100_0000) >> 1 => 0 + * ([a-f] & 0b100_0000) >> 1 => 32 + * + * [0-9] - 0 => [0-9] + * [a-f] - 32 => [A-F] + */ + short v = DIGITS[i & 0xff]; + return ucase + ? (short) (v - ((v & 0b0100_0000_0100_0000) >> 1)) + : v; + } + /** * Return a little-endian packed integer for the 4 ASCII bytes for an input unsigned 2-byte integer. * {@code b0} is the most significant byte and {@code b1} is the least significant byte. diff --git a/test/micro/org/openjdk/bench/java/util/HexFormatBench.java b/test/micro/org/openjdk/bench/java/util/HexFormatBench.java index c554f70c351..aa81a7c25ba 100644 --- a/test/micro/org/openjdk/bench/java/util/HexFormatBench.java +++ b/test/micro/org/openjdk/bench/java/util/HexFormatBench.java @@ -93,6 +93,25 @@ public class HexFormatBench { return UPPER_FORMATTER.formatHex(builder, bytes); } + @Benchmark + public String formatLower() { + return HexFormat.of().formatHex(bytes); + } + + @Benchmark + public String formatUpper() { + return HexFormat.of().withUpperCase().formatHex(bytes); + } + + @Benchmark + public String formatLowerCached() { + return LOWER_FORMATTER.formatHex(bytes); + } + + @Benchmark + public String formatUpperCached() { + return UPPER_FORMATTER.formatHex(bytes); + } @Benchmark public void toHexLower(Blackhole bh) {