java源码学习-String

String 源码学习

1. String

1.1不变性

不可变指的是类值一旦被初始化,就不能被修改,如果被修改那将会是新的类。

        String s  = "hello";
        s = "world";

从代码上看,s的值好像被修改了但是debug来看


看地址为820

地址变为822

s的内存地址已经被修改了,也就是说 s= "world",看似简单的赋值,其实已经将s的引用指向了新的String。
补充(使用idea dubug模式,@XXX;就是变量的相对内存地址)

查看源码


string源码

通过源码可以看出

  1. string类被final修饰,说明string类不能够被继承,也就是说对任何string的操作方法,都不会被继承覆盖。
  2. string保存数据是byte[]。我们看到value也是被final修饰的,也就是说value一旦被赋值,那内存地址就无法被修改,而且value的权限是private的,外部访问不到,string也没有开放出对value进行赋值的方法,所以说value一旦产生,内存地址根本无法被修改。
    注:(在jdk8中string实现是char[] ,而在jdk9中变更成byte[] ,使用byte数组可以减少一半的内存,byte使用一个字节来存储一个char字符,char使用两个字节来存储一个char字符。只有当一个char字符大小超过0xFF时,才会将byte数组变为原来的两倍,用两个字节存储一个char字符。)

以上两点是string不变性原因,充分利用了final关键字的特性,如果你定义类时也希望是不变的,可以参考string这两点操作。

因为string的不变性,所以string的大多数操作方法,都会返回新的string,下面需要注意

        String str ="hello world";
        // 这种写法是替换不掉的,必须接受 replace 方法返回的参数才行,这样才行:str = str.replace("h","xx");
        str.replace("h","xx");
        System.out.println(str);
        str = str.replace("d", "qq");
        System.out.println(str);

1.2 字符串乱码

在平时进行二进制转化操作时,本地测试没问题,但是运行到其他机器上时,会出现字符串乱码情况,主要原因是在进行二进制转化操作时,并没有强制规定文件编码,而不同的环境默认的文件编码不一致导致。

模拟字符串乱码

输出 : nihao ?? ??

打印的结果为??,这就是常见的乱码表现形式。这时候有人说,是不是我把代码修改成 String s2 = new String(bytes,"ISO-8859-1"); 就可以了?这是不行的。主要是因为 ISO-8859-1 这种编码对中文的支持有限,导致中文会显示乱码。唯一的解决办法,就是在所有需要用到编码的地方,都统一使用 UTF-8,对于 String 来说,getBytes 和 new String 两个方法都会使用到编码,我们把这两处的编码替换成 UTF-8 后,打印出的结果就正常了。

1.3首字母大小写

如果项目被spring托管,有时候我们会通过 application.getBean(className); 这种方式得到SpringBean,这时className必须满足首字母小写。除了该场景,在反射场景下,我们也经常要使类属性的首字母小写。

str.substring(0,1).toLowerCase()+ str.substring(1);

使用 substring 方法,该方法主要是为了截取字符串连续的一部分,substring 有两个方法:

/*beginIndex :开始位置,结束位置为文本末尾 */ 
public java.lang.String substring(int beginIndex)  

/*beginIndex :开始位置,endIndex 结束位置   */ 
public java.lang.String substring(int beginIndex, int endIndex) 

substring 方法的底层使用的是字符数组范围截取的方法 :

Arrays.copyOfRange(字符数组, 开始位置, 结束位置); 

从字符数组中进行一段范围的拷贝。

1.4相等判断

我们判断相等有两种办法,equals 和 equalsIgnoreCase。后者判断相等时,会忽略大小写。

一些面试题在问:如果让你写判断两个 String 相等的逻辑,应该如何写,我们来一起看下 equals 的源码(之前的源码),整理一下思路:

