-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add utility classes for regex and ID generation
添加了正则表达式和ID生成的实用工具类,包括RegexUtils、RegexCache、SnowflakeGenerator和NanoIdGenerator。提供正则匹配、ID生成和相关功能支持。
- Loading branch information
1 parent
eab0aa8
commit f7a28a9
Showing
6 changed files
with
802 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
56 changes: 56 additions & 0 deletions
56
radp-components/radp-commons/src/main/java/space/x9x/radp/commons/id/NanoIdGenerator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
package space.x9x.radp.commons.id; | ||
|
||
import lombok.experimental.UtilityClass; | ||
|
||
import java.security.SecureRandom; | ||
import java.util.Random; | ||
|
||
/** | ||
* @author x9x | ||
* @since 2024-12-27 12:08 | ||
*/ | ||
@UtilityClass | ||
public class NanoIdGenerator { | ||
|
||
public static final SecureRandom DEFAULT_NUMBER_GENERATOR = new SecureRandom(); | ||
private static final char[] DEFAULT_ALPHABET = "_-0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray(); | ||
public static final int DEFAULT_SIZE = 21; | ||
|
||
public static String randomNanoId() { | ||
return randomNanoId(DEFAULT_NUMBER_GENERATOR, DEFAULT_ALPHABET, DEFAULT_SIZE); | ||
} | ||
|
||
@SuppressWarnings("t") | ||
public static String randomNanoId(final Random random, final char[] alphabet, final int size) { | ||
if (random == null) { | ||
throw new IllegalArgumentException("random cannot be null."); | ||
} | ||
if (alphabet == null) { | ||
throw new IllegalArgumentException("alphabet cannot be null."); | ||
} | ||
if (alphabet.length == 0 || alphabet.length >= 256) { | ||
throw new IllegalArgumentException("alphabet must contain between 1 and 255 symbols."); | ||
} | ||
if (size <= 0) { | ||
throw new IllegalArgumentException("size must be greater than zero."); | ||
} | ||
|
||
final int mask = (2 << (int) Math.floor(Math.log(alphabet.length - 1) / Math.log(2))) - 1; | ||
final int step = (int) Math.ceil(1.6 * mask * size / alphabet.length); | ||
|
||
final StringBuilder idBuilder = new StringBuilder(); | ||
while (true) { | ||
final byte[] bytes = new byte[step]; | ||
random.nextBytes(bytes); | ||
for (int i = 0; i < step; i++) { | ||
final int alphabetIndex = bytes[i] & mask; | ||
if (alphabetIndex < alphabet.length) { | ||
idBuilder.append(alphabet[alphabetIndex]); | ||
if (idBuilder.length() == size) { | ||
return idBuilder.toString(); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} |
196 changes: 196 additions & 0 deletions
196
radp-components/radp-commons/src/main/java/space/x9x/radp/commons/id/SnowflakeGenerator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,196 @@ | ||
package space.x9x.radp.commons.id; | ||
|
||
import lombok.Builder; | ||
import lombok.NoArgsConstructor; | ||
import lombok.Synchronized; | ||
|
||
/** | ||
* Twitter 雪花算法生成器 | ||
* <p> | ||
* 共 64 位, 0 + 41 位时间戳 + 10 位机器 ID(5 位机器 ID + 5 位数据中心 ID)+ 12 位序列号 | ||
* <pre> | ||
* ┌─────────────── 41 bits ────────────────┐┌──── 10 bits ────┐┌─ 12 bits ─┐ | ||
* │ 时间戳(毫秒数) offset ││数据中心ID+机器ID ││ 序列号 │ | ||
* └────────────────────────────────────────┘└─────────────────┘└───────────┘ | ||
* </pre> | ||
* <pre>{@code | ||
* public class SnowflakeDemo { | ||
* public static void main(String[] args) { | ||
* // 1) 直接 new 一个默认的 SnowflakeGenerator(dataCenterId=1, workerId=1) | ||
* SnowflakeGenerator generator = new SnowflakeGenerator(); | ||
* | ||
* // 2) 调用 nextId() 方法生成 Snowflake ID | ||
* long id = generator.nextId(); | ||
* System.out.println("Snowflake ID: " + id); | ||
* | ||
* // 3) 使用静态方法一次生成 | ||
* long staticId = SnowflakeGenerator.nextId(2, 3); | ||
* System.out.println("Snowflake ID (static): " + staticId); | ||
* } | ||
* } | ||
* }</pre> | ||
* | ||
* @author x9x | ||
* @since 2024-12-27 12:36 | ||
*/ | ||
@NoArgsConstructor | ||
public class SnowflakeGenerator { | ||
|
||
/** | ||
* 开始时间截(2018-01-01) | ||
*/ | ||
private final long twepoch = 1514736000000L; | ||
|
||
/** | ||
* 机器 ID 占 5 位(机器 ID 和数据中心 ID 加起来不能超过 10) | ||
*/ | ||
private final long workerIdBits = 5L; | ||
|
||
/** | ||
* 数据中心 ID 占 5 位 | ||
*/ | ||
private final long datacenterIdBits = 5L; | ||
|
||
/** | ||
* 序列号占 12 位 | ||
*/ | ||
private final long sequenceBits = 12L; | ||
|
||
/** | ||
* 支持的最大机器 ID | ||
*/ | ||
private final long maxWorkerId = -1L ^ (-1L << workerIdBits); | ||
|
||
/** | ||
* 支持的最大数据标识 ID | ||
*/ | ||
private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits); | ||
|
||
/** | ||
* 机器 ID 向左移 12 位 | ||
*/ | ||
private final long workerIdShift = sequenceBits; | ||
|
||
/** | ||
* 数据标识 ID 向左移 17 位 | ||
*/ | ||
private final long datacenterIdShift = sequenceBits + workerIdBits; | ||
|
||
/** | ||
* 时间截向左移 22 位 | ||
*/ | ||
private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits; | ||
|
||
/** | ||
* 生成序列的掩码 | ||
*/ | ||
private final long sequenceMask = -1L ^ (-1L << sequenceBits); | ||
|
||
/** | ||
* 机器 ID | ||
*/ | ||
private long workerId = 1L; | ||
|
||
/** | ||
* 数据中心 ID | ||
*/ | ||
private long dataCenterId = 1L; | ||
|
||
/** | ||
* 毫秒内序列号 | ||
*/ | ||
private long sequence = 0L; | ||
|
||
/** | ||
* 上次生成 ID 的时间截 | ||
*/ | ||
private long lastTimestamp = -1L; | ||
|
||
/** | ||
* 构造函数 | ||
* | ||
* @param dataCenterId 数据中心 ID | ||
* @param workerId 工作 ID | ||
*/ | ||
@Builder | ||
public SnowflakeGenerator(long dataCenterId, long workerId) { | ||
if (dataCenterId > maxDatacenterId || dataCenterId < 0) { | ||
throw new IllegalArgumentException(String.format("dataCenterId 不能大于 %d 或者小于 0", maxDatacenterId)); | ||
} | ||
if (workerId > maxWorkerId || workerId < 0) { | ||
throw new IllegalArgumentException(String.format("workerId 不能大于 %d 或者小于 0", maxWorkerId)); | ||
} | ||
this.dataCenterId = dataCenterId; | ||
this.workerId = workerId; | ||
} | ||
|
||
/** | ||
* 获得下一个 ID | ||
* | ||
* @param dataCenterId 数据中心 ID | ||
* @param workerId 工作 ID | ||
* @return snowflakeId | ||
*/ | ||
public static long nextId(long dataCenterId, long workerId) { | ||
return SnowflakeGenerator.builder().dataCenterId(dataCenterId).workerId(workerId).build().nextId(); | ||
} | ||
|
||
/** | ||
* 获得下一个 ID | ||
* | ||
* @return snowflakeId | ||
*/ | ||
@Synchronized | ||
public long nextId() { | ||
long timestamp = timeGen(); | ||
|
||
// 如果当前时间小于上一次 ID 生成的时间戳,说明系统时钟回退过,抛出异常 | ||
if (timestamp < lastTimestamp) { | ||
throw new RuntimeException(String.format("时钟被回退 %d 毫秒,无法生成", lastTimestamp - timestamp)); | ||
} | ||
|
||
// 如果是同一时间生成的,则进行毫秒内序列 | ||
if (lastTimestamp == timestamp) { | ||
sequence = (sequence + 1) & sequenceMask; | ||
// 毫秒内序列溢出 | ||
if (sequence == 0) { | ||
// 阻塞到下一个毫秒,获得新的时间戳 | ||
timestamp = tillNextMillis(lastTimestamp); | ||
} | ||
} else { // 时间戳改变,毫秒内序列重置 | ||
sequence = 0L; | ||
} | ||
|
||
// 上次生成 ID 的时间截 | ||
lastTimestamp = timestamp; | ||
|
||
// 移位并通过或运算拼到一起组成 64 位的 ID | ||
return ((timestamp - twepoch) << timestampLeftShift) | ||
| (dataCenterId << datacenterIdShift) | ||
| (workerId << workerIdShift) | ||
| sequence; | ||
} | ||
|
||
/** | ||
* 阻塞到下一个毫秒,直到获得新的时间戳 | ||
* | ||
* @param lastTimestamp 上次生成 ID 的时间截 | ||
* @return 当前时间戳 | ||
*/ | ||
protected long tillNextMillis(long lastTimestamp) { | ||
long timestamp = timeGen(); | ||
while (timestamp <= lastTimestamp) { | ||
timestamp = timeGen(); | ||
} | ||
return timestamp; | ||
} | ||
|
||
/** | ||
* 返回以毫秒为单位的当前时间 | ||
* | ||
* @return 当前时间戳 | ||
*/ | ||
protected long timeGen() { | ||
return System.currentTimeMillis(); | ||
} | ||
} |
85 changes: 85 additions & 0 deletions
85
radp-components/radp-commons/src/main/java/space/x9x/radp/commons/regex/RegexUtils.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
package space.x9x.radp.commons.regex; | ||
|
||
import lombok.NonNull; | ||
import lombok.experimental.UtilityClass; | ||
import space.x9x.radp.commons.regex.pattern.RegexCache; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.regex.Matcher; | ||
import java.util.regex.Pattern; | ||
|
||
/** | ||
* <p><strong>使用示例:</strong></p> | ||
* <pre>{@code | ||
* public class Demo { | ||
* public static void main(String[] args) { | ||
* // 1) 判断整个字符串是否匹配正则 | ||
* boolean isMatch = RegexUtils.isMatch("\\d+", "12345"); | ||
* System.out.println(isMatch); // true | ||
* | ||
* // 2) 查找目标字符串中是否存在匹配 | ||
* boolean found = RegexUtils.find("abc", "xxx abc yyy"); | ||
* System.out.println(found); // true | ||
* | ||
* // 3) 分组匹配示例 | ||
* List<String> groups = RegexUtils.group("([a-z]+)", "abc def ghi"); | ||
* System.out.println(groups); // [abc, def, ghi] | ||
* | ||
* // 4) 替换所有匹配项 | ||
* String replaced = RegexUtils.replaceAll("\\s+", "abc def", "_"); | ||
* System.out.println(replaced); // "abc_def" | ||
* } | ||
* } | ||
* }</pre> | ||
* | ||
* @author x9x | ||
* @see space.x9x.radp.commons.regex.pattern.Regex | ||
* @since 2024-12-27 12:50 | ||
*/ | ||
@UtilityClass | ||
public class RegexUtils { | ||
|
||
public static boolean isMatch(@NonNull String regex, @NonNull CharSequence input) { | ||
return Pattern.matches(regex, input); | ||
} | ||
|
||
public static boolean find(@NonNull String regex, @NonNull CharSequence input) { | ||
Pattern pattern = RegexCache.get(regex, Pattern.CASE_INSENSITIVE); | ||
return pattern.matcher(input).find(); | ||
} | ||
|
||
public static List<String> group(@NonNull String regex, @NonNull CharSequence input) { | ||
List<String> matches = new ArrayList<>(); | ||
Pattern pattern = RegexCache.get(regex, Pattern.DOTALL); | ||
Matcher matcher = pattern.matcher(input); | ||
int i = 1; | ||
while (matcher.find()) { | ||
matches.add(matcher.group(i)); | ||
i++; | ||
} | ||
return matches; | ||
} | ||
|
||
public static String groupFirst(@NonNull String regex, @NonNull CharSequence input) { | ||
return group(regex, input, 1); | ||
} | ||
|
||
public static String group(@NonNull String regex, @NonNull CharSequence input, int groupIndex) { | ||
Pattern pattern = RegexCache.get(regex, Pattern.DOTALL); | ||
Matcher matcher = pattern.matcher(input); | ||
if (matcher.find()) { | ||
return matcher.group(groupIndex); | ||
} | ||
return null; | ||
} | ||
|
||
public static String replaceAll(@NonNull String regex, @NonNull CharSequence input, @NonNull String replacement) { | ||
return Pattern.compile(regex).matcher(input).replaceAll(replacement); | ||
} | ||
|
||
public static String replaceFirst(@NonNull String regex, @NonNull CharSequence input, @NonNull String replacement) { | ||
Pattern pattern = RegexCache.get(regex, Pattern.DOTALL); | ||
return pattern.matcher(input).replaceFirst(replacement); | ||
} | ||
} |
Oops, something went wrong.