Java当中的String类(从源码角度来看(上))

Java当中的String类(从源码角度来看(上)),第1张

文章目录
  • 概述
  • String的创建
  • 判断功能的方法
    • equals()方法
    • equalsIgnoreCase()方法
    • contains()方法
    • isEmpty()方法
    • startsWith()和endWith()方法
    • compareTo()方法
  • 获取功能的方法
    • length()方法
    • charAt()方法
    • indexOf()方法
    • substring()方法

概述

字符串是由多个字符组成的一串数据(字符序列)的字符串常量,java中所有字符串都是String类的实例(对象)。

字符串的值是不能改变的(一旦给定字符串的值,值就是不能改变的)因为它的底层用的是一个final修饰的char 数组

为什么是不可变的,用语言的表达来是苍白的,直接看源码:

哟,看时数组,我们都知道数组的长度是不可变的,那么String的底层就是将字符串里面的没有字母取出来,以数组的形式进行保存。

那么我们平时在写字符串的时候,感受完全是字符串是可变的。
这是因为当我们在改变字符串的长度的时候,底层代码会另创建一个新的数组,将之前的内容也保存进去。(看下面动图里面的value值,每次在增加字符串里面的元素的时候,就是另外创建了一个数组)

String的创建

创建字符串是有两种创建的方式:

  1. String s = “abc”;

  2. 一旦在堆中创建对象,值存储在堆内存的对象中。
    String s = new String(“abc”);

下面我就对这两种创建做一个演示:

信息有一些小伙伴会对第一个输出为true有些疑惑,前面不刚刚都说了,字符串的底层是数组呀,数组用==号的话,可是看地址的。那么这里出现了true是不是也说明了一个问题,s1和s2的地址就是一个呢?

对,这个想法是对的。在用String s1 = “abc”的时候就是先在栈中创建一个对String类的对象引用变量s,然后去字符串常量池中查找有没有“abc”。如果没有的话才在常量池中添加“abc”,s1引用变量指向常量池中的“abc”,如果常量池中已经存在了的话,则直接指向改地址即可,不用再重复创建。
所以在这s1和s2指向的就是同一东西。这也是java的一种空间的优化。

判断功能的方法 equals()方法

判断两各String字符串是否相等的。

equalsIgnoreCase()方法

这个也是判断两个字符串是否相等的语法,但是这里的区分是不分大小写的,比如我们经常在登录的时候,输入验证码,就是不区分大小写字母的。

contains()方法

判断方法里面的字符串是外面字符串的子串。

返回true/false后面的更上层的源码我就不再这做具体演示了。

isEmpty()方法

用来判断一个字符串是否为空。

若字符串为空就返回true,否则就返回false。

startsWith()和endWith()方法

startsWith()方法使用的是判断括号内的字符串是否是需要判断的字符串的开始
endWith()方法是用来判断括号内的字符时需要判断的字符串的结尾。

这两个方法在底层其实都用了一个方法进行判断

startWith()直接调用的代码:

    public boolean startsWith(String prefix) {
        return startsWith(prefix, 0);
        //直接调用了startsWith方法,从初位置开始
    }

endWith()方法指定调用的代码:

    public boolean endsWith(String suffix) {
        return startsWith(suffix, value.length - suffix.value.length);
        //表示suffix这个数组,然后从用数组长度减去传入参数的位置开始。
    }
compareTo()方法

用于判断前面字符是否比后面的大或者小。这里是将字母转换为ASCII码进行比较的。

这里有点说头,先给大家看一下测试代码和源码:

来在这源码给大家分析一波:

    public int compareTo(String anotherString) {
        int len1 = value.length;  //获得调用这个方法的字符长度
        int len2 = anotherString.value.length; //获得传入的字符长度
        int lim = Math.min(len1, len2); //将两个字符小的长度记录下来
        char v1[] = value;  //调用其他方法,将调用方法的字符串以数组的形式保存下来
        char v2[] = anotherString.value;//同上面的,这个是将传入的字符串以数组的形式保存下来

        int k = 0;
        while (k < lim) {  //以两个字符串最小的长度做判定
            char c1 = v1[k]; //拿到当前k位置的两个字符串中的元素
            char c2 = v2[k];
            if (c1 != c2) {  //若当前同一位置的字符不相等的时候
                return c1 - c2; //return出两个字母ASCII码的差
            }
            k++;
        }
        return len1 - len2; 
        //以最小的字符长度循环结束了,都没return的话。就直接
        // return调用方法的字符串长度-传入的字符串长度
    }
