目录

Java集合(1)一 集合框架

Java集合(2)一 ArrayList 与 LinkList

Java集合(3)一 红黑树、TreeMap与TreeSet(上)

Java集合(4)一 红黑树、TreeMap与TreeSet(下)

Java集合(5)一 HashMap与HashSet

引言

集合在任何语言中都是比较重要的基础知识,不同的集合在实现上采用了各种不同的数据结构,导致了各个集合的性能以及使用方式上存在很大差异,深入了解集合框架的整体结构以及各个集合类的实现原理,并灵活使用各个集合对编码有很大帮助。

本系列文章从集合框架的整体设计到源码细节分析了java.util包下各个集合相关接口、抽象类以及各种常用的集合实现类,希望通过这个系列的文章对大家理解各个集合有一定帮助。

如未做特殊说明,本系列所有源码出自以下JDK环境:

java version "1.8.0_131"

Java(TM) SE Runtime Environment (build 1.8.0_131-b11)

Java HotSpot(TM) 64-Bit Server VM (build 25.131-b11, mixed mode)

接口框架

一个好的框架在设计上都会考虑将接口与实现分离,java.util也不例外。要快速理解java.util,第一步就是从他的接口设计入手,从接口的整体设计可以快速明确这个框架的作用和功能。

*图1 集合接口框架*

通过上面的接口框架图可以看出:Collection<E>和Map<K,V>是java.util框架中的两个根接口,代表了两种不同的数据结构:集合和映射表。而List<E>、Set<E>则是继承自Collection<E>下最核心的两个接口,List<E>有序可重复并可以通过整数索引来访问,Set<E>不包含重复元素。下面我们来分别来说下这些核心接口的基本功能。

Collection<E>

public interface Collection<E> extends Iterable<E>

Collection<E>接口是集合的根接口,他代表了一组元素。但是Collection<E>并不关心这组元素是否重复,是否有序。他只提供操作对这组元素的基本操作方法,怎么添加,怎么删除,怎么循环。所有的实现类都必须提供这些方法,下面列出了Collection<E>接口的部分方法:

int size();
boolean contains(Object o);
//Returns an iterator over the elements in this collection.
Iterator<E> iterator();
//Returns an array containing all of the elements in this collection.
Object[] toArray();
//Returns an array containing all of the elements in this collection; the runtime type of the returned array is that of the specified array.
<T> T[] toArray(T[] a);
boolean add(E e);
boolean remove(Object o);
void clear();

在Collection<E>接口的方法中有几个需要注意的地方

iterator()

Iterator<E> iterator();

iterator方法返回一个实现了Iterator接口的对象,作用是依次访问集合中的元素,Iterator<E>接口包含3个方法:

boolean hasNext();
E next();
void remove();

通过多次调用next()方法可遍历集合中的所有元素,需要注意的是需要在调用next()之前调用hasNext()方法,并在hasNext()返回true的时候才可以调用next(),例如:

private static void collectionIterator() {
//不用关注Arrays.asList,只需要知道他能返回一个Collection<E>接口就行
Collection<String> collection = Arrays.asList("Java", "C++", "Python");
Iterator<String> iterator = collection.iterator();
while (iterator.hasNext()) {
String string = (String) iterator.next();
System.out.println(string);
}
}
//output:
//Java
//C++
//Python

从JDK5开始使用“for each”这种更加方便的方式来遍历集合,只要实现了Iterable接口,都可以使用“for each”来遍历,效果和使用iterator一样。Iterable接口只包含一个方法:

Iterator<E> iterator();
private static void foreachCollectionIterator() {
Collection<String> collection = Arrays.asList("Java", "C++", "Python");
for (String string : collection) {
System.out.println(string);
}
}
//output:
//Java
//C++
//Python

toArray( ) 以及 toArray(T[ ] a)

toArray和toArray(T[ ] a)返回的都是当前所有元素的数组。

toArray返回的是一个Object[]数组,类型不能改变。

toArray(T[ ] a)返回的是当前传入的类型T的数组,更方便用户操作,比如需要获取一个String类型的数组:toArray(new String[0])。

List<E>

public interface List<E> extends Collection<E>

