写点什么

java 之类集框架

作者:andy
  • 2022-10-28
    北京
  • 本文字数:9474 字

    阅读完需:约 31 分钟

类集框架,是数据结构思想在 Java 中的实现应用,更具体而言,就是一系列处理动态数组的工具类


一、Collection 接口


整个类集之中单值保存的最大父接口。所谓单值,就是每次可以向集合里面保存一个对象。Colletion 定义了单值操作的能力。

接口定义

public interface Collection

extends Iterable

常用方法

增:

public boolean add​(E e):增加(最后一个节点有下一个节点)

public boolean addAll​(Collection c):追加(追加的集合根元素连接上一个集合的最后一个节点)

删:

public void clear​():清空(根元素为空)

public boolean remove​(Object o):删除对象(类似判断包含方法)

boolean removeAll(Collection c):删除集合

查:

public boolean isEmpty​():判空(根节点是否为空或者集合个数是否为 0)

public int size​():取得集合个数

public boolean contains​(Object o):判断是否包含(需要进行对象比较,即 equals()支持)

public boolean containsAll(Collection c):判断是否包含集合(需要进行对象比较,即 equals()支持)

public Iterator iterator​():Iterator 接口实例化(用于集合遍历

public Object[] toArray​():集合变为对象数组

JDK1.8 补充方法(服务于处理大数据):

default Stream stream():获取集合的数据流

default Stream parallelStream():获取集合的并行数据流

default boolean removeIf(Predicate filter):删除满足给定条件的集合元素

default Spliterator spliterator():获取分割对象

随着 Java 开发的发展,已不再直接使用 Collection 接口,而是使用其子接口 List 和 Set。这要追溯历史,谈到 PetShop 项目了,List 允许重复,Set 不允许重复。


二、List 接口

List 接口,是 Colletion 单值保存允许重复的子接口,代表一个有序集合,集合中每个元素都有对应的索引值

接口定义:

public interface List

extends Collection

List 接口是 Collection 接口的主要子接口,是开发中常用的类集接口,继承了 Colletion 的常用方法,同时,还增加了索引操作的方法。

增加方法(针对索引的处理):

public E get​(int index):取得指定索引的对象

public E set​(int index,E element):修改指定索引的内容

public int indexOf(Object o):顺序获取指定对象的索引值

public int lastIndexOf(Object o):倒序获取指定对象的索引值

public ListIterator listIterator​():ListIterator 接口实例化(用于集合遍历)

JDK1.9 增加方法(集合工厂):

static List of​():获取个数为 0 的不可变集合

static List of​(E e1):获取包含一个对象的不可变集合

@SafeVarargs

static List of​(E... elements):获取多个对象的不可变集合

List 通过子类实例化对象,常用 ArrayList、LinkedList 进行实例化。

主要子类:

ArrayList、LinkedList、Vector、Stack

对于 List 接口存储的数据,可以由 size()方法得到集合个数,然后,通过 get()方法获取内容。同时,List 集合是顺序存放数据的。请注意没有直接使用 ListIterator 接口进行遍历,而是使用 ListIterator 的父接口 Iterator。


1、ArrayList

ArrayList 是 List 接口的实现子类,是一个动态数组。ArrayList 能够进行随机访问,同时是非线程安全的

定义结构:


public class ArrayList<E>extends AbstractList<E>implements List<E>, RandomAccess, Cloneable, Serializable{
private static final int DEFAULT_CAPACITY = 10;
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
transient Object[] elementData;
private int size;}
复制代码


ArrayList 的初始化容量为 10,代表动态数组的大小。当集合进行元素增加时,会进行容量判断,一旦容量即将满时,会进行容量扩充。尽量一开始便指定合适的容量,以便减少扩容时导致的时间损耗。

构造方法:

public ArrayList():默认初始容量 10

public ArrayList(int initialCapacity):指定初始容量大小

ArrayList 是 List 接口的子类,实现了 List 接口的方法。鉴于 ArrayList 存储的数据结构是动态数组,故所有方法的实现都是围绕动态数组的变化而展开。进行元素添加时,进行容量判断和扩容操作,但在元素删除时,不进行容量缩容操作。

常用方法:

public void add(int index,E element):添加指定索引位置的元素

public boolean addAll(int index,Collection c):指定索引位置的添加集合

public E remove(int index):删除指定索引位置的元素

protected void removeRange(int fromIndex,int toIndex):删除指定索引范围位置的元素

public E set(int index,E element):修改指定索引位置的元素

public E get(int index):获取指定索引位置的元素


2、LinkedList

LinkedList 是 List 的子类。LinkedList 是双向链表不能进行随机访问,同时,是非线程安全的

定义结构:


public class LinkedList<E>extends AbstractSequentialList<E>implements List<E>, Deque<E>, Cloneable, Serializable{
transient int size = 0;
transient Node<E> first;
transient Node<E> last;
private static class Node<E> { E item; Node<E> next; Node<E> prev; Node(Node<E> prev, E element, Node<E> next) { this.item = element; this.next = next; this.prev = prev; }}}
复制代码


LinkedList 是 List 的子类,实现了 List 接口的方法,同时,还增加了根据双向链表操作的方法。对于索引而言,链表从开头或者结尾进行遍历操作,这样的目的便于快速进行增加和删除操作。

增加方法如下:

public void addFirst(E e):增加开头元素

public void addLast(E e):增加结尾元素

public E poll():删除开头元素

public E pollFirst():删除开头元素

public E pollLast():删除结尾元素

public E remove(int index):删除指定索引位置的元素

public E removeFirst():删除开头元素

public E removeLast():删除结尾元素

public E set(int index,E element):修改指定索引位置的元素

public E get(int index):获取指定索引位置的元素

public E getFirst():获取开头元素

public E getLast():获取结尾元素


3、Vector

Vector 是 List 的子类,是一个动态数组,能够实现随机访问,同时,Vector 是线程安全的。Vector 与 ArrayList 几乎相同。Vector 类,最早起始于 jdk1.0,但是当时并没有成熟的数据结构。随着发展,将 Vector 作为了 List 的子类。Java 图形界面开发上能够见到 Vector,其他情况很难见到。

定义结构:


public class Vector<E>extends AbstractList<E>implements List<E>, RandomAccess, Cloneable, Serializable{
protected Object[] elementData;
protected int elementCount;
protected int capacityIncrement;}
复制代码


4、Stack

Stack 是 Vector 的子类。Stack 表示的是栈操作,栈就是先进后出的数据结构。对于栈的操作,体现在 Android 开发上,就是回到之前的状态。

定义结构:


public class Stack<E>extends Vector<E>
复制代码


Stack 是 Vector 的子类,增加了以下 5 个方法,将 Vector 衍生为实现栈操作。

增加方法:

public boolean empty():栈是否为空

public E peek():获取栈顶元素

public E push​(E item):入栈

public E pop​():出栈

public int search(Object o):返回指定对象在栈中的位置,以栈顶是 1 为基础



三、Set 接口

Set 是 Collection 单值保存不允许重复的子接口。存于 Set 集合中的所有元素必须不同,像 List 一样,也可以存储 null,但只能存储一个 null。

Set 是无序的,能够维护自身内部的排序,不需要提供随机访问的方式。虽然 Set 是无序的,但是集合中的元素位置由该元素的 HashCode 决定,故具体位置是固定的。

定义结构:

public interface Set

extends Collection

JDK1.9 之前,对比 Set 接口与 Collection 接口的方法,Set 接口只是简单继承了 Collection 接口,并没有像 List 接口那样扩充了 get()方法。JDK1.9 增加集合工厂操作方法。

JDK1.9 增加的方法(集合工厂,依赖 java.util.ImmutableCollections 类):

static Set of​():获取包含 0 个元素的不可变集合

static Set of​(E e1):获取包含 1 个元素的不可变集合

@SafeVarargs

static Set of​(E... elements):获取 N 个元素的不可变集合

Set 接口通过 HashSet 和 TreeSet 子类实例化。

主要子类:

散列集 HashSet、树形集 TreeSet、链式散列集 LinkedHashSet


1、HashSet

HashSet 存储的数据是不能重复的,允许存储 null,但只能有一个。同时,HashSet 是非线程安全的。

HashSet 数据存储是无序的,按照 Hash 算法存储集合的元素,具有很好的存取和查找能力。存储数据的方式实际上由 HashMap 实现,通过一个 HashMap 对象,元素存储在 key 中,value 统一使用一个 Object 对象。

定义结构:


public class HashSet<E>extends AbstractSet<E>implements Set<E>, Cloneable, Serializable{
private transient HashMap<E,Object> map;
private static final Object PRESENT = new Object();
}
复制代码


通过查看源代码,HashSet 的各种方法操作实际上使用 HashMap 中的方法。

常用方法如下:

public boolean add​(E e):增加元素

public boolean remove​(Object o):删除指定元素

public boolean contains​(Object o):包含指定元素

public int size​():获取元素个数

public Iterator iterator​():集合遍历

为了加深 HashSet 依赖于 HashMap 的理解,将 HashSet 的 add()和 remove()方法源码展示如下。

增加元素:

public boolean add(E e) {

return map.put(e, PRESENT)==null;

}

删除元素:

public boolean remove(Object o) {

return map.remove(o)==PRESENT;

}


2、LinkedHashSet

LinkedHashSet 是 HashSet 的子类,存储的数据不可重复,是非线程安全的。

LinkedHashSet 是有序的,根据 hashCode 值决定元素的存储位置,同时,使用链表维护元素的次序。这样,访问集合的顺序如同元素插入集合的顺序。实际上 LinkedHashSet 基于 LinkedHashMap 实现。

定义结构:

public class LinkedHashSet

extends HashSet

implements Set, Cloneable, Serializable

LinkedHashSet 是 HashSet 的子类,继承了 HashSet 的方法。为了加深 LinkedHashSet 存储的数据结构依赖于 LinkedHashMap 实现,特将构造方法调用原理展示如下。

LinkedHashSet 构造方法:

public LinkedHashSet(int initialCapacity, float loadFactor) {

super(initialCapacity, loadFactor, true);

}

HashSet 构造方法(注意以下构造方法访问权限为 default,故只有 HashSet 子类调用):

HashSet(int initialCapacity, float loadFactor, boolean dummy) {

map = new LinkedHashMap<>(initialCapacity, loadFactor);

}


3、TreeSet

TreeSet 是 Set 的子类,存储的数据是不能重复的。同时,TreeSet 是非线程安全的。

TreeSet 数据存储是有序的,基于 TreeMap 实现的。为了达到排序目的,支持两种排序方式:自然排序和定制排序。TreeSet 默认自然排序,当使用无参构造函数,TreeSet 使用自然排序;当使用带有比较器参数的构造函数,需要用户定义比较器。

定义结构:

public class TreeSet

extends AbstractSet

implements NavigableSet, Cloneable, Serializable{

private transient NavigableMapm;

private static final Object PRESENT = new Object();

}


构造方法源码如下:

默认自然排序的无参构造方法

public TreeSet() {

this(new TreeMap());

}

自定义比较器的构造函数

public TreeSet(Comparator comparator) {

this(new TreeMap<>(comparator));

}

新生成一个 Set 集合

public TreeSet(Collection c) {

this();

addAll(c);

}

新生成一个 Set 集合

public TreeSet(SortedSet s) {

this(s.comparator());

addAll(s);

}


TreeSet 是 Set 的子类,使用的方法来自于 Set,与 HashSet、LinkedHashSet 相同。

TreeSet 并不是通过 hashCode()和 equals()方法进行对象比较,而是通过 compare 或者 compareTo 函数进行判断。

HashSet、LinkedHashSet、TreeSet 对象比较的区别



总结:

1、需要排序集合的数据类可以依靠 Comparable 或者 comparator 接口实现;

2、不需要排序集合的数据类则依靠 Object 类中的 hashCode()和 equals()方法实现,带有 hash 的表示无序。


四、Map

Map 接口是一系列键值对的集合,提供了 key 到 value 的映射,保证键值对一一对应,故 key 不能重复,value 可以重复。使用 Map 存储数据,便于快速查找。

定义结构:

public interface Map {

interface Entry {

}

}


Map 集合里通过 Set> entrySet​()方法取得 Set 集合,然后,由 Set 的 iterator()方法遍历取得每个 Map.Entry 对象,最后,取得 key 和 value 数据。

常用方法:

public V put​(K key,V value):增加键值对

public void putAll​(Map m):增加键值对集合

public V remove​(Object key):删除键值对

public V get​(Object key):获取 value 值

public boolean containsKey​(Object key):是否包含 key

public boolean containsValue​(Object value):是否包含 value

public Set keySet​():取出所有的 key

public Set> entrySet​():将 Map 集合转换为 Set 集合


JDK1.8 增加方法:

default V getOrDefault​(Object key,V defaultValue):获取指定 key 的 value 值

default V putIfAbsent(K key,V value):设置映射为空时的默认值

default boolean remove(Object key,Object value):删除 key 映射为 value 值的键值对

default boolean replace(K key,V oldValue,V newValue):替换

JDK1.9 增加方法(集合工厂):

static Map of​():获取 0 个键值对的不可变集合

static Map of​(K k1,V v1):获取包含 1 个键值对的不可变集合

@SafeVarargs

static Map ofEntries​(Map.Entry... entries):

获取多个键值对的不可变集合


Map 常用 HashMap 进行实例化。

主要子类:

HashMap、HashTable、SortedMap、TreeMap、WeakHashMap



图 Map 内部存储结构


1、Map.Entry

Map.Entry 是 Map 接口封装的内部接口,作用是定义 Map 集合中存储数据对象的能力

核心方法:

public V setValue​(V value):设置 value 值

public K getKey​():获取 key

public V getValue​():获取 value 值

public int hashCode​():获取 hash 编码

public boolean equals​(Object o):对象比较

JDK1.9 增加方法:

static Comparator> comparingByKey​(Comparator cmp):

通过 key 进行比较

static Comparator> comparingByValue​(Comparator cmp):

通过 value 进行比较

2、HashMap


HashMap 是 Map 的子类,存储的数据是无序的,对于键值对而言,通过 key 获取 value,是否有序无太大意义。key 可以存 null。同时,HashMap 是非线程安全的。

HashMap 以链表散列(数组与链表的结合体)的数据结构实现。HashMap.Entry 是一个单向链表结构。

往 HashMap 中 put 元素的时候,先根据 key 的 hashCode 重新计算 hash 值,根据 hash 值得到这个元素在数组(Node[] table)中的位置(即下标)。

如果数组该位置上没有元素,就直接将该元素放到数组中的该位置上;

如果数组该位置上已经存放有相同 key 的元素,则数组中的该位置元素将被替换;

如果数组该位置上已经存放有其他不同 key 的元素,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。


定义结构:



public class HashMap<K,V>extends AbstractMap<K,V>implements Map<K,V>, Cloneable, Serializable{static class Node<K,V> implements Map.Entry<K,V> { final int hash; final K key; V value; Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) { this.hash = hash; this.key = key; this.value = value; this.next = next; }}
transient Node<K,V>[] table;
transient Set<Map.Entry<K,V>> entrySet;
transient int size;}
复制代码


