String

不变性

代码demo:

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


从代码表面看,s 的值好像被修改了,但从 debug 的日志来看,其实是 s 的内存地址被修改了,也就是说 ​​s ="world"​​ 这一行看似简单的赋值,其实已经把 s 的引用指向了新的 String。

Java8源码解析-String、Long_字符串

Java8源码解析-String、Long_缓存_02

String源码:

public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
}


  1. String 类被 final 修饰,说明 String 类绝不可能被继承了,也就是说任何对 String 的操作方法,都不会被继承覆写;
  2. String 中保存数据的是一个 char 的数组 value。 value 也是被 final 修饰的,也就是说 value 一旦被赋值,内存地址是绝对无法修改的,而且 value 的权限是 private 的,外部绝对访问不到,String 也没有开放出可以对 value 进行赋值的方法,所以说 value 一旦产生,内存地址就根本无法被修改。

因为String 的大多数操作方法,都会返回新的 String,所以以下写法是不对的。

String str ="hello world !!";
// 这种写法是替换不掉的,必须接受 replace 方法返回的参数才行,这样才行:str = str.replace("l","dd");
str.replace("l","dd");


substring

类属性的首字母转化为小写一般使用​​substring​​截取字符串如下:

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:开始位置,结束位置为文本末尾。

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

equals

判断相等有​​equals​​ 和 ​​equalsIgnoreCase​​,后者判断相等时,会忽略大小写

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;
}


replace

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

replace 有两个方法,一个入参是 char,一个入参是 String,前者表示替换所有字符,如:​​name.replace('a','b')​​,后者表示替换所有字符串,如:​​name.replace("a","b")​

split

demo代码;

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 开源的技术工具) 提供了一些可靠的工具类,可以帮助快速去掉空值,如下:

String a =",a, ,  b  c ,";
// Splitter 是 Guava 提供的 API
List<String> list = Splitter.on(',')
.trimResults()// 去掉空格
.omitEmptyStrings()// 去掉空值
.splitToList(a);
log.info("Guava 去掉空格的分割方法:{}",JSON.toJSONString(list));
// 打印出的结果为:
["a","b c"]


join

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

  1. 不支持依次 join 多个字符串,比如想依次 join 字符串 s 和 s1,如果这么写的话 ​​String.join(",",s).join(",",s1)​​ 最后得到的是 s1 的值,第一次 join 的值被第二次 join 覆盖了;
  2. 如果 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


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);
}
}


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


每个人都有潜在的能量,只是很容易被习惯所掩盖,被时间所迷离,被惰性所消磨~