关于集合中排序有关的比较方式和排序原理


  1.首先了解概念

  (1) 哈希表

     一般翻译做“散列”,也有译为"哈希"的,就是把任意长度的输入(又叫做预映射, pre-image),通过散列算法,变换成固定长度的输出。简单的说,就是hashcode = hash(key),这种转换是一种压缩映射,散列值的空间通常远小于输入的空间,在查找的时候也能提高效率。  Hashtables(哈希表)在计算机领域中已不是一个新概念了。它们是用来加快计算机的处理速度的,用当今的标准方法来处理,速度非常慢,而它们可以让你在查询许多数据条目时,很快地找到一个特殊的条目。尽管现代的机器速度已快了几千倍,但是为了得到应用程序的最佳性能,hashtables仍然是个很有用的方法。

(2).    HashSet概述:

       HashSet实现Set接口,由哈希表(实际上是一个HashMap实例)支持。它不保证set 的迭代顺序;特别是它不保证该顺序恒久不变。此类允许使用null元素

(3)    .Hashmap 要知道hashmap是什么,首先要搞清楚它的数据结构,在java编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),所有的数据结构都可以用这两个基本结构来构造的,hashmap也不例外。java的Hashmap实际上是一个数组和链表的结合体(在数据结构中,一般称之为“链表散列“)

(4)  HashSet的实现:

      对于HashSet而言,它是基于HashMap实现的,HashSet底层使用HashMap来保存所有元素,因此HashSet 的实现比较简单,相关HashSet的操作,基本上都是直接调用底层HashMap的相关方法来完成

    2.现在来观察HashSet

1.
   class Name  
{  
      private String first;   
    private String last;   
      
    public Name(String first, String last)   
    {   
        this.first = first;   
        this.last = last;   
    }   
  
    public boolean equals(Object o)   
    {   
        if (this == o)   
        {   
            return true;   
        }   
          
    if (o.getClass() == Name.class)   
        {   
            Name n = (Name)o;   
            return n.first.equals(first)   
                && n.last.equals(last);   
        }   
        return false;   
    }   
}  
  
public class HashSetTest  
{  
    public static void main(String[] args)  
    {   
        Set<Name> s = new HashSet<Name>();  
        s.add(new Name("abc", "123"));  
        System.out.println(  
            s.contains(new Name("abc", "123")));  
    }  
} 


上面程序中向 HashSet 里添加了一个 new Name("abc", "123") 对象之后,立即通过程序判断该 HashSet 是否包含一个 new Name("abc", "123") 对象。粗看上去,很容易以为该程序会输出 true。 


实际运行上面程序将看到程序输出 false,这是因为 HashSet 判断两个对象相等的标准除了要求通过 equals() 方法比较返回 true 之外,还要求两个对象的 hashCode() 返回值相等。而上面程序没有重写 Name 类的 hashCode() 方法,两个 Name 对象的 hashCode() 返回值并不相同,因此 HashSet 会把它们当成 2 个对象处理,因此程序返回 false。 


由此可见,当我们试图把某个类的对象当成 HashMap 的 key,或试图将这个类的对象放入 HashSet 中保存时,重写该类的 equals(Object obj) 方法和 hashCode() 方法很重要,而且这两个方法的返回值必须保持一致:当该类的两个的 hashCode() 返回值相同时,它们通过 equals() 方法比较也应该返回 true。通常来说,所有参与计算 hashCode() 返回值的关键属性,都应该用于作为 equals() 比较的标准。 
如下程序就正确重写了 Name 类的 hashCode() 和 equals() 方法,程序如下:
class Name   
{   
    private String first;  
    private String last;  
    public Name(String first, String last)  
    {   
        this.first = first;   
        this.last = last;   
    }   
    // 根据 first 判断两个 Name 是否相等  
    public boolean equals(Object o)   
    {   
        if (this == o)   
        {   
            return true;   
        }   
        if (o.getClass() == Name.class)   
        {   
            Name n = (Name)o;   
            return n.first.equals(first);   
        }   
        return false;   
    }   
       
    // 根据 first 计算 Name 对象的 hashCode() 返回值  
    public int hashCode()   
    {   
        return first.hashCode();   
    }  
  
