集合框架中的两大父接口: Collection 和 Map
Collection
Collection --> List Set Queue
List --> LinkedList ArrayList Vector
Set --> HashSet TreeSet
Map --> HashMap TreeMap
其中,List中的元素是有序且可以重复的,长度是size()-1,Set中的元素是无序(无序指的是输入顺序和输出顺序不同,但是每次的输出顺序是不变的)且不可以重复的,Queue的底层是一个特殊的线性表(先进先出原则)
ArrayList -- 动态数组实现的List,查找操作时效率高(常用的方法已经在之前的博文中大致说了)。
LinkedList -- 链表实现的List,插入和删除操作效率高。
Vector -- 线程安全的,效率低,已经被淘汰了。
集合框架的类都重写了toString方法,使输出的不再是地址而是数组,如果自定义的类添加到集合类的对象中,需要重写toString方法,得到非地址的输出。
Queue
从一端进,从另一端出,只有支持保持这种存储方式的函数。一般使用LinkedList实现,效率高。
常用API:
boolean offer(E e) //添加
E poll() //移除
E peek() //查找
也有提供add、remove之类的方法。
Deque
双端队列,每一端都可以进,也可以出,当我们限制双端队列的一端不能进或者出时,即为栈。
import java.util.Deque;
import java.util.LinkedList;
public class TestDeque {
public static void main(String[] args) {
Deque<String> deque = new LinkedList<String>();
deque.offer("a"); //加入元素
deque.offer("b");
deque.offerLast("c"); //向末尾加入元素
deque.offerFirst("d"); //向头加入元素
System.out.println(deque);
System.out.println(deque.pollFirst()); //取出头的元素
System.out.println(deque);
System.out.println(deque.pollLast()); //取出尾的元素
System.out.println(deque);
System.out.println(deque.peekFirst()); //查看头的元素
System.out.println(deque.peekLast()); //查看尾的元素
}
}
Set
无序(元素没有下标的概念)、遍历时的顺序和添加时的顺序不一致。常用实现类:HashSet、LinkedHashSet、TreesSet
元素不能重复:是指调用equals方法相等,即内容相等。可以添加null元素。
在向Hashset中添加元素时,调用的add方法是计算两个对象的hash值是否相等来判断的。
其中较重要的实现类
HashSet
当Set集合中已经存在大量元素,查找Set中是否有重复元素会耗费大量的时间,为节省时间,采用Hash函数来减少时间。
HashSet的底层是HashMap加上操作,作为Map中的key存储。
Hash函数原理: 调用HashCode方法,返回一个int型数据。Set集合在存入第一个元素时,先计算第一个元素的HashCode值,放到该返回值相应的存储空间里。如果两个不同的数返回的HashCode值是相同的,则在这个HashCode后形成一个链表来存放数据(尽量避免使用链表,因为会降低查找速度,即尽量让数据的HashCode值都不同)。
所以在实现HashSet时,都会重写HashCode方法,让类中的每个属性都参与计算,尽量让集合中的对象的HashCode值都不同。同样,也需要重写equals,不然可能会发生Set中放入了两个相同元素的情况。(在equals中比较了哪些性质,在hashCode中让这些性质参与计算即可)
可得出结论: HashCode相同,equals不一定相同
HashCode不同,equals一定不同
需要特别注意的是: 对象添加到Set后,不能改变其属性。因为属性改变后,其HashCode也会改变,再根据新产生的HashCode值寻找对象的话,将无法找到,也会导致更改之前的对象不会被找到,也无法被删除清理,积累多了会造成内存溢出(在删除时,需要将集合中的对象取出后计算其HashCode再根据hash值找到元素再删除该位置的元素,改变后的元素其HashCode与存入时不一样,则会删除新值对应的位置,会造成内存溢出,而旧值对应的则一直存在原处)。在遍历时,只是把集合中的所有元素取出来,不涉及计算hash值,则没有问题。
如果想删除地址信息不知道的对象时,就创建一个和它内容完全相同的对象,比较集合中是否有HashCode与之相同的。例如set.remove(new Student("毛毛",1)。
LinkedHashSet
使用hash算法来存储元素,但是使用链表维护次序,输入是按插入顺序的。
TreeSet
使用二叉树维护元素的次序,输出次序是二叉树的中根遍历。是自然序输出的。
LinkedHashSet与HashSet比较:
在插入元素性能上,HashSet性能高
在遍历元素性能上,LinkedHashSet性能高
Collections
Collections是集合框架的工具类,提供了对List、Set、Map的操作,如排序、查找、修改等。
之前的文章说过了Collections.sort的相关方法,除此之外还有很多常用API:
reverse(Collection) //集合反转,没有返回值
shuffle(Collection) //对集合进行随机排序(混洗)
swap(Collection,int,int) //交换两个int代表的索引位元素
Object max(Collection) //返回集合中的最大值(也有相同的min方法)
Object max(Collection,Comparator) //返回接口定义的规则找到最大的
int frequency(Collection,Object) //返回Object对象出现的次数
replaceAll(List list,Object old,Object new) //替换集合中的全部old元素为new元素
Map
是集合框架中的一个独立的映射接口,用于保存具有映射关系的数据,以key-value键值对来保存数据的,没有顺序。
特点:(1)作为key的对象是不能重复的
(2)key可以为null
(3)key和value必须是单向的一对一的映射关系
(4)key和value都必须是引用类型的对象
对map的遍历:
(1)entrySet方法(将每一个key-value封装成一个entry)
(2) keySet方法(将所有的key放在一个新的Set里)
(3) values方法(将所有的value放在一个新的set里)
map的底层: 用数组和链表来存储数据,数组是hash表,通过map的key的hashcode返回值来定位,数组的每一个元素对应一个单向链表。
常用的实现类:
HashMap
使用了hash算法存储key-value键值对,key是无序的,value可以重复。key对象需要重写equals和hashcode方法。
线程不安全,效率高。
常用方法:
import java.util.HashMap;
import java.util.Map;
public class TestMap {
public static void main(String[] args) {
Map<String,String> map =new HashMap<String,String>();
/*
* put(E key,E value)
* 存入一对key-value
* 如果key在map上出现过,那么会覆盖原来的key-value
*/
map.put("毛毛", "90");
map.put("毛一", "90");
map.put("毛二", "89");
/*
* 输出格式; {key=value,key=value...}
*/
System.out.println(map);
/*
* key重复时 替换原值 (如果是覆盖添加,返回原value)
*/
map.put("毛毛", "91");
System.out.println(map);
/*
* 能否添加null为key?
*/
map.put(null,"20");
System.out.println(map);
/*
* containsKey(Object obj)
* 查看map中是否包含某个key
*/
System.out.println(map.containsKey("毛毛"));
System.out.println(map.containsValue("90"));
/*
* 清空key-value
*/
map.clear();
System.out.println(map);
}
}
import java.util.HashMap;
import java.util.Map;
public class TestMap1 {
public static void main(String[] args) {
Map<String,Double> map =new HashMap<String,Double>();
map.put("毛毛",10.0);
map.put("毛一",100.0);
map.put("毛二", 20.0);
/*
* V remove(Object obj)
* 移除map中的一个key-value 返回value数据
* 如果key不存在 返回值为null
*/
System.out.println(map.remove("毛一"));
/*
* V get(Object key)
* 如果没有此key,返回null
*/
System.out.println(map.get("毛毛"));
}
}
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
public class TestMap2 {
public static void main(String[] args) {
Map<String,Double> map =new HashMap<String,Double>();
map.put("毛毛",10.0);
map.put("毛一",100.0);
map.put("毛二", 20.0);
/*
* Set<Entry<K,V>> entrySet()
* 返回值为set集合
* 集合里封装的是每一对的key-value的Entry对象
*
* Entry是Map的内部类
* Entry对象封装了一对key-value
*/
Set<Entry<String,Double>> set = map.entrySet();
/*
* 增强for循环,也叫for-each,即遍历
*/
for(Entry<String,Double> a:set) {
System.out.println(a);
/*
* Entry内部类的方法getKey getValue
*/
System.out.println(a.getKey());
System.out.println(a.getValue());
}
/*
* Set<K> keySet()
* 返回map中所有的key 封装成一个set集合
*/
Set<String> keys = map.keySet();
for(String key:keys) {
Double b = map.get(key);
System.out.println(key+"--"+b);
}
/*
* Collection<V> values()
* 获取map中所有的value封装成Collection对象
*/
Collection<Double> col= map.values();
for(Double d:col)
System.out.println(d);
}
}
TreeMap
底层使用二叉树维护key-value的次序,可以使用自定义排序和自然排序(即放入key的对象要实现Comparable接口),TreeSet也是。
HashTable
比较古老,线程安全,效率低,key和value都不能使用null。
LinkedHashMap
底层使用链表来维护次序。
Properties
也是以key-value作为键值对存储信息,不需要加泛型,父类型是HashTable。