黑马程序员---看ArrayList_HashSet的比较及Hashcode分析视频查阅的资料总结

来源:互联网 发布:alphabeta算法 编辑:程序博客网 时间:2024/05/16 19:04
---------------------- android培训、java培训、期待与您交流! ----------------------

一: hashCode方法和HashSet类

如果想要查找一个集合是否包含某个对象,大概的程序代码怎么样写呢?你通常是逐一取出每个元素与要查找的对象进行比较,当发现某个元素与要查找的对象进行equals方法比较的结果相等时,则停止继续查找,并返回肯定的消息,否则,返回否定的信息,如果一个集合中有很多个元素,譬如有一万个元素,并且没有包含要查找的对象时,则意味着你的程序需要从该集合中取出一万个元素进行逐一比较才能得到结论,有人发明了一种哈希算法来提高从集合中查找元素的效率,这种方式将集合分成若干存储区域,每个对象可以计算出一个哈希码,可以将哈希码分组,每组分别对应某个存储区域,根据一个对象的哈希码就可以确定该对象应该存储在哪个区域。

        HashSet就是采用哈希算法进行存取对象的集合,它内部采用对某个数组n进行取余的方式对哈希码进行分组和划分对象的存储区域。Object类中定义了一个hashCode()方法来返回每个java对象的哈希码,当从HashSet集合中查找某个对象时,java系统首先调用对象的hashCode方法,获得该对象的哈希码,根据hash码找到对应的存储区域,最后取出该存储区域的每个元素与该对象进行equals方法比较,这样就不用遍历集合中的所有元素就可以得到结论,可见,HashSet集合具有很好的对象检索性能,但是,Hash集合存储的对象的效率相对要低一些,因为想HashSet集合中添加一个对象时,首先计算出对象的哈希码和根据这个哈希码确定对象在集合中存放位置。

         为了保证一个类中实例对象能在HashSet正常存储,要求这个类的两个实例对象用equals方法比较结果相等时,他们的哈希码也必须相等,也就是说,如果obj1.equals(obj2)结果为true,那么他们的哈希码比较也要为true:obj1.hashCode()==obj2.hashCode()

         如果一个类的hashCode()方法没有遵循上述要求,那么,当这个类的两个实例对象用equals()方法比较的结果相等时,他们本来应该无法被同时存进Set集合中,但是,如果将他们存储进HashSet集合中时,由于他们的hashCode()方法的返回值不同,第二个对象首先按哈希码计算可能会会被放进与第一个对象不同的区域中,这样它就不可能与第一个对象进行equals方法比较了,也就可能存储进HashSet集合中了,Objet类中hashCode()方法不能满足对象被存入到HashSet中的要求,因为它的返回值是通过对象的内存地址推算出来的同一个对象在程序运行期间的任何时候返回的哈希码都是始终不变的,所以,只要两个不同的实例对象,即时他们的equals方法比较结果相等,它们默认的hashcode方法返回值也是不同的。

         只有类的实例对象要被采用哈希算法进行存储和检索时,这个类才需要按要求覆盖hashCode方法,即时程序可能暂时不会用到当前类的hashCode方法,但是为它提供一个hashCode方法也不会有什么不好,没准以后什么时候又用到了这个方法。所以,通常要求hashCode方法和equals方法一并被同时覆盖。

提示

(1)             通常来说,一个类的两个实例对象用equals()方法比较的结果相同时,他们的哈希码也必须相等,但反之则不然,即equals方法比较的结果不相等的对象可以有相同的哈希码,或者说哈希码相同的两个对象的equals方法比较的结果可以不等。

(2)             当一个对象被存储进HashSet集合中以后,就不能修改这个对象的那些参与计算哈希码的字段了,否则,对象修改后的哈希码与最初存储进HashSet集合时的哈希值就不同了,在这种情况下即时在contains方法使用该对象的当前引用作为参数去HashSet集合中检索对象,也会返回找不到对象的结果,这也会导致无法从HashSet集合中单独删除当前对象,从而造成内存溢出。

 

二:java类存泄露

1:java如何管理内存

为了判断Java中是否有内存泄露,我们首先必须了解Java是如何管理内存的。Java的内存管理就是对象的分配和释放问题。在Java中,内存的分配是由程序完成的,而内存的释放是由垃圾收集器(Garbage Collection,GC)完成的,程序员不需要通过调用函数来释放内存,但它只能回收无用并且不再被其它对象引用的那些对象所占用的空间。

Java的内存垃圾回收机制是从程序的主要运行对象开始检查引用链,当遍历一遍后发现没有被引用的孤立对象就作为垃圾回收。GC为了能够正确释放对象,必须监控每一个对象的运行状态,包括对象的申请、引用、被引用、赋值等,GC都需要进行监控。监视对象状态是为了更加准确地、及时地释放对象,而释放对象的根本原则就是该对象不再被引用。