    public String toString()   
    {   
        return "Name[first=" + first + ", last=" + last + "]";   
    }   
 }   
   
 public class HashSetTest2   
 {   
    public static void main(String[] args)   
    {   
        HashSet<Name> set = new HashSet<Name>();   
        set.add(new Name("abc" , "123"));   
        set.add(new Name("abc" , "456"));   
        System.out.println(set);   
    }   
} 

上面程序中提供了一个 Name 类,该 Name 类重写了 equals() 和 toString() 两个方法,这两个方法都是根据 Name 类的 first 实例变量来判断的,当两个 Name 对象的 first 实例变量相等时,这两个 Name 对象的 hashCode() 返回值也相同,通过 equals() 比较也会返回 true。 


程序主方法先将第一个 Name 对象添加到 HashSet 中,该 Name 对象的 first 实例变量值为"abc",接着程序再次试图将一个 first 为"abc"的 Name 对象添加到 HashSet 中,很明显,此时没法将新的 Name 对象添加到该 HashSet 中,因为此处试图添加的 Name 对象的 first 也是" abc",HashSet 会判断此处新增的 Name 对象与原有的 Name 对象相同,因此无法添加进入,程序在①号代码处输出 set 集合时将看到该集合里只包含一个 Name 对象,就是第一个、last 为"123"的 Name 对象。



3.为什么要覆写这hashCode和equals两个方法呢?

假设我们要判断HashSet集合中的元素是否唯一,首先判断的是元素的地址值。每当我们new一个对象时,这时在系统底层默认给新创建的元素一个新的地址,这样他们的hashcode永远都是不一样的。就没有条件去使用equlas了。
所以我们要覆写hashcode,让系统分配哈希值的时候不按照创建对象而分配,而是按照我们需要的方式来分配哈希值例如我们要判断人是否为同一人(暂且设为同名同岁为同一人)这是就可以覆写hashcode:
public int hashCode()
	{
		System.out.println(this.name+"....hashCode");
		return name.hashCode()+age*37;
	}
这样才有使用equals判断元素对象是否唯一的前提,这是再覆写equals

public boolean equals(Object obj)
	{

		if(!(obj instanceof Person))
			return false;

		Person p = (Person)obj;
		System.out.println(this.name+"...equals.."+p.name);

		return this.name.equals(p.name) && this.age == p.age;
	}
覆写equals的原因:Object中的equals方法仅仅是比较两个对象的地址值,我们在其他情况下,并不能满足我们的需求所以要覆写以达到我们的要求。

下面是完整的例子

class HashSetTest 
{
	public static void sop(Object obj)
	{
		System.out.println(obj);
	}
	public static void main(String[] args) 
	{
		HashSet hs = new HashSet();

		hs.add(new Person("a1",11));
		hs.add(new Person("a2",12));
		hs.add(new Person("a3",13));
//		hs.add(new Person("a2",12));
//		hs.add(new Person("a4",14));

		//sop("a1:"+hs.contains(new Person("a2",12)));
			
//		hs.remove(new Person("a4",13));
		

		Iterator it = hs.iterator();

		while(it.hasNext())
		{
			Person p = (Person)it.next();
			sop(p.getName()+"::"+p.getAge());
		}
	}
}
class Person
{
	private String name;
	private int age;
	Person(String name,int age)
	{
		this.name = name;
		this.age = age;
	}
	
	public int hashCode()
	{
		System.out.println(this.name+"....hashCode");
		return name.hashCode()+age*37;
	}

	public boolean equals(Object obj)
	{

		if(!(obj instanceof Person))
			return false;

		Person p = (Person)obj;
		System.out.println(this.name+"...equals.."+p.name);

		return this.name.equals(p.name) && this.age == p.age;
	}

	
	public String getName()
	{
		return name;
	}
	public int getAge()
	{
		return age;
	}
}

4.小知识点"=="运算和equlas()方法的区别


(1).如果是基本变量,没有hashcode和equals方法,基本变量的比较方式就只有==,;

(2).如果是变量,由于在java中所有变量定义都是一个指向实际存储的一个句柄(你可以理解为c++中的指针),在这里==是比较句柄的地址(你可以理解为指针的存储地址),而不是句柄指向的实际内存中的内容,如果要比较实际内存中的内容,那就要用equals方法,但是!!!

