- 浏览: 218510 次
- 性别:
- 来自: 广州
文章分类
- 全部博客 (397)
- j2se (28)
- nio (3)
- 易错点 (3)
- 面试ssh (9)
- ssh整合 (11)
- jbpm+spring (2)
- js (15)
- 高级技术 (59)
- swing (3)
- 数据库 (16)
- hibernate (18)
- spring (19)
- 开发网站知识点 (9)
- jbpm (4)
- json (5)
- 设计模式 (22)
- 自定义标签 (1)
- j2ee (9)
- lucene (3)
- cahce (11)
- maven (5)
- html5 (1)
- 多数据源 (10)
- 页面聊天 (9)
- 富客户端 (1)
- android (13)
- aop+拦截器+jms (13)
- 框架整合 (1)
- 非阻塞io (24)
- 暂时不看 (13)
- webservice (3)
- oracle (3)
- 算法 (4)
- 协程 (2)
- netty (1)
- 爬虫 (0)
- 高级基础 (1)
- JVM调优总结 (12)
- 知识点技巧 (1)
- REST (0)
- 基础 io (2)
- dubbo (8)
- 线程 (1)
- spring源码 (2)
- git (1)
- office (2)
最新评论
-
sjzcmlt:
,写的挺好的啊
一个完整的负载均衡的例子 . -
他大姨妈:
网上大部分例子都是直接通过IdleStateHandler来实 ...
Netty的超时机制 心跳机制
痴情研究java内存中的对象
前记:
几天前,在浏览网页时偶然的发现一道以前就看过很多遍的面试题,题目是:“请说出‘equals’和‘==’的区别”,当时我觉得我还是挺懂的,在心里答了一点(比如我们都知道的:‘==’比较两个引用是否指向同一个对象,‘equals’比较两个对象的内容),可是总觉得心里有点虚虚的,因为这句话好像太概括了,我也无法更深入地说出一些。于是看了几篇别人的技术博客,看完后我心里自信地说,我是真的懂了;后来根据我当时的理解,就在eclipse中敲了些代码验证一下,发现有些运行的结果和我预期的又不一样,怎么找原因都找不到,呵呵~,这时就感觉太伤自尊了,于是我觉得我真的还是不懂得,又去上网查找答案,呵呵,这个问题花了我整整三天的时间,现在想把我的一些总结写下来,以达到检测自己的目的,也欢迎大家浏览、批评、指正。
注:本文不仅研究类类型的对象,还研究基本数据类型
线索:
我想采用实例代码驱动的方式来一步步地分析,这也符合我们探知新事物的过程。
一、基本数据类型的内存分配
代码1:
Java代码
1.int p1=1000;
2.static int p2=1000;
3.public void myTest(){
4.System.err.println("****************Integer*********************");
5.int i1=1000;
6.int i2=1000;
7.Integer i3=1000;
8.Integer i4=1000;
9.Integer i5=100;
10.Integer i6=100;
11.Integer i7=new Integer(1000);
12.Integer i8=new Integer(1000);
13.System.err.println(i1==p1); //true(输出结果) 1(编号,便于分析)
14.System.err.println(i1==p2); //true 2
15.System.err.println(i1==i2); //true 3
16.System.err.println(i3==i4); //false 4
17.System.err.println(i5==i6); //true 5
18.System.err.println(i7==i8); //false 6
19.System.err.println(i1==i3); //true 7
20.System.err.println(i1==i7); //true 8
21.System.err.println(i3==i7); //false 9
22.System.err.println("****************Integer*********************");
23.}
int p1=1000;
static int p2=1000;
public void myTest(){
System.err.println("****************Integer*********************");
int i1=1000;
int i2=1000;
Integer i3=1000;
Integer i4=1000;
Integer i5=100;
Integer i6=100;
Integer i7=new Integer(1000);
Integer i8=new Integer(1000);
System.err.println(i1==p1); //true(输出结果) 1(编号,便于分析)
System.err.println(i1==p2); //true 2
System.err.println(i1==i2); //true 3
System.err.println(i3==i4); //false 4
System.err.println(i5==i6); //true 5
System.err.println(i7==i8); //false 6
System.err.println(i1==i3); //true 7
System.err.println(i1==i7); //true 8
System.err.println(i3==i7); //false 9
System.err.println("****************Integer*********************");
}
看到上面的输出结果,如果你还是有些不能理解的,那就耐心地接着看我的分析吧。
分析:
编号1:在java编译时期,当编译到“int p1=1000; ”时会在栈中压入1000,其实后面的p2,i1,i2都是指向这个1000,这样可以提高java的性能,所以编号1、编号2、编号3的输出结果都是true.其实char,float,double等基本数据类型都是这样的。
编号2、编号3:同编号1
编号4:这是java中的自动装箱机制,将基本数据类型int自动转为类类型Integer,这是jdk1.5以上才有的功能,jdk1.5以下编译时会报错。自动装箱时java底层会调用Integer.valueOf(int i)方法自动装箱,下面我们来看看Integer.valueOf(int i)的源码吧:
Java代码
1./**
2.* @param i an <code>int</code> value.
3. * @return a <tt>Integer</tt> instance representing <tt>i</tt>.
4. * @since 1.5
5. */
6. public static Integer valueOf(int i) {
7. if(i >= -128 && i <= IntegerCache.high)
8. return IntegerCache.cache[i + 128];
9. else
10. return new Integer(i);
11. }
/**
* @param i an <code>int</code> value.
* @return a <tt>Integer</tt> instance representing <tt>i</tt>.
* @since 1.5
*/
public static Integer valueOf(int i) {
if(i >= -128 && i <= IntegerCache.high)
return IntegerCache.cache[i + 128];
else
return new Integer(i);
}
注:分析源码我们知道IntegerCache.high其实就是127,在IntergerCache的静态块中定义的。
源码的意思是当i的值在-128—127之间时会返回IntegerCache.cache[]中的对象,其他的新建一个Integer对象。其实Integer类是这样实现的:考虑到-128—127之间的对象经常使用,就在Integer创建时将值在-128—127之间的对象先创建好,放在池中,以后要使用时,这些对象就不用重新创建了,目的在于提高性能。其实这种机制在Character中也用到了,Character是创建ASCII在0—127之间的对象。补充说明:Integer创建的对象引用在栈中,对象的内容在堆区,栈中的值是堆中对象的地址。Character、Long、Short等包装类都是这样的。所以编号4的输出结果是false,因为值大于127,java新创建了一个对象。
编号5:因为值在-128—127之间,所以两个引用指向的是堆区的同一个对象。
编号6:当使用new创建对象时,都会新创建一个对象,即在栈中创建一个引用,在堆中创建该对象,引用指向对象。
编号7:这种情况有些人可能会不太清楚,其实这是java的自动拆箱机制,当int和Integer发生操作时,Integer类型对象会自动拆箱成int值,这时比较的是两个int值,而我们前面分析了,int值都会指向常量池中的数据,所以,两者指向的是同一块空间。结果编号7输出true
编号8:同编号7,也是Integer的自动拆箱。
编号9:我想,分析了这么多,编号9不用我说,你也应该懂了,呵呵,这里就不赘述了哦~
分析了这么多,终于第一块代码分析完了。
二、String类型的内存分配
大家都知道String类型是类类型,不过String类型是一个特殊的类类型,那它特殊在哪呢?
代码2:
Java代码
1. System.err.println("****************string*********************");
2. String s1="abc";
3. String s2="abc";
4. String s3=new String("abc");
5. String s4=new String("abc");
6. System.err.println(s1==s2);//true (输出结果) 1(编号)
7. System.err.println(s3==s4);//false 2
8. System.err.println(s1==s3);//false 3
9.
10. String a = "abc";
11.String b = "ab";
12.String c = b + "c";
13.System.err.println(a==c);//false 4
14.
15. String s5 = "123";
16. final String s6="12";
17. String s7=s6+"3";
18. System.err.println(s5 == s7);//true 5
19. System.err.println("****************string*********************");
System.err.println("****************string*********************");
String s1="abc";
String s2="abc";
String s3=new String("abc");
String s4=new String("abc");
System.err.println(s1==s2);//true (输出结果) 1(编号)
System.err.println(s3==s4);//false 2
System.err.println(s1==s3);//false 3
String a = "abc";
String b = "ab";
String c = b + "c";
System.err.println(a==c);//false 4
String s5 = "123";
final String s6="12";
String s7=s6+"3";
System.err.println(s5 == s7);//true 5
System.err.println("****************string*********************");
编号1:String类型是一个很特殊的类型,当我们使用String str=”abc”;这种定义方法时,”abc”会放入常量池中,以后如果再有定义String str2=”abc”时,其实str和str2指向的是常量池中同一个对象。而只有当使用new创建时才会每次都创建一个新的对象。(我觉得这是String类型和其他类类型的特殊之处)
编号2、编号3:编号1已经分析了。
编号4:执行到 String c = b + "c"; 这一句时,java底层会先创建一个StringBuilder对象,封装b,接着再加上“c”,最后再创建一个String对象,将StringBuilder中的值赋给该String对象,用c来指向它。.其实此时的c指向的对象已经不是a指向的对象了。
编号5:当用final修饰后,s6就变为了常量,在常量池中创建“12”,当执行到String s7=s6+"3";时,编译器直接就把s6当成了“12”,s7此时就已是“123”,它指向常量池中的“123”,所以s5和s7指向的是同一个对象,输出为true。
三、StringBuilder,StringBuffer,String的对比
(一)String
String类型的值是不可变的,听到这句话后可能你会有疑问,我们的String对象可以重新赋值呀,这里有两种情况,情况一:String str=”abc”; , 情况二:String str=new String(“abc”);采用情况一重新赋值时,java会先看常量池中有没有“abc”,如果有则直接指向它,如果没有,在编译时就创建一个常量放入常量池中;对于情况二:str则重新指向一个先创建的对象,该新对象在堆中。下面提出问题:为什么String是不可变的呢?我们来看看String的源码:
Java代码
1.public final class String
2. implements java.io.Serializable, Comparable<String>, CharSequence
3.{
4. /** The value is used for character storage. */
5. private final char value[];
6.
7. /** The offset is the first index of the storage that is used. */
8. private final int offset;
9.
10. /** The count is the number of characters in the String. */
11. private final int count;
12. //••••••••••••••••••••
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence
{
/** The value is used for character storage. */
private final char value[];
/** The offset is the first index of the storage that is used. */
private final int offset;
/** The count is the number of characters in the String. */
private final int count;
//••••••••••••••••••••
我们看到String类型是用一个用final修饰的char数组来存储字符串的,所以String类型是不可变的,(其实Short,Character,Long等包装类型也是这样实现的),根据上面对String类型的分析,如果要改变String的值,就要重新创建一个对象,这无疑性能会很差。为了优化String,sun公司添加了StringBuffer,在jdk1.5之后又添加了StringBuilder。
(二)下面我们来分析一下StringBuffer
StringBuffer作为字符串缓冲类,当进行字符串拼接时,不会重新创建一个StringBuffer对象,而是直接在原有值后面添加,因为StringBuffer类继承了AbstractStringBuffer类,分析后者的源码后,我们发现存储字符串的char[]没有被final修饰。至于StringBuffer类是怎样扩充自己的长度的,我们可以参考它的append()方法,这里不再赘述。不过一定要提出的是:StringBuffer是线程安全的,它的方法体是被synchronized修饰了的。
(三)StringBuilder有是怎么样的呢?
StringBuilder基本实现了StringBuffer的功能,最大的不同之处在于StringBuilder不是线程安全的。
(四)String、StringBuffer、StringBuilder的性能比较
代码三:
Java代码
1. StringBuffer b = new StringBuffer("abc");
2.long t3 = System.currentTimeMillis();
3.for (int i = 0; i < 1000000; i++) {
4. b = b.append("def");
5.}
6.long t4 = System.currentTimeMillis();
7.System.out.println("1000000次拼接,StringBuffer所花时间为:" + (t4 - t3));
8.System.out.println("*************************************");
9.StringBuilder c = new StringBuilder("abc");
10.long t5 = System.currentTimeMillis();
11.for (int i = 0; i < 1000000; i++) {
12. c = c.append("def");
13.}
14.long t6 = System.currentTimeMillis();
15.System.out.println("1000000次拼接,StringBuilder所花时间为:" + (t6 - t5));
16.System.out.println("*************************************");
17.String S1 = "abc";
18.long t1 = System.currentTimeMillis();
19.for (int i = 0; i < 10000; i++) {
20. S1 += "def";
21.}
22.long t2 = System.currentTimeMillis();
23.System.out.println("10000次拼接,String所花时间为:" + (t2 - t1));
StringBuffer b = new StringBuffer("abc");
long t3 = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
b = b.append("def");
}
long t4 = System.currentTimeMillis();
System.out.println("1000000次拼接,StringBuffer所花时间为:" + (t4 - t3));
System.out.println("*************************************");
StringBuilder c = new StringBuilder("abc");
long t5 = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
c = c.append("def");
}
long t6 = System.currentTimeMillis();
System.out.println("1000000次拼接,StringBuilder所花时间为:" + (t6 - t5));
System.out.println("*************************************");
String S1 = "abc";
long t1 = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
S1 += "def";
}
long t2 = System.currentTimeMillis();
System.out.println("10000次拼接,String所花时间为:" + (t2 - t1));
实验结果为:
Java代码
1.1000000次拼接,StringBuffer所花时间为:203
2.*************************************
3.1000000次拼接,StringBuilder所花时间为:79
4.*************************************
5.10000次拼接,String所花时间为:640
1000000次拼接,StringBuffer所花时间为:203
*************************************
1000000次拼接,StringBuilder所花时间为:79
*************************************
10000次拼接,String所花时间为:640
显然,StringBuilder的性能最好,String的性能最差,而且差很多;不过StringBuffer的线程安全性很好,性能也比较接近StringBuilder,所以我推荐的选择使用顺序为:StringBuffer>StringBuilder>String;
四、java传参
下面我们我看一段代码,不过有点长,请大家有点耐心哦~
代码四:
Java代码
1.public class VariableTest {
2.public static void main(String[] args) {
3.VariableTest t = new VariableTest();
4. t.test();
5. }
6.
7. class Point {
8. int x;
9. String y;
10. StringBuffer sb;
11. public Point(int x, String y, StringBuffer sb) {
12. this.x = x;
13. this.y = y;
14. this.sb = sb;
15. }
16. }
17.
18. public void test() {
19. int i = 1;
20. String str = "abc";
21. StringBuffer bs = new StringBuffer("abc");
22. Point p = new Point(1, "2", new StringBuffer("abc"));
23. System.out.println("***********函数调用之前****************");
24. System.out.println("i为:" + i);
25. System.out.println("str为:" + str);
26. System.out.println("bs为:" + bs);
27. System.out.println("p的x为:" + p.x + " p的y为:" + p.y
28. + " p的sb为:" + p.sb);
29. change(i, str, bs, p);
30. System.out.println("***********函数调用之后****************");
31. System.out.println("i为:" + i);
32. System.out.println("str为:" + str);
33. System.out.println("bs为:" + bs);
34. System.out.println("p的x为:" + p.x + " p的y为:" + p.y
35. + " p的sb为:" + p.sb);
36. }
37.
38. public void change(int p1, String p2, StringBuffer p3, Point p4) {
39. p1 = 2;
40. p2 = "I have changed!";
41. p3 = p3.append(" I have changed!");
42. p4.x = 5;
43. p4.y = "I have changed!";
44. p4.sb = p4.sb.append(" I have changed!");
45. }
46.
47.}
public class VariableTest {
public static void main(String[] args) {
VariableTest t = new VariableTest();
t.test();
}
class Point {
int x;
String y;
StringBuffer sb;
public Point(int x, String y, StringBuffer sb) {
this.x = x;
this.y = y;
this.sb = sb;
}
}
public void test() {
int i = 1;
String str = "abc";
StringBuffer bs = new StringBuffer("abc");
Point p = new Point(1, "2", new StringBuffer("abc"));
System.out.println("***********函数调用之前****************");
System.out.println("i为:" + i);
System.out.println("str为:" + str);
System.out.println("bs为:" + bs);
System.out.println("p的x为:" + p.x + " p的y为:" + p.y
+ " p的sb为:" + p.sb);
change(i, str, bs, p);
System.out.println("***********函数调用之后****************");
System.out.println("i为:" + i);
System.out.println("str为:" + str);
System.out.println("bs为:" + bs);
System.out.println("p的x为:" + p.x + " p的y为:" + p.y
+ " p的sb为:" + p.sb);
}
public void change(int p1, String p2, StringBuffer p3, Point p4) {
p1 = 2;
p2 = "I have changed!";
p3 = p3.append(" I have changed!");
p4.x = 5;
p4.y = "I have changed!";
p4.sb = p4.sb.append(" I have changed!");
}
}
输出结果为:
Java代码
1.***********函数调用之前****************
2.i为:1
3.str为:abc
4.bs为:abc
5.p的x为:1 p的y为:2 p的sb为:abc
6.***********函数调用之后****************
7.i为:1
8.str为:abc
9.bs为:abc I have changed!
10.p的x为:5 p的y为:I have changed! p的sb为:abc I have changed!
***********函数调用之前****************
i为:1
str为:abc
bs为:abc
p的x为:1 p的y为:2 p的sb为:abc
***********函数调用之后****************
i为:1
str为:abc
bs为:abc I have changed!
p的x为:5 p的y为:I have changed! p的sb为:abc I have changed!
分析:
这个例子我举得有点大,不过我觉得如果把我举得这个例子的参数传递完全搞懂了,你对java的参数传递过程就比较了解了。
不过在分析之前,我想给大家java传参的一个思想:java只有值传递,没有引用传递,也没有指针传递。对于基本数据类型,java是直接传值,其实就是将形参指向栈中的那个值;对于类类型(比如String,StringBuffer,自定义类类型等)是传引用(在栈中)的值,也就是堆中对应对象的地址。这个在我认为也是值传递。
下面我们开始分析test()方法
1、首先定义了int类型变量,int类型变量传入change()方法是简单的值传递,这个大家都知道,所以就不说了;
2、下面是String类型的变量,大家可能会想,String类型是类类型啊,当调用change方法后test方法中也应该会发生变化呀,呵呵,其实这时你忘了String类型是不可变的,因为它存储数据的char[]是用final修饰过的。当change方法中改变了p2的值后,其实p2指向的已经是另一块内存空间了。
3、下面是StringBuffer类型,之前已说类类型传递变量的地址,所以bs和p3指向的是同一块内存空间,当p3重新赋值时,bs也会跟着变得。
4、下面是自定义的类类型,我不想再用文字述说了,就用一个图来表示吧,我相信你现在可以自己分析了。
五、java对象的克隆机制(以上概念的应用)
概念引入:
我相信大家都听过java中的“克隆”这个名词,在Object类中有一个本地化clone()方法就是用来克隆对象的,其实我们自己也可以用new来克隆对象,但这样的效率会比较低。
概念名词:
浅度克隆:要克隆对象的属性如果是类类型变量,只在栈中创建一个该属性的新引用,指向源属性对象;如果是基本数据类型,我相信你懂得。
深度克隆:对于类类型的属性,在栈中和堆中都重新开辟空间,创建一个全新的属性对象。
其实Object中的clone()方法就是一种浅度克隆,不过当我们重写该方法时一定要实现Cloneable接口,否则会报异常,代码验证如下:
代码五:
Java代码
1.public class CloneTest {
2.
3. public static void main(String[] args) {
4. // TODO Auto-generated method stub
5. Point p1 = new CloneTest().new Point(1, "abc", new StringBuffer("def"));//源对象
6. Point p2=p1.clone(); //克隆对象
7. System.out.println("*************源对象的值如下****************");
8. System.out.println(p1.x);
9. System.out.println(p1.y);
10. System.out.println(p1.sb);
11. System.out.println("************修改克隆对象的值*****************");
12. p2.x=2;
13. p2.y="ddddddd";
14. p2.sb=p2.sb.append("dfsfdsfsd");
15. System.out.println("************修改克隆对象的值后 ,源对象的值如下*****************");
16. System.out.println(p1.x);
17. System.out.println(p1.y);
18. System.out.println(p1.sb);
19. }
20.
21. /**
22. * 内部类,用于克隆实验
23. */
24. class Point implements Cloneable{
25. int x;
26. String y;
27. StringBuffer sb;
28. //构造方法
29. public Point(int x, String y, StringBuffer sb) {
30. this.x = x;
31. this.y = y;
32. this.sb = sb;
33. }
34.
35. /**
36. * 重写Object类的clone方法,不过默认情况下只能浅克隆,不过我们可以给类类型的变量
37. * 重新new一块空间实现深度克隆,String类型就不用了哦~ ,呵呵,如果你现在还不知道
38. * 为什么,那就把博客再看一遍吧,我充分相信你会懂得,这里我不想再赘述了,总之要知道,String
39. * 类型和其他的类类型总是有一些区别,看到现在我希望你可以总结出一些
40. */
41. public Point clone(){
42. Point o=null;
43. try {
44. o = (Point)super.clone();
45. //o.sb=new StringBuffer(); //实现深度克隆
46. } catch (CloneNotSupportedException e) {
47. // TODO Auto-generated catch block
48. e.printStackTrace();
49. } return o;
50. }
51.}
52.}
public class CloneTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
Point p1 = new CloneTest().new Point(1, "abc", new StringBuffer("def"));//源对象
Point p2=p1.clone(); //克隆对象
System.out.println("*************源对象的值如下****************");
System.out.println(p1.x);
System.out.println(p1.y);
System.out.println(p1.sb);
System.out.println("************修改克隆对象的值*****************");
p2.x=2;
p2.y="ddddddd";
p2.sb=p2.sb.append("dfsfdsfsd");
System.out.println("************修改克隆对象的值后 ,源对象的值如下*****************");
System.out.println(p1.x);
System.out.println(p1.y);
System.out.println(p1.sb);
}
/**
* 内部类,用于克隆实验
*/
class Point implements Cloneable{
int x;
String y;
StringBuffer sb;
//构造方法
public Point(int x, String y, StringBuffer sb) {
this.x = x;
this.y = y;
this.sb = sb;
}
/**
* 重写Object类的clone方法,不过默认情况下只能浅克隆,不过我们可以给类类型的变量
* 重新new一块空间实现深度克隆,String类型就不用了哦~ ,呵呵,如果你现在还不知道
* 为什么,那就把博客再看一遍吧,我充分相信你会懂得,这里我不想再赘述了,总之要知道,String
* 类型和其他的类类型总是有一些区别,看到现在我希望你可以总结出一些
*/
public Point clone(){
Point o=null;
try {
o = (Point)super.clone();
//o.sb=new StringBuffer(); //实现深度克隆
} catch (CloneNotSupportedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} return o;
}
}
}
这时的运行结果如下,很显然是浅克隆。
Java代码
1.*************源对象的值如下****************
2.1
3.abc
4.def
5.************修改克隆对象的值*****************
6.************修改克隆对象的值后 ,源对象的值如下*****************
7.1
8.abc
9.defdfsfdsfsd
*************源对象的值如下****************
1
abc
def
************修改克隆对象的值*****************
************修改克隆对象的值后 ,源对象的值如下*****************
1
abc
defdfsfdsfsd
当我们把clone()方法中的注释语句“//o.sb=new StringBuffer(); ”启用后,这就是深度克隆了哦,运行结果如下:
Java代码
1.*************源对象的值如下****************
2.1
3.abc
4.def
5.************修改克隆对象的值*****************
6.************修改克隆对象的值后 ,源对象的值如下*****************
7.1
8.abc
9.def
*************源对象的值如下****************
1
abc
def
************修改克隆对象的值*****************
************修改克隆对象的值后 ,源对象的值如下*****************
1
abc
def
上面实现深度克隆的方法是基于Object的clone()方法的,其实我们也可以采用序列化的方式来实现深度克隆的,这样就不用重写clone()方法了,我们给Point类添加一个deepClone方法,不过一定要让Point类实现Serializeble接口哦~,deepClone方法如下:
Java代码
1. /**
2. * 采用序列化的方式实现深度克隆
3. */
4.public Point deepClone() throws IOException, ClassNotFoundException {
5.//将对象写入流中
6.ByteArrayOutputStream bs= new ByteArrayOutputStream();
7.ObjectOutputStream os = new ObjectOutputStream(bs);
8.os.writeObject(this);
9. //从流中读取对象
10.ByteArrayInputStream is= new ByteArrayInputStream(bs.toByteArray());
11.ObjectInputStream ois=new ObjectInputStream(is);
12.return (Point) ois.readObject();
13.}
/**
* 采用序列化的方式实现深度克隆
*/
public Point deepClone() throws IOException, ClassNotFoundException {
//将对象写入流中
ByteArrayOutputStream bs= new ByteArrayOutputStream();
ObjectOutputStream os = new ObjectOutputStream(bs);
os.writeObject(this);
//从流中读取对象
ByteArrayInputStream is= new ByteArrayInputStream(bs.toByteArray());
ObjectInputStream ois=new ObjectInputStream(is);
return (Point) ois.readObject();
}
呵呵,通过这些实验,我想你对java的克隆机制还是比较了解了,具体的分析我也没有必要再说了。就到此为止吧•••
如需转载,请注明出处:http://weixiaolu.iteye.com/blog/1290821
前记:
几天前,在浏览网页时偶然的发现一道以前就看过很多遍的面试题,题目是:“请说出‘equals’和‘==’的区别”,当时我觉得我还是挺懂的,在心里答了一点(比如我们都知道的:‘==’比较两个引用是否指向同一个对象,‘equals’比较两个对象的内容),可是总觉得心里有点虚虚的,因为这句话好像太概括了,我也无法更深入地说出一些。于是看了几篇别人的技术博客,看完后我心里自信地说,我是真的懂了;后来根据我当时的理解,就在eclipse中敲了些代码验证一下,发现有些运行的结果和我预期的又不一样,怎么找原因都找不到,呵呵~,这时就感觉太伤自尊了,于是我觉得我真的还是不懂得,又去上网查找答案,呵呵,这个问题花了我整整三天的时间,现在想把我的一些总结写下来,以达到检测自己的目的,也欢迎大家浏览、批评、指正。
注:本文不仅研究类类型的对象,还研究基本数据类型
线索:
我想采用实例代码驱动的方式来一步步地分析,这也符合我们探知新事物的过程。
一、基本数据类型的内存分配
代码1:
Java代码
1.int p1=1000;
2.static int p2=1000;
3.public void myTest(){
4.System.err.println("****************Integer*********************");
5.int i1=1000;
6.int i2=1000;
7.Integer i3=1000;
8.Integer i4=1000;
9.Integer i5=100;
10.Integer i6=100;
11.Integer i7=new Integer(1000);
12.Integer i8=new Integer(1000);
13.System.err.println(i1==p1); //true(输出结果) 1(编号,便于分析)
14.System.err.println(i1==p2); //true 2
15.System.err.println(i1==i2); //true 3
16.System.err.println(i3==i4); //false 4
17.System.err.println(i5==i6); //true 5
18.System.err.println(i7==i8); //false 6
19.System.err.println(i1==i3); //true 7
20.System.err.println(i1==i7); //true 8
21.System.err.println(i3==i7); //false 9
22.System.err.println("****************Integer*********************");
23.}
int p1=1000;
static int p2=1000;
public void myTest(){
System.err.println("****************Integer*********************");
int i1=1000;
int i2=1000;
Integer i3=1000;
Integer i4=1000;
Integer i5=100;
Integer i6=100;
Integer i7=new Integer(1000);
Integer i8=new Integer(1000);
System.err.println(i1==p1); //true(输出结果) 1(编号,便于分析)
System.err.println(i1==p2); //true 2
System.err.println(i1==i2); //true 3
System.err.println(i3==i4); //false 4
System.err.println(i5==i6); //true 5
System.err.println(i7==i8); //false 6
System.err.println(i1==i3); //true 7
System.err.println(i1==i7); //true 8
System.err.println(i3==i7); //false 9
System.err.println("****************Integer*********************");
}
看到上面的输出结果,如果你还是有些不能理解的,那就耐心地接着看我的分析吧。
分析:
编号1:在java编译时期,当编译到“int p1=1000; ”时会在栈中压入1000,其实后面的p2,i1,i2都是指向这个1000,这样可以提高java的性能,所以编号1、编号2、编号3的输出结果都是true.其实char,float,double等基本数据类型都是这样的。
编号2、编号3:同编号1
编号4:这是java中的自动装箱机制,将基本数据类型int自动转为类类型Integer,这是jdk1.5以上才有的功能,jdk1.5以下编译时会报错。自动装箱时java底层会调用Integer.valueOf(int i)方法自动装箱,下面我们来看看Integer.valueOf(int i)的源码吧:
Java代码
1./**
2.* @param i an <code>int</code> value.
3. * @return a <tt>Integer</tt> instance representing <tt>i</tt>.
4. * @since 1.5
5. */
6. public static Integer valueOf(int i) {
7. if(i >= -128 && i <= IntegerCache.high)
8. return IntegerCache.cache[i + 128];
9. else
10. return new Integer(i);
11. }
/**
* @param i an <code>int</code> value.
* @return a <tt>Integer</tt> instance representing <tt>i</tt>.
* @since 1.5
*/
public static Integer valueOf(int i) {
if(i >= -128 && i <= IntegerCache.high)
return IntegerCache.cache[i + 128];
else
return new Integer(i);
}
注:分析源码我们知道IntegerCache.high其实就是127,在IntergerCache的静态块中定义的。
源码的意思是当i的值在-128—127之间时会返回IntegerCache.cache[]中的对象,其他的新建一个Integer对象。其实Integer类是这样实现的:考虑到-128—127之间的对象经常使用,就在Integer创建时将值在-128—127之间的对象先创建好,放在池中,以后要使用时,这些对象就不用重新创建了,目的在于提高性能。其实这种机制在Character中也用到了,Character是创建ASCII在0—127之间的对象。补充说明:Integer创建的对象引用在栈中,对象的内容在堆区,栈中的值是堆中对象的地址。Character、Long、Short等包装类都是这样的。所以编号4的输出结果是false,因为值大于127,java新创建了一个对象。
编号5:因为值在-128—127之间,所以两个引用指向的是堆区的同一个对象。
编号6:当使用new创建对象时,都会新创建一个对象,即在栈中创建一个引用,在堆中创建该对象,引用指向对象。
编号7:这种情况有些人可能会不太清楚,其实这是java的自动拆箱机制,当int和Integer发生操作时,Integer类型对象会自动拆箱成int值,这时比较的是两个int值,而我们前面分析了,int值都会指向常量池中的数据,所以,两者指向的是同一块空间。结果编号7输出true
编号8:同编号7,也是Integer的自动拆箱。
编号9:我想,分析了这么多,编号9不用我说,你也应该懂了,呵呵,这里就不赘述了哦~
分析了这么多,终于第一块代码分析完了。
二、String类型的内存分配
大家都知道String类型是类类型,不过String类型是一个特殊的类类型,那它特殊在哪呢?
代码2:
Java代码
1. System.err.println("****************string*********************");
2. String s1="abc";
3. String s2="abc";
4. String s3=new String("abc");
5. String s4=new String("abc");
6. System.err.println(s1==s2);//true (输出结果) 1(编号)
7. System.err.println(s3==s4);//false 2
8. System.err.println(s1==s3);//false 3
9.
10. String a = "abc";
11.String b = "ab";
12.String c = b + "c";
13.System.err.println(a==c);//false 4
14.
15. String s5 = "123";
16. final String s6="12";
17. String s7=s6+"3";
18. System.err.println(s5 == s7);//true 5
19. System.err.println("****************string*********************");
System.err.println("****************string*********************");
String s1="abc";
String s2="abc";
String s3=new String("abc");
String s4=new String("abc");
System.err.println(s1==s2);//true (输出结果) 1(编号)
System.err.println(s3==s4);//false 2
System.err.println(s1==s3);//false 3
String a = "abc";
String b = "ab";
String c = b + "c";
System.err.println(a==c);//false 4
String s5 = "123";
final String s6="12";
String s7=s6+"3";
System.err.println(s5 == s7);//true 5
System.err.println("****************string*********************");
编号1:String类型是一个很特殊的类型,当我们使用String str=”abc”;这种定义方法时,”abc”会放入常量池中,以后如果再有定义String str2=”abc”时,其实str和str2指向的是常量池中同一个对象。而只有当使用new创建时才会每次都创建一个新的对象。(我觉得这是String类型和其他类类型的特殊之处)
编号2、编号3:编号1已经分析了。
编号4:执行到 String c = b + "c"; 这一句时,java底层会先创建一个StringBuilder对象,封装b,接着再加上“c”,最后再创建一个String对象,将StringBuilder中的值赋给该String对象,用c来指向它。.其实此时的c指向的对象已经不是a指向的对象了。
编号5:当用final修饰后,s6就变为了常量,在常量池中创建“12”,当执行到String s7=s6+"3";时,编译器直接就把s6当成了“12”,s7此时就已是“123”,它指向常量池中的“123”,所以s5和s7指向的是同一个对象,输出为true。
三、StringBuilder,StringBuffer,String的对比
(一)String
String类型的值是不可变的,听到这句话后可能你会有疑问,我们的String对象可以重新赋值呀,这里有两种情况,情况一:String str=”abc”; , 情况二:String str=new String(“abc”);采用情况一重新赋值时,java会先看常量池中有没有“abc”,如果有则直接指向它,如果没有,在编译时就创建一个常量放入常量池中;对于情况二:str则重新指向一个先创建的对象,该新对象在堆中。下面提出问题:为什么String是不可变的呢?我们来看看String的源码:
Java代码
1.public final class String
2. implements java.io.Serializable, Comparable<String>, CharSequence
3.{
4. /** The value is used for character storage. */
5. private final char value[];
6.
7. /** The offset is the first index of the storage that is used. */
8. private final int offset;
9.
10. /** The count is the number of characters in the String. */
11. private final int count;
12. //••••••••••••••••••••
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence
{
/** The value is used for character storage. */
private final char value[];
/** The offset is the first index of the storage that is used. */
private final int offset;
/** The count is the number of characters in the String. */
private final int count;
//••••••••••••••••••••
我们看到String类型是用一个用final修饰的char数组来存储字符串的,所以String类型是不可变的,(其实Short,Character,Long等包装类型也是这样实现的),根据上面对String类型的分析,如果要改变String的值,就要重新创建一个对象,这无疑性能会很差。为了优化String,sun公司添加了StringBuffer,在jdk1.5之后又添加了StringBuilder。
(二)下面我们来分析一下StringBuffer
StringBuffer作为字符串缓冲类,当进行字符串拼接时,不会重新创建一个StringBuffer对象,而是直接在原有值后面添加,因为StringBuffer类继承了AbstractStringBuffer类,分析后者的源码后,我们发现存储字符串的char[]没有被final修饰。至于StringBuffer类是怎样扩充自己的长度的,我们可以参考它的append()方法,这里不再赘述。不过一定要提出的是:StringBuffer是线程安全的,它的方法体是被synchronized修饰了的。
(三)StringBuilder有是怎么样的呢?
StringBuilder基本实现了StringBuffer的功能,最大的不同之处在于StringBuilder不是线程安全的。
(四)String、StringBuffer、StringBuilder的性能比较
代码三:
Java代码
1. StringBuffer b = new StringBuffer("abc");
2.long t3 = System.currentTimeMillis();
3.for (int i = 0; i < 1000000; i++) {
4. b = b.append("def");
5.}
6.long t4 = System.currentTimeMillis();
7.System.out.println("1000000次拼接,StringBuffer所花时间为:" + (t4 - t3));
8.System.out.println("*************************************");
9.StringBuilder c = new StringBuilder("abc");
10.long t5 = System.currentTimeMillis();
11.for (int i = 0; i < 1000000; i++) {
12. c = c.append("def");
13.}
14.long t6 = System.currentTimeMillis();
15.System.out.println("1000000次拼接,StringBuilder所花时间为:" + (t6 - t5));
16.System.out.println("*************************************");
17.String S1 = "abc";
18.long t1 = System.currentTimeMillis();
19.for (int i = 0; i < 10000; i++) {
20. S1 += "def";
21.}
22.long t2 = System.currentTimeMillis();
23.System.out.println("10000次拼接,String所花时间为:" + (t2 - t1));
StringBuffer b = new StringBuffer("abc");
long t3 = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
b = b.append("def");
}
long t4 = System.currentTimeMillis();
System.out.println("1000000次拼接,StringBuffer所花时间为:" + (t4 - t3));
System.out.println("*************************************");
StringBuilder c = new StringBuilder("abc");
long t5 = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
c = c.append("def");
}
long t6 = System.currentTimeMillis();
System.out.println("1000000次拼接,StringBuilder所花时间为:" + (t6 - t5));
System.out.println("*************************************");
String S1 = "abc";
long t1 = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
S1 += "def";
}
long t2 = System.currentTimeMillis();
System.out.println("10000次拼接,String所花时间为:" + (t2 - t1));
实验结果为:
Java代码
1.1000000次拼接,StringBuffer所花时间为:203
2.*************************************
3.1000000次拼接,StringBuilder所花时间为:79
4.*************************************
5.10000次拼接,String所花时间为:640
1000000次拼接,StringBuffer所花时间为:203
*************************************
1000000次拼接,StringBuilder所花时间为:79
*************************************
10000次拼接,String所花时间为:640
显然,StringBuilder的性能最好,String的性能最差,而且差很多;不过StringBuffer的线程安全性很好,性能也比较接近StringBuilder,所以我推荐的选择使用顺序为:StringBuffer>StringBuilder>String;
四、java传参
下面我们我看一段代码,不过有点长,请大家有点耐心哦~
代码四:
Java代码
1.public class VariableTest {
2.public static void main(String[] args) {
3.VariableTest t = new VariableTest();
4. t.test();
5. }
6.
7. class Point {
8. int x;
9. String y;
10. StringBuffer sb;
11. public Point(int x, String y, StringBuffer sb) {
12. this.x = x;
13. this.y = y;
14. this.sb = sb;
15. }
16. }
17.
18. public void test() {
19. int i = 1;
20. String str = "abc";
21. StringBuffer bs = new StringBuffer("abc");
22. Point p = new Point(1, "2", new StringBuffer("abc"));
23. System.out.println("***********函数调用之前****************");
24. System.out.println("i为:" + i);
25. System.out.println("str为:" + str);
26. System.out.println("bs为:" + bs);
27. System.out.println("p的x为:" + p.x + " p的y为:" + p.y
28. + " p的sb为:" + p.sb);
29. change(i, str, bs, p);
30. System.out.println("***********函数调用之后****************");
31. System.out.println("i为:" + i);
32. System.out.println("str为:" + str);
33. System.out.println("bs为:" + bs);
34. System.out.println("p的x为:" + p.x + " p的y为:" + p.y
35. + " p的sb为:" + p.sb);
36. }
37.
38. public void change(int p1, String p2, StringBuffer p3, Point p4) {
39. p1 = 2;
40. p2 = "I have changed!";
41. p3 = p3.append(" I have changed!");
42. p4.x = 5;
43. p4.y = "I have changed!";
44. p4.sb = p4.sb.append(" I have changed!");
45. }
46.
47.}
public class VariableTest {
public static void main(String[] args) {
VariableTest t = new VariableTest();
t.test();
}
class Point {
int x;
String y;
StringBuffer sb;
public Point(int x, String y, StringBuffer sb) {
this.x = x;
this.y = y;
this.sb = sb;
}
}
public void test() {
int i = 1;
String str = "abc";
StringBuffer bs = new StringBuffer("abc");
Point p = new Point(1, "2", new StringBuffer("abc"));
System.out.println("***********函数调用之前****************");
System.out.println("i为:" + i);
System.out.println("str为:" + str);
System.out.println("bs为:" + bs);
System.out.println("p的x为:" + p.x + " p的y为:" + p.y
+ " p的sb为:" + p.sb);
change(i, str, bs, p);
System.out.println("***********函数调用之后****************");
System.out.println("i为:" + i);
System.out.println("str为:" + str);
System.out.println("bs为:" + bs);
System.out.println("p的x为:" + p.x + " p的y为:" + p.y
+ " p的sb为:" + p.sb);
}
public void change(int p1, String p2, StringBuffer p3, Point p4) {
p1 = 2;
p2 = "I have changed!";
p3 = p3.append(" I have changed!");
p4.x = 5;
p4.y = "I have changed!";
p4.sb = p4.sb.append(" I have changed!");
}
}
输出结果为:
Java代码
1.***********函数调用之前****************
2.i为:1
3.str为:abc
4.bs为:abc
5.p的x为:1 p的y为:2 p的sb为:abc
6.***********函数调用之后****************
7.i为:1
8.str为:abc
9.bs为:abc I have changed!
10.p的x为:5 p的y为:I have changed! p的sb为:abc I have changed!
***********函数调用之前****************
i为:1
str为:abc
bs为:abc
p的x为:1 p的y为:2 p的sb为:abc
***********函数调用之后****************
i为:1
str为:abc
bs为:abc I have changed!
p的x为:5 p的y为:I have changed! p的sb为:abc I have changed!
分析:
这个例子我举得有点大,不过我觉得如果把我举得这个例子的参数传递完全搞懂了,你对java的参数传递过程就比较了解了。
不过在分析之前,我想给大家java传参的一个思想:java只有值传递,没有引用传递,也没有指针传递。对于基本数据类型,java是直接传值,其实就是将形参指向栈中的那个值;对于类类型(比如String,StringBuffer,自定义类类型等)是传引用(在栈中)的值,也就是堆中对应对象的地址。这个在我认为也是值传递。
下面我们开始分析test()方法
1、首先定义了int类型变量,int类型变量传入change()方法是简单的值传递,这个大家都知道,所以就不说了;
2、下面是String类型的变量,大家可能会想,String类型是类类型啊,当调用change方法后test方法中也应该会发生变化呀,呵呵,其实这时你忘了String类型是不可变的,因为它存储数据的char[]是用final修饰过的。当change方法中改变了p2的值后,其实p2指向的已经是另一块内存空间了。
3、下面是StringBuffer类型,之前已说类类型传递变量的地址,所以bs和p3指向的是同一块内存空间,当p3重新赋值时,bs也会跟着变得。
4、下面是自定义的类类型,我不想再用文字述说了,就用一个图来表示吧,我相信你现在可以自己分析了。
五、java对象的克隆机制(以上概念的应用)
概念引入:
我相信大家都听过java中的“克隆”这个名词,在Object类中有一个本地化clone()方法就是用来克隆对象的,其实我们自己也可以用new来克隆对象,但这样的效率会比较低。
概念名词:
浅度克隆:要克隆对象的属性如果是类类型变量,只在栈中创建一个该属性的新引用,指向源属性对象;如果是基本数据类型,我相信你懂得。
深度克隆:对于类类型的属性,在栈中和堆中都重新开辟空间,创建一个全新的属性对象。
其实Object中的clone()方法就是一种浅度克隆,不过当我们重写该方法时一定要实现Cloneable接口,否则会报异常,代码验证如下:
代码五:
Java代码
1.public class CloneTest {
2.
3. public static void main(String[] args) {
4. // TODO Auto-generated method stub
5. Point p1 = new CloneTest().new Point(1, "abc", new StringBuffer("def"));//源对象
6. Point p2=p1.clone(); //克隆对象
7. System.out.println("*************源对象的值如下****************");
8. System.out.println(p1.x);
9. System.out.println(p1.y);
10. System.out.println(p1.sb);
11. System.out.println("************修改克隆对象的值*****************");
12. p2.x=2;
13. p2.y="ddddddd";
14. p2.sb=p2.sb.append("dfsfdsfsd");
15. System.out.println("************修改克隆对象的值后 ,源对象的值如下*****************");
16. System.out.println(p1.x);
17. System.out.println(p1.y);
18. System.out.println(p1.sb);
19. }
20.
21. /**
22. * 内部类,用于克隆实验
23. */
24. class Point implements Cloneable{
25. int x;
26. String y;
27. StringBuffer sb;
28. //构造方法
29. public Point(int x, String y, StringBuffer sb) {
30. this.x = x;
31. this.y = y;
32. this.sb = sb;
33. }
34.
35. /**
36. * 重写Object类的clone方法,不过默认情况下只能浅克隆,不过我们可以给类类型的变量
37. * 重新new一块空间实现深度克隆,String类型就不用了哦~ ,呵呵,如果你现在还不知道
38. * 为什么,那就把博客再看一遍吧,我充分相信你会懂得,这里我不想再赘述了,总之要知道,String
39. * 类型和其他的类类型总是有一些区别,看到现在我希望你可以总结出一些
40. */
41. public Point clone(){
42. Point o=null;
43. try {
44. o = (Point)super.clone();
45. //o.sb=new StringBuffer(); //实现深度克隆
46. } catch (CloneNotSupportedException e) {
47. // TODO Auto-generated catch block
48. e.printStackTrace();
49. } return o;
50. }
51.}
52.}
public class CloneTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
Point p1 = new CloneTest().new Point(1, "abc", new StringBuffer("def"));//源对象
Point p2=p1.clone(); //克隆对象
System.out.println("*************源对象的值如下****************");
System.out.println(p1.x);
System.out.println(p1.y);
System.out.println(p1.sb);
System.out.println("************修改克隆对象的值*****************");
p2.x=2;
p2.y="ddddddd";
p2.sb=p2.sb.append("dfsfdsfsd");
System.out.println("************修改克隆对象的值后 ,源对象的值如下*****************");
System.out.println(p1.x);
System.out.println(p1.y);
System.out.println(p1.sb);
}
/**
* 内部类,用于克隆实验
*/
class Point implements Cloneable{
int x;
String y;
StringBuffer sb;
//构造方法
public Point(int x, String y, StringBuffer sb) {
this.x = x;
this.y = y;
this.sb = sb;
}
/**
* 重写Object类的clone方法,不过默认情况下只能浅克隆,不过我们可以给类类型的变量
* 重新new一块空间实现深度克隆,String类型就不用了哦~ ,呵呵,如果你现在还不知道
* 为什么,那就把博客再看一遍吧,我充分相信你会懂得,这里我不想再赘述了,总之要知道,String
* 类型和其他的类类型总是有一些区别,看到现在我希望你可以总结出一些
*/
public Point clone(){
Point o=null;
try {
o = (Point)super.clone();
//o.sb=new StringBuffer(); //实现深度克隆
} catch (CloneNotSupportedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} return o;
}
}
}
这时的运行结果如下,很显然是浅克隆。
Java代码
1.*************源对象的值如下****************
2.1
3.abc
4.def
5.************修改克隆对象的值*****************
6.************修改克隆对象的值后 ,源对象的值如下*****************
7.1
8.abc
9.defdfsfdsfsd
*************源对象的值如下****************
1
abc
def
************修改克隆对象的值*****************
************修改克隆对象的值后 ,源对象的值如下*****************
1
abc
defdfsfdsfsd
当我们把clone()方法中的注释语句“//o.sb=new StringBuffer(); ”启用后,这就是深度克隆了哦,运行结果如下:
Java代码
1.*************源对象的值如下****************
2.1
3.abc
4.def
5.************修改克隆对象的值*****************
6.************修改克隆对象的值后 ,源对象的值如下*****************
7.1
8.abc
9.def
*************源对象的值如下****************
1
abc
def
************修改克隆对象的值*****************
************修改克隆对象的值后 ,源对象的值如下*****************
1
abc
def
上面实现深度克隆的方法是基于Object的clone()方法的,其实我们也可以采用序列化的方式来实现深度克隆的,这样就不用重写clone()方法了,我们给Point类添加一个deepClone方法,不过一定要让Point类实现Serializeble接口哦~,deepClone方法如下:
Java代码
1. /**
2. * 采用序列化的方式实现深度克隆
3. */
4.public Point deepClone() throws IOException, ClassNotFoundException {
5.//将对象写入流中
6.ByteArrayOutputStream bs= new ByteArrayOutputStream();
7.ObjectOutputStream os = new ObjectOutputStream(bs);
8.os.writeObject(this);
9. //从流中读取对象
10.ByteArrayInputStream is= new ByteArrayInputStream(bs.toByteArray());
11.ObjectInputStream ois=new ObjectInputStream(is);
12.return (Point) ois.readObject();
13.}
/**
* 采用序列化的方式实现深度克隆
*/
public Point deepClone() throws IOException, ClassNotFoundException {
//将对象写入流中
ByteArrayOutputStream bs= new ByteArrayOutputStream();
ObjectOutputStream os = new ObjectOutputStream(bs);
os.writeObject(this);
//从流中读取对象
ByteArrayInputStream is= new ByteArrayInputStream(bs.toByteArray());
ObjectInputStream ois=new ObjectInputStream(is);
return (Point) ois.readObject();
}
呵呵,通过这些实验,我想你对java的克隆机制还是比较了解了,具体的分析我也没有必要再说了。就到此为止吧•••
如需转载,请注明出处:http://weixiaolu.iteye.com/blog/1290821
发表评论
-
Java 如何有效地避免OOM:善于利用软引用和弱引用
2016-04-26 23:32 0想必很多朋友对OOM ... -
Java内部类详解
2016-04-26 23:21 0说起内部类这个词,想 ... -
浅析Java中的final关键字
2016-04-26 23:14 0浅析Java中的final关键字 谈到final关键 ... -
http和socket之长连接和短连接区别
2016-04-15 11:02 625TCP/IP TCP/IP是个协议组,可分为三个层次:网络层 ... -
一个对象占用多少字节?
2015-06-06 11:43 547老早之前写过一篇博客,是关于一个Integer对象到底占用 ... -
JVM学习笔记四 之 运行时数据区
2015-06-06 11:43 335一、概述 运行时数据区是jvm运行时的内存布局,类装载到 ... -
JVM学习笔记三 之 ClassLoader
2015-06-04 20:37 0一、ClassLoader 负责装载class文件;这个文 ... -
jvm运行期打印汇编信息
2016-04-26 22:51 728如果只在jvm参数中加入-XX:+PrintAssembl ... -
查看java对象在内存中的布局
2015-06-04 20:05 1012接着上篇《一个对象占用多少字节?》中遇到的问题: ... -
一个对象占用多少字节?
2015-06-04 20:04 391老早之前写过一篇博客,是关于一个Integer对象到底占 ... -
[JavaScript]多文件上传时动态添加及删除文件选择框
2014-11-07 10:41 608多文件上传时,首先要解决的一个问题就是动态去添加或删除文件选 ... -
java对象的内存计算
2014-11-01 13:07 457我们讨论的是java heap中对象所占内存。 1.基本类型 ... -
Jsoup模拟登陆例子
2014-10-30 09:12 560Java代码 package c ... -
JAVA多线程和并发基础面试问答
2014-10-28 09:47 435Java多线程面试问题 1. ... -
java消息插件开发
2014-10-25 22:39 467package my.addon; public int ... -
java监听器原理
2014-10-24 15:39 676public class MyActivity implem ... -
java常用设计模式应用案例 .
2014-06-06 09:39 542<div class="iteye-blog- ... -
Java中GC的工作原理 .
2014-06-09 10:19 414一个优秀的Java程序员必须了解GC的工作原理、如何优化G ... -
java中易出错的且常被面试的几点 .
2014-06-09 10:19 471一、关于Switch 代码 ... -
面试中排名前10的算法介绍 .
2014-06-11 21:40 583以下是在编程面试中排名前10的算法相关的概念,我会通过 ...
相关推荐
下面将详细讨论Java3D的关键知识点和其在开发中的应用。 1. **Java3D架构**: Java3D是建立在Java Foundation Classes (JFC) 和Abstract Window Toolkit (AWT) 之上的扩展,它遵循 scenegraph 模型,允许开发者...
本题主要考察学生对Java中基本输入输出方法以及数学运算的理解。具体而言,学生需要掌握如何通过`BufferedReader`类读取控制台输入,并理解如何利用数学库`Math`中的`sqrt`函数来完成平方根计算。 **代码解读:** `...
在一个千年流转的传说中,三生石上的誓言承载了无数痴情人的泪水,它不仅是情感的寄托,更是对忠贞不渝爱情的颂扬。这篇文章,虽取材于传统的初中语文经典美文,其深层的情感表达,为我们提供了对人类情感复杂性的...
1. 痴情与岁月的关系:歌词中“痴情岁月”重复出现,暗示着一段深厚而持久的感情需要经历时间的考验。时间可以加深人们的感情,也可以消磨掉激情,但痴情的人却愿意让感情在岁月中沉淀,哪怕结果是破灭。 2. 情感的...
【痴情留言本】是一款多用户互动平台,其核心功能在于提供一个安全、有序的信息交流空间,用户可以发表留言,但这些留言需经过管理员审核后才会公开显示。这样的设计确保了内容的质量,避免了不当言论的传播,为用户...
如《蒹葭》中的芦苇象征着追求者心中的可望而不可即,展现了诗人的深深痴情。此外,诗中的反复咏唱增强了情感的感染力,如《桃夭》的三次重叠,强化了对美好婚姻的祝福。 《诗经》的婚恋诗对中国后世文学产生了深远...
皇后对乾隆帝的关怀无微不至,她不仅是乾隆帝生活中的贤内助,更是孝敬婆婆、履行儿媳责任的典范。她对其他嫔妃宽厚,不争宠,不骄矜,赢得了宫廷上下的一致好评。乾隆帝对她的宠爱,是基于她那贤良淑德的品性和出众...
她的酒词中痴情与怨恨交织,如《声声慢》中的“寻寻觅觅,冷冷清清,凄凄惨惨戚戚”,酒成了她表达内心痛苦和寂寞的工具,这种情感深度和广度在男性词人中亦不多见,展示了女性特有的敏感和细腻。 三、追求雅士的...
诗篇中的爱情故事通常以日常生活的场景为背景,通过比兴手法和固定的抒情范式,生动展现了男女之间的情感互动,如《关雎》中的痴情追求,或是《静女》中的俏皮嬉戏,都是《诗经》爱情诗的经典代表。 同时,《诗经》...
在裘立新的建议下,她转向演戏,并在《义本同心》中认真对待小角色,通过《多情女人痴情男》的角色获得了更多关注。在《战火中的青春》中,她饰演女一号童卉,尽管面临艰苦环境和外界质疑,但她凭借坚韧和学习精神,...
这些书生既有温文尔雅的读书人,也有贫困潦倒的落魄才子,甚至还有与狐仙、鬼魅相爱的痴情男子。他们的性格各异,命运多舛,反映出社会各个阶层书生的生活状态和心理世界。蒲松龄通过这些角色,揭示了封建科举制度下...
这段文字是一篇充满浪漫和幽默情感的文本,从整体上看,它似乎是模拟一个痴情的恋人对心仪对象的倾诉。以下是从这段文字中提取的知识点: 1. 使用比喻和夸张的修辞手法:文本中多次使用了比喻和夸张的修辞手法,...
书中的大孔雀蛾以其独特的求偶行为引人入胜,它们的“痴情昆虫的生死恋”展示了昆虫界中情感的一面,尽管这种情感可能并不等同于人类的情感,但却揭示了生存繁衍的重要驱动力。食尸虫和坑鹰则在清理死亡有机物方面...
题目中给出了几个词语的拼音,例如“执着(zhuó zhí)”、“吝啬(lìn sè)”、“痴情(chī qíng)”、“癖好(pǐ hào)”、“轻手蹑脚(qīng shǒu niè jiǎo)”、“咂摸(zā mō)”、“面面相觑(miàn miàn xiā...
总的来说,通过研究《天龙八部》以及相关的文学作品,学生们不仅能够领略到中国武侠小说的魅力,还能深刻体会到中国传统文化中的侠义精神,这对于培养他们的文化素养和批判思考能力具有重要意义。这样的教学方法既...
BDB提供事务处理,适用于需要数据一致性的场景,MEMORY引擎则将所有数据存储在内存中,适合临时表或快速查询。 2. **SQL语法**:这个版本支持基本的SQL标准,如SELECT、INSERT、UPDATE、DELETE语句,以及JOIN操作,...
《2020版高中语文 专题3 笔落惊风雨 风骚比兴 氓古今对译 苏教版必修4》是一份针对高中学生学习语文的重要参考资料,旨在帮助学生深入理解古代文学作品,特别是《诗经》中的经典篇章《氓》。这份文档包含了对《氓》...
课件中提及的两个关键事件,生动地展现了法布尔对昆虫的痴情。第一个事件发生在他童年时期,法布尔在放鸭子时也不忘昆虫研究,他将捕捉蝴蝶、甲虫视为一种乐趣,对水底世界中的螺壳、游鱼和蠕虫等生物充满了浓厚的...
【文件标题】与【描述】均提及“最伤感情书”,暗示这是一份...综上所述,这份文档通过一系列情书片段展现了爱情中的各种情感体验,包括甜蜜、苦涩、期待、失落、痴情和遗憾,反映出人类情感世界的丰富多样和复杂微妙。
《邶风·静女》作为《诗经》中的一篇精彩诗作,不仅以其生动的笔触刻画了一段古朴的爱情故事,更以其独特的艺术风格和深远的文化意义成为高中语文教学中不可多得的文学珍品。通过对这首诗的学习,学生可以深入领会到...