List<E>接口最重要的特点在有序(ordered collection)这个关键字上面,实现这个接口的类可以通过整数索引来访问元素。他可以包含重复的元素。

除了包含Collection<E>接口的所有方法外,还包括跟索引有关的部分方法:

E get(int index);
E set(int index, E element);
void add(int index, E element);
E remove(int index);
int indexOf(Object o);
ListIterator<E> listIterator();
ListIterator<E> listIterator(int index);
List<E> subList(int fromIndex, int toIndex);

其中需要引起注意的地方是ListIterator这个类:

List<E>接口在Iterator迭代器的基础上提供了另一个迭代器ListIterator,先来看看ListIterator<E>接口的定义:

public interface ListIterator<E> extends Iterator<E> {
boolean hasNext();
E next();
boolean hasPrevious();
E previous();
int nextIndex();
int previousIndex();
void remove();
void set(E e);
void add(E e);
}

ListIterator<E>接口继承自Iterator<E>接口,所以他们的差异在ListIterator<E>接口新增的功能上:

  • ListIterator<E>可以向后迭代previous()
  • ListIterator<E>可以获取前后索引nextIndex()
  • ListIterator<E>可以添加新值add(E e)
  • ListIterator<E>可以设置新值set(E e)

Set<E>

public interface Set<E> extends Collection<E>

Set<E>接口在方法签名上与Collection<E>接口是一样的,只不过在方法的说明上有更严格的定义,最重要的特点是他拒绝添加重复元素,不能通过整数索引来访问。Set<E>的equals方法定义如果两个集相等是他们包含相同的元素但顺序不必相同。

至于为什么要定义一个方法签名完全重复的接口,我的理解是为了让框架结构更加清晰,将集合从可以添加重复元素和不可以添加重复元素,可以通过整数索引访问和不可以通过整数索引这几点上区别开来,这样当程序员需要实现自己的集合时能够更准确的继承相应接口。

Map<K,V>

public interface Map<K,V>

API说明上关于Map<K,V>的说明非常精炼:从键映射到值的一个对象,键不能重复,每个键至多映射到一个值。

从键不能重复这个特点很容易想到通过Set<E>来实现键,他的接口方法Set<K> keySet()也证明了这点,下面选取了Map<K,V>接口中的一些典型方法:

int size();
boolean containsKey(Object key);
V get(Object key);
V put(K key, V value);
V remove(Object key);
void clear();
//Returns a Set view of the keys contained in this map.
Set<K> keySet();
Returns a Collection view of the values contained in this map.
Collection<V> values();
//Returns a Set view of the mappings contained in this map.
Set<Map.Entry<K, V>> entrySet();
boolean equals(Object o);
int hashCode();

java Set<K> keySet()

返回映射中包含的键集视图,是一个Set<E>,说明了映射中键是不可重复的。

java Collection<V> values()

返回映射中包含的值得集合视图,Collection<E>,说明了映射中值是可以重复的。

java Set<Map.Entry<K,V>> entrySet()

返回映射中包含的映射集合视图,这个视图是一个Set<E>,这是由他的键集不能重复的特点决定的。

entrySet()返回的是一个Map.Entry<K,V>类型的集,Map.Entry<K,V>接口定义了获取键值、设置值的方法,定义如下:

interface Entry<K,V> {
K getKey();
V getValue();
V setValue(V value);
boolean equals(Object o);
int hashCode();
}

类框架

从一个框架的接口知道了这个框架的结构和功能,能够用来做什么。但具体怎么使用,怎么扩展,那就需要了解框架中实现这些接口的部分。

java.util提供了一系列抽象类,来实现上面的接口,这些抽象类中提供了大量基本的方法。如果需要实现自己的集合类,扩展这些抽象类比直接继承接口方便的多。

*图2 抽象类以及实现类框架*

除了图中的抽象类和具体实现类,还有部分历史版本遗留下来的类,包括Vetor,Stack,Hashtable,Properties等,在这里就不做说明,重点关注图中的类即可。

抽象类

需要关注几个关键的抽象类包括AbstractCollection<E>;、AbstractMap<K,V>、AbstractList<E>和AbstractSet<E>,从命名可以看出他们分别实现了Collection<E>、Map<K,V>、List<E>和Set<E>接口。