这里需要注意父类结构,对于理解 HashMap 的方法操作能够有进一步的理解。

定义结构:


public abstract class AbstractMap<K,V>extends Objectimplements Map<K,V>{transient volatile Set<K>        keySet;transient volatile Collection<V> values;}
复制代码


3、LinkedHashMap

LinkedHashMap 是 HashMap 的子类,是有序的,保留了插入时的顺序。同时,LinkedHashMap 是非线程安全的。

LinkedHashMap 是 Map 接口的哈希表和链接列表实现。LinkedHashMap 数据的操作使用继承的 HashMap 方法。同时,通过维护一个双向链表,保证元素的迭代顺序,迭代顺序可以是插入顺序或者是访问顺序。

定义结构:


public class LinkedHashMap<K,V>extends HashMap<K,V>implements Map<K,V>{
static class Entry<K,V> extends HashMap.Node<K,V> { Entry<K,V> before, after; Entry(int hash, K key, V value, Node<K,V> next) { super(hash, key, value, next); }}
transient LinkedHashMap.Entry<K,V> head;
transient LinkedHashMap.Entry<K,V> tail;
final boolean accessOrder;}
复制代码


4、TreeMap

TreeMap 是 Map 的子类,但并不是直接子类。同时,TreeMap 是非线程安全的。

