String 和 Long 的底层源码实现

1.1 String类,底层实现
public final class String 
implements java.io.Serializable, Comparable<String>, CharSequence {    
   private final char value[]; 
   ...
}

可以看出来两点:

  1. final 修饰的类,不能被继承,也就是说任何对 String 的操作方法,都不会被继承覆写;
  2. String 中保存数据的是一个 char 的数组,是被 final 修饰的,也就是说 value 一旦被赋值,内存地址是绝对无法修改的,而且 value 的权限是 private 的,外部绝对访问不到,String 也没有开放出可以对 value 进行set赋值的方法,所以说 value 一旦产生,内存地址就根本无法被修改。
    以上两点就是 String 不变性的原因,充分利用了 final 关键字的特性,如果你自定义类时,希望也是不可变的,也可以模仿 String 的这两点操作。
1.2 字符串乱码

由于在二进制转化操作时,并没有强制规定文件编码,而不同的环境默认的文件编码不一致导致了中文乱码问题。
如:

String str ="nihao 你好 喬亂"; 
// 字符串转化成 byte 数组 
byte[] bytes = str.getBytes("ISO-8859-1"); 
// byte 数组转化成字符串 
String s2 = new String(bytes,"ISO-8859-1"); 
log.info(s2); 
// 结果打印为: nihao ?? ??

对于 String 来说,getBytes 和 new String 两个方法都会使用到编码,我们把这两处的编码替换成 UTF-8 后,打印出的结果就正常了

1.3 首字母大小写

在反射场景下面,我们也经常要使类属性的首字母小写,这时候我们一般都会这么做:

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

substring 有两个方法:

  1. public String substring(int beginIndex, int endIndex) beginIndex:开始位置,endIndex:
    结束位置;
  2. public String substring(int beginIndex) beginIndex:开始位置,结束位置为文本末尾。
  3. substring 方法的底层使用的是字符数组范围截取的方法 : Arrays.copyOfRange(字符数组, 开始 位置, 结束位置); 从字符数组中进行一段范围的拷贝。
1.4 String中相等判断equal

equals 和 equalsIgnoreCase。后者判断相等时,会忽略大小写, 近期看见一些面试题在问:如果让你写判断两个 String 相等的逻辑,应该如何写

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 底层的数据结构是 char的数组一样,判断相等时,就挨个比较 char 数组中的字符是否相等即可。

1.5 替换、删除

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

public void testReplace(){ 
String str ="hello word !!"; 
log.info("替换之前 :{}",str); 
str = str.replace('l','d'); 
log.info("替换所有字符 :{}",str); 
str = str.replaceAll("d","l"); 
log.info("替换全部 :{}",str); 
str = str.replaceFirst("l",""); 
log.info("替换第一个 l :{}",str); }
//输出的结果是: 
替换之前 :hello word !! 
替换所有字符 :heddo word !! 
替换全部 :hello worl !! 
替换第一个 :helo worl !!
1.6 拆分和合并

拆分我们使用 split 方法,该方法有两个入参数。第一个参数是我们拆分的标准字符,第二个参 数是一个 int 值,叫 limit,来限制我们需要拆分成几个元素。

String s ="boo:and:foo"; 
// 我们对 s 进行了各种拆分,演示的代码和结果是: 
s.split(":") 结果:["boo","and","foo"] 
s.split(":",2) 结果:["boo","and:foo"] 
s.split(":",5) 结果:["boo","and","foo"] 
s.split(":",-2) 结果:["boo","and","foo"] 
s.split("o") 结果:["b","",":and:f"] 
s.split("o",2) 结果:["b","o:and:foo"]

那如果字符串里面有一些空值呢,拆分的结果如下:

String a =",a,,b,"; a.split(",") 
结果:["","a","","b"]

但 Guava(Google 开源的技术工具) 提供了一些可靠的工具类,可以帮助我们快速去掉空值,如下:

List<String> list = Splitter.on(',') 
                    .trimResults()// 去掉空格 
                    .omitEmptyStrings()// 去掉空值 
                    .splitToList(a); 
log.info("Guava 去掉空格的分割方法:{}",JSON.toJSONString(list)); 
// 打印出的结果为: ["a","b c"]
1.7 组合使用String.join()方法
String result = String.join("-",“a”,“b”,“c”,“d”);
输出结果如下:a-b-c-d
也可使用如下方式:
String[] arr = {“a”,“b”,“c”,“d”};
String result = String.join("-",arr);
输出结果如下:a-b -c-d

如果 join 的是一个 List,无法自动过滤掉 null 值。
而 Guava 正好提供了 API,解决上述问题,我们来演示一下:

// 依次 join 多个字符串,Joiner 是 Guava 提供的 API 
Joiner joiner = Joiner.on(",").skipNulls(); 
String result = joiner.join("hello",null,"china"); 
log.info("依次 join 多个字符串:{}",result); 
List<String> list = Lists.newArrayList(new String[]{"hello","china",null}); 
log.info("自动删除 list 中空值:{}",joiner.join(list)); 
// 输出的结果为; 
依次 join 多个字符串:hello,china 
自动删除 list 中空值:hello,china
1.8 Long的缓存

Long 最被我们关注的就是 Long 的缓存问题,Long 自己实现了一种缓存机制,缓存了从 -128 到 127 内的所有 Long 值,如果是这个范围内的 Long 值,就不会初始化,而是从缓存中拿,缓存初始化源码如下:

private static class LongCache { 
private LongCache(){} 
// 缓存,范围从 -128 到 127,+1 是因为有个 0 
static final Long cache[] = new Long[-(-128) + 127 + 1]; 
// 容器初始化时,进行加载 
static { 
  // 缓存 Long 值,注意这里是 i - 128 ,所以再拿的时候就需要 + 128 
  for(int i = 0; i < cache.length; i++) 
     cache[i] = new Long(i - 128); 
} 
}
1.9 为什么使用 Long 时,大家推荐多使用 valueOf 方法,少使用 parseLong 方法

答:因为 Long 本身有缓存机制,缓存了 -128 到 127 范围内的 Long,valueOf 方法会从缓存中去拿值,如果命中缓存,会减少资源的开销,parseLong 方法就没有这个机制。

2.0 如何解决 String 乱码的问题

答:乱码的问题的根源主要是两个:字符集不支持复杂汉字、二进制进行转化时字符集不匹配,
所以在 String 乱码时我们可以这么做:

  1. 所有可以指定字符集的地方强制指定字符集,比如 new String 和 getBytes 这两个地方;
  2. 我们应该使用 UTF-8 这种能完整支持复杂汉字的字符集。
2.1 为什么大家都说 String 是不可变的

答:主要是因为 String 和保存数据的 char 数组,都被 final 关键字所修饰,所以是不可变的,具体细节描述可以参考上文。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。