各个集合的关键区别就在每个集合所使用的数据结构和算法上,所以在抽象类层面都没有涉及具体的数据结构和算法,只对操作这些数据结构的方法做了基本实现。

AbstractCollection<E>

public abstract class AbstractCollection<E> implements Collection<E>

AbstractCollection<E>基本实现了Collection<E>下的所有方法,除了以下几个方法:

public abstract Iterator<E> iterator();

public abstract int size();

public boolean add(E e) {
throw new UnsupportedOperationException();
}

如果需要实现的是一个不可修改的集合,只需要实现iterator()和size()方法即可,如果需要实现一个可修改的集合,必须重写add(E e)方法。

在AbstractCollection<E>已经实现的方法中可以发现,AbstractCollection<E>所实现的所有方法都是通过Iterator<E>来操作的。

public boolean contains(Object o) {
//获取迭代器
Iterator<E> it = iterator();
if (o==null) {
while (it.hasNext())
if (it.next()==null)
return true;
} else {
while (it.hasNext())
if (o.equals(it.next()))
return true;
}
return false;
}

AbstractList<E>

public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E>

AbstractList<E>抽象类在AbstractCollection<E>抽象类的基础上添加了专属于List<E>接口的部分方法,但大部分方法都是空方法,没有具体实现。

abstract public E get(int index);

public E set(int index, E element) {
throw new UnsupportedOperationException();
} public void add(int index, E element) {
throw new UnsupportedOperationException();
} public E remove(int index) {
throw new UnsupportedOperationException();
} public Iterator<E> iterator() {
return new Itr();
} public ListIterator<E> listIterator() {
return listIterator(0);
} public ListIterator<E> listIterator(final int index) {
rangeCheckForAdd(index); return new ListItr(index);
}

没有实现的原因在于AbstractList<E>是一个抽象类,他并没有确定具体的数据结构,当在数据结构没有确定的情况下,是直接使用整数索引的方式还是通过迭代器循环遍历的方式来查找具体的位置更方便是不确定的,所以在具体实现上都由他的子类来决定。

值得注意的是AbstractList<E>实现了Iterator以及ListIterator两种类型的迭代器,很大程度上方便了子类的扩展:

private class Itr implements Iterator<E> {
//......
public E next() {
checkForComodification();
try {
int i = cursor;
//向后遍历集合,通过get(i)获取当前索引的元素,每次调用之后cursor = i + 1,get(i)为抽象方法
E next = get(i);
lastRet = i;
cursor = i + 1;
return next;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
} public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification(); try {
//通过AbstractList<E>类的remove方法来删除元素,AbstractList<E>中remove(int index)是一个空方法,需要子类来实现
AbstractList.this.remove(lastRet);
if (lastRet < cursor)
cursor--;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException e) {
throw new ConcurrentModificationException();
}
}
}
//...... private class ListItr extends Itr implements ListIterator<E> {
//......
public E previous() {
checkForComodification();
try {
int i = cursor - 1;
//向前遍历集合,通过get(i)获取当前索引的元素,每次调用之前cursor - 1,get(i)为抽象方法
E previous = get(i);
lastRet = cursor = i;
return previous;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
//......
}

AbstractSet<E>

public abstract class AbstractSet<E> extends AbstractCollection<E> implements Set<E>

AbstractSet<E>抽象类在实现上非常简单,只在AbstractCollection<E>抽象类的基础上实现了equal 和 hashCode 方法,但具体的实现还是需要通过contain()方法来判断,由于Set<E>接口类型不考虑元素的顺序,所以只要两个AbstractSet<E>包含相同元素就判断为相等,不需要元素顺序相同,而AbstractList<E>则需要顺序也相同。

//AbstractSet<E> 中的 equals
public boolean equals(Object o) {
if (o == this)
return true; if (!(o instanceof Set))
return false;
Collection<?> c = (Collection<?>) o;
if (c.size() != size())
return false;
try {
//containsAll在AbstractCollection<E>中已经实现,只要包含所有元素就可以
return containsAll(c);
} catch (ClassCastException unused) {
return false;
} catch (NullPointerException unused) {
return false;
}
} //AbstractCollection<E> 中的containsAll
public boolean containsAll(Collection<?> c) {
for (Object e : c)
if (!contains(e))
return false;
return true;
} //AbstractList<E> 中的equals
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof List))
return false; ListIterator<E> e1 = listIterator();
ListIterator<?> e2 = ((List<?>) o).listIterator();
//需要两个集合中的元素以及元素顺序都相同才返回true
while (e1.hasNext() && e2.hasNext()) {
E o1 = e1.next();
Object o2 = e2.next();
if (!(o1==null ? o2==null : o1.equals(o2)))
return false;
}
return !(e1.hasNext() || e2.hasNext());
}

