集合就是一个放数据的容器,准确的说是放数据对象引用的容器;集合类存放的都是对象的引用,而不是对象的本身;集合类型主要有3种:set(集)、list(列表)和map(映射)。
Java1.5引入了泛型,所有的集合接口和实现都大量地使用它。泛型允许我们为集合提供一个可以容纳的对象类型,因此,如果你添加其它类型的任何元素,它会在编译时报错。这避免了在运行时出现ClassCastException,因为你将会在编译时得到报错信息。泛型也使得代码整洁,我们不需要使用显式转换和instanceOf操作符。它也给运行时带来好处,因为不会产生类型检查的字节码指令。
Iterator接口提供遍历任何Collection的接口。我们可以从一个Collection中使用迭代器方法来获取迭代器实例。迭代器取代了Java集合框架中的Enumeration。迭代器允许调用者在迭代过程中移除元素。
数组是固定长度的;集合可变长度的。
数组可以存储基本数据类型,也可以存储引用数据类型;集合只能存储引用数据类型。
数组存储的元素必须是同一个数据类型;集合存储的对象可以是不同数据类型。
Map接口和Collection接口是所有集合框架的父接口:
Collection接口的子接口包括:Set接口和List接口
Map接口的实现类主要有:HashMap、TreeMap、Hashtable、ConcurrentHashMap以及Properties等
Set接口的实现类主要有:HashSet、TreeSet、LinkedHashSet等
List接口的实现类主要有:ArrayList、LinkedList、Stack以及Vector等
java.util.Collection 是一个集合接口(集合类的一个顶级接口)。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式,其直接继承接口有List与Set。
Collections则是集合类的一个工具类/帮助类,其中提供了一系列静态方法,用于对集合中元素进行排序、搜索以及线程安全等各种操作。
Set 接口实例存储的是无序的,不重复的数据。List 接口实例存储的是有序的,可以重复的元素。都可以存储null值,但是set不能重复所以最多只能有一个空元素。
Set检索效率低下,删除和插入效率高,插入和删除不会引起元素位置改变 <实现类有HashSet,TreeSet>。
List和数组类似,可以动态增长,根据实际存储的数据的长度自动增长List的长度。查找元素效率高,插入删除效率低,因为会引起其他元素位置改变 <实现类有ArrayList,LinkedList,Vector> 。
1)Arraylist 底层使用的是Object数组;LinkedList 底层使用的是双向循环链表数据结构;
2)ArrayList 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。插入末尾还好,如果是中间,则(add(int index, E element))接近O(n);LinkedList 采用链表存储,所以插入,删除元素时间复杂度不受元素位置的影响,都是近似 O(1)而数组为近似 O(n)。对于随机访问get和set,ArrayList优于LinkedList,因为LinkedList要移动指针。
3)LinkedList 不支持高效的随机元素访问,而ArrayList 实现了RandmoAccess 接口,所以有随机访问功能。快速随机访问就是通过元素的序号快速获取元素对象(对应于get(int index)方法)。所以ArrayList随机访问快,插入慢;LinkedList随机访问慢,插入快。
4)ArrayList的空 间浪费主要体现在在list列表的结尾会预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗比ArrayList更多的空间(因为要存放直接后继和直接前驱以及数据)。
ArrayList和Vector在很多时候都很类似。
(1)两者都是基于索引的,内部由一个数组支持。
(2)两者维护插入的顺序,我们可以根据插入顺序来获取元素。
(3)ArrayList和Vector的迭代器实现都是fail-fast的。
(4)ArrayList和Vector两者允许null值,也可以使用索引值对元素进行随机访问。
以下是ArrayList和Vector的不同点。
(1)Vector是同步的,而ArrayList不是。然而,如果你寻求在迭代的时候对列表进行改变,你应该使用CopyOnWriteArrayList。
(2)ArrayList比Vector快,它因为有同步,不会过载。
(3)ArrayList更加通用,因为我们可以使用Collections工具类轻易地获取同步列表和只读列表。
List<String> strList = new ArrayList<>();
//使用for-each循环
for(String obj : strList){
System.out.println(obj);
}
//using iterator
Iterator<String> it = strList.iterator();
while(it.hasNext()){
String obj = it.next();
System.out.println(obj);
}
当把对象加入到HashSet中时,HashSet会先计算对象的hashCode值来判断对象加入的下标位置,同时也会与其他的对象的hashCode进行比较,如果没有相同的,就直接插入数据;如果有相同的,就进一步使用equals来进行比较对象是否相同,如果相同,就不会加入成功。
1.使用foreach循环遍历
Map<String, String> hashMap = new HashMap<String,String>();
hashMap.put("1", "good");
hashMap.put("2", "study");
hashMap.put("3", "day");
hashMap.put("4", "up");
for (Map.Entry<String, String> entry : hashMap.entrySet()) {
System.out.println(entry.getKey()+":"+entry.getValue());
}
2.使用foreach迭代键值对
Map<String, String> hashMap = new HashMap<String,String>();
hashMap.put("1", "good");
hashMap.put("2", "study");
hashMap.put("3", "day");
hashMap.put("4", "up");
for (String key : hashMap.keySet()) {
System.out.println(key);
}
for (String value : hashMap.values()) {
System.out.println(value);
}
3.使用迭代器
Map<String, String> hashMap = new HashMap<String,String>();
hashMap.put("1", "good");
hashMap.put("2", "study");
hashMap.put("3", "day");
hashMap.put("4", "up");
Iterator<Map.Entry<String, String>> iterator = hashMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, String> next = iterator.next();
System.out.println(next.getKey()+":"+next.getValue());
}
4.使用lambda表达式
Map<String, String> hashMap = new HashMap<String,String>();
hashMap.put("1", "good");
hashMap.put("2", "study");
hashMap.put("3", "day");
hashMap.put("4", "up");
hashMap.forEach((k,v)-> System.out.println(k+":"+v));
相同点:
都是实现来Map接口(hashTable还实现了Dictionary 抽象类)。
不同点:
1. 历史原因:Hashtable 是基于陈旧的 Dictionary 类的,HashMap 是 Java 1.2 引进的 Map 接口
的一个实现,HashMap把Hashtable 的contains方法去掉了,改成containsvalue 和containsKey。因为contains方法容易让人引起误解。
2. 同步性:Hashtable 的方法是 Synchronize 的,线程安全;而 HashMap 是线程不安全的,不是同步的。所以只有一个线程的时候使用hashMap效率要高。
3. 值:HashMap对象的key、value值均可为null。HahTable对象的key、value值均不可为null。
4. 容量:HashMap的初始容量为16,Hashtable初始容量为11,两者的填充因子默认都是0.75。
5. HashMap扩容时是当前容量翻倍即:capacity * 2,Hashtable扩容时是容量翻倍+1 即:capacity * 2+1。
HashSet 底层就是基于 HashMap 实现的。只不过HashSet里面的HashMap所有的value都是同一个Object而已,因此HashSet也是非线程安全的。
1. HashMap通过hashcode对其内容进行快速查找,而TreeMap中所有的元素都保持着某种固定的顺序,如果你需要得到一个有序的结果你就应该使用TreeMap(HashMap中元素的排列顺序是不固定的)。
2. 在Map 中插入、删除和定位元素,HashMap是最好的选择。但如果您要按自然顺序或自定义顺序遍历键,那么TreeMap会更好。使用HashMap要求添加的键类明确定义了hashCode()和 equals()的实现。
每当向数组中添加元素时,都要去检查添加后元素的个数是否会超出当前数组的长度,如果超出,数组将会进行扩容,以满足添加数据的需求。数组扩容通过ensureCapacity(int minCapacity)方法来实现。在实际添加大量元素前,我也可以使用ensureCapacity来手动增加ArrayList实例的容量,以减少递增式再分配的数量。 数组进行扩容时,会将老数组中的元素重新拷贝一份到新的数组中,每次数组容量的增长大约是其原容量的1.5倍。这种操作的代价是很高的,因此在实际使用时,我们应该尽量避免数组容量的扩张。当我们可预知要保存的元素的多少时,要在构造ArrayList实例时,就指定其容量,以避免数组扩容的发生。或者根据实际需求,通过调用ensureCapacity方法来手动增加ArrayList实例的容量。
ArrayList也采用了快速失败的机制,通过记录modCount参数来实现。在面对并发的修改时,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险。