集合类结构图:
图片来源于网络,侵删。
Collection
List和Set接口的父接口,继承了Iterator接口,所以List,Set均可以使用迭代器输出。定义了add(),remove()这些方法,不同类型的集合,对collection中方法的实现有所不同。
que:Collection和Collections的区别?
Collection是集合的root interface,定义了集合的一些基础方法,Collections像是一个工具类,定义了与集合相关的方法,一般来说Collection的子类集合(arrayList,hashMap等)都是非线程安全的,就可以通过Collections来获得相应的线程安全集合。令外Collections还提供unmodifiableMap/List/Set()方法来保证集合不可被修改。
·List
有序集合(sequence),允许重复(duplicate)元素,允许多个null元素。
另外值得提一嘴的是,List有一个单独的迭代器:ListIterator。
-ArrayList
非线程安全。可以通过Collections获得一个synchronizedList。
有序,允许重复元素和null元素。
继承了List和AbstractList,是一个长度可变的数组(resizable-array)。默认长度为10。扩容方案为原来的1.5倍。
底层就是用数组来实现的,这决定了其查询快的特点。
ArrayList的capacity就是数组的size。
看一下源码:
/**
* Default initial capacity.
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* Shared empty array instance used for empty instances.
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* Shared empty array instance used for default sized empty instances. We
* distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
* first element is added.
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
如果在创建ArrayList时没有指定initialCapacity,会创造一个默认DEFAULT_CAPACITY为10的数组。
另外在源码中说明: (This class is roughly equivalent to Vector, except that it is unsynchronized.)
Vector(已过时)是线程安全的ArrayList,其中所有方法都加了synchronized。此外二者的区别还在于扩容方案,Vector的扩容方案为原来两倍。
-LinkedList
底层实现是双向链表(Doubly-linked),所以查询慢,增删快。因为在查询时只能从头一个一个遍历元素。
在eclipse中查看LinkedList类的outline,可以看到有first,last,node这些,可以实锤是链表。
有序,允许重复元素和Null元素。
非线程安全,同样是通过collections.synochronizedList(list)得到一个线程安全的linkedList。
List集合代码:
//list有序集合,可以重复,可为null
public class ListTest {
public static void main(String[] args) {
// ArrayList底层实现是长度可以改变的数组,查询快,增删慢
List<String> list1 = new ArrayList<>();
// LinkedList底层实现是链表,查询慢,增删快
// 提供了专门对头尾元素的操作
List<String> list2 = new LinkedList<>();
list1.add("hehe");
list2.add("haha");
// 1.for-each输出
for (String str1 : list1) {
System.out.println(str1);
}
// 2.iterator输出,Iterator只能向后输出,ListIter可向前(previous())、向后输出
Iterator<String> iterator = list1.iterator();
while (iterator.hasNext()) {
String str2 = iterator.next();
System.out.println(str2);
}
// ListIterator输出
ListIterator<String> listIterator = list2.listIterator();
while (listIterator.hasNext()) {
String str3 = (String) listIterator.next();
System.out.println(str3);
}
// for循环输出
for (int i = 0; i < list1.size(); i++) {
System.out.println(list1.get(i) + "----");
}
}
}
que:Iterator和ListIterator的区别?
ListIterator接口继承(extends)Iterator接口,查看二者源码,可以看到ListIterator对Iterator做了扩展,额外提供了对index的操作和上一个元素的操作,previous()获取上一个元素,可以实现向前遍历集合。但要注意向前遍历时,index不能处于第一个位置。要确保这一点,可以在获取listIterator(int)时指定index位置。
ps:remove()方法:
/**
* Removes from the list the last element that was returned by {@link
* #next} or {@link #previous} (optional operation). This call can
* only be made once per call to {@code next} or {@code previous}.
* It can be made only if {@link #add} has not been
* called after the last call to {@code next} or {@code previous}.
*
* @throws UnsupportedOperationException if the {@code remove}
* operation is not supported by this list iterator
* @throws IllegalStateException if neither {@code next} nor
* {@code previous} have been called, or {@code remove} or
* {@code add} have been called after the last call to
* {@code next} or {@code previous}
*/
void remove();
看remove()方法的说明,注意抛出IllegalStateException异常的情况:如果previous()或next()方法没有被调用,或是在调用previous()或next()方法之后已经调用过add()或是remove()方法,调用remove()会报异常IllegalStateException。
ListIterator独有的set()方法与remove()类似。
·Set
不可重复(no dulicate elements),JDK文档指出Set判断元素是否重复依据的是equals()的比较结果,但还要考虑到hashCode。
无序,最多允许一个null元素(TreeSet不允许Null元素)。
对于重复元素的判断,要同时判断equals()和hashCode。
Set接口的底层是根据Map接口实现的:TreeSet-TreeMap,HashSet-HashMap
-HashSet
继承自Set接口,其底层实际是一个HashMap,查看其源码可以看到HashSet的构造方法是直接new一个HashMap(或是LinkedHashMap)出来。
至多一个null element,多个判定为重复元素。可以指定初始容量int initialCapacity,默认为16,扩容方案为原来2倍。load factor默认为0.75。
HashSet中的元素就是HashMap的key,key不可以重复,可以为null,对应HashSet元素的属性,可以为null,不可以重复。
HashSet构造方法源码:
/**
* Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
* default initial capacity (16) and load factor (0.75).
*/
public HashSet() {
map = new HashMap<>();
}
load factor:加载因子,hashSet达到0.75的容量时进行扩容,扩容方案为当前容量的2倍。
que:HashSet是如何判断元素是否重复?
先说结论:只有同时满足1)equals()返回true和2)hashCode相同两个条件,HashSet才会认定是重复元素。所以在存入对象时,必须要重写equals()和hashCode()两个方法。这样才能达到开发者不让HashSet(HashMap)中的元素重复的要求。
HashSet/HashMap判断元素是否重复的依据是equals()方法,但是由于其底层是一个hash table,而hash table是根据hashCode来认定元素是否重复的,所以就导致这样的情况:equals()的结果返回为false,认定为两个不同对象,如果equals()返回为true,本应该认定为同一个对象,但是如果两个对象hashCode不同,在hash table中仍然被认为两个元素,所以就是即便equals()返回为true,却还是在hashSet中存入了两个相同元素。
说起来有点费解,可移步:https://www.cnblogs.com/skywang12345/p/3324958.html
-LinkedHashSet
文档中的说明:This implementation differs from HashSet</tt> in that it maintains a doubly-linked list running through all of its entries.
即LinkedHashSet和HashSet的区别是在hash算法的基础上多了一个双向链表(doubly-linked list)。
同hashSet一样,可以有一个null元素,初始容量为16,load factor为0.75。
继承HashSet类、实现Set接口,底层其实就是LinkedHashMap,可以查看源码:
public class LinkedHashSet<E>
extends HashSet<E>
implements Set<E>, Cloneable, java.io.Serializable {
private static final long serialVersionUID = -2851667679971038690L;
/**
* Constructs a new, empty linked hash set with the specified initial
* capacity and load factor.
*
* @param initialCapacity the initial capacity of the linked hash set
* @param loadFactor the load factor of the linked hash set
* @throws IllegalArgumentException if the initial capacity is less
* than zero, or if the load factor is nonpositive
*/
public LinkedHashSet(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor, true);
}
//点进super(),到HashSet的构造方法,可以看到是直接创建一个LinkedHashMap:
/**
* Constructs a new, empty linked hash set. (This package private
* constructor is only used by LinkedHashSet.) The backing
* HashMap instance is a LinkedHashMap with the specified initial
* capacity and the specified load factor.
*
* @param initialCapacity the initial capacity of the hash map
* @param loadFactor the load factor of the hash map
* @param dummy ignored (distinguishes this
* constructor from other int, float constructor.)
* @throws IllegalArgumentException if the initial capacity is less
* than zero, or if the load factor is nonpositive
*/
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
-TreeSet
底层是通过treeMap来实现的,可以看到直接new一个treeMap出来。
存入其中的元素会被排序,通过比较器Comparator或者Comparable接口的compareTo()方法,在使用时,必须覆写compareTo()方法。
元素不可重复,元素不可以为null,非线程安全。
TreeSet在使用的时候必须要传入Comparator或者元素必须继承Comparable接口并复写CompareTo()方法,可看作是定义排序规则。
ps:集合中存放的都是对象,TreeSet存入简单类型(int,char)等,也会转成Integer,而Integer类本身已经实现了Comparable接口,复写了compareTo()方法。而如果是存入自定义对象,一定要重写compareTo()方法,实现排序。
Set集合代码:
//Set无序不重复,不可通过下标来访问,元素在set中的位置由其hash码决定,位置固定;至多一个null元素(TreeSet除外);不允许重复
public class SetTest {
public static void main(String[] args) {
//hashSet,当查询时根据哈希码来比较元素,查询速度快
//底层是用hashMap实现
Set<String> hashSet = new HashSet<>();
hashSet.add("heihei");
//linkedHashSet,底层采用链表和哈希表算法,链表查询慢,增删快
Set<String> linkedHashSet = new LinkedHashSet<>();
linkedHashSet.add("xixi");
//多个Null判定为元素重复
linkedHashSet.add(null);
linkedHashSet.add(null);
System.out.println(linkedHashSet.size());
//treeSet,红黑树算法,擅长范围查询;不允许null;不保证元素的添加顺序,但会对存入其中的元素进行排序
//底层是用treeMap实现
Set<String> treeSet = new TreeSet<>();
treeSet.add("lalala");
//解决Set实现类线程不安全:以hashSet为例
Set<String> synchronizedSet = Collections.synchronizedSet(hashSet);
for (String string : synchronizedSet) {
System.out.println(string);
}
Iterator<String> iterator = hashSet.iterator();
while (iterator.hasNext()) {
Object object = (Object) iterator.next();
System.out.println(object);
}
//for循环,将set转换为数组后for输出
String setArray[] = new String[hashSet.size()];
hashSet.toArray(setArray);
for (int i = 0; i < setArray.length; i++) {
System.out.println(setArray[i]+"---");
}
}
}
·Map
key-value形式,key与value一一对应,key不可以重复,可以为null(只能有一个?这样说是因为HashSet只能有一个null)
本质上 Map 并不是一个集合,而是两个集合之间的映射关系。这两个集合每一条数据通过映射关系,可以看成是一条数据。
即 Entry(key,value)。Map 可以看成是由多个 Entry 组成。
注意Map并没有继承Iterator接口,其继承类也都没有实现Iterator接口
注意一个方法:Set<Map.Entry<K, V>> entrySet(),Entry是用来存放键值对(key-value pair)的
这个方法返回一个泛型为Entry的Set集合,Map没有实现Iteraotr接口,但是可以用Iterator来遍历Set<Entry>,这样就可以输出Map中的内容。
-HashMap
允许key和value均为null,key不可以重复,只能有一个null
非线程安全,Connections.synchronizedMap()获得一个线程安全的hashMap
在创建HashMap时可以加入initial capacity、load factor两个参数:默认初始容量为16,load factor为0.75
que:HashMap与HashTable(已弃用)的区别?
源码:The <tt>HashMap</tt> class is roughly equivalent to <tt>Hashtable</tt>, except that it is unsynchronized and permits nulls.
即HashMap为非线程安全,HashTable是线程安全的,HashMap允许key和value都为空,HashTable不允许null。
-LinkedHashMap
继承了HashMap类,实现Map接口,允许key和value均为null
-TreeMap
非线程安全
会对元素根据key进行排序,元素不可为null
存入自定义对象需要Comparator或是实现Comparable接口,覆写compareTo()方法
Map操作代码:
//Map
public class MapTest {
public static void main(String[] args) {
//hashMap,哈希算法,查询效率高,允许一个key为null,多个value为null
Map<String, String> hashMap = new HashMap<>();
hashMap.put("1", "hehe");
hashMap.put("2", "haha");
//linkedHashMap,哈希算法和链表,链表增删快,查询慢
Map<String, String> linkedHashMap = new LinkedHashMap<>();
//红黑树算法,范围查询
Map<String, String> treeMap = new TreeMap<>();
//根据key获取value
System.out.println(hashMap.get("1"));
//获取所有key保存到set
Set<String> keys = hashMap.keySet();
for (String str : keys) {
System.out.println(str);
}
//获取所有value保存到collection
Collection<String> values = hashMap.values();
Iterator<String> iterator = values.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
//获取所有entrys输出
Set<Entry<String, String>> entrys = hashMap.entrySet();
Iterator<Entry<String, String>> entryIterator = entrys.iterator();
while (entryIterator.hasNext()) {
System.out.println(entryIterator.next());
}
}
}