AbstractMap<K,V>

public abstract class AbstractMap<K,V> implements Map<K,V>

AbstractMap<K,V>抽象类中实现了除entrySet()方法外的基本所有方法,其中返回键集的Set<K> keySet()和返回值集的Collection<V> values()在实现上非常有趣,从返回值上看是创建了一个新的集合,但实际实现上是返回来一个实现Set<K>或Collection<V>的类对象,类对象的所有操作都是在原映射表的基础上进行的,这种有趣的操作叫视图,java.util框架中存在大量应用。

这里使用视图的好处在于抽象类中你不需要确定返回的Set<K>或Collection<V>的具体实现类是什么,这样就可以在抽象类中没有决定使用哪种数据结构的时候最大化抽象类的功能,增加扩展的方便性。

keySet()的源码:

public Set<K> keySet() {
Set<K> ks = keySet;
if (ks == null) {
ks = new AbstractSet<K>() {
public Iterator<K> iterator() {
return new Iterator<K>() {
//获取原映射表的迭代器来实现自己的迭代器
private Iterator<Entry<K,V>> i = entrySet().iterator(); public boolean hasNext() {
return i.hasNext();
} public K next() {
return i.next().getKey();
} public void remove() {
i.remove();
}
};
} public int size() {
//直接操作原映射表的size()方法
return AbstractMap.this.size();
} public boolean isEmpty() {
return AbstractMap.this.isEmpty();
} public void clear() {
AbstractMap.this.clear();
} public boolean contains(Object k) {
return AbstractMap.this.containsKey(k);
}
};
keySet = ks;
}
return ks;
}

总结

java.util这个框架的结构还是非常清晰的,从接口的分类,每个接口的抽象类实现,都很好的保证了框架的伸缩性,为后续的实现和自定义扩展提供了极大地方便。

除了上述的接口以及抽象类以外,java.util框架还提供了一些其他结构,在使用频率上不是太高,比如Queue<E> ,Deque<E> 等。

在后面的章节中我们会详细讲解几个关键集合的实现,从数据结构、算法以及性能等各方面分析孰优孰劣。

Java集合(1)一 集合框架的更多相关文章

  1. java多线程系类:JUC集合:01之框架

    概要 之前,在"Java 集合系列目录(Category)"中,讲解了Java集合包中的各个类.接下来,将展开对JUC包中的集合进行学习.在学习之前,先温习一下"Java ...

  2. Java多线程系列--“JUC集合”01之 框架

    概要 之前,在"Java 集合系列目录(Category)"中,讲解了Java集合包中的各个类.接下来,将展开对JUC包中的集合进行学习.在学习之前,先温习一下"Java ...

  3. Java 集合系列 01 总体框架

    java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...

  4. 【Java集合系列】---总体框架

    个的组合,这些数据项可能共享某些特征,需要以某种操作方式一起进行操作,一般来说,这些数据项的类型都是相同的,或者基类相同(若使用的语言支持继承),列表或数组通常不认为是集合,因为其大小固定,但是事实上 ...

  5. java第七章集合框架

    如果想存储多个人物信息可以使用数组实现但是采用数组存以下明显缺陷: 数组长度不变不能适应元素变化情况,若存储大于20个英雄信息则长度不够,若只存储10个则造成内存空间浪费.可用.length获取数组中 ...

  6. 【由浅入深理解java集合】(一)——集合框架 Collction、Map

    本篇文章主要对java集合的框架进行介绍,使大家对java集合的整体框架有个了解.具体介绍了Collection接口,Map接口以及Collection接口的三个子接口Set,List,Queue. ...

  7. Java OOP——第六章 框架集合

    1.集合框架包含的主要内容及彼此之间的关系: 图1:   集合框架:是为了表示和操作集合而统一规定的一种统一的标准体系结构.               包含三大块的内容:对外的接口.接口的是实现和对 ...

  8. 已看1.熟练的使用Java语言进行面向对象程序设计,有良好的编程习惯,熟悉常用的Java API,包括集合框架、多线程(并发编程)、I/O(NIO)、Socket、JDBC、XML、反射等。[泛型]\

    1.熟练的使用Java语言进行面向对象程序设计,有良好的编程习惯,熟悉常用的Java API,包括集合框架.多线程(并发编程).I/O(NIO).Socket.JDBC.XML.反射等.[泛型]\1* ...

  9. Java基础系列7——集合系列(1)框架概述

    该系列博文会告诉你如何从入门到进阶,一步步地学习Java基础知识,并上手进行实战,接着了解每个Java知识点背后的实现原理,更完整地了解整个Java技术体系,形成自己的知识框架. 集合框架概述 Jav ...

  10. [Java核心技术]第九章-集合(Java集合框架、具体的集合、映射)

    9.1Java集合框架 一些有的没的 可以使用接口类型存放集合的引用.一旦改变了想法,只需要在调用构造函数的地方做一处修改. add方法用于向集合添加元素,如果添加元素确实改变了集合就返回true. ...

