没有在深夜痛哭过的人,不足以谈人生。 ——托马斯·卡莱尔
本文主要结合实际应用及面试热点,来一起研究 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