集合框架可以分为两个部分:Collection和Map,详细的框架图如下:
我们先来分析一下Collection集合框架,Collection包括两大体系List和Set,其中List中的元素存取有序、元素可重复、有索引,可以根据索引获取值,Set的元素存取无序、不能存储重复元素。
List下面有有三个实现类ArrayList、vector、LinkedList,ArrayList和Vector底层是数组,LinkedList底层实现是链表。
ArrayList和Vector底层是数组实现,能够根据索引直接获取值,所以查找快,但是删除和添加操作慢,因为需要向前或向后挪动多个元素。Vector是旧版本的,线程安全,所以如果是单线程进行存取,最好用ArrayList,效率快,如果是多线程我们最好用Vector来进行存储,因为Vector里面的方法是线程安全的。
LinkedList是基于链表实现的,查找必须从头开始,所以查找速度慢,但是删除和添加只需要挪动两个节点,所以删除和添加速度快。链表提供了特殊的方法,所以链表可以实现栈或者队列。
Set接口有三个实现类HashSet、LinkedHashSet、TreeSet
HashSet集合存储不重复,无序,原理是什么?因为HashSet底层实现是哈希表,哈希表通过hashCode()和equals()方法来共同保证元素不重复。首先根据存储的元素算出hashCode值,然后根据算出的hashCode值和数组的长度算出存储的下标;如果下标位置无元素,那么直接存储,如果有元素,那么就要使用存入的元素和已经存在的元素进行equals方法进行比较,如果结果为真就不存储,如果为假就进行存储,以链表方式进行存储。
注意:一般我们自定义的类都需要重新写hashCode()和equals(),必须要重写Object类的这两个方法,因为hash值是根据存储的元素获得的
如:
@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; }
LinkedHashSet基于链表和哈希表实现的,所以具有存取有序,元素唯一的特点。
package cn.yqg.day4; public class Person { private int age ; private String name; public Person(int age,String name) { this.age=age; this.name=name; } @Override public String toString() { return "Person [age=" + age + ", name=" + name + "]"; } @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; } }
package cn.yqg.day4; import java.util.Iterator; import java.util.LinkedHashSet; public class Test { public static void main(String[] args) { LinkedHashSet<Person> list=new LinkedHashSet<>(); list.add(new Person(1,"12")); list.add(new Person(1,"12")); list.add(new Person(3,"14")); list.add(new Person(4,"15")); Iterator<Person> it=list.iterator(); while(it.hasNext()) { Person p=it.next(); System.out.println(p); } } }
运行结果:
Person [age=1, name=12] Person [age=3, name=14] Person [age=4, name=15]
TreeSet:特点存取无序,元素唯一,可以进行排序;TreeSet是基于二叉树实现。
存储过程:如果是第一个元素,直接存入,作为根节点,下一个元素进来就会进行比较,如果大于加点就放在节点右边,如果小于节点就放在节点左边,等于节点就不存储,后面的元素会依次进行比较直到有存储的位置为止。
package cn.yqg.day4; import java.util.TreeSet; public class Test2 { public static void main(String[] args) { TreeSet<String> set=new TreeSet<>(); set.add("abd"); set.add("abc"); set.add("bcd"); set.add("bce"); set.add("bce"); for(String str : set) { System.out.println(str); } } }
运行结果:
abc
abd
bcd
bce
TreeSet保证元素唯一有两种方式:
1.自定义对象实现Comparable()接口,重写CompareTo()方法,该方法返回0表示相等,大于0表示存入的元素比被比较的元素大。反之小于0。
2.在创建TreeSet的时候向构造器中传入比较器Comparator接口实现类的对象,实现Comparator接口重写compare方法。
如果向TreeSet中存储自定义类没实现Comparable接口,或者没有传入Comparator比较器时,会出现ClassCastException异常。
下面演示用两种方式存储自定义对象
package cn.yqg.day4; import java.util.TreeSet; public class Test4 { public static void main(String[] args) { TreeSet<Person> treeSet=new TreeSet<>(); treeSet.add(new Person(4,"1")); treeSet.add(new Person(1,"张三")); treeSet.add(new Person(1,"李四")); treeSet.add(new Person(3,"大王")); treeSet.add(new Person(2,"小王")); treeSet.add(new Person(3,"大王")); treeSet.add(new Person(4,"1")); treeSet.add(new Person(4,"1")); for(Person p : treeSet) { System.out.println(p); } } }
package cn.yqg.day4; public class Person implements Comparable<Person>{ private int age ; private String name; public Person(int age,String name) { this.age=age; this.name=name; } @Override public String toString() { return "Person [age=" + age + ", name=" + name + "]"; } @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; } @Override public int compareTo(Person o) { int rusult=this.age-o.age; if(rusult==0) { return this.name.compareTo(o.name); } return rusult; } }
另一种方式:使用比较器Comparator
package cn.yqg.day4; import java.util.Comparator; import java.util.TreeSet; public class Test5 { public static void main(String[] args) { TreeSet<Person2> treeSet2=new TreeSet<>(new Comparator<Person2>() { @Override public int compare(Person2 o1, Person2 o2) { if(o1==o2) { return 0; } int result=o1.getAge()-o2.getAge(); if(result==0) { return o1.getName().compareTo(o2.getName()); } return result; } }); treeSet2.add(new Person2(1,"张三")); treeSet2.add(new Person2(5,"小龙")); treeSet2.add(new Person2(4,"3")); treeSet2.add(new Person2(5,"小庆")); treeSet2.add(new Person2(4,"1")); treeSet2.add(new Person2(1,"1")); for(Person2 p : treeSet2) { System.out.println(p); } } }
package cn.yqg.day4; public class Person2 { private int age ; private String name; public Person2(int age,String name) { this.age=age; this.name=name; } @Override public String toString() { return "Person [age=" + age + ", name=" + name + "]"; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
运行结果:
Person [age=1, name=1] Person [age=1, name=张三] Person [age=4, name=1] Person [age=4, name=3] Person [age=5, name=小庆] Person [age=5, name=小龙]
-------------------------------------------------------------------------------------------------------------------
Collection体系总结:
List:“特点”,存取有序,可存重复值,元素有索引。
ArrayList:数组结构,查询速度快,增删慢,线程不安全,效率高。
Vector:数组结构,查询快,增删慢,线程安全,效率低。
LinkedList:链表结构,增删快,查询慢,线程不安全,效率高。
Set:“特点”,存取无序,不可存重复值,无索引。
HashSet:哈希表,存储无序,元素不重复,无索引。
LinkedHashSet:链表加哈希表,存储有序,无索引,值不重复。
TreeSet:二叉树,元素不重复,存取无序,但是可以进行排序。
两种排序方式:
1.自然排序:我们的元素必须实现Comparable接口,实现CompareTo()方法。
2.比较器排序:我们自定义的类实现Comparator接口,比较器实现Compare方法。然后创建TreeSet的时候把比较器对象当做参数传递给TreeSet。
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
现在我们来看看Map集合框架
Map是一个双列集合,保存的是键值对,键要求保持唯一性,值可以重复。键值一一对应。Map存储是将键值传入Entry,然后存储Entry对象。
Map接口实现类有三个,分别为HashMap、TreeMap、LinkedHashMap。
HashMap:是基于hash表实现的,所以存储自定义对象作为键时,必须重写hashCode和equals方法。存取无序
package cn.yqg.day4; import java.util.HashMap; import java.util.Iterator; import java.util.Map.Entry; import java.util.Set; public class Test6 { public static void main(String[] args) { HashMap<Person,String> map=new HashMap<Person,String>(); map.put(new Person(1,"pp"),"java"); map.put(new Person(2,"kk"),"c"); map.put(new Person(1,"pp"),"c++"); map.put(new Person(3,"ll"),"java"); Set<Entry<Person,String>> entrySet=map.entrySet(); Iterator<Entry<Person,String>> it=entrySet.iterator(); while(it.hasNext()) { Entry<Person,String> entry=it.next(); System.out.println(entry.getKey()+"-----"+entry.getValue()); } } }
结果:
Person [age=1, name=pp]-----c++ Person [age=3, name=ll]-----java Person [age=2, name=kk]-----c
我们发现如果键重复,后面的值会覆盖前面的值。存取无序。
LinkedHashMap:用法基本和HashMap一致,基于链表和哈希表来实现的,所以有存取有序,键不重复的特点。
package cn.yqg.day4; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map.Entry; public class Test7 { public static void main(String[] args) { LinkedHashMap<Person,String> map=new LinkedHashMap<Person,String>(); map.put(new Person(1,"pp"),"java"); map.put(new Person(2,"kk"),"c"); map.put(new Person(1,"pp"),"c++"); map.put(new Person(1,"pp"),"R"); for(Entry<Person,String> entry : map.entrySet()) { System.out.println(entry.getKey()+"-----"+entry.getValue()); } } }
实现结果:
Person [age=1, name=pp]-----R Person [age=2, name=kk]-----c
我们注意到键如果相同,值会被后面的覆盖掉。而且存取有序。
TreeMap集合存储自定义对象,自定义对象始终作为TreeMap的key值,由于TreeMap底层实现是二叉树,所有存进去的数据都要进行排序,排序有两种方法,一种自定义类实现Comparable接口,实现CompareTo方法,另一种实现Comparator接口,实现自定义比较器Compare方法。
package cn.yqg.day4; import java.util.Comparator; import java.util.TreeMap; import java.util.Map.Entry; public class Test8 { public static void main(String[] args) { TreeMap<Person,String> map=new TreeMap<>(new Comparator<Person>() { @Override public int compare(Person o1, Person o2) { if(o1==o2) { return 0; } int result=o1.getAge()-o2.getAge(); if(result==0) { result=o1.getName().compareTo(o2.getName()); } return result; } }); map.put(new Person(1,"pp"),"java"); map.put(new Person(2,"kk"),"c"); map.put(new Person(6,"pp"),"c++"); map.put(new Person(0,"pp"),"R"); map.put(new Person(-7,"pp"),"jsp"); map.put(new Person(0,"pp"),"js"); for(Entry<Person,String> entry : map.entrySet()) { System.out.println(entry.getKey()+"-----"+entry.getValue()); } } }
运行结果:
Person [age=-7, name=pp]-----jsp Person [age=0, name=pp]-----js Person [age=1, name=pp]-----java Person [age=2, name=kk]-----c Person [age=6, name=pp]-----c++