第10章、集合
10.1、概述
- 集合是Java中提供的一种容器,可以用来存储多个数据。
- 集合和数组都是容器,区别如下:
- 数组长度是固定的。集合的长度是可变的;
- 数组中可以存储基本数据类型值,也可以存储对象,而集合中只能存储对象;
- 集合分为两大系列:
- Collection表示一组对象;
- Map表示一组映射关系或键值对;
10.2、Collection
10.2.1、Collection的主要方法
1、添加元素
- add(E 0bj):添加元素对象到当前集合;
- addAll(Collection<? extends E> other):添加other集合中所有元素对象到当前集合中。
2、删除元素
- boolean remove(Object obj):从当前集合中删除第一个找到的与obj对象equals返回true的元素;
- boolean removeAll(Collection<?> coll):从当前集合中删除所有与coll相同的元素。
3、判断
- boolean isEmpty():判断当前集合是否为空集合;
- boolean contains(Object obj):判断当前集合中是否存在一个与obj对象equals返回true的元素;
- boolean containsAll(Collection<?> c):判断c集合中是否在当前集合中都存在。
4、获取元素个数
- int size():获取当前集合中实际存储的元素个数。
5、交集
- boolean retainAll(Collection<?> coll):当前集合仅保留与c集合中元素相同的元素。
6、转为数组
- Object[] toArray():返回包含当前集合中所有的元素数组。
7、迭代器
- public Iterator iterator():获取集合对应的迭代器,用来遍历集合中的元素。
- 迭代器的常用方法:
- public E next():返回迭代的下一个元素;
- public boolean hasNext():如果仍有元素可以迭代,则返回true。
- 注意:
- 不要在使用迭代器进行迭代时,调用Collection的remove方法,否则会报异常。
- 增强for循环(for each)底层也是迭代器。
10.2.2、List集合
1、List接口概述
- List接口的元素有序、可重复、有索引。
2、List接口中常用方法
- List作为Collection集合的子接口,不但继承了Collection接口中的全部方法,而且还增加了一些根据元素索引来操作集合的特有方法。
- 添加元素:
- void add(int index, E ele)
- boolean addAll(int index, Collection<? extends E> eles)
- 获取元素:
- E get(int index)
- 获取元素索引:
- int indexOf(Object obj)
- int lastIndexOf(Object obj)
- 删除和替换元素:
- E remove(int index)
- E set(int index, E ele)
3、List接口的实现类
- ArrayList:
- 底层使用可变长度的Object类型数组实现;
- 从JDK1.8开始构造方法不会为保存元素的数组初始化长度
- 从JDK1.8开始第一次调用添加方法时,将初始化保存元素的数组,初始化长度为10;
- 当元素已满时,数组需要扩容,默认扩容为1.5倍。
- ArrayList遍历查询效率较高,增加删除效率较低。
- LinkedList:
- 底层使用双向链表实现;
- 频繁存取效率高,遍历查询效率低。
- Vector:动态数组
- Stack:栈
4、ListIterator
List 集合额外提供了一个 listIterator() 方法,该方法返回一个 ListIterator 对象, ListIterator 接口继承了 Iterator 接口,提供了专门操作 List 的方法:
- void add():通过迭代器添加元素到对应集合
- void set(Object obj):通过迭代器替换正迭代的元素
- void remove():通过迭代器删除刚迭代的元素
- boolean hasPrevious():如果以逆向遍历列表,往前是否还有元素。
- Object previous():返回列表中的前一个元素。
- int previousIndex():返回列表中的前一个元素的索引
- boolean hasNext()
- Object next()
- int nextIndex()
10.2.3、Set集合
1、Set概述
- Set接口是Collection的子接口,set接口没有提供额外的方法;
- Set 集合不允许包含相同的元素;
- Set的常用实现类有:HashSet、TreeSet、LinkedHashSet;
- Set集合支持的遍历方式和Collection集合一样:foreach和Iterator。
2、HashSet
- HashSet 是 Set 接口的典型实现,大多数时候使用 Set 集合时都使用这个实现类;
- java.util.HashSet底层的实现其实是一个java.util.HashMap支持;
- HashSet 按 Hash 算法来存储集合中的元素,因此具有很好的存取和查找性能;
- HashSet 集合判断两个元素相等的标准: hashCode() 方法比较相等,equals() 方法返回值也相等。因此,存储到HashSet的元素要重写hashCode和equals方法。
3、LinkedHashSet
- LinkedHashSet是HashSet的子类,它在HashSet的基础上,在结点中增加两个属性before和after维护了结点的前后添加顺序;
- 它是链表和哈希表组合的一个数据存储结构;
- LinkedHashSet插入性能略低于 HashSet,但在迭代访问 Set 里的全部元素时有很好的性能。
4、TreeSet
- 底层结构:里面维护了一个TreeMap,都是基于红黑树实现的;
- 不允许重复;
- 实现排序:
- 自然排序
- 如果使用的是自然排序,则通过调用实现的compareTo方法;
- 让待添加的元素类型实现Comparable接口,并重写compareTo方法;
- 定制排序
- 如果使用的是定制排序,则通过调用比较器的compare方法;
- 创建Set对象时,指定Comparator比较器接口,并实现compare方法。
10.2.4、Collections工具类
List list = new ArrayList();
list.add("罗老师");
list.add("马老师");
System.out.println(list);
// 将多个元素一次性添加到一个集合中
Collections.addAll(list, "老王", "老张");
System.out.println(list);
// 反转集合元素
Collections.reverse(list);
System.out.println(list);
// 随机排列集合中的元素
Collections.shuffle(list);
System.out.println(list);
// 二分查找的方式在List集合中查找参数元素的索引
int binarySearch = Collections.binarySearch(list, "马老师");
System.out.println(binarySearch);
// 交换两个索引的位置
Collections.swap(list,3,2);
System.out.println(list);
// 找出集合中的最大元素
Comparable max = Collections.max(list);
System.out.println(max);
// 排序
Collections.sort(list);
System.out.println(list);
// 返回参数集合中参数元素出现的次数
int frequency = Collections.frequency(list,"马老师");
System.out.println(frequency);
10.3、Map
10.3.1、Map常用方法
1、添加
- V put(K key,V value)
- void putAll(Map<? extends K,? extends V> m)
2、删除
- void clear()
- V remove(Object key)
3、查询
- V get(Object key)
- boolean containsKey(Object key)
- boolean containsValue(Object value)
- boolean isEmpty()
- int size()
4、元视图操作
- Set keySet()
- Collection values()
- Set<Map.Entry<K,V>> entrySet()
5、遍历
- 分开遍历:
- 单独遍历所有key
- 单独遍历所有value
- 成对遍历:
- 通过Map.Entry
10.3.2、HashMap
1、概述
- HashMap是Map接口使用频率最高的实现类;
- 允许使用null键和null值,与HashSet一样,不保证映射的顺序;
- 所有的key构成的集合时Set:无序的、不可重复的。所以,key在的类要重写:equals()和hashCode();
- 所有的value构成的集合时Collection:无序的、可以重复。所以,value所在的类要重写:equals();
- 一个key-value构成一个entry;
- 所有的entry构成的集合是Set:无序的、不可重复的;
- HashMap 判断两个 key 相等的标准是:两个 key 通过 equals() 方法返回 true,hashCode 值也相等。
- HashMap 判断两个 value相等的标准是:两个 value 通过 equals() 方法返回 true。
2、存储结构(JDK1.8)
- HashMap的内部存储结构其实是数组+链表+树的结合。当实例化一个HashMap时,会初始化initialCapacity和loadFactor,在put第一对映射关系时,系统会创建一个长度为initialCapacityNode数组,这个长度在哈希表中被称为容量(Capacity),在这个数组中可以存放元素的位置我们称之为“桶”(bucket),每个bucket都有自己的索引,系统可以根据索引快速的查找bucket中的元素。
- 每个bucket中存储一个元素,即一个Node对象,但每一个Node对象可以带一个引用变量next,用于指向下一个元素,因此,在一个桶中,就有可能生成一个Node链。也可能是一个一个TreeNode对象,每一个TreeNode对象可以有两个叶子结点left和right,因此,在一个桶中,就有可能生成一个TreeNode树。而新添加的元素作为链表的last,或树的叶子结点。
- 当HashMap中的元素个数超过数组大小(数组总大小length,不是数组中个数size)loadFactor 时 , 就会进行数组扩容 , loadFactor 的默认值(DEFAULT_LOAD_FACTOR)为0.75,这是一个折中的取值。也就是说,默认情况下,数组大小(DEFAULT_INITIAL_CAPACITY)为16,那么当HashMap中元素个数超过160.75=12(这个值就是代码中的threshold值,也叫做临界值)的时候,就把数组的大小扩展为 2*16=32,即扩大一倍,然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作,所以如果我们已经预知HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能。
- 当HashMap中的其中一个链的对象个数如果达到了8个,此时如果capacity没有达到64,那么HashMap会先扩容解决,如果已经达到了64,那么这个链会变成树,结点类型由Node变成TreeNode类型。当然,如果当映射关系被移除后,下次resize方法时判断树的结点个数低于6个,也会把树再转为链表。
3、源码中的重要常量
- DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16;
- MAXIMUM_CAPACITY : HashMap的最大支持容量,2^30;
- DEFAULT_LOAD_FACTOR:HashMap的默认加载因子;
- TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树;
- UNTREEIFY_THRESHOLD:Bucket中红黑树存储的Node小于该默认值,转化为链表;
- MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量。(当桶中Node的数量大到需要变红黑树时,若hash表容量小MIN_TREEIFY_CAPACITY时,此时应执行resize扩容操作这个MIN_TREEIFY_CAPACITY的值至少是TREEIFY_THRESHOLD的4倍。)
- table:存储元素的数组,总是2的n次幂;
- entrySet:存储具体元素的集;
- size:HashMap中存储的键值对的数量;
- modCount:HashMap扩容和结构改变的次数;
- threshold:扩容的临界值,=容量*填充因子;
- loadFactor:填充因子;
10.3.3、Hashtable
- HashMap和Hashtable都是哈希表。
- HashMap和Hashtable判断两个 key 相等的标准是:两个 key 的hashCode 值相等,并且 equals() 方法也返回 true。因此,为了成功地在哈希表中存储和获取对象,用作键的对象必须实现 hashCode 方法和 equals 方法。
- Hashtable是线程安全的,任何非 null 对象都可以用作键或值。
- HashMap是线程不安全的,并允许使用 null 值和 null 键。
10.3.4、LinkedHashMap
LinkedHashMap 是 HashMap 的子类。此实现与 HashMap 的不同之处在于,后者维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,该迭代顺序通常就是将键插入到映射中的顺序(插入顺序)。
10.3.5、TreeMap
基于红黑树(Red-Black tree)的 NavigableMap 实现。该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法。
10.3.6、Properties
- Properties 类是 Hashtable 的子类,Properties 可保存在流中或从流中加载。属性列表中每个键及其对应值都是一个字符串。
- 存取数据时,建议使用setProperty(String key,String value)方法和getProperty(String key)方法。