`
yidongkaifa
  • 浏览: 4064080 次
文章分类
社区版块
存档分类
最新评论

Java集合的排序和HashCode方法详解

 
阅读更多

Set集合的排序
我们知道,Set集合是无序的,
可以使用TreeSet类,那么TreeSet进行排序的规则是怎样的呢?
1TreeSet支持两种排序方式,自然排序和定制排序,在默认情况下,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等
如:

  1. TreeSet<String>ts=newTreeSet<String>();
  2. ts.add("b");
  3. ts.add("c");
  4. ts.add("a");
  5. System.out.println(ts);

我们也可以自定义集合元素:
  1. TreeSet<Person>persons=newTreeSet<Person>();
  2. persons.add(newPerson("abc",21));
  3. persons.add(newPerson("efg",44));
  4. persons.add(newPerson("hij",11));
  5. persons.add(newPerson("klm",34));
  6. System.err.println(persons);//输出集合中元素的toString()
  7. for(Personperson:persons){
  8. System.out.println(person.getName()+""+person.getAge());
  9. }
Person类:
  1. publicclassPersonimplementsComparable<Person>{
  2. privateStringname;
  3. privateintage;
  4. publicPerson(Stringname,intage){
  5. this.age=age;
  6. this.name=name;
  7. }
  8. publicStringgetName(){
  9. returnname;
  10. }
  11. publicvoidsetName(Stringname){
  12. this.name=name;
  13. }
  14. publicintgetAge(){
  15. returnage;
  16. }
  17. publicvoidsetAge(intage){
  18. this.age=age;
  19. }
  20. publicintcompareTo(Persono){
  21. if(this.age>o.age){
  22. return1;
  23. }elseif(this.age==o.age){
  24. return0;
  25. }else{
  26. return-1;
  27. }
  28. }
  29. @Override
  30. publicStringtoString(){
  31. return"name:"+this.name+"age:"+this.age;
  32. }
  33. @Override
  34. publicinthashCode(){
  35. finalintprime=31;
  36. intresult=1;
  37. result=prime*result+age;
  38. result=prime*result+((name==null)?0:name.hashCode());
  39. returnresult;
  40. }
  41. @Override
  42. publicbooleanequals(Objectobj){
  43. if(this==obj)
  44. returntrue;
  45. if(obj==null)
  46. returnfalse;
  47. if(getClass()!=obj.getClass())
  48. returnfalse;
  49. Personother=(Person)obj;
  50. if(age!=other.age)
  51. returnfalse;
  52. if(name==null){
  53. if(other.name!=null)
  54. returnfalse;
  55. }elseif(!name.equals(other.name))
  56. returnfalse;
  57. returntrue;
  58. }
  59. }
当我们把一个对象放入TreeSet中时,重写该对象对应类的equals方法,应该保证equals方法返回true,compareTo方法返回0.
如果两个对象通过compareTo(Object obj)方法比较返回0时,但他们通过equals方法比较返回false,这将导致TreeSet会将把这两个对象保存在不同的位置,从而两个对象都可以添加成功,这与Set集合的规则有点出入.

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


现在我们来通过定制排序来实现降序排序:
  1. TreeSet<Person2>person2s=newTreeSet<Person2>(newComparator<Person2>(){
  2. publicintcompare(Person2o1,Person2o2){
  3. if(o1.getAge()>o2.getAge()){
  4. return-1;
  5. }elseif(o1.getAge()==o2.getAge()){
  6. return0;
  7. }
  8. return1;
  9. }
  10. });
  11. person2s.add(newPerson2("aaa",12));
  12. person2s.add(newPerson2("bbb",34));
  13. person2s.add(newPerson2("ccc",10));
  14. person2s.add(newPerson2("ddd",34));
  15. for(Person2person:person2s){
  16. System.out.println(person.getName()+""+person.getAge());
  17. }

Person2:
  1. publicclassPerson2{
  2. privateStringname;
  3. privateintage;
  4. publicPerson2(Stringname,intage){
  5. this.age=age;
  6. this.name=name;
  7. }
  8. publicStringgetName(){
  9. returnname;
  10. }
  11. publicvoidsetName(Stringname){
  12. this.name=name;
  13. }
  14. publicintgetAge(){
  15. returnage;
  16. }
  17. publicvoidsetAge(intage){
  18. this.age=age;
  19. }
  20. }
输出结果:

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


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

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


例如:定制排序:
  1. TreeMap<Person2,String>map2=newTreeMap<Person2,String>(newComparator<Person2>(){
  2. publicintcompare(Person2o1,Person2o2){
  3. if(o1.getAge()>o2.getAge()){
  4. return-1;
  5. }elseif(o1.getAge()<o2.getAge()){
  6. return1;
  7. }
  8. return0;
  9. }
  10. });
  11. map2.put(newPerson2("aaa",21),"90");
  12. map2.put(newPerson2("bbb",33),"10");
  13. map2.put(newPerson2("ccc",22),"30");
  14. map2.put(newPerson2("ddd",55),"50");
  15. //通过keySet遍历map如果只要获取值直接通过map2.values
  16. for(Person2key:map2.keySet()){
  17. System.out.println(key.getName()+""+key.getAge());
  18. }



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


  1. @SuppressWarnings("unchecked")
  2. publicstaticMap.Entry<String,Integer>[]getSortedHashtableByValue(Mapmap){
  3. Setset=map.entrySet();
  4. Map.Entry<String,Integer>[]entries=(Map.Entry[])set
  5. .toArray(newMap.Entry[set.size()]);
  6. Arrays.sort(entries,newComparator(){
  7. publicintcompare(Objectarg0,Objectarg1){
  8. Longkey1=Long.valueOf(((Map.Entry)arg0).getValue()
  9. .toString());
  10. Longkey2=Long.valueOf(((Map.Entry)arg1).getValue()
  11. .toString());
  12. returnkey2.compareTo(key1);
  13. }
  14. });
  15. returnentries;
  16. }

  1. Map<String,Integer>score=newHashMap<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. Stringkey=entry.getKey();
  9. Integervalue=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方法进行了修改)

我们来看一个程序:

  1. publicclassHashCodeTest{
  2. publicstaticvoidmain(String[]args){
  3. Setstr=newHashSet();
  4. str.add("a");
  5. str.add("a");//虽然添加了2次,但是他们的equals方法返回true
  6. System.out.println(str.size());//返回值是1
  7. Setcats=newHashSet();
  8. Catc1=newCat("jetty",3);
  9. Catc2=newCat("jetty",3);
  10. Catc3=newCat("petty",2);
  11. cats.add(c1);
  12. cats.add(c2);
  13. //把c2加两次
  14. cats.add(c2);
  15. cats.add(c3);
  16. System.out.println(cats.size());//返回3
  17. }
  18. }
  19. classCat{
  20. privateStringname;
  21. privateintage;
  22. publicCat(Stringname,intage){
  23. super();
  24. this.age=age;
  25. this.name=name;
  26. }
  27. publicStringgetName(){
  28. returnname;
  29. }
  30. publicvoidsetName(Stringname){
  31. this.name=name;
  32. }
  33. publicintgetAge(){
  34. returnage;
  35. }
  36. publicvoidsetAge(intage){
  37. this.age=age;
  38. }
  39. }


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


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


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

  1. Setcats=newHashSet();
  2. Catc1=newCat("jetty",3);
  3. Catc2=newCat("jetty",3);
  4. Catc3=newCat("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值,所以就无法删除,从而导致内存泄漏.

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics