转载请注明出处:王亟亟的大牛之路
讲今天的内容之前温故一些理论知识?(部分理论知识来源于网上,谢谢开源大家庭)
1.什么是指针?
—指针是指向内存中的地址,该地址就是存储变量的值。
2.Java中没有了指针,那以什么东西来替代指针相应的功能?
—java中我们所谓的对象引用就是指针,只是没有像C/C++中给出了一个明确的定义。java是为了避免指针带来的使用上的麻烦,所以就使用对象的引用来代替了指针。
3.那么这些对象的引用又存放在哪?
—java中的内存分为堆内存(heap)和栈内存(stack)。堆就是用来存放对象的,而栈则是存放一些数据基本类型的值。
一.引用
平时字面量创建就是简单的
String wjj = "Hello";
JVM检测这个字面量,这里我们认为没有内容为Hello的对象存在。JVM通过字符串常量池查找不到内容为Hello的字符串对象存在,那么会创建这个字符串对象,然后将刚创建的对象的引用放入到字符串常量池中,并且将引用返回给变量wjj。
然后又给第二个对象赋值,如:
String wjj1 = "Hello";
因为在我们的常量池中已经有了hello这个对象,所以把已经存在的字符串对象的引用返回给变量wjj1。
测试下这两个对象指向同一个Hello:
String Wjj= "Hello";
String Wjj2="Hello";
System.out.println(Wjj==Wjj2);
System.out.println(Wjj.equals(Wjj2));
结果:
true
true
那除了直接 String Wjj= “Hello”;我们还用 new String的 方式,像这样:
String Wjj2=new String("Hello");
结果还是一样的给我们的Wjj2这个对象赋值了Hello,那么打印的结果会有区别么?
String Wjj= "Hello";
String Wjj2=new String("Hello");
System.out.println(Wjj==Wjj2);
System.out.println(Wjj.equals(Wjj2));
结果:
false
true
“==”和equals的区别只后会再加解释,基础还是很重要的哈哈!
我们可以看到当我们使用了new来构造字符串对象的时候,这两个变量指向的为不同的对象,不管字符串常量池中有没有相同内容的对象的引用,新的字符串对象都会创建。
二.修改值相同但地址不同的引用
对于上面使用new创建的字符串对象,如果想将这个对象的引用加入到字符串常量池,可以使用intern方法。
调用intern后,首先检查字符串常量池中是否有该对象的引用,如果存在,则将这个引用返回给变量,否则将引用加入并返回给变量。
String Wjj1=Wjj.intern();
System.out.println(Wjj1==Wjj);
结果:
true
-概念:
1.字符串常量池中存放的时引用还是对象,这个问题是最常见的。字符串常量池存放的是对象引用,不是对象。在Java中,对象都创建在堆内存中。
2.因为字符串常量池中持有了共享的字符串对象的引用,这就是说是不是会导致这些对象无法回收?
首先问题中共享的对象一般情况下都比较小。据我查证了解,在早期的版本中确实存在这样的问题,但是随着弱引用的引入,目前这个问题应该没有了。
那么String Wjj3="a"+"b"+"c";又等于"abc"么?
String Wjj= "abc";
String Wjj2="a"+"b"+"c";
System.out.println(Wjj==Wjj2);
System.out.println(Wjj.equals(Wjj2));
结果:
true
true
其实是在编译之前就已经进行了优化所以他们指向了同一个对象
三.String对象
在Java中,String对象是不可变的(Immutable)。在代码中,可以创建多个某一个String对象的别名。但是这些别名都是的引用是相同的。
比如wjj2和wjj都是”hello”对象的别名,别名保存着到真实对象的引用。所以wjj2 = wjj
String Wjj= "hello";
String Wjj2=Wjj;
System.out.println(Wjj==Wjj2);
System.out.println(Wjj.equals(Wjj2));
结果:
true
true
在Java中,唯一被重载的运算符就是字符串的拼接相关的。+,+=。除此之外,Java设计者不允许重载其他的运算符。
字符串操作的性能问题
String s1="a" ;
String s2="b";
String s3="c";
String s4=s1+s2+s3;
System.out.println(s4);
结果:
abc
要得到上面的S4,就会S1和S2拼接生成临时一个String对象t1,内容为ab,然后有t1和s3拼接生成最终我们需要的s4对象,这其中,产生了一个中间的t1,而且t1创建之后,没有主动回收,势必会占一定的空间。如果是一个很多(假设上百个,多见于对对象的toString的调用)字符串的拼接,那么代价就更大了,性能一下会降低很多。
编译器的优化处理
真的会有上面的性能代价么,字符串拼接这么常用,没有特殊的处理优化么,答案是有的,这个优化进行在编译器编译.java到bytecode时。
一个Java程序如果想运行起来,需要经过两个时期,编译时和运行时。在编译时,Java 编译器(Compiler)将java文件转换成字节码。在运行时,Java虚拟机(JVM)运行编译时生成的字节码。通过这样两个时期,Java做到了所谓的一处编译,处处运行。
Java编译器做的优化,当Java编译器遇到字符串拼接的时候,会创建一个StringBuilder对象,后面的拼接,实际上是调用StringBuilder对象的append方法。这样就不会有我们上面担心的问题了。
SO,我们来测试一下:
public static void main(String[] asa) {
String value="";
long a=System.currentTimeMillis();
for(int k=0;k<9000;k++){
value=value+k;
}
System.out.println("a的时间差= "+(System.currentTimeMillis()-a));
StringBuilder stringBuilder=new StringBuilder();
long b=System.currentTimeMillis();
for(int f=0; f<9000; f++){
stringBuilder.append(f);
}
System.out.println("b的时间差="+(System.currentTimeMillis()-b));
}
结果:
a的时间差= 1137
b的时间差=4
性能差距明显哦!!!