Skip to content

Commit eb54ecb

Browse files
authored
Merge pull request #59 from patrickfav/feature/remove-cache
2 parents 165118b + 37874d5 commit eb54ecb

File tree

5 files changed

+153
-7
lines changed

5 files changed

+153
-7
lines changed

.github/workflows/build_deploy.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ jobs:
4141
- name: Build with Maven
4242
run: ./mvnw -B clean verify -DcommonConfig.jarSign.skip=true
4343
- name: Analyze with SonaQube
44-
if: ${{ github.actor != 'dependabot[bot]' }}
44+
if: ${{ github.event.pull_request.head.repo.full_name == github.repository }}
4545
env:
4646
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
4747
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

CHANGELOG

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Releases
22

3+
## 1.6.2
4+
5+
* remove hashCode caching since it could introduce very subtle bugs
6+
37
## v1.6.1
48

59
* now build by JDK 11 and removed errorprone compiler #52

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
</parent>
1212

1313
<artifactId>bytes</artifactId>
14-
<version>1.6.1</version>
14+
<version>1.6.2</version>
1515
<packaging>bundle</packaging>
1616

1717
<name>Bytes Utility Library</name>

src/main/java/at/favre/lib/bytes/Bytes.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -745,7 +745,6 @@ public static Bytes random(int length, Random random) {
745745
private final byte[] byteArray;
746746
private final ByteOrder byteOrder;
747747
private final BytesFactory factory;
748-
private transient int hashCodeCache;
749748

750749
Bytes(byte[] byteArray, ByteOrder byteOrder) {
751750
this(byteArray, byteOrder, new Factory());
@@ -2221,10 +2220,7 @@ public boolean equalsContent(Bytes other) {
22212220

22222221
@Override
22232222
public int hashCode() {
2224-
if (hashCodeCache == 0) {
2225-
hashCodeCache = Util.Obj.hashCode(internalArray(), byteOrder());
2226-
}
2227-
return hashCodeCache;
2223+
return Util.Obj.hashCode(internalArray(), byteOrder());
22282224
}
22292225

22302226
/**

src/test/java/at/favre/lib/bytes/EncodingHexJmhBenchmark.java

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,13 @@
2323

2424
import org.openjdk.jmh.annotations.*;
2525

26+
import java.io.IOException;
2627
import java.math.BigInteger;
2728
import java.nio.ByteOrder;
29+
import java.nio.charset.StandardCharsets;
2830
import java.util.HashMap;
2931
import java.util.Map;
32+
import java.util.Objects;
3033
import java.util.Random;
3134
import 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

Comments
 (0)