Java从入门到放弃(7)String

1.字符串的不可变性

一旦一个string对象在内存(堆)中被创建出来,他就无法被修改。特别要注意的是,String类的所有方法都没有改变字符串本身的值,都是返回了一个新的对象。如果需要一个可修改的字符串,应该使用StringBuffer 或者 StringBuilder。否则会有大量时间浪费在垃圾回收上,因为每次试图修改都有新的string对象被创建出来。

2. substring(int beginIndex, int endIndex)方法

作用: 截取字符串并返回其[beginIndex,endIndex-1]范围内的内容。

3.调用substring时发生了什么

x.substring(1,3);

因为x是不可变的,所以当调用方法给变量赋值时会指向一个新的字符串但是JDK6和JDK7的具体调用细节是不同的

在JDK6中String类包含三个成员变量 char value[], int offset,int count分别用来 存储真正的字符数组,数组的第一个位置索引以及字符串中包含的字符个数。当调用substring时会创建一个新的string对象但是这个string对象仍然指向堆中的同一个字符数组。这两个对象只有中只有count和offset 的值是不同的。

存在的问题:如果只是需要很小一段字符串,但是切割的字符串很长,就会导致这个长字符串一直在被引用,所以会无法被回收,就可能会导致内存泄露,一般使用x = x.substring(x, y) + ""解决这个问题

这个问题在JDK7中得到解决,在使用substring时会在堆内存中创建一个新的数组

4. replaceFirst、replaceAll、replace区别

replace()

在字符串中将指定的字符全部替换为另一字符

replaceFirst()、replaceAll()

用法与replace相同,但是这两个方法都是基于正则表达式替换,replaceFirst只会替换第一次出现的,replaceAll会去用正则表达式匹配整个字符串并替换

public static void main(String[] args) {

String s = "123.dwe.123";

System.out.println(s.replace(".","#"));

System.out.println(s.replaceAll(".","#"));

System.out.println(s.replaceFirst(".","#"));

}

结果

123#dwe#123

###########

#23.dwe.123

5.String对“+”的重载

String s = "a" + "b",编译器会进行常量折叠(因为两个都是编译期常量,编译期可知),即变成 String s = "ab"

对于能够进行优化的(String s = "a" + 变量 等)用 StringBuilder 的 append() 方法替代,最后调用 toString() 方法 (底层就是一个 new String())

6.字符串拼接

String作为不可变类,一旦被实例化就无法修改,所谓的字符串拼接就是重新生成一个新的字符串。

拼接字符串的方法

1)“+”拼接字符串("+"是Java提供的一个语法糖,Java本身是不支持运算符重载的)

public static void main(String[] args) {

String s = "你好";

s +="智障";

System.out.println(s);

}

2)concat

public static void main(String[] args) {

String s = "你好";

String ss = "智障";

String a = s.concat(",").concat(ss);

System.out.println(a);

}

3)StringBuffer

public static void main(String[] args) {

StringBuffer s = new StringBuffer("你好");

s.append(",").append("啊哈");

System.out.println(s);

}

4)StringBuilder(和Stringbuffer用法类似,但是StringBuffer是线程安全的,StringBuilder不是)

5)StringUtils.join( apache.commons.lang中提供的StringUtils类,其中的join方法可以拼接字符串。同时Java8的String类中也提供了一个join的方法用法和StrringUtils中的join方法类似)

public static void main(String[] args) {

String s = "ss";

String a = "aa";

System.out.println(StringUtils.join(s,",",a));

}


不建议在循环体中使用“+”拼接字符串,因为“+”的原理是每次new一个StringBuilder对象,然后进行append操作,最后通过toString()方法返回String对象,造成内存资源的浪费


效率 StringBuilder

由于字符串拼接过程中会创建新的对象,所以如果要在一个循环体中进行字符串拼接,就要考虑内存问题和效率问题。


1)、如果不是在循环体中进行字符串拼接的话,直接使用+就好了。

2)、如果在并发场景中进行字符串拼接的话,要使用StringBuffer来代替StringBuilder。


7.不同方法转成String型的区别


1.int i = 5;

2.String i1 = "" + i;

3.String i2 = String.valueOf(i);