TreeMap 是有序的,基于红黑树(Red-Black tree)结构实现,一对键值对作为红黑树的一个节点,根据 key 对 key-value 进行排序,支持排序方式分为:自然排序、定制排序,何种排序方式取决于所选构造方法。

定义结构:


public class TreeMap<K,V>extends AbstractMap<K,V>implements NavigableMap<K,V>, Cloneable, Serializable{
private final Comparator<? super K> comparator;
private transient Entry<K,V> root;
private transient int size = 0;
private transient int modCount = 0;
static final class Entry<K,V> implements Map.Entry<K,V> { K key; V value; Entry<K,V> left; Entry<K,V> right; Entry<K,V> parent; boolean color = BLACK;}}
复制代码


Map 中的 key 和 value 可以存储任意类型,那么,当 key 存储的是自定义的类,为了保证 key 的唯一性,自定义的类应该实现 Object 类的 hashCode()和 equals()方法,方便比较。但是建议不要使用自定义类型作为 key,因为还需要覆写 hashCode()和 equals()方法,比较麻烦,而使用 String 类较好,String 支持了 hashCode()和 equals()方法。

两种排序比较



5、Hashtable

Hashtable 是 Map 的子类,对于 key 和 value,都不能为 null,同时,是线程安全的。

Hashtable 是 jdk1.0 时就有的,后期才是实现了 Map 接口,继续保留使用。可以看出,jdk1.0 时引入的类集框架中的类,大都采用同步的方式处理。Hashtable 和 Vector 都是这样的。

