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位数字表示,分为: + * * * @author hexiufeng * @since 5.2.5 */ -public class Number128 extends Number { +public class Number128 extends Number implements Comparable{ 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); + } + + /** + * 获取高低位数组,规则为: + * + * + * + * @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){