4.String i3 = Integer.toString(i);

第三行和第四行没有任何区别,因为String.valueOf(i)也是调用Integer.toString(i)来实现的

第二行代码其实是String i1 = (new StringBuilder()).append(i).toString();,首先创建一个StringBuilder对象,然后再调用append方法,再调用toString方法。

8.switch对String的支持

其实switch只支持一种数据类型,那就是整型,其他数据类型都是转换成整型之后在使用switch的。 字符串的switch是通过equals()和hashCode()方法来实现的。会将字符串先转成hashcode,再进行equals判断。 而对char类型进行比较的时候,实际上比较的是ascii码,编译器会把char型变量转换成对应的int型变量。

9.字符串池

Java中有两种 创建字符串对象的方式,一是采用字面量的方式赋值二是通过new关键字新建一个字符串对象,

当采用字面量赋值的方式时,JVM会先去字符串池中查找是否存在这个对象,如果不存在,则在字符串池中新建这个对象,然后将这个对象的引用地址返回给字符串常量,这样对应的字符串常量会指向池中这个字符串对象;如果存在,则不创建任何对象,直接将这个对象的地址返回,赋给字符串常量

采用new关键字新建一个字符串对象时,JVM首先在字符串池中查找有没有这个字符串对象时,如果有,则不在池中再去创建这个对象,直接在堆中new一个对象,然后将堆中的这个对象的地址返回 ,如果没有,则首先在字符串池中创建一个对象,然后再在堆中创建一个字符串对象,然后将堆中的这个对象的地址返回

字符串池的优缺点:字符串池的优点就是避免了相同内容的字符串的创建,节省了内存,省去了创建相同字符串的时间,同时提升了性能,缺点就是牺牲了JVM在常量池中遍历对象所需要的时间,不过时间成本相对较低

10.静态常量池和运行时常量池

静态常量池就是class文件中的常量池,class文件中的常量池不仅仅包含字符串字面量,还包含类、方法的信息,占用class文件绝大部分空间。这种常量池主要存放两大类常量:字面量和符号引用量。而运行时常量池则是在jvm虚拟机完成类装载操作后,将class文件中的常量池载入到内存中,并保存在方法区中。

示例:

public static void main(String[] args) {

String s1 = "Hello";

String s2 = "Hello";

String s3 = "Hel" + "lo";

String s4 = "Hel" + new String("lo");

String s5 = new String("Hello");

String s6 = s5.intern();

String s7 = "H";

String s8 = "ello";

String s9 = s7 + s8;

System.out.println(s1 == s2); // true

System.out.println(s1 == s3); // true

System.out.println(s1 == s4); // false

System.out.println(s1 == s9); // false

System.out.println(s4 == s5); // false

System.out.println(s1 == s6); // true

}

s1 == s3,s3虽然是动态拼接出来的字符串,但是所有参与拼接的部分都是已知的字面量,在编译期间,这种拼接会被优化,编译器直接帮你拼好,因此String s3 = "Hel" + "lo";在class文件中被优化成String s3 = "Hello",所以s1 == s3成立。 s1 == s4当然不相等,s4虽然也是拼接出来的,但new String("lo")这部分不是已知字面量,是一个不可预料的部分,编译器不会优化,必须等到运行时才可以确定结果,结合

字符串不变定理地址肯定不同。

s1 == s9也不相等,虽然s7、s8在赋值的时候使用的字符串字面量,但是拼接成s9的时候,s7、s8作为两个变量,都是不可预料的,编译器毕竟是编译器,不可能当解释器用,不能在编译期被确定,所以不做优化,只能等到运行时,在堆中创建s7、s8拼接成的新字符串,在堆中地址不确定,不可能与方法区常量池中的s1地址相同。s4 == s5,绝对不相等,二者都在堆中,但地址不同。 s1 == s6这两个相等完全归功于intern方法,s5在堆中,内容为Hello ,intern方法会尝试将Hello字符串添加到常量池中,并返回其在常量池中的地址,因为常量池中已经有了Hello字符串,所以intern方法直接返回地址;而s1在编译期就已经指向常量池了,因此s1和s6指向同一地址,相等。



分享到:


相關文章: