com.github.azbh111.utils.java.string.StringUtils Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of utils-java Show documentation
Show all versions of utils-java Show documentation
com.github.azbh111:utils-java
The newest version!
package com.github.azbh111.utils.java.string;
import com.github.azbh111.utils.java.annotation.Nonnull;
import com.github.azbh111.utils.java.annotation.Nullable;
import com.github.azbh111.utils.java.annotation.Unsafe;
import com.github.azbh111.utils.java.charset.Charsets;
import com.github.azbh111.utils.java.string.model.CollationKeyContainer;
import com.github.azbh111.utils.java.string.model.InpuStreamFromString;
import sun.misc.SharedSecrets;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.charset.Charset;
import java.text.CollationKey;
import java.text.Collator;
import java.util.*;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author pyz
* @date 2019/12/19 11:20 上午
*/
public class StringUtils {
/**
* 手机号前缀
* 用来验证是否是手机号
*/
public static final List mobilePhonePrefixes = Collections.unmodifiableList(new ArrayList<>(new HashSet<>(
Arrays.asList(
("133,149,153,173,177,180,181,189,191,193,199,130," +
"131,132,145,155,156,166,171,175,176,185,186," +
"134,135,136,137,138,139,147,150,151,152,157," +
"158,159,172,178,182,183,184,187,188,195,198," +
"145,147,1700,1701,1702,162,1703,1705,1706,165," +
"1704,1707,1708,1709,171,167,1349,134,135,136," +
"137,138,139,147,150,151,152,157,158,159,1705," +
"178,182,183,184,187,188,198,130,131,132,145," +
"155,156,166,176,1709,171,185,186,133,153,180," +
"181,189,1700,177,173,199")
.replace("\n", "")
.split(","))
)));
/**
* 满足mobilePhonePrefixes前缀规则后,用这个来验证手机号
*/
private static final Pattern mobilePhonePattern = Pattern.compile("^1[0-9]{10}$");
/**
* ASCII表中可见字符从!开始,偏移位值为33(Decimal)
*/
static final char DBC_CHAR_START = 33; // 半角!
/**
* ASCII表中可见字符到~结束,偏移位值为126(Decimal)
*/
static final char DBC_CHAR_END = 126; // 半角~
/**
* 全角对应于ASCII表的可见字符从!开始,偏移值为65281
*/
static final char SBC_CHAR_START = 65281; // 全角!
/**
* 全角对应于ASCII表的可见字符到~结束,偏移值为65374
*/
static final char SBC_CHAR_END = 65374; // 全角~
/**
* ASCII表中除空格外的可见字符与对应的全角字符的相对偏移
*/
static final int CONVERT_STEP = 65248; // 全角半角转换间隔
/**
* 全角空格的值,它没有遵从与ASCII的相对偏移,必须单独处理
*/
static final char SBC_SPACE = 12288; // 全角空格 12288
/**
* 半角空格的值,在ASCII中为32(Decimal)
*/
static final char DBC_SPACE = ' '; // 半角空格
private static final Field stringValue;
private static final Pattern CHECK_EMOJI = Pattern.compile("[\ud83c\udc00-\ud83c\udfff]|[\ud83d\udc00-\ud83d\udfff]|[\u2600-\u27ff]", Pattern.UNICODE_CASE | Pattern.CASE_INSENSITIVE);
private static final byte[] emptyByteArray = new byte[0];
static {
Field _stringValue = null;
try {
_stringValue = String.class.getDeclaredField("value");
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
}
_stringValue.setAccessible(true);
stringValue = _stringValue;
}
/**
* 以流的形式读取字符串
*
* @param
* @return java.io.InputStream
* @author zhengyongpan
* @since 2021/12/14 上午10:18
*/
public static InputStream wrapToInputStream(CharSequence charSequence, Charset charset) {
if (charSequence == null || charSequence.length() == 0) {
return new ByteArrayInputStream(emptyByteArray);
}
return new InpuStreamFromString(charSequence, charset);
}
public static String join(@Nonnull CharSequence separator, @Nonnull Iterable iterable) {
return join(null, separator, null, iterable, i -> String.valueOf(i));
}
public static String join(@Nonnull CharSequence separator, @Nonnull Iterable iterable, @Nonnull Function mapping) {
return join(null, separator, null, iterable, mapping);
}
public static String join(CharSequence prefix, @Nonnull CharSequence separator, CharSequence suffix, @Nonnull Iterable iterable) {
return join(prefix, separator, suffix, iterable, i -> String.valueOf(i));
}
public static String join(CharSequence prefix, @Nonnull CharSequence separator, CharSequence suffix, @Nonnull Iterable iterable, @Nonnull Function mapping) {
StringBuilder sb = new StringBuilder();
if (prefix != null) {
sb.append(prefix);
}
Iterator it = iterable.iterator();
if (it.hasNext()) {
sb.append(mapping.apply(it.next()));
}
while (it.hasNext()) {
sb.append(separator).append(mapping.apply(it.next()));
}
if (suffix != null) {
sb.append(suffix);
}
return sb.toString();
}
/**
* 反转字符串
*
* @return
*/
public static String reverse(String str) {
if (str == null || str.isEmpty()) {
return str;
}
int length = str.length();
char[] chars = new char[length];
int maxLength = length - 1;
for (int i = 0; i < length; i++) {
chars[i] = str.charAt(maxLength - i);
}
return newStringZeroCopy(chars);
}
/**
* 将str重复指定次数
*
* @param str
* @param times
* @return
*/
public static String repeat(String str, int times) {
if (times <= 0) {
return "";
}
if (times == 1) {
return str;
}
if (str == null || str.isEmpty()) {
return str;
}
int length = str.length();
int destLength = length * times;
char[] chars = new char[destLength];
char[] src = getStringValueUnsafe(str);
for (int i = 0; i < destLength; i += length) {
System.arraycopy(src, 0, chars, i, length);
}
return newStringZeroCopy(chars);
}
/**
* 直接获取String对象里的字符数组
* 警告: 禁止修改返回的char数组!
*
* @param str
* @return
*/
@Unsafe
public static char[] getStringValueUnsafe(String str) {
if (str == null) {
return null;
}
try {
return (char[]) stringValue.get(str);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
/**
* 判断字符串是否包含emoji
*
* @param source
* @return
*/
public static boolean containsEmoj(String source) {
int len = source.length();
boolean isEmoji = false;
for (int i = 0; i < len; i++) {
char hs = source.charAt(i);
if (0xd800 <= hs && hs <= 0xdbff) {
if (source.length() > 1) {
char ls = source.charAt(i + 1);
int uc = ((hs - 0xd800) * 0x400) + (ls - 0xdc00) + 0x10000;
if (0x1d000 <= uc && uc <= 0x1f77f) {
return true;
}
}
} else {
// non surrogate
if (0x2100 <= hs && hs <= 0x27ff && hs != 0x263b) {
return true;
} else if (0x2B05 <= hs && hs <= 0x2b07) {
return true;
} else if (0x2934 <= hs && hs <= 0x2935) {
return true;
} else if (0x3297 <= hs && hs <= 0x3299) {
return true;
} else if (hs == 0xa9 || hs == 0xae || hs == 0x303d
|| hs == 0x3030 || hs == 0x2b55 || hs == 0x2b1c
|| hs == 0x2b1b || hs == 0x2b50 || hs == 0x231a) {
return true;
}
if (!isEmoji && source.length() > 1 && i < source.length() - 1) {
char ls = source.charAt(i + 1);
if (ls == 0x20e3) {
return true;
}
}
}
}
return isEmoji;
}
public static boolean isEmojiCharacter(char codePoint) {
return !(
(codePoint == 0x0) || (codePoint == 0x9) || (codePoint == 0xA)
|| (codePoint == 0xD)
|| ((codePoint >= 0x20) && (codePoint <= 0xD7FF))
|| ((codePoint >= 0xE000) && (codePoint <= 0xFFFD))
|| ((codePoint >= 0x10000) && (codePoint <= 0x10FFFF))
);
}
/**
* 移除字符串中的emoji
*
* @param source
* @return
*/
public static String removeEmoji(@Nullable String source) {
if (source == null || source.isEmpty()) {
return source;
}
StringBuilder buf = null;
int len = source.length();
for (int i = 0; i < len; i++) {
char codePoint = source.charAt(i);
if (!isEmojiCharacter(codePoint)) {
if (buf == null) {
buf = new StringBuilder(source.length());
}
buf.append(codePoint);
}
}
if (buf == null) {
return source;
} else {
if (buf.length() == len) {
return source;
} else {
return buf.toString();
}
}
}
public static String trimToNull(@Nullable String src) {
if (src == null || src.isEmpty()) {
return null;
}
src = src.trim();
if (src.isEmpty()) {
return null;
}
return src;
}
public static String trimToEmpty(@Nullable String src) {
if (src == null || src.isEmpty()) {
return "";
}
src = src.trim();
if (src.isEmpty()) {
return "";
}
return src;
}
/**
*
* 半角字符->全角字符转换
* 只处理空格,!到˜之间的字符,忽略其他
*
*/
public static String hafl2Full(@Nullable String src) {
if (src == null) {
return src;
}
StringBuilder buf = new StringBuilder(src.length());
char[] ca = src.toCharArray();
for (int i = 0; i < ca.length; i++) {
if (ca[i] == DBC_SPACE) { // 如果是半角空格,直接用全角空格替代
buf.append(SBC_SPACE);
} else if ((ca[i] >= DBC_CHAR_START) && (ca[i] <= DBC_CHAR_END)) { // 字符是!到~之间的可见字符
buf.append((char) (ca[i] + CONVERT_STEP));
} else { // 不对空格以及ascii表中其他可见字符之外的字符做任何处理
buf.append(ca[i]);
}
}
return buf.toString();
}
/**
*
* 全角字符->半角字符转换
* 只处理全角的空格,全角!到全角~之间的字符,忽略其他
*
*/
public static String full2Half(@Nullable String src) {
if (src == null) {
return src;
}
StringBuilder buf = new StringBuilder(src.length());
char[] ca = src.toCharArray();
for (int i = 0; i < src.length(); i++) {
if (ca[i] >= SBC_CHAR_START && ca[i] <= SBC_CHAR_END) { // 如果位于全角!到全角~区间内
buf.append((char) (ca[i] - CONVERT_STEP));
} else if (ca[i] == SBC_SPACE) { // 如果是全角空格
buf.append(DBC_SPACE);
} else { // 不处理全角空格,全角!到全角~区间外的字符
buf.append(ca[i]);
}
}
return buf.toString();
}
public static boolean isMobilePhone(@Nullable String str) {
if (str == null || str.isEmpty()) {
return false;
}
if (str.length() != 11) {
return false;
}
for (String phonePrefix : mobilePhonePrefixes) {
if (!str.startsWith(phonePrefix)) {
return false;
}
}
Matcher m = mobilePhonePattern.matcher(str);
return m.matches();
}
/**
* 计算字符串的ansi字符个数(比如一个汉字占2个字符)
*
* @param str
* @return
*/
public static int ansiCharCount(@Nullable CharSequence str) {
int count = 0;
if (str == null) {
return count;
}
int length = str.length();
for (int i = 0; i < length; i++) {
count += Character.charCount(Character.codePointAt(str, i));
}
return count;
}
/**
* 首字母大写
*
* @param str
* @return
*/
public static String upperCaseFirstCharacter(@Nullable String str) {
return changeFirstLetter(str, 1);
}
/**
* 首字母小写
*
* @param str
* @return
*/
public static String lowerCaseFirstCharacter(@Nullable String str) {
return changeFirstLetter(str, -1);
}
/**
* 字符串按字典排序
*
* @param list
*/
public static void sortNature(@Nullable List list) {
if (list == null || list.isEmpty()) {
return;
}
sortNature(list, Collator.getInstance(Locale.CHINA));
}
/**
* 字符串按字典排序
*
* @param list
*/
public static void sortNature(@Nullable List list, @Nonnull Collator comparetor) {
if (list == null || list.isEmpty()) {
return;
}
// 当需要多次比较时,转换成CollationKey会获得性能提升
CollationKey[] keys = new CollationKey[list.size()];
int index = 0;
for (String str : list) {
keys[index++] = comparetor.getCollationKey(str);
}
Arrays.sort(keys);
ListIterator it = list.listIterator();
for (CollationKey key : keys) {
it.next();
it.set(key.getSourceString());
}
}
/**
* 字符串按字典排序
*
* @param list
*/
public static void sortNature(@Nullable List list, @Nonnull Function keyMapper) {
sortNature(list, Collator.getInstance(Locale.CHINA), keyMapper);
}
/**
* 字符串按字典排序
*
* @param list
*/
public static void sortNature(@Nullable List list, @Nonnull Collator collator, @Nonnull Function keyMapper) {
if (list == null || list.isEmpty()) {
return;
}
// 当需要多次比较时,转换成CollationKey会获得性能提升
CollationKeyContainer[] keys = new CollationKeyContainer[list.size()];
int index = 0;
for (T t : list) {
keys[index++] = new CollationKeyContainer(collator.getCollationKey(keyMapper.apply(t)), t);
}
Arrays.sort(keys);
ListIterator it = list.listIterator();
for (CollationKeyContainer key : keys) {
it.next();
it.set(key.getExtend());
}
}
public static boolean isEmpty(@Nullable CharSequence str) {
return str == null || str.length() == 0;
}
public static boolean isNotEmpty(@Nullable CharSequence str) {
return !isEmpty(str);
}
/**
* Check whether the given {@code CharSequence} contains actual text.
* More specifically, this method returns {@code true} if the
* {@code CharSequence} is not {@code null}, its length is greater than
* 0, and it contains at least one non-whitespace character.
*
* StringUtils.hasText(null) = false
* StringUtils.hasText("") = false
* StringUtils.hasText(" ") = false
* StringUtils.hasText("12345") = true
* StringUtils.hasText(" 12345 ") = true
*
*
* @param str the {@code CharSequence} to check (may be {@code null})
* @return {@code true} if the {@code CharSequence} is not {@code null},
* its length is greater than 0, and it does not contain whitespace only
* @see Character#isWhitespace
*/
public static boolean isBlank(@Nullable CharSequence str) {
return str == null || str.length() == 0 || !containsText(str);
}
public static boolean isNotBlank(@Nullable CharSequence str) {
return !isBlank(str);
}
/**
* 在字符串收尾填充字符,使其达到指定长度
* 当可填充字符数为奇数时,优先填充起始位置
*
* @param content
* @param character
* @param length
* @return
*/
public static String center(String content, char character, int length) {
return center(content, character, character, length);
}
/**
* 在字符串收尾填充字符,使其达到指定长度
* 当可填充字符数为奇数时,优先填充prefix
*
* @param content
* @param prefix
* @param suffix
* @param length
* @return
*/
public static String center(String content, char prefix, char suffix, int length) {
if (content == null) {
return content;
}
if (content.length() >= length) {
return content;
}
char[] chars = new char[length];
int center = content.length();
int right = (length - center) / 2;
int left = length - center - right;
for (int i = 0; i < left; i++) {
chars[i] = prefix;
}
for (int i = 0; i < right; i++) {
chars[chars.length - right + i] = suffix;
}
for (int i = 0; i < center; i++) {
chars[left + i] = content.charAt(i);
}
return newStringZeroCopy(chars);
}
/**
* 用char数组零拷贝创建字符串
* 警告: 创建字符串后,禁止修改源src
*
* @param bytes
* @return
*/
@Unsafe
public static String newStringZeroCopy(char[] bytes) {
return SharedSecrets.getJavaLangAccess().newStringUnsafe(bytes);
}
public static String toString(InputStream stream) throws IOException {
return toString(stream, Charsets.UTF_8);
}
public static String toString(InputStream stream, Charset charset) throws IOException {
StringBuilder sb = new StringBuilder();
char[] chars = new char[128];
BufferedReader reader = new BufferedReader(new InputStreamReader(stream, charset));
int n = -1;
while ((n = reader.read(chars)) != -1) {
sb.append(chars, 0, n);
}
return sb.toString();
}
private static boolean containsText(CharSequence str) {
int strLen = str.length();
for (int i = 0; i < strLen; i++) {
if (!Character.isWhitespace(str.charAt(i))) {
return true;
}
}
return false;
}
private static String changeFirstLetter(String str, int flag) {
if (str == null || str.isEmpty()) {
return str;
}
char[] chars = str.toCharArray();
if (flag == 1) {
chars[0] = Character.toUpperCase(chars[0]);
} else if (flag == -1) {
chars[0] = Character.toLowerCase(chars[0]);
}
String r = newStringZeroCopy(chars);
return r;
}
}