获取功能的方法 length()方法

这方法就是返回字符串的长度的方法。

这个很简单,我也就不多说什么了

charAt()方法

使用这个方法是首先给传入一个int型的参数,返回字符串中传入参数的字符。

多余的话都不说了,一切都在代码中:

indexOf()方法

indexOf方法传入的是字符,然后在检索字符串,返回字符在字符串当中出现的索引。

看代码:

当传入两个数的时候(一个要查询的字符,一个开始的索引位置)

下面我们来对这个底层indexOf()方法进行一个大致分析:

    public int indexOf(int ch, int fromIndex) {
        final int max = value.length;   //获得字符串的长度
        if (fromIndex < 0) {            //若传进来的索引小于0
            fromIndex = 0;              //就将它设置为0
        } else if (fromIndex >= max) {  //若给的索引大于字符串的长度,就返回-1
            // Note: fromIndex might be near -1>>>1.
            return -1;
        }

        if (ch < Character.MIN_SUPPLEMENTARY_CODE_POINT) { //要求传入的索引要小于一个特定的值
            // handle most cases here (ch is a BMP code point or a
            // negative value (invalid code point))
            final char[] value = this.value;
            for (int i = fromIndex; i < max; i++) {  //这就是个循环了,判断出它的值为到底是第几个的
                if (value[i] == ch) {
                    return i;
                }
            }
            return -1;
        } else {
            return indexOfSupplementary(ch, fromIndex);
        }
    }
substring()方法

这个方法是一个截取字符串的方法,将输入的索引后面的字符串截取出来,保存到一个新的字符串当中来。

也可以给它传入传入两个索引,那就返回这两个索引之间的字符串。

那我们接下来就对这两个方法调用的同一个方法进行分析一波:

    public String(char value[], int offset, int count) {
        if (offset < 0) {   //开始截取的索引若小于0,就抛异常
            throw new StringIndexOutOfBoundsException(offset);
        }
        if (count <= 0) {  //count是后面的索引-前面的索引
            if (count < 0) {    //若小于0,就抛异常
                throw new StringIndexOutOfBoundsException(count);
            }
            if (offset <= value.length) {  //若前面的索引小于数组的长度
                this.value = "".value;   //就返回一个""的字符串
                return;
            }
        }
        // Note: offset or count might be near -1>>>1.
        if (offset > value.length - count) {        //若前面的索引大于数组长度,就抛异常
            throw new StringIndexOutOfBoundsException(offset + count);
        }
        this.value = Arrays.copyOfRange(value, offset, offset+count);
        //一切正常的话,就继续向下执行调用copyOfRange方法
        //传入字符串,前面的索引,后面的索引
    }

来,继续分析源代码:

    public static char[] copyOfRange(char[] original, int from, int to) {
        int newLength = to - from;  //记录需要创建的字符串长度
        if (newLength < 0) //若小于0,抛异常
            throw new IllegalArgumentException(from + " > " + to);
        char[] copy = new char[newLength];  //创建截取的字符串长的数组
        System.arraycopy(original, from, copy, 0,
                Math.min(original.length - from, newLength));  //接下来,就调用了系统的本地代码了
        return copy;    //最后返回处理好的字符串(这里还是数组)
    }

本篇博客也就到此为止了,应为整理源码有点消耗时间,明天一定写完

这也是我重读javase基础的一个系列,比起当时初学的时候,现在看问题多了个高度,理解什么也相对轻松一点全面一些。学习起来更加偏向阅读源码来看,所以多为大家分享看源码。
但毕竟学过时间也较长,有什么不对和漏缺的地方,希望大家指出。
欢迎大家在评论区讨论

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/876770.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-13
下一篇 2022-05-13

发表评论

登录后才能评论

评论列表(0条)

保存