Skip to content

Commit

Permalink
feat: add utility classes for regex and ID generation
Browse files Browse the repository at this point in the history
添加了正则表达式和ID生成的实用工具类,包括RegexUtils、RegexCache、SnowflakeGenerator和NanoIdGenerator。提供正则匹配、ID生成和相关功能支持。
  • Loading branch information
xooooooooox committed Dec 27, 2024
1 parent eab0aa8 commit f7a28a9
Show file tree
Hide file tree
Showing 6 changed files with 802 additions and 1 deletion.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## 0.3

-
- Add Utility classes for regex `RegexUtils`, ID generation `NanoIdGenerator` `SnowflakeGenerator`

## 0.2

Expand Down
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();
}
}
}
}
}
}
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();
}
}
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);
}
}
Loading

0 comments on commit f7a28a9

Please sign in to comment.