diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/codec/Number128.java b/hutool-core/src/main/java/org/dromara/hutool/core/codec/Number128.java
index 90043f66c..91cc45bd6 100644
--- a/hutool-core/src/main/java/org/dromara/hutool/core/codec/Number128.java
+++ b/hutool-core/src/main/java/org/dromara/hutool/core/codec/Number128.java
@@ -12,47 +12,43 @@
package org.dromara.hutool.core.codec;
+import org.dromara.hutool.core.util.ByteUtil;
+import org.jetbrains.annotations.NotNull;
+
+import java.nio.ByteOrder;
import java.util.Objects;
/**
- * 128位数字表示,分高位和低位
+ * 128位数字表示,分为:
+ *
{
private static final long serialVersionUID = 1L;
- private long lowValue;
- private long highValue;
+ /**
+ * 最高有效位(Most Significant Bit),64 bit(8 bytes)
+ */
+ private long mostSigBits;
+ /**
+ * 最低有效位(Least Significant Bit),64 bit(8 bytes)
+ */
+ private long leastSigBits;
/**
* 构造
*
- * @param lowValue 低位
- * @param highValue 高位
+ * @param leastSigBits 低位
+ * @param mostSigBits 高位
*/
- public Number128(final long lowValue, final long highValue) {
- this.lowValue = lowValue;
- this.highValue = highValue;
- }
-
- /**
- * 获取低位值
- *
- * @return 地位值
- */
- public long getLowValue() {
- return lowValue;
- }
-
- /**
- * 设置低位值
- *
- * @param lowValue 低位值
- */
- public void setLowValue(final long lowValue) {
- this.lowValue = lowValue;
+ public Number128(final long leastSigBits, final long mostSigBits) {
+ this.mostSigBits = mostSigBits;
+ this.leastSigBits = leastSigBits;
}
/**
@@ -60,8 +56,8 @@ public class Number128 extends Number {
*
* @return 高位值
*/
- public long getHighValue() {
- return highValue;
+ public long getMostSigBits() {
+ return mostSigBits;
}
/**
@@ -69,8 +65,26 @@ public class Number128 extends Number {
*
* @param hiValue 高位值
*/
- public void setHighValue(final long hiValue) {
- this.highValue = hiValue;
+ public void setMostSigBits(final long hiValue) {
+ this.mostSigBits = hiValue;
+ }
+
+ /**
+ * 获取低位值
+ *
+ * @return 地位值
+ */
+ public long getLeastSigBits() {
+ return leastSigBits;
+ }
+
+ /**
+ * 设置低位值
+ *
+ * @param leastSigBits 低位值
+ */
+ public void setLeastSigBits(final long leastSigBits) {
+ this.leastSigBits = leastSigBits;
}
/**
@@ -79,7 +93,26 @@ public class Number128 extends Number {
* @return 高低位数组,long[0]:低位,long[1]:高位
*/
public long[] getLongArray() {
- return new long[]{lowValue, highValue};
+ return getLongArray(ByteUtil.DEFAULT_ORDER);
+ }
+
+ /**
+ * 获取高低位数组,规则为:
+ *
+ * - {@link ByteOrder#LITTLE_ENDIAN},则long[0]:低位,long[1]:高位
+ * - {@link ByteOrder#BIG_ENDIAN},则long[0]:高位,long[1]:低位
+ *
+ *
+ *
+ * @param byteOrder 端续
+ * @return 高低位数组,long[0]:低位,long[1]:高位
+ */
+ public long[] getLongArray(final ByteOrder byteOrder) {
+ if(byteOrder == ByteOrder.BIG_ENDIAN){
+ return new long[]{leastSigBits, mostSigBits};
+ } else{
+ return new long[]{mostSigBits, leastSigBits};
+ }
}
@Override
@@ -89,7 +122,7 @@ public class Number128 extends Number {
@Override
public long longValue() {
- return this.lowValue;
+ return this.leastSigBits;
}
@Override
@@ -109,13 +142,21 @@ public class Number128 extends Number {
}
if (o instanceof Number128) {
final Number128 number128 = (Number128) o;
- return lowValue == number128.lowValue && highValue == number128.highValue;
+ return leastSigBits == number128.leastSigBits && mostSigBits == number128.mostSigBits;
}
return false;
}
@Override
public int hashCode() {
- return Objects.hash(lowValue, highValue);
+ return Objects.hash(leastSigBits, mostSigBits);
}
+
+ @Override
+ public int compareTo(@NotNull final Number128 o) {
+ final int mostSigBits = Long.compare(this.mostSigBits, o.mostSigBits);
+ return mostSigBits != 0 ? mostSigBits : Long.compare(this.leastSigBits, o.leastSigBits);
+ }
+
+
}
diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/codec/binary/CrockfordBase32Codec.java b/hutool-core/src/main/java/org/dromara/hutool/core/codec/binary/CrockfordBase32Codec.java
new file mode 100644
index 000000000..3d6c71c54
--- /dev/null
+++ b/hutool-core/src/main/java/org/dromara/hutool/core/codec/binary/CrockfordBase32Codec.java
@@ -0,0 +1,732 @@
+/*
+ * Copyright (c) 2024. looly(loolly@aliyun.com)
+ * Hutool is licensed under Mulan PSL v2.
+ * You can use this software according to the terms and conditions of the Mulan PSL v2.
+ * You may obtain a copy of Mulan PSL v2 at:
+ * https://license.coscl.org.cn/MulanPSL2
+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
+ * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
+ * See the Mulan PSL v2 for more details.
+ */
+
+package org.dromara.hutool.core.codec.binary;
+
+import org.dromara.hutool.core.util.CharsetUtil;
+
+import java.nio.charset.Charset;
+import java.util.Objects;
+
+/**
+ * Crockford`s Base32实现
+ * 来自:https://gist.github.com/markov/5206312
+ *
+ * Provides Base32 encoding and decoding as defined by RFC 4648.
+ * However it uses a custom alphabet first coined by Douglas Crockford. Only addition to the alphabet is that 'u' and
+ * 'U' characters decode as if they were 'V' to improve mistakes by human input.
+ *
+ *
+ *
+ * This class operates directly on byte streams, and not character streams.
+ *
+ *
+ * @version $Id: Base32.java 1382498 2012-09-09 13:41:55Z sebb $
+ * @see RFC 4648
+ * @see Douglas Crockford's Base32 Encoding
+ * @since 1.5
+ */
+public class CrockfordBase32Codec {
+
+ /**
+ * Mask used to extract 8 bits, used in decoding bytes
+ */
+ protected static final int MASK_8BITS = 0xff;
+ private static final Charset DEFAULT_CHARSET = CharsetUtil.UTF_8;
+ private static final int DEFAULT_BUFFER_RESIZE_FACTOR = 2;
+ /**
+ * Defines the default buffer size - currently {@value}
+ * - must be large enough for at least one encoded block+separator
+ */
+ private static final int DEFAULT_BUFFER_SIZE = 8192;
+ /**
+ * Mask used to extract 5 bits, used when encoding Base32 bytes
+ */
+ private static final int MASK_5BITS = 0x1f;
+ /**
+ * BASE32 characters are 5 bits in length.
+ * They are formed by taking a block of five octets to form a 40-bit string,
+ * which is converted into eight BASE32 characters.
+ */
+ private static final int BITS_PER_ENCODED_BYTE = 5;
+ private static final int BYTES_PER_ENCODED_BLOCK = 8;
+ private static final int BYTES_PER_UNENCODED_BLOCK = 5;
+ private static final byte PAD = '=';
+ /**
+ * This array is a lookup table that translates 5-bit positive integer index values into their "Base32 Alphabet"
+ * equivalents as specified in Table 3 of RFC 2045.
+ */
+ private static final byte[] ENCODE_TABLE = {
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'M',
+ 'N', 'P', 'Q', 'R', 'S', 'T', 'V', 'W', 'X', 'Y', 'Z'
+ };
+ /**
+ * Convenience variable to help us determine when our buffer is going to run out of room and needs resizing.
+ * decodeSize = {@link #BYTES_PER_ENCODED_BLOCK} - 1 + lineSeparator.length;
+ */
+ private final int decodeSize;
+ /**
+ * Convenience variable to help us determine when our buffer is going to run out of room and needs resizing.
+ * encodeSize = {@link #BYTES_PER_ENCODED_BLOCK} + lineSeparator.length;
+ */
+ private final int encodeSize;
+ /**
+ * Wheather this encoder should use a padding character at the end of encoded Strings.
+ */
+ private final boolean usePaddingCharacter;
+ /**
+ * Buffer for streaming.
+ */
+ protected byte[] buffer;
+ /**
+ * Position where next character should be written in the buffer.
+ */
+ protected int pos;
+ /**
+ * Boolean flag to indicate the EOF has been reached. Once EOF has been reached, this object becomes useless,
+ * and must be thrown away.
+ */
+ protected boolean eof;
+ /**
+ * Writes to the buffer only occur after every 3/5 reads when encoding, and every 4/8 reads when decoding.
+ * This variable helps track that.
+ */
+ protected int modulus;
+ /**
+ * Place holder for the bytes we're dealing with for our based logic.
+ * Bitwise operations store and extract the encoding or decoding from this variable.
+ */
+ private long bitWorkArea;
+
+ /**
+ * 构造
+ */
+ public CrockfordBase32Codec() {
+ this(false);
+ }
+
+ /**
+ * Creates a Base32 codec used for decoding and encoding.
+ *
+ * When encoding the line length is 0 (no chunking).
+ *
+ *
+ * @param usePaddingCharacter 是否填充字符
+ */
+ public CrockfordBase32Codec(final boolean usePaddingCharacter) {
+ this.usePaddingCharacter = usePaddingCharacter;
+ this.encodeSize = BYTES_PER_ENCODED_BLOCK;
+ this.decodeSize = this.encodeSize - 1;
+ }
+
+ /**
+ * Checks if a byte value is whitespace or not.
+ * Whitespace is taken to mean: space, tab, CR, LF
+ *
+ * @param byteToCheck the byte to check
+ * @return true if byte is whitespace, false otherwise
+ */
+ protected static boolean isWhiteSpace(final byte byteToCheck) {
+ switch (byteToCheck) {
+ case ' ':
+ case '\n':
+ case '\r':
+ case '\t':
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Tests a given String to see if it contains only valid characters within the alphabet.
+ * The method treats whitespace and PAD as valid.
+ *
+ * @param base32 String to test
+ * @return {@code true} if all characters in the String are valid characters in the alphabet or if
+ * the String is empty; {@code false}, otherwise
+ * @see #isInAlphabet(byte[], boolean)
+ */
+ public static boolean isInAlphabet(final String base32) {
+ return isInAlphabet(base32.getBytes(DEFAULT_CHARSET), true);
+ }
+
+ /**
+ * Tests a given byte array to see if it contains only valid characters within the alphabet.
+ * The method optionally treats whitespace and pad as valid.
+ *
+ * @param arrayOctet byte array to test
+ * @param allowWSPad if {@code true}, then whitespace and PAD are also allowed
+ * @return {@code true} if all bytes are valid characters in the alphabet or if the byte array is empty;
+ * {@code false}, otherwise
+ */
+ public static boolean isInAlphabet(final byte[] arrayOctet, final boolean allowWSPad) {
+ for (final byte b : arrayOctet) {
+ if (!isInAlphabet(b) &&
+ (!allowWSPad || (b != PAD) && !isWhiteSpace(b))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns whether the {@code octet} is in the Base32 alphabet.
+ *
+ * @param octet The value to test
+ * @return {@code true} if the value is defined in the Base32 alphabet {@code false} otherwise.
+ */
+ public static boolean isInAlphabet(final byte octet) {
+ return decode(octet) != -1;
+ }
+
+ /**
+ * Returns the amount of buffered data available for reading.
+ *
+ * @return The amount of buffered data available for reading.
+ */
+ int available() { // package protected for access from I/O streams
+ return buffer != null ? pos : 0;
+ }
+
+ /**
+ * Increases our buffer by the {@link #DEFAULT_BUFFER_RESIZE_FACTOR}.
+ */
+ private void resizeBuffer() {
+ if (buffer == null) {
+ buffer = new byte[DEFAULT_BUFFER_SIZE];
+ pos = 0;
+ } else {
+ final byte[] b = new byte[buffer.length * DEFAULT_BUFFER_RESIZE_FACTOR];
+ System.arraycopy(buffer, 0, b, 0, buffer.length);
+ buffer = b;
+ }
+ }
+
+ /**
+ * Ensure that the buffer has room for {@code size} bytes
+ *
+ * @param size minimum spare space required
+ */
+ protected void ensureBufferSize(final int size) {
+ if ((buffer == null) || (buffer.length < pos + size)) {
+ resizeBuffer();
+ }
+ }
+
+ /**
+ * Extracts buffered data into the provided byte[] array, starting at position bPos,
+ * up to a maximum of bAvail bytes. Returns how many bytes were actually extracted.
+ *
+ * @param b byte[] array to extract the buffered data into.
+ * @return The number of bytes successfully extracted into the provided byte[] array.
+ */
+ int readResults(final byte[] b) { // package protected for access from I/O streams
+ if (buffer != null) {
+ final int len = available();
+ System.arraycopy(buffer, 0, b, 0, len);
+ buffer = null; // so hasData() will return false, and this method can return -1
+ return len;
+ }
+ return eof ? -1 : 0;
+ }
+
+ /**
+ * Resets this object to its initial newly constructed state.
+ */
+ private void reset() {
+ buffer = null;
+ pos = 0;
+ modulus = 0;
+ eof = false;
+ }
+
+ /**
+ * Encodes a String containing characters in the Base32 alphabet.
+ *
+ * @param pArray A String containing Base32 character data
+ * @return A String containing only Base32 character data
+ */
+ public String encodeToString(final String pArray) {
+ return encodeToString(pArray.getBytes(DEFAULT_CHARSET));
+ }
+
+ /**
+ * Encodes a byte[] containing binary data, into a String containing characters in the Base-N alphabet.
+ *
+ * @param pArray a byte array containing binary data
+ * @return A String containing only Base32 character data
+ */
+ public String encodeToString(final byte[] pArray) {
+ return new String(encode(pArray), DEFAULT_CHARSET);
+ }
+
+ /**
+ * Encodes a String containing characters in the Base32 alphabet.
+ *
+ * @param pArray A String containing Base32 character data
+ * @return A UTF-8 decoded String
+ */
+ public String decodeToString(final String pArray) {
+ return decodeToString(pArray.getBytes(DEFAULT_CHARSET));
+ }
+
+ /**
+ * Decodes a byte[] containing binary data, into a String containing UTF-8 decoded String.
+ *
+ * @param pArray a byte array containing binary data
+ * @return A UTF-8 decoded String
+ */
+ public String decodeToString(final byte[] pArray) {
+ return new String(decode(pArray), DEFAULT_CHARSET);
+ }
+
+ /**
+ * Decodes a String containing characters in the Base-N alphabet.
+ *
+ * @param pArray A String containing Base-N character data
+ * @return a byte array containing binary data
+ */
+ public byte[] decode(final String pArray) {
+ return decode(pArray.getBytes(DEFAULT_CHARSET));
+ }
+
+ /**
+ * Encodes a String containing characters in the Base32 alphabet.
+ *
+ * @param pArray A String containing Base-N character data
+ * @return a byte array containing binary data
+ */
+ public byte[] encode(final String pArray) {
+ return encode(pArray.getBytes(DEFAULT_CHARSET));
+ }
+
+ /**
+ * Decodes a byte[] containing characters in the Base-N alphabet.
+ *
+ * @param pArray A byte array containing Base-N character data
+ * @return a byte array containing binary data
+ */
+ public byte[] decode(final byte[] pArray) {
+ reset();
+ if (pArray == null || pArray.length == 0) {
+ return pArray;
+ }
+ decode(pArray, 0, pArray.length);
+ decode(pArray, 0, -1); // Notify decoder of EOF.
+ final byte[] result = new byte[pos];
+ readResults(result);
+ return result;
+ }
+
+ // The static final fields above are used for the original static byte[] methods on Base32.
+ // The private member fields below are used with the new streaming approach, which requires
+ // some state be preserved between calls of encode() and decode().
+
+ /**
+ * Encodes a byte[] containing binary data, into a byte[] containing characters in the alphabet.
+ *
+ * @param pArray a byte array containing binary data
+ * @return A byte array containing only the basen alphabetic character data
+ */
+ public byte[] encode(final byte[] pArray) {
+ reset();
+ if (pArray == null || pArray.length == 0) {
+ return pArray;
+ }
+ encode(pArray, 0, pArray.length);
+ encode(pArray, 0, -1); // Notify encoder of EOF.
+ final byte[] buf = new byte[pos];
+ readResults(buf);
+ return buf;
+ }
+
+ /**
+ * Calculates the amount of space needed to encode the supplied array.
+ *
+ * @param pArray byte[] array which will later be encoded
+ * @return amount of space needed to encode the supplied array.
+ * Returns a long since a max-len array will require > Integer.MAX_VALUE
+ */
+ public long getEncodedLength(final byte[] pArray) {
+ // Calculate non-chunked size - rounded up to allow for padding
+ // cast to long is needed to avoid possibility of overflow
+ return ((pArray.length + BYTES_PER_UNENCODED_BLOCK - 1) / BYTES_PER_UNENCODED_BLOCK) * (long) BYTES_PER_ENCODED_BLOCK;
+ }
+
+ /**
+ *
+ * Decodes all of the provided data, starting at inPos, for inAvail bytes. Should be called at least twice: once
+ * with the data to decode, and once with inAvail set to "-1" to alert decoder that EOF has been reached. The "-1"
+ * call is not necessary when decoding, but it doesn't hurt, either.
+ *
+ *
+ * Ignores all non-Base32 characters. This is how chunked (e.g. 76 character) data is handled, since CR and LF are
+ * silently ignored, but has implications for other bytes, too. This method subscribes to the garbage-in,
+ * garbage-out philosophy: it will not check the provided data for validity.
+ *
+ *
+ * @param in byte[] array of ascii data to Base32 decode.
+ * @param inPos Position to start reading data from.
+ * @param inAvail Amount of bytes available from input for encoding.Output is written to {@link #buffer} as 8-bit octets, using {@link #pos} as the buffer position
+ */
+ void decode(final byte[] in, int inPos, final int inAvail) { // package protected for access from I/O streams
+ if (eof) {
+ return;
+ }
+ if (inAvail < 0) {
+ eof = true;
+ }
+ for (int i = 0; i < inAvail; i++) {
+ final byte b = in[inPos++];
+ if (b == PAD) {
+ // We're done.
+ eof = true;
+ break;
+ } else {
+ ensureBufferSize(decodeSize);
+ if (isInAlphabet(b)) {
+ final int result = decode(b);
+ modulus = (modulus + 1) % BYTES_PER_ENCODED_BLOCK;
+ bitWorkArea = (bitWorkArea << BITS_PER_ENCODED_BYTE) + result; // collect decoded bytes
+ if (modulus == 0) { // we can output the 5 bytes
+ buffer[pos++] = (byte) ((bitWorkArea >> 32) & MASK_8BITS);
+ buffer[pos++] = (byte) ((bitWorkArea >> 24) & MASK_8BITS);
+ buffer[pos++] = (byte) ((bitWorkArea >> 16) & MASK_8BITS);
+ buffer[pos++] = (byte) ((bitWorkArea >> 8) & MASK_8BITS);
+ buffer[pos++] = (byte) (bitWorkArea & MASK_8BITS);
+ }
+ }
+ }
+ }
+
+ // Two forms of EOF as far as Base32 decoder is concerned: actual
+ // EOF (-1) and first time '=' character is encountered in stream.
+ // This approach makes the '=' padding characters completely optional.
+ if (eof && modulus >= 2) { // if modulus < 2, nothing to do
+ ensureBufferSize(decodeSize);
+
+ // we ignore partial bytes, i.e. only multiples of 8 count
+ switch (modulus) {
+ case 2: // 10 bits, drop 2 and output one byte
+ buffer[pos++] = (byte) ((bitWorkArea >> 2) & MASK_8BITS);
+ break;
+ case 3: // 15 bits, drop 7 and output 1 byte
+ buffer[pos++] = (byte) ((bitWorkArea >> 7) & MASK_8BITS);
+ break;
+ case 4: // 20 bits = 2*8 + 4
+ bitWorkArea = bitWorkArea >> 4; // drop 4 bits
+ buffer[pos++] = (byte) ((bitWorkArea >> 8) & MASK_8BITS);
+ buffer[pos++] = (byte) ((bitWorkArea) & MASK_8BITS);
+ break;
+ case 5: // 25bits = 3*8 + 1
+ bitWorkArea = bitWorkArea >> 1;
+ buffer[pos++] = (byte) ((bitWorkArea >> 16) & MASK_8BITS);
+ buffer[pos++] = (byte) ((bitWorkArea >> 8) & MASK_8BITS);
+ buffer[pos++] = (byte) ((bitWorkArea) & MASK_8BITS);
+ break;
+ case 6: // 30bits = 3*8 + 6
+ bitWorkArea = bitWorkArea >> 6;
+ buffer[pos++] = (byte) ((bitWorkArea >> 16) & MASK_8BITS);
+ buffer[pos++] = (byte) ((bitWorkArea >> 8) & MASK_8BITS);
+ buffer[pos++] = (byte) ((bitWorkArea) & MASK_8BITS);
+ break;
+ case 7: // 35 = 4*8 +3
+ bitWorkArea = bitWorkArea >> 3;
+ buffer[pos++] = (byte) ((bitWorkArea >> 24) & MASK_8BITS);
+ buffer[pos++] = (byte) ((bitWorkArea >> 16) & MASK_8BITS);
+ buffer[pos++] = (byte) ((bitWorkArea >> 8) & MASK_8BITS);
+ buffer[pos++] = (byte) ((bitWorkArea) & MASK_8BITS);
+ break;
+ }
+ }
+ }
+
+ /**
+ *
+ * Encodes all of the provided data, starting at inPos, for inAvail bytes. Must be called at least twice: once with
+ * the data to encode, and once with inAvail set to "-1" to alert encoder that EOF has been reached, so flush last
+ * remaining bytes (if not multiple of 5).
+ *
+ *
+ * @param in byte[] array of binary data to Base32 encode.
+ * @param inPos Position to start reading data from.
+ * @param inAvail Amount of bytes available from input for encoding.
+ */
+ void encode(final byte[] in, int inPos, final int inAvail) { // package protected for access from I/O streams
+ if (eof) {
+ return;
+ }
+ // inAvail < 0 is how we're informed of EOF in the underlying data we're
+ // encoding.
+ if (inAvail < 0) {
+ eof = true;
+ if (0 == modulus) {
+ return; // no leftovers to process
+ }
+ ensureBufferSize(encodeSize);
+ final int savedPos = pos;
+ switch (modulus) { // % 5
+ case 1: // Only 1 octet; take top 5 bits then remainder
+ buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 3) & MASK_5BITS]; // 8-1*5 = 3
+ buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea << 2) & MASK_5BITS]; // 5-3=2
+ if (usePaddingCharacter) {
+ buffer[pos++] = PAD;
+ buffer[pos++] = PAD;
+ buffer[pos++] = PAD;
+ buffer[pos++] = PAD;
+ buffer[pos++] = PAD;
+ buffer[pos++] = PAD;
+ }
+ break;
+
+ case 2: // 2 octets = 16 bits to use
+ buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 11) & MASK_5BITS]; // 16-1*5 = 11
+ buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 6) & MASK_5BITS]; // 16-2*5 = 6
+ buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 1) & MASK_5BITS]; // 16-3*5 = 1
+ buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea << 4) & MASK_5BITS]; // 5-1 = 4
+ if (usePaddingCharacter) {
+ buffer[pos++] = PAD;
+ buffer[pos++] = PAD;
+ buffer[pos++] = PAD;
+ buffer[pos++] = PAD;
+ }
+ break;
+ case 3: // 3 octets = 24 bits to use
+ buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 19) & MASK_5BITS]; // 24-1*5 = 19
+ buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 14) & MASK_5BITS]; // 24-2*5 = 14
+ buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 9) & MASK_5BITS]; // 24-3*5 = 9
+ buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 4) & MASK_5BITS]; // 24-4*5 = 4
+ buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea << 1) & MASK_5BITS]; // 5-4 = 1
+ if (usePaddingCharacter) {
+ buffer[pos++] = PAD;
+ buffer[pos++] = PAD;
+ buffer[pos++] = PAD;
+ }
+ break;
+ case 4: // 4 octets = 32 bits to use
+ buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 27) & MASK_5BITS]; // 32-1*5 = 27
+ buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 22) & MASK_5BITS]; // 32-2*5 = 22
+ buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 17) & MASK_5BITS]; // 32-3*5 = 17
+ buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 12) & MASK_5BITS]; // 32-4*5 = 12
+ buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 7) & MASK_5BITS]; // 32-5*5 = 7
+ buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 2) & MASK_5BITS]; // 32-6*5 = 2
+ buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea << 3) & MASK_5BITS]; // 5-2 = 3
+ if (usePaddingCharacter) {
+ buffer[pos++] = PAD;
+ }
+ break;
+ }
+ } else {
+ for (int i = 0; i < inAvail; i++) {
+ ensureBufferSize(encodeSize);
+ modulus = (modulus + 1) % BYTES_PER_UNENCODED_BLOCK;
+ int b = in[inPos++];
+ if (b < 0) {
+ b += 256;
+ }
+ bitWorkArea = (bitWorkArea << 8) + b; // BITS_PER_BYTE
+ if (0 == modulus) { // we have enough bytes to create our output
+ buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 35) & MASK_5BITS];
+ buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 30) & MASK_5BITS];
+ buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 25) & MASK_5BITS];
+ buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 20) & MASK_5BITS];
+ buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 15) & MASK_5BITS];
+ buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 10) & MASK_5BITS];
+ buffer[pos++] = ENCODE_TABLE[(int) (bitWorkArea >> 5) & MASK_5BITS];
+ buffer[pos++] = ENCODE_TABLE[(int) bitWorkArea & MASK_5BITS];
+ }
+ }
+ }
+ }
+
+ /**
+ * 写出Crockford`s Base32值 到buffer指定位置
+ *
+ * @param buffer buffer
+ * @param value 值
+ * @param count 字符数量
+ * @param offset 开始位置
+ */
+ public static void writeCrockford(final char[] buffer, final long value, final int count, final int offset) {
+ for (int i = 0; i < count; i++) {
+ final int index = (int) ((value >>> ((count - i - 1) * BITS_PER_ENCODED_BYTE)) & MASK_5BITS);
+ buffer[offset + i] = (char) ENCODE_TABLE[index];
+ }
+ }
+
+ /**
+ * 追加Crockford`s Base32值 到buffer指定位置
+ *
+ * @param builder {@link StringBuilder}
+ * @param value 值
+ * @param count 字符数量
+ */
+ public static void appendCrockford(final StringBuilder builder, final long value, final int count) {
+ for (int i = count - 1; i >= 0; i--) {
+ final int index = (int) ((value >>> (i * BITS_PER_ENCODED_BYTE)) & MASK_5BITS);
+ builder.append(ENCODE_TABLE[index]);
+ }
+ }
+
+ /**
+ * 解析Crockford`s Base32值
+ *
+ * @param input Crockford`s Base32值
+ * @return ID值
+ */
+ public static long parseCrockford(final String input) {
+ Objects.requireNonNull(input, "input must not be null!");
+ final int length = input.length();
+ if (length > 12) {
+ throw new IllegalArgumentException("input length must not exceed 12 but was " + length + "!");
+ }
+
+ long result = 0;
+ for (int i = 0; i < length; i++) {
+ final char current = input.charAt(i);
+ final byte value = decode((byte) current);
+ if (value < 0) {
+ throw new IllegalArgumentException("Illegal character '" + current + "'!");
+ }
+ result |= ((long) value) << ((length - 1 - i) * BITS_PER_ENCODED_BYTE);
+ }
+ return result;
+ }
+
+ private static byte decode(final byte octet) {
+ switch (octet) {
+ case '0':
+ case 'O':
+ case 'o':
+ return 0;
+
+ case '1':
+ case 'I':
+ case 'i':
+ case 'L':
+ case 'l':
+ return 1;
+
+ case '2':
+ return 2;
+ case '3':
+ return 3;
+ case '4':
+ return 4;
+ case '5':
+ return 5;
+ case '6':
+ return 6;
+ case '7':
+ return 7;
+ case '8':
+ return 8;
+ case '9':
+ return 9;
+
+ case 'A':
+ case 'a':
+ return 10;
+
+ case 'B':
+ case 'b':
+ return 11;
+
+ case 'C':
+ case 'c':
+ return 12;
+
+ case 'D':
+ case 'd':
+ return 13;
+
+ case 'E':
+ case 'e':
+ return 14;
+
+ case 'F':
+ case 'f':
+ return 15;
+
+ case 'G':
+ case 'g':
+ return 16;
+
+ case 'H':
+ case 'h':
+ return 17;
+
+ case 'J':
+ case 'j':
+ return 18;
+
+ case 'K':
+ case 'k':
+ return 19;
+
+ case 'M':
+ case 'm':
+ return 20;
+
+ case 'N':
+ case 'n':
+ return 21;
+
+ case 'P':
+ case 'p':
+ return 22;
+
+ case 'Q':
+ case 'q':
+ return 23;
+
+ case 'R':
+ case 'r':
+ return 24;
+
+ case 'S':
+ case 's':
+ return 25;
+
+ case 'T':
+ case 't':
+ return 26;
+
+ case 'U':
+ case 'u':
+ case 'V':
+ case 'v':
+ return 27;
+
+ case 'W':
+ case 'w':
+ return 28;
+
+ case 'X':
+ case 'x':
+ return 29;
+
+ case 'Y':
+ case 'y':
+ return 30;
+
+ case 'Z':
+ case 'z':
+ return 31;
+
+ default:
+ return -1;
+ }
+ }
+}
diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/codec/hash/CityHash.java b/hutool-core/src/main/java/org/dromara/hutool/core/codec/hash/CityHash.java
index 58a2dc3d2..01afb8b26 100644
--- a/hutool-core/src/main/java/org/dromara/hutool/core/codec/hash/CityHash.java
+++ b/hutool-core/src/main/java/org/dromara/hutool/core/codec/hash/CityHash.java
@@ -164,13 +164,13 @@ public class CityHash implements Hash32, Hash64, Hash128
len = (len - 1) & ~63;
int pos = 0;
do {
- x = Long.rotateRight(x + y + v.getLowValue() + fetch64(data, pos + 8), 37) * k1;
- y = Long.rotateRight(y + v.getHighValue() + fetch64(data, pos + 48), 42) * k1;
- x ^= w.getHighValue();
- y += v.getLowValue() + fetch64(data, pos + 40);
- z = Long.rotateRight(z + w.getLowValue(), 33) * k1;
- v = weakHashLen32WithSeeds(data, pos, v.getHighValue() * k1, x + w.getLowValue());
- w = weakHashLen32WithSeeds(data, pos + 32, z + w.getHighValue(), y + fetch64(data, pos + 16));
+ x = Long.rotateRight(x + y + v.getLeastSigBits() + fetch64(data, pos + 8), 37) * k1;
+ y = Long.rotateRight(y + v.getMostSigBits() + fetch64(data, pos + 48), 42) * k1;
+ x ^= w.getMostSigBits();
+ y += v.getLeastSigBits() + fetch64(data, pos + 40);
+ z = Long.rotateRight(z + w.getLeastSigBits(), 33) * k1;
+ v = weakHashLen32WithSeeds(data, pos, v.getMostSigBits() * k1, x + w.getLeastSigBits());
+ w = weakHashLen32WithSeeds(data, pos + 32, z + w.getMostSigBits(), y + fetch64(data, pos + 16));
// swap z,x value
final long swapValue = x;
x = z;
@@ -178,8 +178,8 @@ public class CityHash implements Hash32, Hash64, Hash128
pos += 64;
len -= 64;
} while (len != 0);
- return hashLen16(hashLen16(v.getLowValue(), w.getLowValue()) + shiftMix(y) * k1 + z,
- hashLen16(v.getHighValue(), w.getHighValue()) + x);
+ return hashLen16(hashLen16(v.getLeastSigBits(), w.getLeastSigBits()) + shiftMix(y) * k1 + z,
+ hashLen16(v.getMostSigBits(), w.getMostSigBits()) + x);
}
/**
@@ -243,66 +243,66 @@ public class CityHash implements Hash32, Hash64, Hash128
// v, w, x, y, and z.
Number128 v = new Number128(0L, 0L);
Number128 w = new Number128(0L, 0L);
- long x = seed.getLowValue();
- long y = seed.getHighValue();
+ long x = seed.getLeastSigBits();
+ long y = seed.getMostSigBits();
long z = len * k1;
- v.setLowValue(Long.rotateRight(y ^ k1, 49) * k1 + fetch64(byteArray, start));
- v.setHighValue(Long.rotateRight(v.getLowValue(), 42) * k1 + fetch64(byteArray, start + 8));
- w.setLowValue(Long.rotateRight(y + z, 35) * k1 + x);
- w.setHighValue(Long.rotateRight(x + fetch64(byteArray, start + 88), 53) * k1);
+ v.setLeastSigBits(Long.rotateRight(y ^ k1, 49) * k1 + fetch64(byteArray, start));
+ v.setMostSigBits(Long.rotateRight(v.getLeastSigBits(), 42) * k1 + fetch64(byteArray, start + 8));
+ w.setLeastSigBits(Long.rotateRight(y + z, 35) * k1 + x);
+ w.setMostSigBits(Long.rotateRight(x + fetch64(byteArray, start + 88), 53) * k1);
// This is the same inner loop as CityHash64(), manually unrolled.
int pos = start;
do {
- x = Long.rotateRight(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1;
- y = Long.rotateRight(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1;
- x ^= w.getHighValue();
- y += v.getLowValue() + fetch64(byteArray, pos + 40);
- z = Long.rotateRight(z + w.getLowValue(), 33) * k1;
- v = weakHashLen32WithSeeds(byteArray, pos, v.getHighValue() * k1, x + w.getLowValue());
- w = weakHashLen32WithSeeds(byteArray, pos + 32, z + w.getHighValue(), y + fetch64(byteArray, pos + 16));
+ x = Long.rotateRight(x + y + v.getLeastSigBits() + fetch64(byteArray, pos + 8), 37) * k1;
+ y = Long.rotateRight(y + v.getMostSigBits() + fetch64(byteArray, pos + 48), 42) * k1;
+ x ^= w.getMostSigBits();
+ y += v.getLeastSigBits() + fetch64(byteArray, pos + 40);
+ z = Long.rotateRight(z + w.getLeastSigBits(), 33) * k1;
+ v = weakHashLen32WithSeeds(byteArray, pos, v.getMostSigBits() * k1, x + w.getLeastSigBits());
+ w = weakHashLen32WithSeeds(byteArray, pos + 32, z + w.getMostSigBits(), y + fetch64(byteArray, pos + 16));
long swapValue = x;
x = z;
z = swapValue;
pos += 64;
- x = Long.rotateRight(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1;
- y = Long.rotateRight(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1;
- x ^= w.getHighValue();
- y += v.getLowValue() + fetch64(byteArray, pos + 40);
- z = Long.rotateRight(z + w.getLowValue(), 33) * k1;
- v = weakHashLen32WithSeeds(byteArray, pos, v.getHighValue() * k1, x + w.getLowValue());
- w = weakHashLen32WithSeeds(byteArray, pos + 32, z + w.getHighValue(), y + fetch64(byteArray, pos + 16));
+ x = Long.rotateRight(x + y + v.getLeastSigBits() + fetch64(byteArray, pos + 8), 37) * k1;
+ y = Long.rotateRight(y + v.getMostSigBits() + fetch64(byteArray, pos + 48), 42) * k1;
+ x ^= w.getMostSigBits();
+ y += v.getLeastSigBits() + fetch64(byteArray, pos + 40);
+ z = Long.rotateRight(z + w.getLeastSigBits(), 33) * k1;
+ v = weakHashLen32WithSeeds(byteArray, pos, v.getMostSigBits() * k1, x + w.getLeastSigBits());
+ w = weakHashLen32WithSeeds(byteArray, pos + 32, z + w.getMostSigBits(), y + fetch64(byteArray, pos + 16));
swapValue = x;
x = z;
z = swapValue;
pos += 64;
len -= 128;
} while (len >= 128);
- x += Long.rotateRight(v.getLowValue() + z, 49) * k0;
- y = y * k0 + Long.rotateRight(w.getHighValue(), 37);
- z = z * k0 + Long.rotateRight(w.getLowValue(), 27);
- w.setLowValue(w.getLowValue() * 9);
- v.setLowValue(v.getLowValue() * k0);
+ x += Long.rotateRight(v.getLeastSigBits() + z, 49) * k0;
+ y = y * k0 + Long.rotateRight(w.getMostSigBits(), 37);
+ z = z * k0 + Long.rotateRight(w.getLeastSigBits(), 27);
+ w.setLeastSigBits(w.getLeastSigBits() * 9);
+ v.setLeastSigBits(v.getLeastSigBits() * k0);
// If 0 < len < 128, hash up to 4 chunks of 32 bytes each from the end of s.
for (int tail_done = 0; tail_done < len; ) {
tail_done += 32;
- y = Long.rotateRight(x + y, 42) * k0 + v.getHighValue();
- w.setLowValue(w.getLowValue() + fetch64(byteArray, pos + len - tail_done + 16));
- x = x * k0 + w.getLowValue();
- z += w.getHighValue() + fetch64(byteArray, pos + len - tail_done);
- w.setHighValue(w.getHighValue() + v.getLowValue());
- v = weakHashLen32WithSeeds(byteArray, pos + len - tail_done, v.getLowValue() + z, v.getHighValue());
- v.setLowValue(v.getLowValue() * k0);
+ y = Long.rotateRight(x + y, 42) * k0 + v.getMostSigBits();
+ w.setLeastSigBits(w.getLeastSigBits() + fetch64(byteArray, pos + len - tail_done + 16));
+ x = x * k0 + w.getLeastSigBits();
+ z += w.getMostSigBits() + fetch64(byteArray, pos + len - tail_done);
+ w.setMostSigBits(w.getMostSigBits() + v.getLeastSigBits());
+ v = weakHashLen32WithSeeds(byteArray, pos + len - tail_done, v.getLeastSigBits() + z, v.getMostSigBits());
+ v.setLeastSigBits(v.getLeastSigBits() * k0);
}
// At this point our 56 bytes of state should contain more than
// enough information for a strong 128-bit hash. We use two
// different 56-byte-to-8-byte hashes to get a 16-byte final result.
- x = hashLen16(x, v.getLowValue());
- y = hashLen16(y + z, w.getLowValue());
- return new Number128(hashLen16(x + v.getHighValue(), w.getHighValue()) + y,
- hashLen16(x + w.getHighValue(), y + v.getHighValue()));
+ x = hashLen16(x, v.getLeastSigBits());
+ y = hashLen16(y + z, w.getLeastSigBits());
+ return new Number128(hashLen16(x + v.getMostSigBits(), w.getMostSigBits()) + y,
+ hashLen16(x + w.getMostSigBits(), y + v.getMostSigBits()));
}
@@ -427,9 +427,9 @@ public class CityHash implements Hash32, Hash64, Hash128
private long hash128to64(final Number128 number128) {
// Murmur-inspired hashing.
final long kMul = 0x9ddfea08eb382d69L;
- long a = (number128.getLowValue() ^ number128.getHighValue()) * kMul;
+ long a = (number128.getLeastSigBits() ^ number128.getMostSigBits()) * kMul;
a ^= (a >>> 47);
- long b = (number128.getHighValue() ^ a) * kMul;
+ long b = (number128.getMostSigBits() ^ a) * kMul;
b ^= (b >>> 47);
b *= kMul;
return b;
@@ -482,8 +482,8 @@ public class CityHash implements Hash32, Hash64, Hash128
private Number128 cityMurmur(final byte[] byteArray, final Number128 seed) {
final int len = byteArray.length;
- long a = seed.getLowValue();
- long b = seed.getHighValue();
+ long a = seed.getLeastSigBits();
+ long b = seed.getMostSigBits();
long c;
long d;
int l = len - 16;
diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/data/id/ULID.java b/hutool-core/src/main/java/org/dromara/hutool/core/data/id/ULID.java
new file mode 100644
index 000000000..5b6acd32b
--- /dev/null
+++ b/hutool-core/src/main/java/org/dromara/hutool/core/data/id/ULID.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright (c) 2024. looly(loolly@aliyun.com)
+ * Hutool is licensed under Mulan PSL v2.
+ * You can use this software according to the terms and conditions of the Mulan PSL v2.
+ * You may obtain a copy of Mulan PSL v2 at:
+ * https://license.coscl.org.cn/MulanPSL2
+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
+ * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
+ * See the Mulan PSL v2 for more details.
+ */
+
+package org.dromara.hutool.core.data.id;
+
+import org.dromara.hutool.core.codec.Number128;
+import org.dromara.hutool.core.codec.binary.CrockfordBase32Codec;
+import org.dromara.hutool.core.lang.Assert;
+import org.dromara.hutool.core.util.ByteUtil;
+import org.dromara.hutool.core.util.RandomUtil;
+
+import java.io.Serializable;
+import java.nio.ByteOrder;
+import java.util.Objects;
+
+/**
+ * 参考:https://github.com/zjcscut/framework-mesh/blob/master/ulid4j/src/main/java/cn/vlts/ulid/ULID.java
+ * {@code
+ * 01AN4Z07BY 79KA1307SR9X4MV3
+ * |----------| |----------------|
+ * Timestamp Randomness
+ * 48bits 80bits
+ * }
+ *
+ * @author throwable
+ * @since 6.0.0
+ */
+public class ULID implements Comparable, Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Timestamp component mask
+ */
+ private static final long TIMESTAMP_MASK = 0xffff000000000000L;
+ /**
+ * The length of randomness component of ULID
+ */
+ private static final int RANDOMNESS_BYTE_LEN = 10;
+ /**
+ * The least significant 64 bits increase overflow, 0xffffffffffffffffL + 1
+ */
+ private static final long OVERFLOW = 0x0000000000000000L;
+
+ // region ----- Factory methods
+ public static ULID of() {
+ return of(System.currentTimeMillis(), RandomUtil.randomBytes(RANDOMNESS_BYTE_LEN));
+ }
+
+ public static ULID of(final String ulidString) {
+ Objects.requireNonNull(ulidString, "ulidString must not be null!");
+ if (ulidString.length() != 26) {
+ throw new IllegalArgumentException("ulidString must be exactly 26 chars long.");
+ }
+
+ final String timeString = ulidString.substring(0, 10);
+ final long time = CrockfordBase32Codec.parseCrockford(timeString);
+ checkTimestamp(time);
+
+ final String part1String = ulidString.substring(10, 18);
+ final String part2String = ulidString.substring(18);
+ final long part1 = CrockfordBase32Codec.parseCrockford(part1String);
+ final long part2 = CrockfordBase32Codec.parseCrockford(part2String);
+
+ final long most = (time << 16) | (part1 >>> 24);
+ final long least = part2 | (part1 << 40);
+ return new ULID(new Number128(least, most));
+ }
+
+ public static ULID of(final byte[] data) {
+ Objects.requireNonNull(data, "data must not be null!");
+ if (data.length != 16) {
+ throw new IllegalArgumentException("data must be 16 bytes in length!");
+ }
+ long mostSignificantBits = 0;
+ long leastSignificantBits = 0;
+ for (int i = 0; i < 8; i++) {
+ mostSignificantBits = (mostSignificantBits << 8) | (data[i] & 0xff);
+ }
+ for (int i = 8; i < 16; i++) {
+ leastSignificantBits = (leastSignificantBits << 8) | (data[i] & 0xff);
+ }
+ return new ULID(new Number128(leastSignificantBits, mostSignificantBits));
+ }
+
+ public static ULID of(final long timestamp, final byte[] randomness) {
+ // 时间戳最多为48 bit(6 bytes)
+ checkTimestamp(timestamp);
+ Assert.notNull(randomness);
+ // 随机数部分长度必须为80 bit(10 bytes)
+ Assert.isTrue(RANDOMNESS_BYTE_LEN == randomness.length, "Invalid randomness");
+
+ long msb = 0;
+ // 时间戳左移16位,低位补零准备填入部分随机数位,即16_bit_uint_random
+ msb |= timestamp << 16;
+ // randomness[0]左移0位填充到16_bit_uint_random的高8位,randomness[1]填充到16_bit_uint_random的低8位
+ msb |= (long) (randomness[0x0] & 0xff) << 8;
+ // randomness[1]填充到16_bit_uint_random的低8位
+ msb |= randomness[0x1] & 0xff;
+
+ return new ULID(new Number128(ByteUtil.toLong(randomness, 2, ByteOrder.BIG_ENDIAN), msb));
+ }
+
+ // endregion
+
+ private final Number128 idValue;
+
+ /**
+ * Creates a new ULID with the high 64 bits and low 64 bits as long value.
+ *
+ * @param number128 the low 8 bytes of ULID
+ */
+ public ULID(final Number128 number128) {
+ this.idValue = number128;
+ }
+
+ public long getMostSignificantBits() {
+ return this.idValue.getMostSigBits();
+ }
+
+ public long getLeastSignificantBits() {
+ return this.idValue.getLeastSigBits();
+ }
+
+ /**
+ * Get the timestamp component of ULID
+ *
+ * @return the timestamp component
+ */
+ public long getTimestamp() {
+ return this.idValue.getMostSigBits() >>> 16;
+ }
+
+ /**
+ * Get the randomness component of ULID
+ *
+ * @return the randomness component
+ */
+ public byte[] getRandomness() {
+ final long msb = this.idValue.getMostSigBits();
+ final long lsb = this.idValue.getLeastSigBits();
+ final byte[] randomness = new byte[RANDOMNESS_BYTE_LEN];
+ // 这里不需要& 0xff,因为多余的位会被截断
+ randomness[0x0] = (byte) (msb >>> 8);
+ randomness[0x1] = (byte) msb;
+
+ ByteUtil.fill(lsb, 2, ByteOrder.BIG_ENDIAN, randomness);
+ return randomness;
+ }
+
+ public ULID increment() {
+ final long msb = this.idValue.getMostSigBits();
+ final long lsb = this.idValue.getLeastSigBits();
+ long newMsb = msb;
+ final long newLsb = lsb + 1;
+ if (newLsb == OVERFLOW) {
+ newMsb += 1;
+ }
+ return new ULID(new Number128(lsb, msb));
+ }
+
+ public byte[] toBytes() {
+ final long msb = this.idValue.getMostSigBits();
+ final long lsb = this.idValue.getLeastSigBits();
+ final byte[] result = new byte[16];
+ for (int i = 0; i < 8; i++) {
+ result[i] = (byte) ((msb >> ((7 - i) * 8)) & 0xFF);
+ }
+ for (int i = 8; i < 16; i++) {
+ result[i] = (byte) ((lsb >> ((15 - i) * 8)) & 0xFF);
+ }
+
+ return result;
+ }
+
+ public UUID toUUID() {
+ final long msb = this.idValue.getMostSigBits();
+ final long lsb = this.idValue.getLeastSigBits();
+ return new UUID(msb, lsb);
+ }
+
+ public java.util.UUID toJdkUUID() {
+ final long msb = this.idValue.getMostSigBits();
+ final long lsb = this.idValue.getLeastSigBits();
+ return new java.util.UUID(msb, lsb);
+ }
+
+ @Override
+ public int compareTo(final ULID o) {
+ return this.idValue.compareTo(o.idValue);
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if ((Objects.isNull(obj)) || (obj.getClass() != ULID.class)) {
+ return false;
+ }
+ final ULID id = (ULID) obj;
+ return this.idValue.equals(id.idValue);
+ }
+
+ @Override
+ public int hashCode() {
+ return this.idValue.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ final long msb = this.idValue.getMostSigBits();
+ final long lsb = this.idValue.getLeastSigBits();
+ final char[] buffer = new char[26];
+
+ CrockfordBase32Codec.writeCrockford(buffer, getTimestamp(), 10, 0);
+ long value = ((msb & 0xFFFFL) << 24);
+ final long interim = (lsb >>> 40);
+ value = value | interim;
+ CrockfordBase32Codec.writeCrockford(buffer, value, 8, 10);
+ CrockfordBase32Codec.writeCrockford(buffer, lsb, 8, 18);
+
+ return new String(buffer);
+ }
+
+ /**
+ * 检查日期
+ *
+ * @param timestamp 时间戳
+ */
+ private static void checkTimestamp(final long timestamp) {
+ Assert.isTrue((timestamp & TIMESTAMP_MASK) == 0), "ULID does not support timestamps after +10889-08-02T05:31:50.655Z!")
+ ;
+ }
+}
diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/util/ByteUtil.java b/hutool-core/src/main/java/org/dromara/hutool/core/util/ByteUtil.java
index 35ca74c1d..dbcb89872 100644
--- a/hutool-core/src/main/java/org/dromara/hutool/core/util/ByteUtil.java
+++ b/hutool-core/src/main/java/org/dromara/hutool/core/util/ByteUtil.java
@@ -55,6 +55,7 @@ public class ByteUtil {
*/
public static final ByteOrder CPU_ENDIAN = "little".equals(System.getProperty("sun.cpu.endian")) ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN;
+ // region ----- toBytes
/**
* 编码字符串,编码为UTF-8
*
@@ -93,59 +94,6 @@ public class ByteUtil {
return (byte) intValue;
}
- /**
- * byte转无符号int
- *
- * @param byteValue byte值
- * @return 无符号int值
- * @since 3.2.0
- */
- public static int toUnsignedInt(final byte byteValue) {
- // Java 总是把 byte 当做有符处理;我们可以通过将其和 0xFF 进行二进制与得到它的无符值
- return byteValue & 0xFF;
- }
-
- /**
- * byte数组转short
- * 默认以小端序转换
- *
- * @param bytes byte数组
- * @return short值
- */
- public static short toShort(final byte[] bytes) {
- return toShort(bytes, DEFAULT_ORDER);
- }
-
- /**
- * byte数组转short
- * 自定义端序
- *
- * @param bytes byte数组,长度必须为2
- * @param byteOrder 端序
- * @return short值
- */
- public static short toShort(final byte[] bytes, final ByteOrder byteOrder) {
- return toShort(bytes, 0, byteOrder);
- }
-
- /**
- * byte数组转short
- * 自定义端序
- *
- * @param bytes byte数组,长度必须大于2
- * @param start 开始位置
- * @param byteOrder 端序
- * @return short值
- */
- public static short toShort(final byte[] bytes, final int start, final ByteOrder byteOrder) {
- if (ByteOrder.LITTLE_ENDIAN == byteOrder) {
- //小端模式,数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中
- return (short) (bytes[start] & 0xff | (bytes[start + 1] & 0xff) << Byte.SIZE);
- } else {
- return (short) (bytes[start + 1] & 0xff | (bytes[start] & 0xff) << Byte.SIZE);
- }
- }
-
/**
* short转byte数组
* 默认以小端序转换
@@ -177,54 +125,6 @@ public class ByteUtil {
return b;
}
- /**
- * byte[]转int值
- * 默认以小端序转换
- *
- * @param bytes byte数组
- * @return int值
- */
- public static int toInt(final byte[] bytes) {
- return toInt(bytes, DEFAULT_ORDER);
- }
-
- /**
- * byte[]转int值
- * 自定义端序
- *
- * @param bytes byte数组
- * @param byteOrder 端序
- * @return int值
- */
- public static int toInt(final byte[] bytes, final ByteOrder byteOrder) {
- return toInt(bytes, 0, byteOrder);
- }
-
- /**
- * byte[]转int值
- * 自定义端序
- *
- * @param bytes byte数组
- * @param start 开始位置(包含)
- * @param byteOrder 端序
- * @return int值
- * @since 5.7.21
- */
- public static int toInt(final byte[] bytes, final int start, final ByteOrder byteOrder) {
- if (ByteOrder.LITTLE_ENDIAN == byteOrder) {
- return bytes[start] & 0xFF | //
- (bytes[1 + start] & 0xFF) << 8 | //
- (bytes[2 + start] & 0xFF) << 16 | //
- (bytes[3 + start] & 0xFF) << 24; //
- } else {
- return bytes[3 + start] & 0xFF | //
- (bytes[2 + start] & 0xFF) << 8 | //
- (bytes[1 + start] & 0xFF) << 16 | //
- (bytes[start] & 0xFF) << 24; //
- }
-
- }
-
/**
* int转byte数组
* 默认以小端序转换
@@ -286,22 +186,226 @@ public class ByteUtil {
* @param byteOrder 端序
* @return byte数组
*/
- public static byte[] toBytes(long longValue, final ByteOrder byteOrder) {
+ public static byte[] toBytes(final long longValue, final ByteOrder byteOrder) {
final byte[] result = new byte[Long.BYTES];
+ return fill(longValue, 0, byteOrder, result);
+ }
+
+ /**
+ * 将long值转为bytes并填充到给定的bytes中
+ *
+ * @param longValue long值
+ * @param start 开始位置(包含)
+ * @param byteOrder 端续
+ * @param bytes 被填充的bytes
+ * @return 填充后的bytes
+ * @since 6.0.0
+ */
+ public static byte[] fill(long longValue, final int start, final ByteOrder byteOrder, final byte[] bytes) {
if (ByteOrder.LITTLE_ENDIAN == byteOrder) {
- for (int i = 0; i < result.length; i++) {
- result[i] = (byte) (longValue & 0xFF);
+ for (int i = start; i < bytes.length; i++) {
+ bytes[i] = (byte) (longValue & 0xFF);
longValue >>= Byte.SIZE;
}
} else {
- for (int i = (result.length - 1); i >= 0; i--) {
- result[i] = (byte) (longValue & 0xFF);
+ for (int i = (bytes.length - 1); i >= start; i--) {
+ bytes[i] = (byte) (longValue & 0xFF);
longValue >>= Byte.SIZE;
}
}
- return result;
+ return bytes;
}
+ /**
+ * float转byte数组,默认以小端序转换
+ *
+ * @param floatValue float值
+ * @return byte数组
+ * @since 5.7.18
+ */
+ public static byte[] toBytes(final float floatValue) {
+ return toBytes(floatValue, DEFAULT_ORDER);
+ }
+
+ /**
+ * float转byte数组,自定义端序
+ *
+ * @param floatValue float值
+ * @param byteOrder 端序
+ * @return byte数组
+ * @since 5.7.18
+ */
+ public static byte[] toBytes(final float floatValue, final ByteOrder byteOrder) {
+ return toBytes(Float.floatToIntBits(floatValue), byteOrder);
+ }
+
+ /**
+ * double转byte数组
+ * 默认以小端序转换
+ *
+ * @param doubleValue double值
+ * @return byte数组
+ */
+ public static byte[] toBytes(final double doubleValue) {
+ return toBytes(doubleValue, DEFAULT_ORDER);
+ }
+
+ /**
+ * double转byte数组
+ * 自定义端序
+ * from: https://stackoverflow.com/questions/4485128/how-do-i-convert-long-to-byte-and-back-in-java
+ *
+ * @param doubleValue double值
+ * @param byteOrder 端序
+ * @return byte数组
+ */
+ public static byte[] toBytes(final double doubleValue, final ByteOrder byteOrder) {
+ return toBytes(Double.doubleToLongBits(doubleValue), byteOrder);
+ }
+
+ /**
+ * 将{@link Number}转换为
+ *
+ * @param number 数字
+ * @return bytes
+ */
+ public static byte[] toBytes(final Number number) {
+ return toBytes(number, DEFAULT_ORDER);
+ }
+
+ /**
+ * 将{@link Number}转换为
+ *
+ * @param number 数字
+ * @param byteOrder 端序
+ * @return bytes
+ */
+ public static byte[] toBytes(final Number number, final ByteOrder byteOrder) {
+ if (number instanceof Byte) {
+ return new byte[]{number.byteValue()};
+ } else if (number instanceof Double) {
+ return toBytes(number.doubleValue(), byteOrder);
+ } else if (number instanceof Long) {
+ return toBytes(number.longValue(), byteOrder);
+ } else if (number instanceof Integer) {
+ return ByteUtil.toBytes(number.intValue(), byteOrder);
+ } else if (number instanceof Short) {
+ return ByteUtil.toBytes(number.shortValue(), byteOrder);
+ } else if (number instanceof Float) {
+ return toBytes(number.floatValue(), byteOrder);
+ } else if (number instanceof BigInteger) {
+ return ((BigInteger) number).toByteArray();
+ } else {
+ return toBytes(number.doubleValue(), byteOrder);
+ }
+ }
+ // endregion
+
+ // region ----- toShort
+ /**
+ * byte数组转short
+ * 默认以小端序转换
+ *
+ * @param bytes byte数组
+ * @return short值
+ */
+ public static short toShort(final byte[] bytes) {
+ return toShort(bytes, DEFAULT_ORDER);
+ }
+
+ /**
+ * byte数组转short
+ * 自定义端序
+ *
+ * @param bytes byte数组,长度必须为2
+ * @param byteOrder 端序
+ * @return short值
+ */
+ public static short toShort(final byte[] bytes, final ByteOrder byteOrder) {
+ return toShort(bytes, 0, byteOrder);
+ }
+
+ /**
+ * byte数组转short
+ * 自定义端序
+ *
+ * @param bytes byte数组,长度必须大于2
+ * @param start 开始位置
+ * @param byteOrder 端序
+ * @return short值
+ */
+ public static short toShort(final byte[] bytes, final int start, final ByteOrder byteOrder) {
+ if (ByteOrder.LITTLE_ENDIAN == byteOrder) {
+ //小端模式,数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中
+ return (short) (bytes[start] & 0xff | (bytes[start + 1] & 0xff) << Byte.SIZE);
+ } else {
+ return (short) (bytes[start + 1] & 0xff | (bytes[start] & 0xff) << Byte.SIZE);
+ }
+ }
+ // endregion
+
+ // region ----- toInt
+ /**
+ * byte[]转int值
+ * 默认以小端序转换
+ *
+ * @param bytes byte数组
+ * @return int值
+ */
+ public static int toInt(final byte[] bytes) {
+ return toInt(bytes, DEFAULT_ORDER);
+ }
+
+ /**
+ * byte[]转int值
+ * 自定义端序
+ *
+ * @param bytes byte数组
+ * @param byteOrder 端序
+ * @return int值
+ */
+ public static int toInt(final byte[] bytes, final ByteOrder byteOrder) {
+ return toInt(bytes, 0, byteOrder);
+ }
+
+ /**
+ * byte[]转int值
+ * 自定义端序
+ *
+ * @param bytes byte数组
+ * @param start 开始位置(包含)
+ * @param byteOrder 端序
+ * @return int值
+ * @since 5.7.21
+ */
+ public static int toInt(final byte[] bytes, final int start, final ByteOrder byteOrder) {
+ if (ByteOrder.LITTLE_ENDIAN == byteOrder) {
+ return bytes[start] & 0xFF | //
+ (bytes[1 + start] & 0xFF) << 8 | //
+ (bytes[2 + start] & 0xFF) << 16 | //
+ (bytes[3 + start] & 0xFF) << 24; //
+ } else {
+ return bytes[3 + start] & 0xFF | //
+ (bytes[2 + start] & 0xFF) << 8 | //
+ (bytes[1 + start] & 0xFF) << 16 | //
+ (bytes[start] & 0xFF) << 24; //
+ }
+ }
+
+ /**
+ * byte转无符号int
+ *
+ * @param byteValue byte值
+ * @return 无符号int值
+ * @since 3.2.0
+ */
+ public static int toUnsignedInt(final byte byteValue) {
+ // Java 总是把 byte 当做有符处理;我们可以通过将其和 0xFF 进行二进制与得到它的无符值
+ return byteValue & 0xFF;
+ }
+ // endregion
+
+ // region ----- toLong
/**
* byte数组转long
* 默认以小端序转换
@@ -354,30 +458,9 @@ public class ByteUtil {
return values;
}
+ // endregion
- /**
- * float转byte数组,默认以小端序转换
- *
- * @param floatValue float值
- * @return byte数组
- * @since 5.7.18
- */
- public static byte[] toBytes(final float floatValue) {
- return toBytes(floatValue, DEFAULT_ORDER);
- }
-
- /**
- * float转byte数组,自定义端序
- *
- * @param floatValue float值
- * @param byteOrder 端序
- * @return byte数组
- * @since 5.7.18
- */
- public static byte[] toBytes(final float floatValue, final ByteOrder byteOrder) {
- return toBytes(Float.floatToIntBits(floatValue), byteOrder);
- }
-
+ // region ----- toFloat
/**
* byte数组转float
* 默认以小端序转换
@@ -402,31 +485,9 @@ public class ByteUtil {
public static float toFloat(final byte[] bytes, final ByteOrder byteOrder) {
return Float.intBitsToFloat(toInt(bytes, byteOrder));
}
+ // endregion
- /**
- * double转byte数组
- * 默认以小端序转换
- *
- * @param doubleValue double值
- * @return byte数组
- */
- public static byte[] toBytes(final double doubleValue) {
- return toBytes(doubleValue, DEFAULT_ORDER);
- }
-
- /**
- * double转byte数组
- * 自定义端序
- * from: https://stackoverflow.com/questions/4485128/how-do-i-convert-long-to-byte-and-back-in-java
- *
- * @param doubleValue double值
- * @param byteOrder 端序
- * @return byte数组
- */
- public static byte[] toBytes(final double doubleValue, final ByteOrder byteOrder) {
- return toBytes(Double.doubleToLongBits(doubleValue), byteOrder);
- }
-
+ // region ----- toDouble
/**
* byte数组转Double
* 默认以小端序转换
@@ -449,43 +510,7 @@ public class ByteUtil {
public static double toDouble(final byte[] bytes, final ByteOrder byteOrder) {
return Double.longBitsToDouble(toLong(bytes, byteOrder));
}
-
- /**
- * 将{@link Number}转换为
- *
- * @param number 数字
- * @return bytes
- */
- public static byte[] toBytes(final Number number) {
- return toBytes(number, DEFAULT_ORDER);
- }
-
- /**
- * 将{@link Number}转换为
- *
- * @param number 数字
- * @param byteOrder 端序
- * @return bytes
- */
- public static byte[] toBytes(final Number number, final ByteOrder byteOrder) {
- if (number instanceof Byte) {
- return new byte[]{number.byteValue()};
- } else if (number instanceof Double) {
- return toBytes(number.doubleValue(), byteOrder);
- } else if (number instanceof Long) {
- return toBytes(number.longValue(), byteOrder);
- } else if (number instanceof Integer) {
- return ByteUtil.toBytes(number.intValue(), byteOrder);
- } else if (number instanceof Short) {
- return ByteUtil.toBytes(number.shortValue(), byteOrder);
- } else if (number instanceof Float) {
- return toBytes(number.floatValue(), byteOrder);
- } else if (number instanceof BigInteger) {
- return ((BigInteger) number).toByteArray();
- } else {
- return toBytes(number.doubleValue(), byteOrder);
- }
- }
+ // endregion
/**
* byte数组转换为指定类型数字
@@ -539,6 +564,7 @@ public class ByteUtil {
return (T) number;
}
+ // region ----- toUnsignedByteArray and fromUnsignedByteArray
/**
* 以无符号字节数组的形式返回传入值。
*
@@ -612,6 +638,7 @@ public class ByteUtil {
}
return new BigInteger(1, mag);
}
+ // endregion
/**
* 连接多个byte[]
diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/codec/hash/metro/MetroHash128Test.java b/hutool-core/src/test/java/org/dromara/hutool/core/codec/hash/metro/MetroHash128Test.java
index c222d5eaf..ffc67763e 100644
--- a/hutool-core/src/test/java/org/dromara/hutool/core/codec/hash/metro/MetroHash128Test.java
+++ b/hutool-core/src/test/java/org/dromara/hutool/core/codec/hash/metro/MetroHash128Test.java
@@ -120,7 +120,7 @@ public class MetroHash128Test {
static String h128(final String input) {
final MetroHash128 mh = MetroHash128.of(0).apply(ByteBuffer.wrap(ByteUtil.toUtf8Bytes(input)));
final Number128 hash = mh.get();
- return hex(hash.getHighValue()) + hex(hash.getLowValue());
+ return hex(hash.getMostSigBits()) + hex(hash.getLeastSigBits());
}
private static String hex(final long value){