一、 java
1.1 hashcode & equals之5重天
当一个类有自己特有的“逻辑相等”概念(不同于对象身份的概念)。
1 使用instanceof操作符检查“实参是否为正确的类型”。
2 对于类中的每一个“关键域”,检查实参中的域与当前对象中对应的域值。
3. 对于非float和double类型的原语类型域,使用==比较;
4 对于对象引用域,递归调用equals方法;
5 对于float域,使用Float.floatToIntBits(afloat)转换为int,再使用==比较;
6 对于double域,使用Double.doubleToLongBits(adouble)转换为int,再使用==比较;
7 对于数组域,调用Arrays.equals方法。
1. 把某个非零常数值,例如17,保存在int变量result中;
2. 对于对象中每一个关键域f(指equals方法中考虑的每一个域):
3, boolean型,计算(f? 0 : 1);
4. byte,char,short型,计算(int);
5. long型,计算(int)(f ^ (f>>>32));
6. float型,计算Float.floatToIntBits(afloat);
7. double型,计算Double.doubleToLongBits(adouble)得到一个long,再执行[2.3];
8. 对象引用,递归调用它的hashCode方法;
9. 数组域,对其中每个元素调用它的hashCode方法。
10. 将上面计算得到的散列码保存到int变量c,然后执行result=37*result+c;
11. 返回result。
为何使用31
A.31是一个素数,素数作用就是如果我用一个数字来乘以这个素数,那么最终的出来的结果只能被素数本身和被乘数还有1来整除!。(减少冲突)
B.31可以 由i*31== (i<<5)-1来表示,现在很多虚拟机里面都有做相关优化.(提高算法效率)
C.选择系数的时候要选择尽量大的系数。因为如果计算出来的hash地址越大,所谓的“冲突”就越少,查找起来效率也会提高。(减少冲突)
D.并且31只占用5bits,相乘造成数据溢出的概率较小。
举个例子:
[java] view plaincopy
1. public class MyUnit{
2. private short ashort;
3. private char achar;
4. private byte abyte;
5. private boolean abool;
6. private long along;
7. private float afloat;
8. private double adouble;
9. private Unit aObject;
10. private int[] ints;
11. private Unit[] units;
12.
13. public boolean equals(Object o) {
14. if (!(o instanceof Unit))
15. return false;
16. Unit unit = (Unit) o;
17. return unit.ashort == ashort
18. && unit.achar == achar
19. && unit.abyte == abyte
20. && unit.abool == abool
21. && unit.along == along
22. && Float.floatToIntBits(unit.afloat) == Float
23. .floatToIntBits(afloat)
24. && Double.doubleToLongBits(unit.adouble) == Double
25. .doubleToLongBits(adouble)
26. && unit.aObject.equals(aObject)
27. && equalsInts(unit.ints)
28. && equalsUnits(unit.units);
29. }
30.
31. private boolean equalsInts(int[] aints) {
32. return Arrays.equals(ints, aints);
33. }
34.
35. private boolean equalsUnits(Unit[] aUnits) {
36. return Arrays.equals(units, aUnits);
37. }
38.
39. public int hashCode() {
40. int result = 17;
41. result = 31 * result + (int) ashort;
42. result = 31 * result + (int) achar;
43. result = 31 * result + (int) abyte;
44. result = 31 * result + (abool ? 0 : 1);
45. result = 31 * result + (int) (along ^ (along >>> 32));
46. result = 31 * result + Float.floatToIntBits(afloat);
47. long tolong = Double.doubleToLongBits(adouble);
48. result = 31 * result + (int) (tolong ^ (tolong >>> 32));
49. result = 31 * result + aObject.hashCode();
50. result = 37 * result + intsHashCode(ints);
51. result = 37 * result + unitsHashCode(units);
52. return result;
53. }
54.
55. private int intsHashCode(int[] aints) {
56. int result = 17;
57. for (int i = 0; i < aints.length; i++)
58. result = 37 * result + aints[i];
59. return result;
60. }
61.
62. private int unitsHashCode(Unit[] aUnits) {
63. int result = 17;
64. for (int i = 0; i < aUnits.length; i++)
65. result = 37 * result + aUnits[i].hashCode();
66. return result;
67. }
68. }
当改写equals()的时候,总是要改写hashCode()
根据一个类的equals方法(改写后),两个截然不同的实例有可能在逻辑上是相等的,但是,根据Object.hashCode方法,它们仅仅是两个对象。因此,违反了“相等的对象必须具有相等的散列码”。
两个对象如果equals那么这两个对象的hashcode一定相等,如果两个对象的hashcode相等那么这两个对象是否一定equals?
回答是不一定,这要看这两个对象有没有重写Object的hashCode方法和equals方法。如果没有重写,是按Object默认的方式去处理。
试想我有一个桶,这个桶就是hashcode,桶里装的是西瓜我们认为西瓜就是object,有的桶是一个桶装一个西瓜,有的桶是一个桶装多个西瓜。
比如String重写了Object的hashcode和equals,但是两个String如果hashcode相等,那么equals比较肯定是相等的,但是“==”比较却不一定相等。如果自定义的对象重写了hashCode方法,有可能hashcode相等,equals却不一定相等,“==”比较也不一定相等。
此处考的是你对object的hashcode的意义的真正的理解!!!如果作为一名高级开发人员或者是架构师,必须是要有这个概念的,否则,直接ban掉了。
为什么我们在定义hashcode时如: h = 31*h + val[off++]; 要使用31这个数呢?
public int hashCode() {
int h = hash;
int len = count;
if (h == 0 && len > 0) {
int off = offset;
char val[] = value;
for (int i = 0; i < len; i++) {
h = 31*h + val[off++];
}
hash = h;
}
return h;
}
来看一段hashcode的覆写案例:
其实上面的实现也可以总结成数数里面下面这样的公式:
s[0]*31^(n-1) + s[1]*31^(n-2) + … + s[n-1]
我们来看这个要命的31这个系数为什么总是在里面乘啊乘的?为什么不适用32或者其他数字?
大家都知道,计算机的乘法涉及到移位计算。当一个数乘以2时,就直接拿该数左移一位即可!选择31原因是因为31是一个素数!
所谓素数:
质数又称素数
素数在使用的时候有一个作用就是如果我用一个数字来乘以这个素数,那么最终的出来的结果只能被素数本身和被乘数还有1来整除!如:我们选择素数3来做系数,那么3*n只能被3和n或者1来整除,我们可以很容易的通过3n来计算出这个n来。这应该也是一个原因!
在存储数据计算hash地址的时候,我们希望尽量减少有同样的hash地址,所谓“冲突”。
31是个神奇的数字,因为任何数n * 31就可以被JVM优化为 (n << 5) -n,移位和减法的操作效率要比乘法的操作效率高的多,对左移现在很多虚拟机里面都有做相关优化,并且31只占用5bits!
hashcode & equals基本问到第三问,很多人就已经挂了,如果问到了为什么要使用31比较好呢,90%的人无法回答或者只回答出一半。
此处考的是编程者在平时开发时是否经常考虑“性能”这个问题。
两种比较接口分析
前者应该比较固定,和一个具体类相绑定,而后者比较灵活,它可以被用于各个需要比较功能的类使用。
一个类实现了 Camparable 接口表明这个类的对象之间是可以相互比较的。如果用数学语言描述的话就是这个类的对象组成的集合中存在一个全序。这样,这个类对象组成的集合就可以使用 Sort 方法排序了。
而 Comparator 的作用有两个:
1. 如果类的设计师没有考虑到 Compare 的问题而没有实现 Comparable 接口,可以通过 Comparator 来实现比较算法进行排序;
2. 为了使用不同的排序标准做准备,比如:升序、降序或其他什么序。
在 “集合框架” 中有两种比较接口: Comparable 接口和 Comparator 接口。
Comparable 是通用的接口,用户可以实现它来完成自己特定的比较,而 Comparator 可以看成一种算法的实现,在需要容器集合实现比较功能的时候,来指定这个比较器,这可以看成一种设计模式,将算法和数据分离。
来看样例:
PersonBean类
[java] view plaincopy
1. <span style="font-size:12px;">public class PersonBean implements Comparable<PersonBean> {
2. public PersonBean(int age, String name) {
3. this.age = age;
4. this.name = name;
5. }
6.
7. int age = 0;
8. String name = "";
9.
10. public int getAge() {
11. return age;
12. }
13.
14. public void setAge(int age) {
15. this.age = age;
16. }
17.
18. public String getName() {
19. return name;
20. }
21.
22. public void setName(String name) {
23. this.name = name;
24. }
25.
26. public boolean equals(Object o) {
27. if (!(o instanceof PersonBean)) {
28. return false;
29. }
30. PersonBean p = (PersonBean) o;
31. return (age == p.age) && (name.equals(p.name));
32. }
33.
34. public int hashCode() {
35. int result = 17;
36. result = 31 * result + age;
37. result = 31 * result + name.hashCode();
38. return result;
39.
40. }
41.
42. public String toString() {
43. return (age + "{" + name + "}");
44. }
45.
46. public int compareTo(PersonBean person) {
47. int cop = age - person.getAge();
48. if (cop != 0)
49. return cop;
50. else
51. return name.compareTo(person.name);
52. }
53. }
54. </span>
AlphDesc类
[java] view plaincopy
1. <span style="font-size:12px;">import java.util.Comparator;
2.
3. public class AlphDesc implements Comparator<PersonBean> {
4.
5. public int compare(PersonBean personA, PersonBean personB) {
6. int cop = personA.age - personB.age;
7. if (cop != 0)
8. return cop;
9. else
10. return personB.getName().compareTo(personA.getName());
11.
12. }
13.
14. }
15. </span>
TestComparable类
[java] view plaincopy
1. <span style="font-size:12px;">import java.util.*;
2.
3. public class TestComparable {
4.
5. /**
6. * @param args
7. */
8. public void compare() {
9. PersonBean[] p = { new PersonBean(20, "Tom"),
10. new PersonBean(20, "Jeff"),
11. new PersonBean(30, "Mary"),
12. new PersonBean(20, "Ada"),
13. new PersonBean(40, "Walton"),
14. new PersonBean(61, "Peter"),
15. new PersonBean(20, "Bush") };
16. System.out.println("before sort:\n" + Arrays.toString(p));
17. AlphDesc desc = new AlphDesc();
18. Arrays.sort(p,desc);
19. System.out.println("after sort:\n" + Arrays.toString(p));
20. }
21.
22. public static void main(String[] args) {
23. TestComparable tc = new TestComparable();
24. tc.compare();
25. }
26.
27. }</span>
克隆就是复制一个对象的复本.但一个对象中可能有基本数据类型,如:int,long,float 等,也同时含有非基本数据类型如(数组,集合等)被克隆得到的对象基本类型的值修改了,原对象的值不会改变.这种适合shadow clone(浅克隆).
但如果你要改变一个非基本类型的值时,原对象的值却改变了,.比如一个数组,内存中只copy他的地址,而这个地址指向的值并没有 copy,当clone时,两个地址指向了一个值,这样一旦这个值改变了,原来的值当然也变了,因为他们共用一个值.,这就必须得用深克隆(deep clone)
以下举个例子,说明以上情况.
被克隆类:ShadowClone.java
[java] view plaincopy
1. <span style="font-size:12px;">public class ShadowClone implements Cloneable
2. {
3. // 基本类型
4. private int a;
5. // 非基本类型
6. private String b;
7. // 非基本类型
8. private int[] c;
9. // 重写Object.clone()方法,并把protected改为public
10. @Override
11. public Object clone()
12. {
13. ShadowClone sc = null;
14. try
15. {
16. sc = (ShadowClone) super.clone();
17. } catch (CloneNotSupportedException e)
18. {
19. e.printStackTrace();
20. }
21. return sc;
22. }
23. public int getA()
24. {
25. return a;
26. }
27. public void setA(int a)
28. {
29. this.a = a;
30. }
31. public String getB()
32. {
33. return b;
34. }
35. public void setB(String b)
36. {
37. this.b = b;
38. }
39. public int[] getC()
40. {
41. return c;
42. }
43. public void setC(int[] c)
44. {
45. this.c = c;
46. }
47. }</span>
测试类Test.java
[java] view plaincopy
1. <span style="font-size:12px;">public class Test
2. {
3. public static void main(String[] args) throws CloneNotSupportedException
4. {
5. ShadowClone c1 = new ShadowClone();
6. //对c1赋值
7. c1.setA(100) ;
8. c1.setB("clone1") ;
9. c1.setC(new int[]{1000}) ;
10.
11. System.out.println("克隆前: c1.a="+c1.getA() );
12. System.out.println("克隆前: c1.b="+c1.getB() );
13. System.out.println("克隆前: c1.c[0]="+c1.getC()[0]);
14. System.out.println("-----------") ;
15.
16. //克隆出对象c2,并对c2的属性A,B,C进行修改
17.
18. ShadowClone c2 = (ShadowClone) c1.clone();
19.
20. //对c2进行修改
21. c2.setA(50) ;
22. c2.setB("clone2");
23. int []a = c2.getC() ;
24. a[0]=500 ;
25. c2.setC(a);
26.
27. System.out.println("克隆后: c1.a="+c1.getA() );
28. System.out.println("克隆后: c1.b="+c1.getB() );
29. System.out.println("克隆后: c1.c[0]="+c1.getC()[0]);
30. System.out.println("---------------") ;
31.
32. System.out.println("克隆后: c2.a=" + c2.getA());
33. System.out.println("克隆后: c2.b=" + c2.getB());
34. System.out.println("克隆后: c2.c[0]=" + c2.getC()[0]);
35. }
36. }</span>
结果:
克隆前: c1.a=100
克隆前: c1.b=clone1
克隆前: c1.c[0]=1000
-----------
克隆后: c1.a=100
克隆后: c1.b=clone1
克隆后: c1.c[0]=500
---------------
克隆后: c2.a=50
克隆后: c2.b=clone2
克隆后: c2.c[0]=500
问题出现了,我指修改了克隆后的对象c2.c的值,但c1.c的值也改变了,与c2的值相等.
以下针对浅克隆得出结论:基本类型是可以被克隆的,但引用类型只是copy地址,并没有copy这个地址指向的对象的值,这使得两个地址指向同一值,修改其中一个,当然另一个也就变了.
由此可见,浅克隆只适合克隆基本类型,对于引用类型就不能实现克隆了.
那如何实现克隆引用对象呢,以下提供一种方法. 用序列化与反序列化实现深克隆(deep copy)
被克隆对象.DeepClone.java
[java] view plaincopy
1. <span style="font-size:12px;">import java.io.Serializable;
2. //要实现深克隆必须实现Serializable接口
3. public class DeepClone implements Serializable
4. {
5. private int a;
6. private String b;
7. private int[] c;
8. public int getA()
9. {
10. return a;
11. }
12. public void setA(int a)
13. {
14. this.a = a;
15. }
16. public String getB()
17. {
18. return b;
19. }
20. public void setB(String b)
21. {
22. this.b = b;
23. }
24. public int[] getC()
25. {
26. return c;
27. }
28. public void setC(int[] c)
29. {
30. this.c = c;
31. }
32. }</span>
测试类Test.java
[java] view plaincopy
1. <span style="font-size:12px;">import java.io.ByteArrayInputStream;
2. import java.io.ByteArrayOutputStream;
3. import java.io.IOException;
4. import java.io.ObjectInputStream;
5. import java.io.ObjectOutputStream;
6. public class Test
7. {
8. public static void main(String[] args) throws CloneNotSupportedException
9. {
10. Test t = new Test();
11. DeepClone dc1 = new DeepClone();
12. // 对dc1赋值
13. dc1.setA(100);
14. dc1.setB("clone1");
15. dc1.setC(new int[] { 1000 });
16. System.out.println("克隆前: dc1.a=" + dc1.getA());
17. System.out.println("克隆前: dc1.b=" + dc1.getB());
18. System.out.println("克隆前: dc1.c[0]=" + dc1.getC()[0]);
19. System.out.println("-----------");
20. DeepClone dc2 = (DeepClone) t.deepClone(dc1);
21. // 对c2进行修改
22. dc2.setA(50);
23. dc2.setB("clone2");
24. int[] a = dc2.getC();
25. a[0] = 500;
26. dc2.setC(a);
27. System.out.println("克隆前: dc1.a=" + dc1.getA());
28. System.out.println("克隆前: dc1.b=" + dc1.getB());
29. System.out.println("克隆前: dc1.c[0]=" + dc1.getC()[0]);
30. System.out.println("-----------");
31. System.out.println("克隆后: dc2.a=" + dc2.getA());
32. System.out.println("克隆后: dc2.b=" + dc2.getB());
33. System.out.println("克隆后: dc2.c[0]=" + dc2.getC()[0]);
34. }
35. // 用序列化与反序列化实现深克隆
36. public Object deepClone(Object src)
37. {
38. Object o = null;
39. try
40. {
41. if (src != null)
42. {
43. ByteArrayOutputStream baos = new ByteArrayOutputStream();
44. ObjectOutputStream oos = new ObjectOutputStream(baos);
45. oos.writeObject(src);
46. oos.close();
47. ByteArrayInputStream bais = new ByteArrayInputStream(baos
48. .toByteArray());
49. ObjectInputStream ois = new ObjectInputStream(bais);
50. o = ois.readObject();
51. ois.close();
52. }
53. } catch (IOException e)
54. {
55. e.printStackTrace();
56. } catch (ClassNotFoundException e)
57. {
58. e.printStackTrace();
59. }
60. return o;
61. }
62. }</span>
结果:
克隆前: dc1.a=100
克隆前: dc1.b=clone1
克隆前: dc1.c[0]=1000
-----------
克隆前: dc1.a=100
克隆前: dc1.b=clone1
克隆前: dc1.c[0]=1000
-----------
克隆后: dc2.a=50
克隆后: dc2.b=clone2
克隆后: dc2.c[0]=500
深克隆后:修改dc1或者dc2,无论是基本类型还是引用类型,他们的值都不会随着一方改变另一方也改变.
总结:
当克隆的对象只有基本类型,不含引用类型时,可以用浅克隆实现.
当克隆的对象含有引用类型时,必须使用深克隆实现.
final修饰符(关键字)
如果一个类被声明为final,意味着它不能再派生出新的子类,不能作为父类被继承。因此一个类不能既被声明为 abstract的,又被声明为final的。将变量或方法声明为final,可以保证它们在使用中不被改变。被声明为final的变量必须在声明时给定初值,而在以后的引用中只能读取,不可修改。被声明为final的方法也同样只能使用,不能重载finally?再异常处理时提供 finally 块来执行任何清除操作。如果抛出一个异常,那么相匹配的 catch 子句就会执行,然后控制就会进入 finally 块(如果有的话)。
finalize方法名
Java 技术允许使用 finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。它是在 Object 类中定义的,因此所有的类都继承了它。子类覆盖 finalize()方法以整理系统资源或者执行其他清理工作。finalize()方法是在垃圾收集器删除对象之前对这个对象调用的。
[java] view plaincopy
1. <span style="font-size:12px;">Object o=new Object();
2. Object o1=o; </span>
上面代码中第一句是在heap堆中创建新的Object对象通过o引用这个对象,第二句是通过o建立o1到new Object()这个heap堆中的对象的引用,这两个引用都是强引用.只要存在对heap中对象的引用,gc就不会收集该对象.如果通过如下代码:
[java] view plaincopy
1. <span style="font-size:12px;">o=null;
2. o1=null;</span>
如果显式地设置o和o1为null,或超出范围,则gc认为该对象不存在引用,这时就可以收集它了。可以收集并不等于就一会被收集,什么时候收集这要取决于gc的算法,这要就带来很多不确定性。例如你就想指定一个对象,希望下次gc运行时把它收集了,那就没办法了,有了其他的三种引用就可以做到了。其他三种引用在不妨碍gc收集的情况下,可以做简单的交互。
heap中对象有强可及对象、软可及对象、弱可及对象、虚可及对象和不可到达对象。应用的强弱顺序是强、软、弱、和虚。对于对象是属于哪种可及的对象,由他的最强的引用决定。如下:
[java] view plaincopy
1. <span style="font-size:12px;">String abc=new String("abc"); //1
2. SoftReference<String> abcSoftRef=new SoftReference<String>(abc); //2
3. WeakReference<String> abcWeakRef = new WeakReference<String>(abc); //3
4. abc=null; //4
5. abcSoftRef.clear();//5</span>
第一行在heap对中创建内容为“abc”的对象,并建立abc到该对象的强引用,该对象是强可及的。
第二行和第三行分别建立对heap中对象的软引用和弱引用,此时heap中的对象仍是强可及的。
第四行之后heap中对象不再是强可及的,变成软可及的。同样第五行执行之后变成弱可及的。
软引用是主要用于内存敏感的高速缓存。在jvm报告内存不足之前会清除所有的软引用,这样以来gc就有可能收集软可及的对象,可能解决内存吃紧问题,避免内存溢出。什么时候会被收集取决于gc的算法和gc运行时可用内存的大小。当gc决定要收集软引用是执行以下过程,以上面的abc SoftRef为例:
1、首先将abcSoftRef的referent设置为null,不再引用heap中的new String("abc")对象。
2、将heap中的new String("abc")对象设置为可结束的(finalizable)。
3、当heap中的new String("abc")对象的finalize()方法被运行而且该对象占用的内存被释放, abcSoftRef被添加到它的ReferenceQueue中。
注:对ReferenceQueue软引用和弱引用可以有可无,但是虚引用必须有,参见:
[java] view plaincopy
1. <span style="font-size:12px;">Reference(T paramT, ReferenceQueue<? super T>paramReferenceQueue) </span>
被 Soft Reference 指到的对象,即使没有任何 Direct Reference,也不会被清除。
一直要到 JVM 内存不足且没有 Direct Reference 时才会清除,SoftReference 是用来设计 object-cache 之用的。
如此一来 SoftReference 不但可以把对象 cache 起来,也不会造成内存不足的错误(OutOfMemoryError)。我觉得 Soft Reference 也适合拿来实作 pooling 的技巧。
[java] view plaincopy
1. <span style="font-size:12px;">A obj = new A();
2. SoftRefenrence sr = new SoftReference(obj);
3.
4. //引用时
5. if(sr!=null){
6. obj = sr.get();
7. }else{
8. obj = new A();
9. sr = new SoftReference(obj);
10. }</span>
当gc碰到弱可及对象,并释放abcWeakRef的引用,收集该对象。但是gc可能需要对此运用才能找到该弱可及对象。通过如下代码可以了明了的看出它的作用:
[java] view plaincopy
1. <span style="font-size:12px;">String abc=new String("abc");
2. WeakReference<String> abcWeakRef = new WeakReference<String>(abc);
3. abc=null;
4. System.out.println("before gc: "+abcWeakRef.get());
5. System.gc();
6. System.out.println("after gc: "+abcWeakRef.get()); </span>
运行结果:
before gc: abc
after gc: null
gc收集弱可及对象的执行过程和软可及一样,只是gc不会根据内存情况来决定是不是收集该对象。
如果你希望能随时取得某对象的信息,但又不想影响此对象的垃圾收集,那么你应该用 Weak Reference 来记住此对象,而不是用一般的 reference。
[java] view plaincopy
1. <span style="font-size:12px;">A obj = new A();
2.
3. WeakReference wr = new WeakReference(obj);
4.
5. obj = null;
6.
7. //等待一段时间,obj对象就会被垃圾回收
8. ...
9.
10. if (wr.get()==null) {
11. System.out.println("obj 已经被清除了 ");
12. } else {
13. System.out.println("obj 尚未被清除,其信息是 "+obj.toString());
14. }
15. ...</span>
在此例中,透过 get() 可以取得此 Reference 的所指到的对象,如果返回值为 null 的话,代表此对象已经被清除。
这类的技巧,在设计 Optimizer 或 Debugger 这类的程序时常会用到,因为这类程序需要取得某对象的信息,但是不可以影响此对象的垃圾收集。
虚顾名思义就是没有的意思,建立虚引用之后通过get方法返回结果始终为null,通过源代码你会发现,虚引用通向会把引用的对象写进referent,只是get方法返回结果为null。先看一下和gc交互的过程在说一下他的作用。
1 不把referent设置为null,直接把heap中的new String("abc")对象设置为可结束的(finalizable).
2 与软引用和弱引用不同,先把PhantomRefrence对象添加到它的ReferenceQueue中,然后在释放虚可及的对象。
你会发现在收集heap中的new String("abc")对象之前,你就可以做一些其他的事情。通过以下代码可以了解他的作用。
[java] view plaincopy
1. <span style="font-size:12px;">import java.lang.ref.PhantomReference;
2. import java.lang.ref.Reference;
3. import java.lang.ref.ReferenceQueue;
4. import java.lang.reflect.Field;
5.
6. public class Test {
7. public static boolean isRun = true;
8.
9. public static void main(String[] args) throws Exception {
10. String abc = new String("abc");
11. System.out.println(abc.getClass() + "@" + abc.hashCode());
12. final ReferenceQueue referenceQueue = new ReferenceQueue<String>();
13. new Thread() {
14. public void run() {
15. while (isRun) {
16. Object o = referenceQueue.poll();
17. if (o != null) {
18. try {
19. Field rereferent = Reference.class
20. .getDeclaredField("referent");
21. rereferent.setAccessible(true);
22. Object result = rereferent.get(o);
23. System.out.println("gc will collect:"
24. + result.getClass() + "@"
25. + result.hashCode());
26. } catch (Exception e) {
27.
28. e.printStackTrace();
29. }
30. }
31. }
32. }
33. }.start();
34. PhantomReference<String> abcWeakRef = new PhantomReference<String>(abc,
35. referenceQueue);
36. abc = null;
37. Thread.currentThread().sleep(3000);
38. System.gc();
39. Thread.currentThread().sleep(3000);
40. isRun = false;
41. }
42.
43. }</span>
结果为:
class java.lang.String@96354
gc will collect:class java.lang.String@96354
首先,我们看一个雇员信息查询系统的实例。
我们将使用一个Java语言实现的雇员信息查询系统查询存储在磁盘文件或者数据库中的雇员人事档案信息。
作为一个用户,我们完全有可能需要回头去查看几分钟甚至几秒钟前查看过的雇员档案信息(同样,我们在浏览WEB页面的时候也经常会使用“后退”按钮)。
这时我们通常会有两种程序实现方式:
一种是把过去查看过的雇员信息保存在内存中,每一个存储了雇员档案信息的Java对象的生命周期贯穿整个应用程序始终;
另一种是当用户开始查看其他雇员的档案信息的时候,把存储了当前所查看的雇员档案信息的Java对象结束引用,使得垃圾收集线程可以回收其所占用的内存空间,当用户再次需要浏览该雇员的档案信息的时候,重新构建该雇员的信息。
很显然,第一种实现方法将造成大量的内存浪费,而第二种实现的缺陷在于即使垃圾收集线程还没有进行垃圾收集,包含雇员档案信息的对象仍然完好地保存在内存中,应用程序也要重新构建一个对象。
我们知道,访问磁盘文件、访问网络资源、查询数据库等操作都是影响应用程序执行性能的重要因素,如果能重新获取那些尚未被回收的Java对象的引用,必将减少不必要的访问,大大提高程序的运行速度。
实现了Serializable接口的对象,可将它们转换成一系列字节,并可在以后完全恢复回原来的样子。这一过程亦可通过网络进行。这意味着序列化机制能自动补偿操作系统间的差异。换句话说,可以先在Windows机器上创建一个对象,对其序列化,然后通过网络发给一台Unix机器,然后在那里准确无误地重新“装配”。不必关心数据在不同机器上如何表示,也不必关心字节的顺序或者其他任何细节。
1.7 可变类与不可变类
当创建了这个类的实例后,就不允许修改它的属性值。在JDK的基本类库中,所有基本类型的包装类,如Integer和Long类,都是不可变类,java.lang.String也是不可变类。
当你获得这个类的一个实例引用时,你不可以改变这个实例的内容。不可变类的实例一但创建,其内在成员变量的值就不能被修改。
1. 所有成员都是private
2. 不提供对成员的改变方法,例如:setXXXX
3. 确保所有的方法不会被重载。手段有两种:使用final Class(强不可变类),或者将所有类方法加上final(弱不可变类)。
4. 如果某一个类成员不是原始变量(primitive)或者不可变类,必须通过在成员初始化(in)或者get方法(out)时通过深度clone方法,来确保类的不可变。
四点中少一点,回答失败,来看一个例子:
public final class MyImmutableWrong {
private final int[] myArray;
public MyImmutableWrong(int[] anArray) {
this.myArray = anArray; // wrong
}
public String toString() {
StringBuffer sb = new StringBuffer("Numbers are: ");
for (int i = 0; i < myArray.length; i++) {
sb.append(myArray[i] + " ");
}
return sb.toString();
}
}
以上不可变类书写错误,为什么?违犯了第四点
正确的写法:
public final class MyImmutableCorrect {
private final int[] myArray;
public MyImmutableCorrect(int[] anArray) {
this.myArray = anArray.clone();
}
public String toString() {
StringBuffer sb = new StringBuffer("Numbers are: ");
for (int i = 0; i < myArray.length; i++) {
sb.append(myArray[i] + " ");
}
return sb.toString();
}
}
相信有一定java开发经验的人或多或少都会遇到OutOfMemoryError的问题,这个问题曾困扰了我很长时间,随着解决各类问题经验的积累以及对问题根源的探索,终于有了一个比较深入的认识。
在解决java内存溢出问题之前,需要对jvm(java虚拟机)的内存管理有一定的认识。jvm管理的内存大致包括三种不同类型的内存区域:Permanent Generation space(永久保存区域)、Heap space(堆区域)、Java Stacks(Java栈)。其中永久保存区域主要存放Class(类)和Meta的信息,Class第一次被Load的时候被放入PermGen space区域,Class需要存储的内容主要包括方法和静态属性。堆区域用来存放Class的实例(即对象),对象需要存储的内容主要是非静态属性。每次用new创建一个对象实例后,对象实例存储在堆区域中,这部分空间也被jvm的垃圾回收机制管理。而Java栈跟大多数编程语言包括汇编语言的栈功能相似,主要基本类型变量以及方法的输入输出参数。Java程序的每个线程中都有一个独立的堆栈。容易发生内存溢出问题的内存空间包括:Permanent Generation space和Heap space。
第一种OutOfMemoryError: PermGen space
发生这种问题的原意是程序中使用了大量的jar或class,使java虚拟机装载类的空间不够,与Permanent Generation space有关。解决这类问题有以下两种办法:
1. 增加java虚拟机中的XX:PermSize和XX:MaxPermSize参数的大小,其中XX:PermSize是初始永久保存区域大小,XX:MaxPermSize是最大永久保存区域大小。如针对tomcat6.0,在catalina.sh 或catalina.bat文件中一系列环境变量名说明结束处(大约在70行左右)增加一行:
JAVA_OPTS=" -XX:PermSize=64M -XX:MaxPermSize=128m"
如果是windows服务器还可以在系统环境变量中设置。感觉用tomcat发布sprint+struts+hibernate架构的程序时很容易发生这种内存溢出错误。使用上述方法,我成功解决了部署ssh项目的tomcat服务器经常宕机的问题。
2. 清理应用程序中web-inf/lib下的jar,如果tomcat部署了多个应用,很多应用都使用了相同的jar,可以将共同的jar移到tomcat共同的lib下,减少类的重复加载。这种方法是网上部分人推荐的,我没试过,但感觉减少不了太大的空间,最靠谱的还是第一种方法。
第二种OutOfMemoryError: Java heap space
发生这种问题的原因是java虚拟机创建的对象太多,在进行垃圾回收之间,虚拟机分配的到堆内存空间已经用满了,与Heap space有关。解决这类问题有两种思路:
1. 检查程序,看是否有死循环或不必要地重复创建大量对象。找到原因后,修改程序和算法。
我以前写一个使用K-Means文本聚类算法对几万条文本记录(每条记录的特征向量大约10来个)进行文本聚类时,由于程序细节上有问题,就导致了Java heap space的内存溢出问题,后来通过修改程序得到了解决。
2. 增加Java虚拟机中Xms(初始堆大小)和Xmx(最大堆大小)参数的大小。如:set JAVA_OPTS= -Xms256m -Xmx1024m
第三种OutOfMemoryError:unable to create new native thread
这种错误在Java线程个数很多的情况下容易发生,我暂时还没遇到过,发生原意和解决办法可以参考:http://hi.baidu.com/hexiong/blog/item/16dc9e518fb10c2542a75b3c.html
1.9 ThreadLocal是否会导致内存溢出
顾名思义它是local variable(线程局部变量)。它的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本。从线程的角度看,就好像每一个线程都完全拥有该变量。
它主要由四个方法组成initialValue(),get(),set(T),remove(),其中initialValue()方法是一个protected的方法,只有在重写ThreadLocal的时候有用。
对于ThreadLocal的概念,很多人都是比较模糊的,只知道是线程本地变量,而具体这个本地变量是什么含义,有什么作用,如何使用等很多java开发工程师都不知道如何进行使用。从JDK的对ThreadLocal的解释来看
该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量, 它独立于变量的初始化副本。ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。 |
ThreadLocal有一个ThreadLocalMap静态内部类,你可以简单理解为一个MAP,这个‘Map’为每个线程复制一个变量的‘拷贝’存储其中。每一个内部线程都有一个ThreadLocalMap对象。
当线程调用ThreadLocal.set(T object)方法设置变量时,首先获取当前线程引用,然后获取线程内部的ThreadLocalMap对象,设置map的key值为threadLocal对象,value为参数中的object。
当线程调用ThreadLocal.get()方法获取变量时,首先获取当前线程引用,以threadLocal对象为key去获取响应的ThreadLocalMap,如果此‘Map’不存在则初始化一个,否则返回其中的变量。
也就是说每个线程内部的 ThreadLocalMap对象中的key保存的threadLocal对象的引用,从ThreadLocalMap的源代码来看,对threadLocal的对象的引用是WeakReference,也就是弱引用。
二、 oracle
2.1 BTree和BitMap区别
Btree
理上来讲,它的逻辑结构就像一个B-树,一种多路搜索树(非二叉树),它一直保持平衡状态(平衡树)
oracle 默认所有,当基数较高时,有较高的效率。
BitMap索引
Bitmap索引不像B-Tree索引,它的一个index entry可以指向更多的rows。通常它比较适用于以下两种情况:
1. 索引列拥有较低的基数,重复值较少。
2. 表是read only 模式,或者极少更改其中的数据。
不管是OLTP 或者 OLAP ,只要满足上面的情况,都可以使用BitMap索引(当然适用于OLAP比较多)。
使用位图索引时,一个键指向多行(成百上千),如果更新一个位图索引键,会同时将其他行对应位图索引字段进行锁定!
较之B-Tree索引优点:
位图以一种压缩格式存放,因此占用的磁盘空间比B-Tree索引要小得多
较之B-Tree索引缺点:
这种锁定的代价很高,会导致一些DML语句出现“锁等待”,严重影响插入、更新和删除的效率,对于高并发的系统不适用。
位图索引使用原则:
位图索引主要用于决策支持系统或静态数据,不支持索引行级锁定。
位图索引最好用于低cardinality列(即列的唯一值除以行数为一个很小的值,接近零),例如上面的“性别”列,列值有“M”,“F”两种。在这个基本原则的基础上,要认真考虑包含位图索引的表的操作特点,如果是并发操作高的系统,不适合使用位图索引!
2.2 spool和sqlldr
SPOOL导出:
$cat spool.sql
set echo off
set feedback off
set heading off
set verify off
set trimspool on
set pagesize 0
set term off
set linesize 150
spool /ocfs/sql/sqlldrdata/test_spool_file.txt
select
empno||' '||ename||' '||job||' '||mgr||' '||to_char(hiredate,'yyyy-mm-dd')||'||sal||' '||comm||' '||deptno
from emp;
spool off
$cat test_spool.sh
#!/bin/bash
sqlplus -s yp/yp <<EOF
@/ocfs/sql/sqlldr/spool.sql
exit;
EOF
SQLLDR导入:
sqlldr参数:
userid -- Oracle 的 username/password[@servicename]
control -- 控制文件,可能包含表的数据
log -- 记录导入时的日志文件,默认为控制文件(去除扩展名).log
bad -- 坏数据文件,默认为控制文件(去除扩展名).bad
data -- 数据文件,一般在控制文件中指定。用参数控制文件中不指定数据文件更适于自动操作
errors -- 允许的错误记录数,可以用他来控制一条记录都不能错
rows -- 多少条记录提交一次,默认为 64
skip -- 跳过的行数,比如导出的数据文件前面几行是表头或其他描述
$cat test_sqlldr.ctl
LOAD DATA
infile "/ocfs/sql/sqlldrdata/test_spool_file.txt"
into table emp_test append
fields terminated by X'09'
(empno,ename,job,mgr,hiredate date 'yyyy-mm-dd',sal,comm,deptno)
//以下是4种装入表的方式
//APPEND // 原先的表有数据就加在后面
// INSERT // 装载空表,如果原先的表有数据SQLLOADER会停止默认值
// REPLACE // 删除旧记录(用 delete from table 语句),替换成新装载的记录
// TRUNCATE // 删除旧记录(用 truncate table 语句),替换成新装载的记录
// TERMINATED BY X'09' // 以十六进制格式'09'表示文本文件用TAB键分隔
$sqlldr yp/yp control='/ocfs/sql/sqlldr/test_sqlldr.ctl' log='/ocfs/sql/sqlldr/test_sqlldr.log' bad='/ocfs/sql/sqlldr/test_sqlldr.bad'
导出:
set trimspool on
set linesize 120
set pagesize 2000
set newpage 1
set heading off
set term off
spool d:/abc.sql
select goods_sn||','||limit from aaa;
spool off