从结构可以看出,Hashtable 与 HashMap 相似,区别在于 Hashtable 采用同步处理,线程安全,并且不允许 null 值。

定义结构:




public class Hashtable<K,V>extends Dictionary<K,V>implements Map<K,V>, Cloneable, Serializable{private transient Entry<?,?>[] table;
private transient int count;
private int threshold;
private float loadFactor;
private transient int modCount = 0;
private static class Entry<K,V> implements Map.Entry<K,V> { final int hash; final K key; V value; Entry<K,V> next;
protected Entry(int hash, K key, V value, Entry<K,V> next) { this.hash = hash; this.key = key; this.value = value; this.next = next; }}}
复制代码


6、WeakHashMap

WeakHashMap 是也是哈希表;和 HashMap 不同的是,HashMap 的“键”是强引用类型,而 WeakHashMap 的“键”是弱引用类型,也就是说当 WeakHashMap 中的某个键不再正常使用时,会被从 WeakHashMap 中被自动移除。WeakHashMap 也不是线程安全的,只适用于单线程。

结构定义


public class WeakHashMap<K,V>extends AbstractMap<K,V>implements Map<K,V>{
private static final int DEFAULT_INITIAL_CAPACITY = 16;
private static final int MAXIMUM_CAPACITY = 1 << 30;
private static final float DEFAULT_LOAD_FACTOR = 0.75f;
Entry<K,V>[] table;
private int size;
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> { V value; final int hash; Entry<K,V> next;}}
复制代码