如果是你自己定义的一个类,比较自定义类用equals和==是一样的,都是比较句柄地址,因为自定义的类是继承于object,而object中的equals就是用==来实现的,你可以看源码。

那为什么我们用的String等等类型equals是比较实际内容呢,是因为String等常用类已经重写了object中的equals方法,让equals来比较实际内容,你也可以看源码。

(3). hashcode
在一般的应用中你不需要了解hashcode的用法,但当你用到hashmap,hashset等集合类时要注意下hashcode。

你想通过一个object的key来拿hashmap的value,hashmap的工作方法是,通过你传入的object的hashcode在内存中找地址,当找到这个地址后再通过equals方法来比较这个地址中的内容是否和你原来放进去的一样,一样就取出value。

所以这里要匹配2部分,hashcode和equals
但假如说你new一个object作为key去拿value是永远得不到结果的,因为每次new一个object,这个object的hashcode是永远不同的,所以我们要重写hashcode,你可以令你的hashcode是object中的一个恒量,这样永远可以通过你的object的hashcode来找到key的地址,然后你要重写你的equals方法,使内存中的内容也相等。。。

 

 

首先,从语法角度,也就是从强制性的角度来说,hashCode和equals是两个独立的,互不隶属,互不依赖的方法,equals成立与hashCode相等这两个命题之间,谁也不是谁的充分条件或者必要条件。 
  
  但是,从为了让我们的程序正常运行的角度,我们应当向Effective   Java中所言 
  
  重载equals的时候,一定要(正确)重载hashCode 
  
  使得equals成立的时候,hashCode相等,也就是a.equals(b)->a.hashCode()   ==   b.hashCode(),或者说此时,equals是hashCode相等的充分条件,hashCode相等是equals的必要条件(从数学课上我们知道它的逆否命题:hashCode不相等也不会equals),但是它的逆命题,hashCode相等一定equals以及否命题不equals时hashCode不等都不成立

5.TreeSet

TreeSet是依靠TreeMap来实现的 TreeSet是一个有序集合,她的元素 按照升序排列,默认是按照自然顺序排列,也就是说TreeSet中的对象元素需要实现Comparable接口。 TreeSet类中跟HashSet类一样也没有get()方法来获取列表中的元素,所以也只能通过迭代器方法来获取。 

public class TreeSetTest  
{  
      public static void main(String[] args)  
      {  
           TreeSet tr =new TreeSet();  
           tr.add("zhangshan");  
           tr.add("wangwu");  
           tr.add("lisi");  
           Iterator it =tr.iterator();  
           while(it.hasNext())  
           {  
                 System.out.println(it.next());  
           }  
      }  
} //输出结果为:lisi wangwu zhangshan 
//这是因为TreeSet是一个有序并且默认按自然顺序排列,而不像哈希表那样毫无规律。
自定义一个比较器,学生类首先需要实现Compator接口实现compare方法
import java.util.*;
class  TreeSetTest
{
	public static void main(String[] args) 
	{
		TreeSet ts = new TreeSet(new StrLenComparator());

		ts.add("abcd");
		ts.add("cc");
		ts.add("cba");
		ts.add("aaa");
		ts.add("z");
		ts.add("hahaha");

		Iterator it = ts.iterator();

		while(it.hasNext())
		{
			System.out.println(it.next());
		}
	}
}

class StrLenComparator implements Comparator
{
	public int compare(Object o1,Object o2)
	{
		String s1 = (String)o1;
		String s2 = (String)o2;

		/*
		if(s1.length()>s2.length())
			return 1;
		if(s1.length()==s2.length())
			return 0;
			*/

			

		int num = new Integer(s1.length()).compareTo(new Integer(s2.length()));
		if(num==0)
			return s1.compareTo(s2);

		return num;
	}
}

总结:要想弄懂各种排序包括集合中默认的排序原理,就需要掌握hashcode和equals的方法原理和特点,弄懂为什么要对他们进行重写,了解原理后就容易理解和记忆了!


 
 
 
 
 
 
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值