Java中split,reverse,join方法的源码分析

本文详细介绍了Java中字符串处理的三个重要方法:split根据正则表达式拆分字符串,reverse实现字符串反转,以及join方法用于拼接字符串。文中深入到源码层面,解释了这些方法的实现逻辑,并提供了相关示例。同时,文章提到了String的不可变性以及StringBuffer的可变性,强调了Java中字符串操作的安全性和效率考虑。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        在写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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值