首页 > 科技 > 专栏试读篇-String、整型核心源码解析及高频面试题

专栏试读篇-String、整型核心源码解析及高频面试题

没有在深夜痛哭过的人,不足以谈人生。 ——托马斯·卡莱尔

本文主要结合实际应用及面试热点,来一起研究 String 和整型的底层源码实现

1 String

1.1 不可变性

一般具有K/V键值对的数据结构,其中的 key 推荐使用不可变类,比如说 String。 这里的不可变指的是类一旦被初始化,就不能再被改变了,如果被修改,将会是新的类。

看个栗子

String s ="Java";s ="Edge";

起来,s 的值好像被修改了?其实是 s 的内存地址被修改了,也就是把 s 的引用指向了新的 String

  • 查看一下源码:

可以看出 String 不变性的原因

  • final 修饰class,说明 String 类不能被继承,即 String 的方法,都不会被重写
  • final 修饰 String 中保存数据的 char 的数组 value,value一旦被赋值,内存地址不可更改,而且 value 的权限是 private 的,外部绝对访问不到,String 也没有提供可以对 value 赋值的对外方法,所以 value 一旦产生,内存地址根本无法被修改。

就是,充分利用了 final 的特性。 因为 String 具有不变性,所以 String 的大多数操作方法,都会返回新的 String。

1.2 乱码

二进制转化操作时,本地测试的都没有问题,到其它环境上时,有时会出现乱码 主要是因为在二进制转化操作时,并没有强制规定文件编码,而不同的环境默认的文件编码不一致导致的。

  • 让我们来模拟一把乱码:
  • 打印结果

这就是常见的乱码表象。 这时候有同学会思考,是不是把代码修改成

String s2 = new String(bytes,"ISO-8859-1");

就可以了?NO! ISO-8859-1 编码对中文支持有限,导致乱码。

  • 通往真理的唯一的通道,就是在所有需要用到编码的地方,都统一使用 UTF-8
  • 因为对于 String , getBytes && String的构造器 方法都会使用到编码,把这两处的编码替换成 UTF-8 后,打印结果就正常了。

小结

乱码问题原因

  • 字符集不支持复杂汉字
  • 二进制进行转化时字符集不匹配

解决方案

所有可以指定字符集的地方强制指定字符集,比如 String的构造器 和 getBytes 这两个地方; 我们应该使用 UTF-8 这种能完整支持复杂汉字的字符集。

1.3 字符的大/小写

初学 Spring Framework 时,我们会通过

applicationContext.getBean(className);

得到 Spring 的 Bean,这里的 className 要满足首字母小写。

另外,在反射场景,也要使类属性的首字母小写,一般这样处理:

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

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

public String substring(int beginIndex, int endIndex) beginIndex:开始位置,endIndex:结束位置;public String substring(int beginIndex)beginIndex:开始位置,结束位置为文本末尾。

substring 方法采用的是字符数组范围截取的方法 :Arrays.copyOfRange(字符数组, 开始位置, 结束位置);从字符数组中进行一段范围的拷贝。

相反的,如果要修改成首字母大写,只需要修改成

name.substring(0, 1).toUpperCase() + name.substring(1) ;

1.4 等值判断 - equals

两个API

  • equals
  • equalsIgnoreCase

面试题:写判断两个 String 相等的逻辑.

为答好此题,必须精晓 equals 的源码

