聊聊jdk1.8 String源码

String类我们都不陌生,在日常的开发中我们经常使用它的一些方法,如:charAt()、trim()、replace()、compareTo()等等,可是对于它的内部实现却不知道。本文主要解析一些String实现源码。

定义

String的内部存储是是基于char数组的,如:

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];

    /** Cache the hash code for the string */
    private int hash; // Default to 0
    //......
}

从String类的接口实现我们可以看出:

  1. String类被final关键字修饰,不能被继承,创建后不可修改
  2. String类实现了Serializable,可以实现序列化
  3. String类实现了Comparable接口,可以比较大小
  4. String类实现了CharSequence接口,本质上的存储结构为char数组

核心方法

构造方法

String构造方法有很多个,下面列举了几个比较核心的构造方法:

  //1.参数为String的构造方法
  public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
  }
  
  //2.参数为char[]的构造方法,将字符数组转成字符串
  public String(char value[]) {
      this.value = Arrays.copyOf(value, value.length);
  }
  
  //3.参数StringBuffer构造方法
  public String(StringBuffer buffer) {
      synchronized(buffer) {
          this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
      }
  }
  
  //4.参数为StringBuilder构造方法
  public String(StringBuilder builder) {
      this.value = Arrays.copyOf(builder.getValue(), builder.length());
  }  
  
  //5.参数为value[],offset,count,将字符数组的一部分转成字符串
  public String(char value[], int offset, int count) {
    //offset是要转化的数组起始坐标,count是要转化的字符长度
    if (offset < 0) {
    throw new StringIndexOutOfBoundsException(offset);
    }
    if (count <= 0) {
        if (count < 0) {
            throw new StringIndexOutOfBoundsException(count);
        }
         if (offset <= value.length) {
            this.value = "".value;
            return;
        }
    }
    //如果起始位置>字符数组长度 - 个数,则无法截取到count个字符,抛异常
    if (offset > value.length - count) {
        throw new StringIndexOutOfBoundsException(offset + count);
        }
        //从offset开始,截取到offset+count位置,不包括offset+count位置
        this.value = Arrays.copyOfRange(value, offset, offset+count); 
}

常用方法

1. length()、isEmpty()

//String的长度就是value数组的长度
public int length() {
    return value.length;
}

//判断String是否为空,为空返回true,否则返回false
public boolean isEmpty() {
    return value.length == 0;
}

2. equals()
常用的方法,用于比较两个字符串的值是否相等,内部重写了Object的equals方法,传参为Object类型,源码如下:

public boolean equals(Object anObject) {
    // 如果传入的对象跟当前对象引用相同,则返回 true
    if (this == anObject) {
        return true;
    }
    // 判断传入的值是否为 String 类型,如果不是则直接返回 false
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
            // 把两个字符串都转换为 char 数组对比
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            // 循环比对两个字符串的每一个字符
            while (n-- != 0) {
                // 如果其中有一个字符不相等就返回 false,否则继续对比
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    //非string类型直接返回 false
    return false;
}

此外,String类的内部还有一个equalsIgnoreCase(),它是用于忽略字符串大小写之后进行字符串比对的方法。

public boolean equalsIgnoreCase(String anotherString) {
        return (this == anotherString) ? true
                : (anotherString != null)
                && (anotherString.value.length == value.length)
                && regionMatches(true, 0, anotherString, 0, value.length);
    }

3. CompareTo()
CompareTo()方法用于比较两个字符串,它的返回值为int类型,正数为大,负数为小,基于字符的ASSIC码比较的,源码如下:

public int compareTo(String anotherString) {
    int len1 = value.length;
    int len2 = anotherString.value.length;
    // 获取到两个字符串长度最短的那个字符串的 int 值
    int lim = Math.min(len1, len2);
    char v1[] = value;
    char v2[] = anotherString.value;
    int k = 0;
    // 循环对比每一个字符
    while (k < lim) {
        char c1 = v1[k];
        char c2 = v2[k];
        if (c1 != c2) {
            // 有字符不相等就返回差值
            return c1 - c2;
        }
        k++;
    }
    return len1 - len2;
}

从上面我们可以看出,compareTo()方法和equals()方法都是用于比较两个字符串的,他们的不同点在于:

  • equals()的传参是Object对象,而compareTo()是String类型
  • equals()的返回值是Boolean类型,而compareTo()返回值则为int类型

除此之外,String类里还有一个,compareToIgnoreCase()方法,它是忽略大小写的字符串比较,源码如下:

public int compareToIgnoreCase(String str) {
    return CASE_INSENSITIVE_ORDER.compare(this, str);
}

4. replace(),replaceAll()
字符串替换方法,源码如下:

//将older字符全部替换为newChar
public String replace(char oldChar, char newChar) {
    if (oldChar != newChar) {
        int len = value.length;
        int i = -1;
        char[] val = value; /* avoid getfield opcode */

        while (++i < len) {
            if (val[i] == oldChar) {
                break;
            }
        }
        if (i < len) {
            //创建新数组,全部复制过去
            char buf[] = new char[len];
            for (int j = 0; j < i; j++) {
                buf[j] = val[j];    
            }
            while (i < len) {
                char c = val[i];
                buf[i] = (c == oldChar) ? newChar : c;
                i++;
            }
            return new String(buf, true);
        }
    }
    return this;
}

//替换第一个旧字符
public String replaceFirst(String regex, String replacement) {
    return Pattern.compile(regex).matcher(this).replaceFirst(replacement);
}

//用新的字符串替换旧的字符串
public String replace(CharSequence target, CharSequence replacement) {
    return Pattern.compile(target.toString(), Pattern.LITERAL).matcher(
            this).replaceAll(Matcher.quoteReplacement(replacement.toString()));
}

//如果没有正则表达式限制,跟replace功能一样
public String replaceAll(String regex, String replacement) {
    return Pattern.compile(regex).matcher(this).replaceAll(replacement);
}

5.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.
         */
        char ch = 0;
        //1.如果regex只有一位,且不为列出的特殊字符
        if (((regex.value.length == 1 &&
             ".$|()[{^?*+\\".indexOf(ch = regex.charAt(0)) == -1) ||
             //2.如果regex的长度为2,第一位为转义字符且第二位不是数字或字母,“|”表示或,即只要ch小	 于0或者大于9任一成立,小于a或者大于z任一成立,小于A或大于Z任一成立
             (regex.length() == 2 &&
              regex.charAt(0) == '\\' &&
              (((ch = regex.charAt(1))-'0')|('9'-ch)) < 0 &&
              ((ch-'a')|('z'-ch)) < 0 &&
              ((ch-'A')|('Z'-ch)) < 0)) &&
             //3.不属于utf-16之间的字符
            (ch < Character.MIN_HIGH_SURROGATE ||
             ch > Character.MAX_LOW_SURROGATE))
        {
            int off = 0;
            int next = 0;
            boolean limited = limit > 0;
            //为了应对这几种特殊情况,使用一个ArrayList<String>存放每一段找到分割点的字符串,不断循环。
            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
            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);
        }
        //否则,使用Pattern的正则方式去解析并拆分成字符串数组
        return Pattern.compile(regex).split(this, limit);
    }

