java 之类集框架
类集框架,是数据结构思想在 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 能够进行随机访问,同时是非线程安全的。
定义结构:
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 是双向链表,不能进行随机访问,同时,是非线程安全的。
定义结构:
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,其他情况很难见到。
定义结构:
4、Stack
Stack 是 Vector 的子类。Stack 表示的是栈操作,栈就是先进后出的数据结构。对于栈的操作,体现在 Android 开发上,就是回到之前的状态。
定义结构:
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 对象。
定义结构:
通过查看源代码,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 的元素,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。
定义结构:
这里需要注意父类结构,对于理解 HashMap 的方法操作能够有进一步的理解。
定义结构:
3、LinkedHashMap
LinkedHashMap 是 HashMap 的子类,是有序的,保留了插入时的顺序。同时,LinkedHashMap 是非线程安全的。
LinkedHashMap 是 Map 接口的哈希表和链接列表实现。LinkedHashMap 数据的操作使用继承的 HashMap 方法。同时,通过维护一个双向链表,保证元素的迭代顺序,迭代顺序可以是插入顺序或者是访问顺序。
定义结构:
4、TreeMap
TreeMap 是 Map 的子类,但并不是直接子类。同时,TreeMap 是非线程安全的。
TreeMap 是有序的,基于红黑树(Red-Black tree)结构实现,一对键值对作为红黑树的一个节点,根据 key 对 key-value 进行排序,支持排序方式分为:自然排序、定制排序,何种排序方式取决于所选构造方法。
定义结构:
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 值。
定义结构:
6、WeakHashMap
WeakHashMap 是也是哈希表;和 HashMap 不同的是,HashMap 的“键”是强引用类型,而 WeakHashMap 的“键”是弱引用类型,也就是说当 WeakHashMap 中的某个键不再正常使用时,会被从 WeakHashMap 中被自动移除。WeakHashMap 也不是线程安全的,只适用于单线程。
结构定义
五、集合输出
在 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 就是分析数据。
评论