public boolean equals(Object anObject) {
    // 判断内存地址是否相同
    if (this == anObject) {
        return true;
    }
    // 待比较的对象是否是 String,如果不是 String,直接返回不相等
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        // 两个字符串的长度是否相等,不等则直接返回不相等
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            // 依次比较每个字符是否相等,若有一个不等,直接返回不相等
            while (n-- != 0) {
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

目前源码
String重写Object的equals方法,先用“==”判断地址,地址相同则直接返回true;然后再比较类型,类型不同则直接返回false;最后才比较内容。
用于比较两字符串对象是否相等,如果引用相同则返回 true。否则判断比较对象是否为 String 类的实例,是的话转成 String 类型,接着比较编码是否相同,分别以 LATIN1 编码和 UTF16 编码进行比较。
代码如下:

public boolean equals(Object anObject) {
        //判断地址是否相等
        if (this == anObject) {
            return true;
        } else {
            //比较类型
            if (anObject instanceof String) {
                //比较内容
                //转成 String 类型,接着比较编码是否相同,
                //分别以 LATIN1 编 码和 UTF16 编码进行比较
                String aString = (String)anObject;
                if (this.coder() == aString.coder()) {
                    return this.isLatin1() ? StringLatin1.equals(this.value, aString.value) : StringUTF16.equals(this.value, aString.value);
                }
            }

            return false;
        }
    }

由于equals是Objec的方法,意味着任意引用类型对象都可以调用,而且,入参是Object类型,所以,不同类型是可以用equals()方法的,不会像“==”一样编译异常。
注:会遇到的一个小坑,例如:char chr = ‘a’,String str = “a”,我经常会写成str.equals(chr),而且还傻傻的等着返回true,上面说到过,两个不同类型的变量比较,equals()会直接返回false。str.equals(chr+"")倒是可以解决。

1.5替换、删除

有 replace 替换所有字符、replaceAll 批量替换字符串、replaceFirst 替换遇到的第一个字符串三种场景。


替换

replace 有两个方法,一个入参是 char,一个入参是 String,
前者表示替换所有字符,如:str.replace('a','b')
后者表示替换所有字符串,如:str.replace("a","b")两者就是单引号和多引号的区别。

需要注意的是, replace 并不只是替换一个,是替换所有匹配到的字符或字符串哦。

如果是删除 某些字符,也可以使用 replace 方法,把想删除的字符替换成 “” 即可

1.6拆分,合并

拆分我们使用 split 方法,该方法有两个入参数。第一个参数是我们拆分的标准字符,第二个参数是一个 int 值,叫 limit,来限制我们需要拆分成几个元素。如果 limit 比实际能拆分的个数小,按照 limit 的个数进行拆分。


分割

测试数据
输出

从演示的结果来看,limit 对拆分的结果,是具有限制作用的,还有就是拆分结果里面不会出现被拆分的字段。

那如果字符串里面有一些空值呢,空值是拆分不掉的,仍然成为结果数组的一员,如果我们想删除空值,只能自己拿到结果后再做操作。
但 Guava(Google 开源的技术工具) 提供了一些可靠的工具类,可以帮助我们快速去掉空值,如下:

List on = Splitter.on(":")
                .trimResults() //去掉空值
                .omitEmptyStrings() //去掉空格
                .splitToList(s);

输出:

[bbb, aaa, oo, o, asd]

合并我们使用 join 方法,此方法是静态的,我们可以直接使用。方法有两个入参,参数一是合并的分隔符,参数二是合并的数据源,数据源支持数组和 List,在使用的时候,我们发现有两个不太方便的地方:

  1. 不支持依次 join 多个字符串,比如我们想依次 join 字符串 s 和 s1,如果你这么写的话 String.join(",",s).join(",",s1) 最后得到的是 s1 的值,第一次 join 的值被第二次 join 覆盖了;
  2. 如果 join 的是一个 List,无法自动过滤掉 null 值。
// 依次 join 多个字符串,Joiner 是 Guava 提供的 API
Joiner joiner = Joiner.on(",").skipNulls();
String result = joiner.join("hello",null,"china");

List list = Lists.newArrayList(new String[]{"hello","china",null});
// 输出的结果为;
依次 join 多个字符串:hello,china
自动删除 list 中空值:hello,china

你可能感兴趣的:(java源码学习-String)