6. 其他重要方法

//查询字符串首次出现的下标位置
public int indexOf(int ch) {
        return indexOf(ch, 0);
}
//字符串最后出现的下标位置
public int lastIndexOf(int ch) {
        return lastIndexOf(ch, value.length - 1);
}
//字符串是否包含另一个字符串
public boolean contains(CharSequence s) {
        return indexOf(s.toString()) > -1;
}
//将字符串转换为大小写
public String toLowerCase() {
    return toLowerCase(Locale.getDefault());
}
public String toUpperCase() {
    return toUpperCase(Locale.getDefault());
}
//String首尾空格
public String trim() {
    int len = value.length;
    int st = 0;
    char[] val = value;    /* avoid getfield opcode */

    while ((st < len) && (val[st] <= ' ')) {
        st++;
    }
    while ((st < len) && (val[len - 1] <= ' ')) {
        len--;
    }
    return ((st > 0) || (len < value.length)) ? substring(st, len) : this;
}
//检测是否匹配给定的正则表达式
public boolean matches(String regex) {
    return Pattern.matches(regex, this);   
}
//String转Char数组并返回
public char[] toCharArray() {
    // Cannot use Arrays.copyOf because of class initialization order issues
    //String 和Arrays 都属于rt.jar中的类,但是BootstrapClassloader 在加载这两个类的顺序是不同的。
    //所以当String.class被加载进内存的时候,Arrays此时没有被加载,所以直接使用肯定会抛异常。而System.arrayCopy是使用native代码,则不会有这个问题。
    char result[] = new char[value.length];
    System.arraycopy(value, 0, result, 0, value.length);
    return result;
}
//把字符串数组转化为字符串
public static String join(CharSequence delimiter, CharSequence... elements) {
	    Objects.requireNonNull(delimiter);
	     Objects.requireNonNull(elements);
	     // Number of elements not likely worth Arrays.stream overhead.
	     StringJoiner joiner = new StringJoiner(delimiter);
	     for (CharSequence cs: elements) {
	         joiner.add(cs);
	     }
	     return joiner.toString();
}

知识扩展

1. ==和equals的区别
对于基本数据来说,用于比较两个“值”是否相等,而对于引用类型来说,用于比较引用地址是否相同。Object中的equals方法就是==,而String重写了equals()方法,把它修改为比较两个字符串的值是否相等。如上equals方法。
2.使用final修饰的好处
从上面开头写的,我们可以看见String是被final修饰的不可继承类,这样有两个好处:

  • 安全
    当你在调用其他方法时,比如调用一些系统级操作指令之前,可能会有一些列校验,如果是可变类的话,当年校验完之后,内部值又被修改了,可能会引起系统级崩溃问题
  • 高效
    传参的时候不需要考虑它会被哪个修改,如果是可变类,可能需要重新拷贝出来一个新值进行传参,这会在性能上造成一定的损耗

3.String、StringBuilder、StringBuffer的区别
String是不可变的类,因此在做字符串拼接的时候性能比较差,字符串拼接一般通过StringBuilder或者StringBuffer来实现,他们提供了append和insert方法可用于字符串的拼接。StringBuilder是线程不安全的,单线程下比较快,而StringBuffer通过JVM的synchronized来保证线程安全,性能不是很高,源码如下:

@Override
public synchronized StringBuffer append(Object obj) {
    toStringCache = null;
    super.append(String.valueOf(obj));
    return this;
}

总结

本文主要介绍了String类里面常用的几个方法的源码,也扩展了关于String的一些思考问题。有许多类似的方法没有一一列举出来,感兴趣的朋友可以打开String类深挖一下。
参考:
1.https://github.com/nuptkwz/notes/tree/master/technology/java
2.https://blog.csdn.net/qq_34691713/article/details/92572843

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值