Java集合(set,map)的排序和HashCode方法详解
2013-10-14 18:59
1031 查看
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
TreeSet<String> ts = new TreeSet<String>();
ts.add("b");
ts.add("c");
ts.add("a");
System.out.println(ts);
我们也可以自定义集合元素:
[java] view
plaincopy
TreeSet<Person> persons = new TreeSet<Person>();
persons.add(new Person("abc",21));
persons.add(new Person("efg",44));
persons.add(new Person("hij",11));
persons.add(new Person("klm",34));
System.err.println(persons);//输出集合中元素的toString()
for (Person person : persons) {
System.out.println(person.getName()+" "+person.getAge());
}
Person类:
[java] view
plaincopy
public class Person implements Comparable<Person> {
private String name;
private int age;
public Person(String name, int age) {
this.age = age;
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int compareTo(Person o) {
if (this.age > o.age) {
return 1;
} else if (this.age == o.age) {
return 0;
} else {
return -1;
}
}
@Override
public String toString() {
return "name:" + this.name + " age:" + this.age;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Person other = (Person) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
当我们把一个对象放入TreeSet中时,重写该对象对应类的equals方法,应该保证equals方法返回true,compareTo方法返回0.
如果两个对象通过compareTo(Object obj)方法比较返回0时,但他们通过equals方法比较返回false,这将导致TreeSet会将把这两个对象保存在不同的位置,从而两个对象都可以添加成功,这与Set集合的规则有点出入.
定制排序:
因为自然,默认按照升序排序,如果我们需要降序排列,我们可以通过Comparator接口.当然我们也可以通过上面的程序实现,只需要修改compareTo方法一段代码:
[java] view
plaincopy
public int compareTo(Person o) {
if (this.age > o.age) {
return -1;
} else if (this.age == o.age) {
return 0;
} else {
return 1;
}
}
输出结果为:
现在我们来通过定制排序来实现降序排序:
[java] view
plaincopy
TreeSet<Person2> person2s = new TreeSet<Person2>(new Comparator<Person2>(){
public int compare(Person2 o1, Person2 o2) {
if(o1.getAge()>o2.getAge()){
return -1;
}else if (o1.getAge()==o2.getAge()) {
return 0;
}
return 1;
}
} );
person2s.add(new Person2("aaa",12));
person2s.add(new Person2("bbb",34));
person2s.add(new Person2("ccc",10));
person2s.add(new Person2("ddd",34));
for (Person2 person : person2s) {
System.out.println(person.getName()+" "+person.getAge());
}
Person2:
[java] view
plaincopy
public class Person2 {
private String name;
private int age;
public Person2(String name, int age) {
this.age = age;
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
输出结果:
我们发现定制排序的集合元素,不用实现Comparable接口.
Map集合排序:
2,TreeMap
与TreeSet集合类似的是,TreeMap也是基于红黑树对TreeMap中的所有key进行排序,从而保证TreeMap总所有的key-value处于有序状态,TreeMap也有两种方式排序:
自然排序:
所有的key必须实现Comparable接口,而且所有的key应该是同一个类,否则会出现类转换异常
定制排序:
创建TreeMap时,传入一个Comparator对象,该对象负责对TreeMap中所有的key进行排序,采用定制排序kei不用实现Comparable接口.
例如:自然排序:
[java] view
plaincopy
//map
TreeMap<Person,String> map = new TreeMap<Person,String>();
map.put(new Person("zhangsan",21), "90");
map.put(new Person("lisi",33), "10");
map.put(new Person("wangwu",22), "30");
map.put(new Person("zhaoliu",55), "50");
//使用entrySet遍历map
for (Entry<Person, String> entry : map.entrySet()) {
Person person = entry.getKey();
System.out.println(person);
}
结果:
例如:定制排序:
[java] view
plaincopy
TreeMap<Person2,String> map2 = new TreeMap<Person2,String>(new Comparator<Person2>(){
public int compare(Person2 o1, Person2 o2) {
if (o1.getAge()>o2.getAge()) {
return -1;
}else if (o1.getAge()<o2.getAge()) {
return 1;
}
return 0;
}
});
map2.put(new Person2("aaa",21), "90");
map2.put(new Person2("bbb",33), "10");
map2.put(new Person2("ccc",22), "30");
map2.put(new Person2("ddd",55), "50");
//通过keySet遍历map 如果只要获取值直接通过map2.values
for(Person2 key : map2.keySet()){
System.out.println(key.getName()+" "+key.getAge());
}
我们知道TreeMap是按key排序如果想要安装value排序怎么办呢?
[java] view
plaincopy
@SuppressWarnings("unchecked")
public static Map.Entry<String, Integer>[] getSortedHashtableByValue(Map map) {
Set set = map.entrySet();
Map.Entry<String, Integer>[] entries = (Map.Entry[]) set
.toArray(new Map.Entry[set.size()]);
Arrays.sort(entries, new Comparator() {
public int compare(Object arg0, Object arg1) {
Long key1 = Long.valueOf(((Map.Entry) arg0).getValue()
.toString());
Long key2 = Long.valueOf(((Map.Entry) arg1).getValue()
.toString());
return key2.compareTo(key1);
}
});
return entries;
}
[java] view
plaincopy
Map<String, Integer> score = new HashMap<String, Integer>();
score.put("yzq", 21);
score.put("yzh", 23);
score.put("yhf", 11);
score.put("ysq", 45);
Map.Entry<String, Integer>[] entries = getSortedHashtableByValue(score);
for (Entry<String, Integer> entry : entries) {
String key = entry.getKey();
Integer value = entry.getValue();
System.out.println(key+":"+value);
}
输出结果:
-------hashCode()方法的作用------
我们知道set集合的元素是不能重复的,它内部是通过equals方法来比较的,如果这两个元素的equals()方法返回true,那么Set集合就认为这两个元素是一样的,就不会往集合里添加两次.(注意:在没有对equals(Object)方法做任何手脚的情况下,equals(Object)和"=="操作符的功能是同样的,我们打开Object的equals方法源代码发现equals方法里面的判断方法依然是通过"=="来判断.但是如果比较两个String值相等的对象时虽然通过new关键字新建了两个对象,但是他们的equals方法却返回真,因为在String中他已经重写了equals方法了,也就是说在String类中它已经对equals方法进行了修改)
我们来看一个程序:
[java] view
plaincopy
public class HashCodeTest {
public static void main(String[] args) {
Set str = new HashSet();
str.add("a");
str.add("a");//虽然添加了2次,但是他们的equals方法返回true
System.out.println(str.size());//返回值是1
Set cats = new HashSet();
Cat c1 = new Cat("jetty",3);
Cat c2 = new Cat("jetty",3);
Cat c3 = new Cat("petty",2);
cats.add(c1);
cats.add(c2);
//把c2加两次
cats.add(c2);
cats.add(c3);
System.out.println(cats.size());//返回3
}
}
class Cat{
private String name;
private int age;
public Cat(String name,int age) {
super();
this.age = age;
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
哈希集合HashSet是Set集合的子类,既然set集合判断元素是通过equals方法,那么我们就让Cat类来覆写Object的equals方法:
[java] view
plaincopy
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Cat other = (Cat) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
从输出结果我们发现,结果依然是3, c1.equals(c2)返回true,集合就不会把重复的元素放进的,但是为什么结果返回3呢?
现在我们就在Cat类中覆写hashCode方法:
[java] view
plaincopy
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
//hashCode()针对String类型的(如name)
return result;
}
发现结果是2,所以对于哈希集合,我们要注意hashCode方法.
注意:如果我们覆写了hashCode方法,我们就要指定按照这个对象的哪些属性来计算这个对象的hashCode值,如果我们一旦选择了某个属性作为计算hashCode值,我们把这个对象保存到集合中去后,我们最好不要修改该对象的参与计算hashCode值的属性,这样容易导致内存泄漏如:
[java] view
plaincopy
Set cats = new HashSet();
Cat c1 = new Cat("jetty",3);
Cat c2 = new Cat("jetty",3);
Cat c3 = new Cat("petty",2);
cats.add(c1);
cats.add(c2);
cats.add(c3);//到目前位置集合中有两个元素
c2.setName("baby");//修改c2的name属性
cats.remove(c2);//删除c2这个元素
System.out.println(cats.size());
发现我们没有成功删除,
然后我们把修改属性的代码注释,发现就成功删除了,
因为HashSet集合是通过hashCode值来决定元素的存放位置,我们已经修改了c2的name属性,那么它的hashCode值就和以前的不同了,然后再根据新的hashCode值去删除未修改前的对象,发现找不到这样的hashCode值,所以就无法删除,从而导致内存泄漏.
我们知道,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
TreeSet<String> ts = new TreeSet<String>();
ts.add("b");
ts.add("c");
ts.add("a");
System.out.println(ts);
我们也可以自定义集合元素:
[java] view
plaincopy
TreeSet<Person> persons = new TreeSet<Person>();
persons.add(new Person("abc",21));
persons.add(new Person("efg",44));
persons.add(new Person("hij",11));
persons.add(new Person("klm",34));
System.err.println(persons);//输出集合中元素的toString()
for (Person person : persons) {
System.out.println(person.getName()+" "+person.getAge());
}
Person类:
[java] view
plaincopy
public class Person implements Comparable<Person> {
private String name;
private int age;
public Person(String name, int age) {
this.age = age;
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int compareTo(Person o) {
if (this.age > o.age) {
return 1;
} else if (this.age == o.age) {
return 0;
} else {
return -1;
}
}
@Override
public String toString() {
return "name:" + this.name + " age:" + this.age;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Person other = (Person) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
当我们把一个对象放入TreeSet中时,重写该对象对应类的equals方法,应该保证equals方法返回true,compareTo方法返回0.
如果两个对象通过compareTo(Object obj)方法比较返回0时,但他们通过equals方法比较返回false,这将导致TreeSet会将把这两个对象保存在不同的位置,从而两个对象都可以添加成功,这与Set集合的规则有点出入.
定制排序:
因为自然,默认按照升序排序,如果我们需要降序排列,我们可以通过Comparator接口.当然我们也可以通过上面的程序实现,只需要修改compareTo方法一段代码:
[java] view
plaincopy
public int compareTo(Person o) {
if (this.age > o.age) {
return -1;
} else if (this.age == o.age) {
return 0;
} else {
return 1;
}
}
输出结果为:
现在我们来通过定制排序来实现降序排序:
[java] view
plaincopy
TreeSet<Person2> person2s = new TreeSet<Person2>(new Comparator<Person2>(){
public int compare(Person2 o1, Person2 o2) {
if(o1.getAge()>o2.getAge()){
return -1;
}else if (o1.getAge()==o2.getAge()) {
return 0;
}
return 1;
}
} );
person2s.add(new Person2("aaa",12));
person2s.add(new Person2("bbb",34));
person2s.add(new Person2("ccc",10));
person2s.add(new Person2("ddd",34));
for (Person2 person : person2s) {
System.out.println(person.getName()+" "+person.getAge());
}
Person2:
[java] view
plaincopy
public class Person2 {
private String name;
private int age;
public Person2(String name, int age) {
this.age = age;
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
输出结果:
我们发现定制排序的集合元素,不用实现Comparable接口.
Map集合排序:
2,TreeMap
与TreeSet集合类似的是,TreeMap也是基于红黑树对TreeMap中的所有key进行排序,从而保证TreeMap总所有的key-value处于有序状态,TreeMap也有两种方式排序:
自然排序:
所有的key必须实现Comparable接口,而且所有的key应该是同一个类,否则会出现类转换异常
定制排序:
创建TreeMap时,传入一个Comparator对象,该对象负责对TreeMap中所有的key进行排序,采用定制排序kei不用实现Comparable接口.
例如:自然排序:
[java] view
plaincopy
//map
TreeMap<Person,String> map = new TreeMap<Person,String>();
map.put(new Person("zhangsan",21), "90");
map.put(new Person("lisi",33), "10");
map.put(new Person("wangwu",22), "30");
map.put(new Person("zhaoliu",55), "50");
//使用entrySet遍历map
for (Entry<Person, String> entry : map.entrySet()) {
Person person = entry.getKey();
System.out.println(person);
}
结果:
例如:定制排序:
[java] view
plaincopy
TreeMap<Person2,String> map2 = new TreeMap<Person2,String>(new Comparator<Person2>(){
public int compare(Person2 o1, Person2 o2) {
if (o1.getAge()>o2.getAge()) {
return -1;
}else if (o1.getAge()<o2.getAge()) {
return 1;
}
return 0;
}
});
map2.put(new Person2("aaa",21), "90");
map2.put(new Person2("bbb",33), "10");
map2.put(new Person2("ccc",22), "30");
map2.put(new Person2("ddd",55), "50");
//通过keySet遍历map 如果只要获取值直接通过map2.values
for(Person2 key : map2.keySet()){
System.out.println(key.getName()+" "+key.getAge());
}
我们知道TreeMap是按key排序如果想要安装value排序怎么办呢?
[java] view
plaincopy
@SuppressWarnings("unchecked")
public static Map.Entry<String, Integer>[] getSortedHashtableByValue(Map map) {
Set set = map.entrySet();
Map.Entry<String, Integer>[] entries = (Map.Entry[]) set
.toArray(new Map.Entry[set.size()]);
Arrays.sort(entries, new Comparator() {
public int compare(Object arg0, Object arg1) {
Long key1 = Long.valueOf(((Map.Entry) arg0).getValue()
.toString());
Long key2 = Long.valueOf(((Map.Entry) arg1).getValue()
.toString());
return key2.compareTo(key1);
}
});
return entries;
}
[java] view
plaincopy
Map<String, Integer> score = new HashMap<String, Integer>();
score.put("yzq", 21);
score.put("yzh", 23);
score.put("yhf", 11);
score.put("ysq", 45);
Map.Entry<String, Integer>[] entries = getSortedHashtableByValue(score);
for (Entry<String, Integer> entry : entries) {
String key = entry.getKey();
Integer value = entry.getValue();
System.out.println(key+":"+value);
}
输出结果:
-------hashCode()方法的作用------
我们知道set集合的元素是不能重复的,它内部是通过equals方法来比较的,如果这两个元素的equals()方法返回true,那么Set集合就认为这两个元素是一样的,就不会往集合里添加两次.(注意:在没有对equals(Object)方法做任何手脚的情况下,equals(Object)和"=="操作符的功能是同样的,我们打开Object的equals方法源代码发现equals方法里面的判断方法依然是通过"=="来判断.但是如果比较两个String值相等的对象时虽然通过new关键字新建了两个对象,但是他们的equals方法却返回真,因为在String中他已经重写了equals方法了,也就是说在String类中它已经对equals方法进行了修改)
我们来看一个程序:
[java] view
plaincopy
public class HashCodeTest {
public static void main(String[] args) {
Set str = new HashSet();
str.add("a");
str.add("a");//虽然添加了2次,但是他们的equals方法返回true
System.out.println(str.size());//返回值是1
Set cats = new HashSet();
Cat c1 = new Cat("jetty",3);
Cat c2 = new Cat("jetty",3);
Cat c3 = new Cat("petty",2);
cats.add(c1);
cats.add(c2);
//把c2加两次
cats.add(c2);
cats.add(c3);
System.out.println(cats.size());//返回3
}
}
class Cat{
private String name;
private int age;
public Cat(String name,int age) {
super();
this.age = age;
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
哈希集合HashSet是Set集合的子类,既然set集合判断元素是通过equals方法,那么我们就让Cat类来覆写Object的equals方法:
[java] view
plaincopy
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Cat other = (Cat) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
从输出结果我们发现,结果依然是3, c1.equals(c2)返回true,集合就不会把重复的元素放进的,但是为什么结果返回3呢?
现在我们就在Cat类中覆写hashCode方法:
[java] view
plaincopy
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
//hashCode()针对String类型的(如name)
return result;
}
发现结果是2,所以对于哈希集合,我们要注意hashCode方法.
注意:如果我们覆写了hashCode方法,我们就要指定按照这个对象的哪些属性来计算这个对象的hashCode值,如果我们一旦选择了某个属性作为计算hashCode值,我们把这个对象保存到集合中去后,我们最好不要修改该对象的参与计算hashCode值的属性,这样容易导致内存泄漏如:
[java] view
plaincopy
Set cats = new HashSet();
Cat c1 = new Cat("jetty",3);
Cat c2 = new Cat("jetty",3);
Cat c3 = new Cat("petty",2);
cats.add(c1);
cats.add(c2);
cats.add(c3);//到目前位置集合中有两个元素
c2.setName("baby");//修改c2的name属性
cats.remove(c2);//删除c2这个元素
System.out.println(cats.size());
发现我们没有成功删除,
然后我们把修改属性的代码注释,发现就成功删除了,
因为HashSet集合是通过hashCode值来决定元素的存放位置,我们已经修改了c2的name属性,那么它的hashCode值就和以前的不同了,然后再根据新的hashCode值去删除未修改前的对象,发现找不到这样的hashCode值,所以就无法删除,从而导致内存泄漏.
相关文章推荐
- Java集合(set,map)的排序和HashCode方法详解
- Java集合排序及java集合类详解--(Collection, List, Set, Map)
- Java集合的排序和HashCode方法详解
- Java集合的排序和HashCode方法详解
- Java集合的排序和HashCode方法详解
- Java集合的排序和HashCode方法详解
- Java集合(set,map)的排序和HashCode方法详解
- Java集合排序及java集合类详解--(Collection, List, Set, Map)
- 【java】java集合list与set、map集合的区别详解
- Java集合Set、List、Map的遍历方法
- Java学习之容器上(Collection接口常用方法,Iterator接口,使用foreach循环遍历Collection集合元素,Set集合通用知识(Hashset类,hashcode()与Lin
- java中List、Set、Map、Properties集合的遍历方法总结
- java集合――Java中的equals和hashCode方法详解
- [置顶] 【JAVA】java中实现map集合的数据存取详解三种方法。Android程序员也是要会写的
- Java集合排序及java集合类详解:Set
- java如何对map进行排序详解(map集合的使用)
- 【JAVA】java中实现map集合的数据存取详解三种方法。Android程序员也是要会写的
- java的集合-List-Map-Set遍历的方法及相互的区别
- Map、Set、Iterator迭代详解与Java平台的集合框架
- My_Java之笔记(8)- 实现TreeSet集合的排序方法