zoukankan      html  css  js  c++  java
  • Java集合的排序和HashCode方法详解

    Set集合的排序
    我们知道,Set集合是无序的,
    可以使用TreeSet类,那么TreeSet进行排序的规则是怎样的呢?
    1 TreeSet支持两种排序方式,自然排序和定制排序,在默认情况下,TreeSet采用自然排序.
    自然排序:
    TreeSet会调用集合元素的compareTo(Object obj)方法来比较元素之间的大小关系,然后将集合的元素按升序排列,这种方式就是自然排序.
    为什么集合元素有compareTo方法,因为集合元素对象实现了Comparable接口,该方法返回一个整数值,当一个对象调用该方法与另一个对象进行比较,例如:
    obj1.compareTo(obj2)如果返回0,表示这两个对象相等,如果该方法返回一个正整数,表示obj1大于obj2如果该方法返回一个负整数,表示obj1小于obj2
    所以需要使用TreeSet集合进行自然排序,元素必须实现Comparable接口,但是Java一些常用的类已经实现了该接口,例如:String Character Boolean Date Time
    BigDecimal BigInteger等
    如:

    [java] view plaincopy
    1. TreeSet<String> ts = new TreeSet<String>();  
    2. ts.add("b");  
    3. ts.add("c");  
    4. ts.add("a");  
    5. System.out.println(ts);  

    我们也可以自定义集合元素:
    [java] view plaincopy
    1. TreeSet<Person> persons = new TreeSet<Person>();  
    2. persons.add(new Person("abc",21));  
    3. persons.add(new Person("efg",44));  
    4. persons.add(new Person("hij",11));  
    5. persons.add(new Person("klm",34));  
    6. System.err.println(persons);//输出集合中元素的toString()  
    7. for (Person person : persons) {  
    8.     System.out.println(person.getName()+" "+person.getAge());  
    9. }  
    Person类:
    [java] view plaincopy
    1. public class Person implements Comparable<Person> {  
    2.   
    3.     private String name;  
    4.     private int age;  
    5.   
    6.     public Person(String name, int age) {  
    7.         this.age = age;  
    8.         this.name = name;  
    9.     }  
    10.   
    11.     public String getName() {  
    12.         return name;  
    13.     }  
    14.   
    15.     public void setName(String name) {  
    16.         this.name = name;  
    17.     }  
    18.   
    19.     public int getAge() {  
    20.         return age;  
    21.     }  
    22.   
    23.     public void setAge(int age) {  
    24.         this.age = age;  
    25.     }  
    26.   
    27.     public int compareTo(Person o) {  
    28.         if (this.age > o.age) {  
    29.             return 1;  
    30.         } else if (this.age == o.age) {  
    31.             return 0;  
    32.         } else {  
    33.             return -1;  
    34.         }  
    35.   
    36.     }  
    37.   
    38.     @Override  
    39.     public String toString() {  
    40.         return "name:" + this.name + " age:" + this.age;  
    41.     }  
    42.   
    43.     @Override  
    44.     public int hashCode() {  
    45.         final int prime = 31;  
    46.         int result = 1;  
    47.         result = prime * result + age;  
    48.         result = prime * result + ((name == null) ? 0 : name.hashCode());  
    49.         return result;  
    50.     }  
    51.   
    52.     @Override  
    53.     public boolean equals(Object obj) {  
    54.         if (this == obj)  
    55.             return true;  
    56.         if (obj == null)  
    57.             return false;  
    58.         if (getClass() != obj.getClass())  
    59.             return false;  
    60.         Person other = (Person) obj;  
    61.         if (age != other.age)  
    62.             return false;  
    63.         if (name == null) {  
    64.             if (other.name != null)  
    65.                 return false;  
    66.         } else if (!name.equals(other.name))  
    67.             return false;  
    68.         return true;  
    69.     }  
    70.   
    71. }  
    当我们把一个对象放入TreeSet中时,重写该对象对应类的equals方法,应该保证equals方法返回true,compareTo方法返回0.
    如果两个对象通过compareTo(Object obj)方法比较返回0时,但他们通过equals方法比较返回false,这将导致TreeSet会将把这两个对象保存在不同的位置,从而两个对象都可以添加成功,这与Set集合的规则有点出入.

    定制排序:
    因为自然,默认按照升序排序,如果我们需要降序排列,我们可以通过Comparator接口.当然我们也可以通过上面的程序实现,只需要修改compareTo方法一段代码:
    [java] view plaincopy
    1. public int compareTo(Person o) {  
    2.     if (this.age > o.age) {  
    3.         return -1;  
    4.     } else if (this.age == o.age) {  
    5.         return 0;  
    6.     } else {  
    7.         return 1;  
    8.     }  
    9.   
    10. }  
    输出结果为:


    现在我们来通过定制排序来实现降序排序:
    [java] view plaincopy
    1. TreeSet<Person2> person2s = new TreeSet<Person2>(new Comparator<Person2>(){  
    2.     public int compare(Person2 o1, Person2 o2) {  
    3.         if(o1.getAge()>o2.getAge()){  
    4.             return -1;  
    5.         }else if (o1.getAge()==o2.getAge()) {  
    6.             return 0;  
    7.         }  
    8.         return 1;  
    9.     }  
    10. } );  
    11. person2s.add(new Person2("aaa",12));  
    12. person2s.add(new Person2("bbb",34));  
    13. person2s.add(new Person2("ccc",10));  
    14. person2s.add(new Person2("ddd",34));  
    15. for (Person2 person : person2s) {  
    16.     System.out.println(person.getName()+" "+person.getAge());  
    17. }  

    Person2:
    [java] view plaincopy
    1. public class Person2 {  
    2.     private String name;  
    3.     private int age;  
    4.   
    5.     public Person2(String name, int age) {  
    6.         this.age = age;  
    7.         this.name = name;  
    8.     }  
    9.   
    10.     public String getName() {  
    11.         return name;  
    12.     }  
    13.   
    14.     public void setName(String name) {  
    15.         this.name = name;  
    16.     }  
    17.   
    18.     public int getAge() {  
    19.         return age;  
    20.     }  
    21.   
    22.     public void setAge(int age) {  
    23.         this.age = age;  
    24.     }  
    25. }  
    输出结果:

    我们发现定制排序的集合元素,不用实现Comparable接口.


      Map集合排序:
    2,TreeMap
    与TreeSet集合类似的是,TreeMap也是基于红黑树对TreeMap中的所有key进行排序,从而保证TreeMap总所有的key-value处于有序状态,TreeMap也有两种方式排序:
    自然排序:
    所有的key必须实现Comparable接口,而且所有的key应该是同一个类,否则会出现类转换异常
    定制排序:
    创建TreeMap时,传入一个Comparator对象,该对象负责对TreeMap中所有的key进行排序,采用定制排序kei不用实现Comparable接口.

    例如:自然排序:
    [java] view plaincopy
    1. //map  
    2. TreeMap<Person,String> map = new TreeMap<Person,String>();  
    3. map.put(new Person("zhangsan",21), "90");  
    4. map.put(new Person("lisi",33), "10");  
    5. map.put(new Person("wangwu",22), "30");  
    6. map.put(new Person("zhaoliu",55), "50");  
    7. //使用entrySet遍历map  
    8. for (Entry<Person, String> entry : map.entrySet()) {  
    9.     Person person = entry.getKey();  
    10.     System.out.println(person);  
    11. }  
    结果:


    例如:定制排序:
    [java] view plaincopy
    1. TreeMap<Person2,String> map2 = new TreeMap<Person2,String>(new Comparator<Person2>(){  
    2.     public int compare(Person2 o1, Person2 o2) {  
    3.         if (o1.getAge()>o2.getAge()) {  
    4.             return -1;  
    5.         }else if (o1.getAge()<o2.getAge()) {  
    6.             return 1;  
    7.         }  
    8.         return 0;  
    9.     }  
    10. });  
    11.   
    12. map2.put(new Person2("aaa",21), "90");  
    13. map2.put(new Person2("bbb",33), "10");  
    14. map2.put(new Person2("ccc",22), "30");  
    15. map2.put(new Person2("ddd",55), "50");  
    16. //通过keySet遍历map 如果只要获取值直接通过map2.values  
    17. for(Person2 key : map2.keySet()){  
    18.     System.out.println(key.getName()+" "+key.getAge());  
    19. }  



    我们知道TreeMap是按key排序如果想要安装value排序怎么办呢?


    [java] view plaincopy
    1. @SuppressWarnings("unchecked")  
    2. public static Map.Entry<String, Integer>[] getSortedHashtableByValue(Map map) {  
    3.     Set set = map.entrySet();  
    4.     Map.Entry<String, Integer>[] entries = (Map.Entry[]) set  
    5.             .toArray(new Map.Entry[set.size()]);  
    6.     Arrays.sort(entries, new Comparator() {  
    7.         public int compare(Object arg0, Object arg1) {  
    8.             Long key1 = Long.valueOf(((Map.Entry) arg0).getValue()  
    9.                     .toString());  
    10.             Long key2 = Long.valueOf(((Map.Entry) arg1).getValue()  
    11.                     .toString());  
    12.             return key2.compareTo(key1);  
    13.         }  
    14.     });  
    15.     return entries;  
    16. }  

    [java] view plaincopy
    1. Map<String, Integer> score = new HashMap<String, Integer>();  
    2. score.put("yzq"21);  
    3. score.put("yzh"23);  
    4. score.put("yhf"11);  
    5. score.put("ysq"45);  
    6. Map.Entry<String, Integer>[] entries = getSortedHashtableByValue(score);  
    7. for (Entry<String, Integer> entry : entries) {  
    8.     String key = entry.getKey();  
    9.     Integer value = entry.getValue();  
    10.     System.out.println(key+":"+value);  
    11. }  

    输出结果:



    -------hashCode()方法的作用------
     
    我们知道set集合的元素是不能重复的,它内部是通过equals方法来比较的,如果这两个元素的equals()方法返回true,那么Set集合就认为这两个元素是一样的,就不会往集合里添加两次.(注意:在没有对equals(Object)方法做任何手脚的情况下,equals(Object)和"=="操作符是同样的通能的,我们打开Object的equals方法源代码发现equals方法里面的判断方法依然是通过"=="来判断.但是如果比较两个String值相等的对象时虽然通过new关键字新建了两个对象,但是他们的equals方法却返回真,因为在String中他已经重写了equals方法了,也就是说在String类中它已经对equals方法进行了修改)

    我们来看一个程序:

    [java] view plaincopy
    1. public class HashCodeTest {  
    2.   
    3.     public static void main(String[] args) {  
    4.           
    5.         Set str = new HashSet();  
    6.         str.add("a");  
    7.         str.add("a");//虽然添加了2次,但是他们的equals方法返回true  
    8.         System.out.println(str.size());//返回值是1  
    9.           
    10.           
    11.         Set cats = new HashSet();  
    12.         Cat c1 = new Cat("jetty",3);  
    13.         Cat c2 = new Cat("jetty",3);  
    14.         Cat c3 = new Cat("petty",2);  
    15.         cats.add(c1);  
    16.         cats.add(c2);  
    17.         //把c2加两次  
    18.         cats.add(c2);  
    19.         cats.add(c3);  
    20.         System.out.println(cats.size());//返回3         
    21.           
    22.     }  
    23.   
    24. }  
    25. class Cat{  
    26.     private String name;  
    27.     private int age;  
    28.       
    29.       
    30.     public Cat(String name,int age) {  
    31.         super();  
    32.         this.age = age;  
    33.         this.name = name;  
    34.     }  
    35.     public String getName() {  
    36.         return name;  
    37.     }  
    38.     public void setName(String name) {  
    39.         this.name = name;  
    40.     }  
    41.     public int getAge() {  
    42.         return age;  
    43.     }  
    44.     public void setAge(int age) {  
    45.         this.age = age;  
    46.     }  
    47. }  


     

    哈希集合HashSet是Set集合的子类,既然set集合判断元素是通过equals方法,那么我们就让Cat类来覆写Object的equals方法:
    [java] view plaincopy
    1. @Override  
    2. public boolean equals(Object obj) {  
    3.     if (this == obj)  
    4.         return true;  
    5.     if (obj == null)  
    6.         return false;  
    7.     if (getClass() != obj.getClass())  
    8.         return false;  
    9.     Cat other = (Cat) obj;  
    10.     if (age != other.age)  
    11.         return false;  
    12.     if (name == null) {  
    13.         if (other.name != null)  
    14.             return false;  
    15.     } else if (!name.equals(other.name))  
    16.         return false;  
    17.     return true;  
    18. }  


     

    从输出结果我们发现,结果依然是3, c1.equals(c2)返回true,集合就不会把重复的元素放进的,但是为什么结果返回3呢?


    现在我们就在Cat类中覆写hashCode方法:
    [java] view plaincopy
    1. @Override  
    2. public int hashCode() {  
    3.     final int prime = 31;  
    4.     int result = 1;  
    5.     result = prime * result + age;  
    6.     result = prime * result + ((name == null) ? 0 : name.hashCode());  
    7.     return result;  
    8. }  
    发现结果是2,所以对于哈希集合,我们要注意hashCode方法.
    注意:如果我们覆写了hashCode方法,我们就要指定按照这个对象的哪些属性来计算这个对象的hashCode值,如果我们一旦选择了某个属性作为计算hashCode值,我们把这个对象保存到集合中去后,我们最好不要修改该对象的参与计算hashCode值的属性,这样容易导致内存泄漏如:

     

    [java] view plaincopy
    1. Set cats = new HashSet();  
    2. Cat c1 = new Cat("jetty",3);  
    3. Cat c2 = new Cat("jetty",3);  
    4. Cat c3 = new Cat("petty",2);  
    5. cats.add(c1);  
    6. cats.add(c2);  
    7. cats.add(c3);//到目前位置集合中有两个元素  
    8. c2.setName("baby");//修改c2的name属性  
    9. cats.remove(c2);//删除c2这个元素  
    10. System.out.println(cats.size());  
    发现我们没有成功删除,
    然后我们把修改属性的代码注释,发现就成功删除了,
    因为HashSet集合是通过hashCode值来决定元素的存放位置,我们已经修改了c2的name属性,那么它的hashCode值就和以前的不同了,然后再根据新的hashCode值去删除未修改前的对象,发现找不到这样的hashCode值,所以就无法删除,从而导致内存泄漏.
  • 相关阅读:
    TDengine社区版
    进程&线程
    I2总线
    S3C2440的GPIO编程
    NPN&PNP
    旁路电容和去耦电容
    战胜C语言中令人头疼的问题
    今天神经有点大。。
    JZs3c2440裸板程序GPIO操作总结
    JZs3c2440学习笔记一
  • 原文地址:https://www.cnblogs.com/shipeng22022/p/4614080.html
Copyright © 2011-2022 走看看