在Java中,这些无用的对象都由GC负责回收,因此程序员不需要考虑这部分的内存泄露。虽然,我们有几个函数可以访问GC,例如运行GC的函数System.gc(),但是根据Java语言规范定义,该函数不保证JVM的垃圾收集器一定会执行。因为不同的JVM实现者可能使用不同的算法管理GC。通常GC的线程的优先级别较低。JVM调用GC的策略也有很多种,有的是内存使用到达一定程度时,GC才开始工作,也有定时执行的,有的是平缓执行GC,有的是中断式执行GC。但通常来说,我们不需要关心这些。

2. 什么是Java中的内存泄露

导致内存泄漏主要的原因是,先前申请了内存空间而忘记了释放。如果程序中存在对无用对象的引用,那么这些对象就会驻留内存,消耗内存,因为无法让垃圾回收器GC验证这些对象是否不再需要。如果存在对象的引用,这个对象就被定义为"有效的活动",同时不会被释放。要确定对象所占内存将被回收,我们就要务必确认该对象不再会被使用。典型的做法就是把对象数据成员设为null或者从集合中移除该对象。但当局部变量不需要时,不需明显的设为null,因为一个方法执行完毕时,这些引用会自动被清理。

在Java中,内存泄漏就是存在一些被分配的对象,这些对象有下面两个特点,首先,这些对象是有被引用的,即在有向树形图中,存在树枝通路可以与其相连;其次,这些对象是无用的,即程序以后不会再使用这些对象。如果对象满足这两个条件,这些对象就可以判定为Java中的内存泄漏,这些对象不会被GC所回收,然而它却占用内存。

这里引用一个常看到的例子,在下面的代码中,循环申请Object对象,并将所申请的对象放入一个Vector中,如果仅仅释放对象本身,但因为Vector仍然引用该对象,所以这个对象对GC来说是不可回收的。因此,如果对象加入到Vector后,还必须从Vector中删除,最简单的方法就是将Vector对象设置为null。

Vector v = new Vector(10);     

for (int i = 1; i < 100; i++){

     Object o = new Object();       

    v.add(o);       

    o = null;     

    }//此时,所有的Object对象都没有被释放,因为变量v引用这些对象。

 

实际上这些对象已经是无用的,但还被引用,GC就无能为力了(事实上GC认为它还有用),这一点是导致内存泄漏最重要的原因。 再引用另一个例子来说明Java的内存泄漏。假设有一个日志类Logger,其提供一个静态的log(String msg),任何其它类都可以调用Logger.Log(message)来将message的内容记录到系统的日志文件中。

Logger类有一个类型为HashMap的静态变量temp,每次在执行log(message)的时候,都首先将message的值写入temp中(以当前线程+当前时间为键),在退出之前再从temp中将以当前线程和当前时间为键的条目删除。注意,这里当前时间是不断变化的,所以log在退出之前执行删除条目的操作并不能删除执行之初写入的条目。这样,任何一个作为参数传给log的字符串最终由于被Logger的静态变量temp引用,而无法得到回收,这种对象保持就是我们所说的Java内存泄漏。总的来说,内存管理中的内存泄漏产生的主要原因:保留下来却永远不再使用的对象引用。

我们知道了在Java中确实会存在内存泄漏,那么就让我们看一看几种典型的泄漏,并找出他们发生的原因和解决方法。

全局集合:

在大型应用程序中存在各种各样的全局数据仓库是很普遍的,比如一个JNDI-tree或者一个session table。在这些情况下,必须注意管理储存库的大小。必须有某种机制从储存库中移除不再需要的数据。

通常有很多不同的解决形式,其中最常用的是一种周期运行的清除作业。这个作业会验证仓库中的数据然后清除一切不需要的数据。

另一种管理储存库的方法是使用反向链接(referrer)计数。然后集合负责统计集合中每个入口的反向链接的数目。这要求反向链接告诉集合何时会退出入口。当反向链接数目为零时,该元素就可以从集合中移除了。

缓存 :

缓存一种用来快速查找已经执行过的操作结果的数据结构。因此,如果一个操作执行需要比较多的资源并会多次被使用,通常做法是把常用的输入数据的操作结果进行缓存,以便在下次调用该操作时使用缓存的数据。缓存通常都是以动态方式实现的,如果缓存设置不正确而大量使用缓存的话则会出现内存溢出的后果,因此需要将所使用的内存容量与检索数据的速度加以平衡。

常用的解决途径是使用java.lang.ref.SoftReference类坚持将对象放入缓存。这个方法可以保证当虚拟机用完内存或者需要更多堆的时候,可以释放这些对象的引用。

类装载器 :

Java类装载器的使用为内存泄漏提供了许多可乘之机。一般来说类装载器都具有复杂结构,因为类装载器不仅仅是只与"常规"对象引用有关,同时也和对象内部的引用有关。比如数据变量,方法和各种类。这意味着只要存在对数据变量,方法,各种类和对象的类装载器,那么类装载器将驻留在JVM中。既然类装载器可以同很多的类关联,同时也可以和静态数据变量关联,那么相当多的内存就可能发生泄漏。

---------------------- android培训、java培训、期待与您交流! ----------------------详细请查看:http://edu.csdn.net/heima