随机推荐

  1. 【原创】日志文件清理工具V1.0

    最近公司的系统服务器经常出现磁盘空间不足的情况,登陆服务器发现原来是公司的HR系统日志造成的(插个话题:我们公司的HR系统都实施两年多了还没上线,且不说软件功能如何,服务太TMD差劲了,更可气的是软件 ...

  2. 软工实践练习——使用Git进行代码管理

    GITHUB上的预备活动: 注册 创建小组Organization,邀请组员进来 将代码库fork到小组Organization底下 下载并使用GIT: Git的安装 使用Git进行代码管理 1.从百 ...

  3. JAVA设计模式之调停者模式

    在阎宏博士的<JAVA与模式>一书中开头是这样描述调停者(Mediator)模式的: 调停者模式是对象的行为模式.调停者模式包装了一系列对象相互作用的方式,使得这些对象不必相互明显引用.从 ...

  4. Unique Encryption Keys (思维题 预处理)

    题目 题意:给m个数字, q次询问, 询问b到e之间如果有重复数字就输出, 没有就输出OK 思路:用f[i]数组 记录从i开始向后最近的有重复数字的 位置, 如 1 3 2 2, 则f[1] = 4; ...

  5. 3月下旬剩余poj题解

    poj1700 数学推导+简单dp poj2390 水题不说什么了 poj3260 先对找的钱做完全背包,在对能付的钱做多重背包,注意这道题能付的钱数的上界 poj2516 裸的最小费用最大流了没什么 ...

  6. python中去掉空行的问题

    在python中处理空行时,经常会遇到一些问题.现总结经验如下: 1.遇到的空行如果只有换行符,直接使用=='\n'或者 len(line)==line.count('\n') 2.有多个空格+换行符 ...

  7. 基于ssh反向代理实现的远程协助

    本文描述了怎么通过ssh反向代理实现远程协助,并提供了相关代码. 可满足web开启远程协助功能后,维护人员能够通过ssh和http登录客户机器(包括在nat环境下) web开启该功能后,ssh才能登录 ...

  8. 2019.02.21 bzo1038: [ZJOI2008]瞭望塔(半平面交)

    传送门 题意:给出一个nnn个点的轮廓,要求找一个高度最小的点使得它能够看见所有拐点. 思路:之间建半平面交然后取半平面交上的每个交点和每个轮廓更新答案即可. 代码: #include<bits ...

  9. Apache重写规则

    1..htaccess文件使用前提 .htaccess的主要作用就是实现url改写,也就是当浏览器通过url访问到服务器某个文件夹时,作为主人,我们可以来接待这个url,具体 地怎样接待它,就是此文件 ...

  10. 常见的it软件默认端口

    tomcat:8080 nginx:80 mysql:3306 oracle:1521 nexus:8081 浏览器:80 redis:6379 solr:tomcat部署默认8080 jetty部署 ...