2323
2424import org .openjdk .jmh .annotations .*;
2525
26+ import java .io .IOException ;
2627import java .math .BigInteger ;
2728import java .nio .ByteOrder ;
29+ import java .nio .charset .StandardCharsets ;
2830import java .util .HashMap ;
2931import java .util .Map ;
32+ import java .util .Objects ;
3033import java .util .Random ;
3134import java .util .concurrent .TimeUnit ;
3235
@@ -128,6 +131,7 @@ public class EncodingHexJmhBenchmark {
128131 private BinaryToTextEncoding .EncoderDecoder option3 ;
129132 private BinaryToTextEncoding .EncoderDecoder option4 ;
130133 private BinaryToTextEncoding .EncoderDecoder option5 ;
134+ private BinaryToTextEncoding .EncoderDecoder option6 ;
131135 private Random random ;
132136
133137 @ Setup (Level .Trial )
@@ -139,6 +143,7 @@ public void setup() {
139143 option3 = new BigIntegerHexEncoder ();
140144 option4 = new OldBytesImplementation ();
141145 option5 = new StackOverflowAnswer2Encoder ();
146+ option6 = new Jdk17HexFormat ();
142147
143148 rndMap = new HashMap <>();
144149 int [] lengths = new int []{4 , 8 , 16 , 32 , 128 , 512 , 1000000 };
@@ -176,6 +181,11 @@ public String encodeStackOverflowCode2() {
176181 return encodeDecode (option5 );
177182 }
178183
184+ @ Benchmark
185+ public String encodeHexFormatJdk17 () {
186+ return encodeDecode (option6 );
187+ }
188+
179189 private String encodeDecode (BinaryToTextEncoding .EncoderDecoder encoder ) {
180190 Bytes [] bytes = rndMap .get (byteLength );
181191 int rndNum = random .nextInt (bytes .length );
@@ -285,4 +295,140 @@ public byte[] decode(CharSequence encoded) {
285295 throw new UnsupportedOperationException ();
286296 }
287297 }
298+
299+ /**
300+ * Copy of the JDK 17 implementation of HexFormat, only difference is that we need to create new strings, while
301+ * the JDK can create strings without byte copy, I cant here.
302+ */
303+ static final class Jdk17HexFormat implements BinaryToTextEncoding .EncoderDecoder {
304+ private static final byte [] LOWERCASE_DIGITS = {
305+ '0' , '1' , '2' , '3' , '4' , '5' , '6' , '7' ,
306+ '8' , '9' , 'a' , 'b' , 'c' , 'd' , 'e' , 'f' ,
307+ };
308+ private final String delimiter = "" ;
309+ private final String prefix = "" ;
310+ private final String suffix = "" ;
311+ private final byte [] digits = LOWERCASE_DIGITS ;
312+
313+ @ Override
314+ public String encode (byte [] byteArray , ByteOrder byteOrder ) {
315+ return formatHex (byteArray , 0 , byteArray .length );
316+ }
317+
318+ private String formatHex (byte [] bytes , int fromIndex , int toIndex ) {
319+ Objects .requireNonNull (bytes , "bytes" );
320+ //Objects.checkFromToIndex(fromIndex, toIndex, bytes.length);
321+ if (toIndex - fromIndex == 0 ) {
322+ return "" ;
323+ }
324+ // Format efficiently if possible
325+ String s = formatOptDelimiter (bytes , fromIndex , toIndex );
326+ if (s == null ) {
327+ long stride = prefix .length () + 2L + suffix .length () + delimiter .length ();
328+ int capacity = checkMaxArraySize ((toIndex - fromIndex ) * stride - delimiter .length ());
329+ StringBuilder sb = new StringBuilder (capacity );
330+ formatHex (sb , bytes , fromIndex , toIndex );
331+ s = sb .toString ();
332+ }
333+ return s ;
334+ }
335+
336+ private <A extends Appendable > A formatHex (A out , byte [] bytes , int fromIndex , int toIndex ) {
337+ Objects .requireNonNull (out , "out" );
338+ Objects .requireNonNull (bytes , "bytes" );
339+ //Objects.checkFromToIndex(fromIndex, toIndex, bytes.length);
340+
341+ int length = toIndex - fromIndex ;
342+ if (length > 0 ) {
343+ try {
344+ String between = suffix + delimiter + prefix ;
345+ out .append (prefix );
346+ toHexDigits (out , bytes [fromIndex ]);
347+ if (between .isEmpty ()) {
348+ for (int i = 1 ; i < length ; i ++) {
349+ toHexDigits (out , bytes [fromIndex + i ]);
350+ }
351+ } else {
352+ for (int i = 1 ; i < length ; i ++) {
353+ out .append (between );
354+ toHexDigits (out , bytes [fromIndex + i ]);
355+ }
356+ }
357+ out .append (suffix );
358+ } catch (IOException ioe ) {
359+ throw new RuntimeException (ioe .getMessage (), ioe );
360+ }
361+ }
362+ return out ;
363+ }
364+
365+ private <A extends Appendable > A toHexDigits (A out , byte value ) {
366+ Objects .requireNonNull (out , "out" );
367+ try {
368+ out .append (toHighHexDigit (value ));
369+ out .append (toLowHexDigit (value ));
370+ return out ;
371+ } catch (IOException ioe ) {
372+ throw new RuntimeException (ioe .getMessage (), ioe );
373+ }
374+ }
375+
376+ private String formatOptDelimiter (byte [] bytes , int fromIndex , int toIndex ) {
377+ byte [] rep ;
378+ if (!prefix .isEmpty () || !suffix .isEmpty ()) {
379+ return null ;
380+ }
381+ int length = toIndex - fromIndex ;
382+ if (delimiter .isEmpty ()) {
383+ // Allocate the byte array and fill in the hex pairs for each byte
384+ rep = new byte [checkMaxArraySize (length * 2L )];
385+ for (int i = 0 ; i < length ; i ++) {
386+ rep [i * 2 ] = (byte ) toHighHexDigit (bytes [fromIndex + i ]);
387+ rep [i * 2 + 1 ] = (byte ) toLowHexDigit (bytes [fromIndex + i ]);
388+ }
389+ } else if (delimiter .length () == 1 && delimiter .charAt (0 ) < 256 ) {
390+ // Allocate the byte array and fill in the characters for the first byte
391+ // Then insert the delimiter and hexadecimal characters for each of the remaining bytes
392+ char sep = delimiter .charAt (0 );
393+ rep = new byte [checkMaxArraySize (length * 3L - 1L )];
394+ rep [0 ] = (byte ) toHighHexDigit (bytes [fromIndex ]);
395+ rep [1 ] = (byte ) toLowHexDigit (bytes [fromIndex ]);
396+ for (int i = 1 ; i < length ; i ++) {
397+ rep [i * 3 - 1 ] = (byte ) sep ;
398+ rep [i * 3 ] = (byte ) toHighHexDigit (bytes [fromIndex + i ]);
399+ rep [i * 3 + 1 ] = (byte ) toLowHexDigit (bytes [fromIndex + i ]);
400+ }
401+ } else {
402+ // Delimiter formatting not to a single byte
403+ return null ;
404+ }
405+ try {
406+ // Return a new string using the bytes without making a copy -> we cant use this here as we dont have access to JavaLangAccess
407+ //return jla.newStringNoRepl(rep, StandardCharsets.ISO_8859_1);
408+ return new String (rep , StandardCharsets .ISO_8859_1 );
409+ } catch (Exception cce ) {
410+ throw new AssertionError (cce );
411+ }
412+ }
413+
414+ private char toHighHexDigit (int value ) {
415+ return (char ) digits [(value >> 4 ) & 0xf ];
416+ }
417+
418+ private char toLowHexDigit (int value ) {
419+ return (char ) digits [value & 0xf ];
420+ }
421+
422+ private static int checkMaxArraySize (long length ) {
423+ if (length > Integer .MAX_VALUE )
424+ throw new OutOfMemoryError ("String size " + length +
425+ " exceeds maximum " + Integer .MAX_VALUE );
426+ return (int ) length ;
427+ }
428+
429+ @ Override
430+ public byte [] decode (CharSequence encoded ) {
431+ throw new UnsupportedOperationException ();
432+ }
433+ }
288434}
0 commit comments