五、集合输出

在 jdk1.8 之前的线性关系类集框架中,提供了四种方式:Iterator(95%)、ListIterator(0.05%)、Enumeration(0.49%,关于此,它是程序架构的关键接口)、foreach(0.05%)。

Iterator 接口只能实现单向遍历。

ListIterator 接口能够实现双向遍历,但是前提是得先从前往后,才能从后往前。

Enumeration 接口从 jdk1.0 开始就有了,最初的时候,Vector 类使用该接口进行集合遍历。对于该接口,其实例化只能依靠 Vector 子类的 public Enumeration elements​()方法。


六、Properties

Properties 类是 Hashtable 的子类,主要是进行属性的操作,利用字符串设置 key 和 value。定义结构如下:

public class Properties

extends Hashtable

Properties 类不需要设置泛型,因为从一个开始就只能保存 String。

常用方法

设置属性:public Object setProperty​(String key,String value)

取得属性:public String getProperty​(String key)

取得属性(设置默认值):public String getProperty​(String key,String defaultValue)

输出属性(comments 表示说明文档的注释):

public void store​(OutputStream out,String comments)throws IOException

读取属性:public void load​(InputStream inStream)throws IOException

对于资源文件,既可以使用 Properties 类读取,也可以使用 ResourceBundle 读取。这也是属性文件的后缀设置为“*.properties”的原因。