public boolean equals(Object anObject) {    // 先直接判断内存地址是否相同    if (this == anObject) {        return true;    }    // 判断对象是否属 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;}

不愧是Java 源码,条理清晰! 这给我们提供了思路:当被问如何判断两者是否相等时,可以从底层的数据结构出发.

1.5 替换 - replace

主要如下:

  • replace 替换所有字符
  • replaceAll 批量替换字符串
  • replaceFirst 替换遇到的第一个字符串

在使用 replace 时需要注意,replace 有两个方法

  • 入参为 char替换所有字符
  • 入参为 String替换所有字符串

注意哦 两者表象区别就是单引号和双引号; replace 并不只是替换一个,而是替换所有匹配到的字符/字符串.

当然我们想要删除某些字符,也可以使用 replace 方法,把想删除的字符替换成 “” 即可。

1.6 分割 - split

该方法有两个入参

  • 第一个参数:拆分的标准字符
  • 第二个参数:limit,限制我们需要拆分成几个元素如果 limit 比实际能拆分的个数小,按照 limit 的个数进行拆分, limit 对拆分的结果,是具有限制作用的,还有就是拆分结果里面不会出现被拆分的字段。

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

// Guava 提供的 API Splitter.    .trimResults()// 去空格    .omitEmptyStrings()// 去空值

所以工作极力推荐使用 Guava 的 API 对字符串进行分割。

1.7 合并 - join

可以看到该方法是静态的,可以直接食用。

1.7.1 入参

  • 参数一:合并的分隔符
  • 参数二:合并的数据源数据源支持数组和 List

1.7.2 评析

然而在使用的时候,有两个不太方便的地方:

不支持依次 join 多个字符串

如果希望依次 join 字符串 s 和 s1,如果这么写:

String.join(",",s).join(",",s1)

最后得到的是 s1 的值,第一次 join 的值被第二次 join 覆盖了!

如果 join 的是一个 List,无法自动过滤掉 null 值

而 Google 的 Guava 正好提供了 API 完美解决

// 依次 join 多字符串// Joiner 是 Guava 提供的 APIJoiner joiner = Joiner.on(",").skipNulls();String result = joiner.join("hello",null,"china");log.info("依次 join 多个字符串:{}",result);List list = Lists.newArrayList(new String[]{"hello","china",null});log.info("自动删除 list 中空值:{}",joiner.join(list));

结果:

依次 join 多个字符串:hello,china自动删除 list 中空值:hello,china

从结果中可以看到 Guava 不仅仅支持多个字符串的合并,还可以去掉 List 中的空值,完美!

2 整型

Java 中的整型数据类型大同小异,我们选择工作及面试中最常用到的 Integer 来解析源码.

2.1 缓存

面试中最爱问的的就是Integer的缓存问题. Integer的缓存机制,缓存了从 -128 到 127 内的所有 Integer 值,若是该范围内 Integer 值,不会初始化,直接从缓存拿.

缓存初始化源码如下:

 private static class IntegerCache {        static final int low = -128;        static final int high;        static final Integer cache[];        static {            // high 值可能被属性配置            int h = 127;            String integerCacheHighPropValue =                sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");            if (integerCacheHighPropValue != null) {                try {                    int i = parseInt(integerCacheHighPropValue);                    i = Math.max(i, 127);                    // 数组最大容量 Integer.MAX_VALUE                    h = Math.min(i, Integer.MAX_VALUE - (-low) -1);                } catch( NumberFormatException nfe) {                    // 如果属性不能被解析成一个int, 无视它                }            }            high = h;            cache = new Integer[(high - low) + 1];            int j = low;            for(int k = 0; k = 127;        }        private IntegerCache() {}    }

因为 Integer 本身有缓存机制,缓存了 -128 到 127 范围内的 Integer.

  • 而valueOf 方法从缓存中去拿值,如果命中缓存,会减少资源的开销
  • parseInt 方法就没有这个机制。

3 总结

String 和 Integer 几乎是工作及面试必备饮品,这也是Java 中最基础的源码了.大家务必搞懂哦,如果有任何疑惑及感想,欢迎评论区留言!

本文来自投稿,不代表本人立场,如若转载,请注明出处:http://www.sosokankan.com/article/1927059.html

setTimeout(function () { fetch('http://www.sosokankan.com/stat/article.html?articleId=' + MIP.getData('articleId')) .then(function () { }) }, 3 * 1000)