在写LeetCode每日一题——Day27 时,调用 API 解决问题使得问题更加简单,也不用重复造轮子。
下面我们来详细了解一下Java中的split,reverse,join方法。
split() 方法根据匹配给定的正则表达式来拆分字符串。语法如下:
public String[] split(String regex, int limit)
//regex 正则表达式
//limit 切分个数
下面我们来分析一下Java中split的源码实现,源码如下:
public String[] split(String regex, int limit) {
/* fastpath if the regex is a
(1)one-char String and this character is not one of the
RegEx's meta characters ".$|()[{^?*+\\", or
(2)two-char String and the first char is the backslash and
the second is not the ascii digit or ascii letter.
以上是源码的注释
正则表达式只要满足下面其中一种情况使用就fastpath
只有一个字符或者2个字符,但第一个字符是转移字符的话,就启用快速分割模式
(1): 如果只有一个字符并且这个字符不是正则表达式使用的超文本符号(meta characters)
(2): 如果是两个字符的字符串,并且第一个字符是一个反斜线,第二个不是一个ascii的大小写字母或者符号
否者使用正则表达式提供的split函数
*/
char ch = 0;
if (((regex.value.length == 1 &&
".$|()[{^?*+\\".indexOf(ch = regex.charAt(0)) == -1) ||
(regex.length() == 2 &&
regex.charAt(0) == '\\' &&
(((ch = regex.charAt(1))-'0')|('9'-ch)) < 0 &&
((ch-'a')|('z'-ch)) < 0 &&
((ch-'A')|('Z'-ch)) < 0)) &&
(ch < Character.MIN_HIGH_SURROGATE ||
ch > Character.MAX_LOW_SURROGATE))
{
// 偏移量
int off = 0;
// 下一个地址
int next = 0;
// 判断是否限制切片
boolean limited = limit > 0;
// 创建一个list来存放保存的结果
ArrayList<String> list = new ArrayList<>();
while ((next = indexOf(ch, off)) != -1) {
if (!limited || list.size() < limit - 1) {
list.add(substring(off, next));
off = next + 1;
} else { // last one
//assert (list.size() == limit - 1);
list.add(substring(off, value.length));
off = value.length;
break;
}
}
// If no match was found, return this
if (off == 0)
return new String[]{this};
// Add remaining segment
// 如果不限制,或者已经放入的还小于限制长度,把最后剩余的也全都放入进去
if (!limited || list.size() < limit)
list.add(substring(off, value.length));
// Construct result
// 如果不限制长度,从后往前,把找到的元素遍历一遍
//
// 去掉结尾元素是空的元素(长度为0的)
// 但是无法去掉中间元素是空的!
int resultSize = list.size();
if (limit == 0) {
while (resultSize > 0 && list.get(resultSize - 1).length() == 0) {
resultSize--;
}
}
// 将结果返回
String[] result = new String[resultSize];
return list.subList(0, resultSize).toArray(result);
}
// 不满足如上条件,这使用正则表达式自带的split函数
return Pattern.compile(regex).split(this, limit);
}
第一次看到源码可能会云里雾里,我们逐个分析,先看这让人迷茫的if语句。
if (
(
( // 是一个字符,但是不包括正则表达式
regex.value.length == 1 &&
".$|()[{^?*+\\".indexOf(ch = regex.charAt(0)) == -1
)
||
( // 是两个字符,并且第一个字符是反斜杠,然后判断第二个字符是否是数字或者大小写字母
regex.length() == 2 &&
regex.charAt(0) == '\\' &&
(((ch = regex.charAt(1))-'0')|('9'-ch)) < 0 &&
((ch-'a')|('z'-ch)) < 0 &&
((ch-'A')|('Z'-ch)) < 0
)
)
&&
// 判断不是unicode的高代理和低代理
( ch < Character.MIN_HIGH_SURROGATE ||
ch > Character.MAX_LOW_SURROGATE
)
)
if语句解决后,再看源码思路便清晰不少。
reverse方法将此字符序列用其反转形式取代。语法如下:
public StringBuffer reverse()
我们顺便分析一下Java中String的底层源码:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
可以看到在源码中,value数组使用了final关键字修饰,即String底层的数据结构使用的是final修饰的字符数组,所以是不能修改的。
我们再来看看Java中的StringBuffer和StringBuilder,这里以StringBuffer为例,因为他们都是继承了AbstractStringBuilder。
public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
{
/**
* A cache of the last value returned by toString. Cleared
* whenever the StringBuffer is modified.
*/
private transient char[] toStringCache;
abstract class AbstractStringBuilder implements Appendable, CharSequence {
/**
* The value is used for character storage.
*/
char[] value;
StringBuffer的底层仅仅只是一个char字符数组,所以可以被更改。
那我们再来想想String类型为什么是不可更改类型:
在Java的堆内存中,有一个特殊的存储区域字符串常量,当创建了一个新的String对象时,假设字符串值已经存在于常量池中,则不会创建一个新的对象,而是引用当前已经存在的对象,帮我们节省了空间。
String s1 = "花朝九日";
String s2 = "花朝九日";
System.out.println(s1==s2);
//结果为ture
String被许多的Java类和库用来当做参数,比如url,path等等。如果String不是固定不变的将会引起各种安全隐患。
String resource = "mybatis-config.xml";
//创建一个resource对象引入mybatis配置文件
再来看Java中reverse的源码
public AbstractStringBuilder reverse() {
boolean hasSurrogate = false;
int n = count - 1;
for (int j = (n-1) >> 1; j >= 0; --j) {//此处右移一位就是除以2
char temp = value[j];
char temp2 = value[n - j];
if (!hasSurrogate) {
hasSurrogate = (temp >= Character.MIN_SURROGATE && temp <= Character.MAX_SURROGATE)
|| (temp2 >= Character.MIN_SURROGATE && temp2 <= Character.MAX_SURROGATE);
}
value[j] = temp2;
value[n - j] = temp;
}
if (hasSurrogate) {
// Reverse back all valid surrogate pairs
for (int i = 0; i < count - 1; i++) {
char c2 = value[i];
if (Character.isLowSurrogate(c2)) {
char c1 = value[i + 1];
if (Character.isHighSurrogate(c1)) {
value[i++] = c1;
value[i] = c2;
}
}
}
}
return this;
}
大致可以看出是前后字符对换的思想。
最后是join方法
String.join() 方法返回使用指定分隔符拼接一个字符串。语法如下:
public static String join(CharSequence delimiter, CharSequence... elements)
public static String join(CharSequence delimiter, Iterable<? extends CharSequence> elements)
// delimiter表示每个元素要添加的分隔符
//elements表示需要添加分隔符的字符串(表示被连接的数组(也可以是集合),或者是要连接的多个字符串)
源码如下:
//delimiter 分隔符
//elements 需要连接的元素
public static String join(CharSequence delimiter, CharSequence... elements) {
//判断是否为null,如果为null,抛出NullPointerException
Objects.requireNonNull(delimiter);
Objects.requireNonNull(elements);
//构造一个分隔符为delimiter的实例
StringJoiner joiner = new StringJoiner(delimiter);
//循环拼接
for (CharSequence cs: elements) {
joiner.add(cs);
}
return joiner.toString();
}
StringJoiner构造器源码:
//1个参数构造器
public StringJoiner(CharSequence delimiter) {
//调用3个参数构造器
this(delimiter, "", "");
}
//3个参数构造器
//delimiter 分隔符
//prefix 前缀
//suffix 后缀
public StringJoiner(CharSequence delimiter,
CharSequence prefix,
CharSequence suffix) {
//判断是否为null,如果为null,抛出NullPointerException
Objects.requireNonNull(prefix, "The prefix must not be null");
Objects.requireNonNull(delimiter, "The delimiter must not be null");
Objects.requireNonNull(suffix, "The suffix must not be null");
//为成员变量赋值
//前缀
this.prefix = prefix.toString();
//分隔符
this.delimiter = delimiter.toString();
//后缀
this.suffix = suffix.toString();
this.emptyValue = this.prefix + this.suffix;
}
String.join()方法中是通过add方法拼接字符串的,add()源码如下:
public StringJoiner add(CharSequence newElement) {
//prepareBuilder()返回参数,调用append()方法
prepareBuilder().append(newElement);
return this;
}
perpareBuilder()方法源码如下:
private StringBuilder prepareBuilder() {
if (value != null) {
value.append(delimiter);
} else {
value = new StringBuilder().append(prefix);
}
return value;
}