七、Collections

java 中提供了一个工具类,方便对 Map、List、Set 的操作。

常用方法:

追加数据:

@SafeVarargs

public static boolean addAll​(Collection c,T... elements)

反转:public static void reverse​(List list)

该类本质上都是调用了类集中的方法,实际意义不大。


八、数据流


Java8 中提供了一个数据操作的 java.util.stream.Stream 类,可以通过 Collection 接口中的 default Stream stream​()方法取得。使用接口中使用关键字 default 修饰,就是 jdk1.8 增强方法的新特性,也就是普通方法和静态方法。从某种意义上说,Stream 就相当于与 Collection 集合中的数据再进行加工。

Stream 类的常用方法:

取得个数:long count​()

去掉重复数据:Stream distinct​()

收集器: R collect​(Collector collector)

通过 java.util.stream.Collectors 的 public static Collector> toList​()方法辅助实现 Stream 进行收集器功能。

在进行 Stream 操作时,会出现数据处理的问题,尤其是过滤。

过滤方法:Stream filter​(Predicate predicate)

数据处理方法(该方法是 MapReduce 的基础): Stream map​(Function mapper)

Stream 也提供了集合分页的操作方法:

设置跳过的数据行数:Stream skip​(long n)

设置取出的数据个数:Stream limit​(long maxSize)

数据匹配的操作方法:

匹配全部:boolean allMatch​(Predicate predicate)

匹配任意一个:boolean anyMatch​(Predicate predicate)

Predicate 接口中提供了支持多个匹配条件的方法:

与:default Predicate and​(Predicate other)

或:default Predicate or​(Predicate other)

为了体现 Stream 的强大之处,需要与 MapReduce 结合使用。

数据分析方法(做数据统计使用):Optional reduce​(BinaryOperator accumulator)

为了进行更好的数据统计,Stream 中提供了以下方法:

按照 Double 处理:DoubleStream mapToDouble​(ToDoubleFunction mapper)

按照 int 处理:IntStream mapToInt​(ToIntFunction mapper)

按照 long 处理:LongStream mapToLong​(ToLongFunction mapper)

注意:BaseStream 接口下有 DoubleStream, IntStream, LongStream, Stream 子接口。

由上可以通过提供的接口实现数据的完整统计分析。Map 就是处理数据,Reduce 就是分析数据。


用户头像

andy

关注

还未添加个人签名 2019-11-21 加入

还未添加个人简介

评论

发布
暂无评论
java之类集框架_andy_InfoQ写作社区