以前大概分为三种,Set
,List
,Map
三种,JDK5以后,增长Queue
.主要由Collection
和Map
两个接口衍生出来,同时Collection
接口继承Iterable
接口,因此咱们也能够说java里面的集合类主要是由Iterable
和Map
两个接口以及他们的子接口或者其实现类组成。咱们能够认为Collection
接口定义了单列集合的规范,每次只能存储一个元素,而Map
接口定义了双列集合的规范,每次能存储一对元素。java
Collection
接口关系不大,只是个别函数使用到。java集合最源头的接口,实现这个接口的做用主要是集合对象能够经过迭代器去遍历每个元素。git
源码以下:github
// 返回一个内部元素为T类型的迭代器(JDK1.5只有这个接口) Iterator<T> iterator(); // 遍历内部元素,action意思为动做,指能够对每一个元素进行操做(JDK1.8添加) default void forEach(Consumer<? super T> action) {} // 建立并返回一个可分割迭代器(JDK1.8添加),分割的迭代器主要是提供能够并行遍历元素的迭代器,能够适应如今cpu多核的能力,加快速度。 default Spliterator<T> spliterator() { return Spliterators.spliteratorUnknownSize(iterator(), 0); }
从上面能够看出,foreach
迭代以及可分割迭代,都加了default
关键字,这个是Java 8 新的关键字,之前接口的全部接口,具体子类都必须实现,而对于deafult
关键字标识的方法,其子类能够不用实现,这也是接口规范发生变化的一点。
下面咱们分别展现三个接口的调用: redis
public static void iteratorHasNext(){ List<String> list=new ArrayList<String>(); list.add("Jam"); list.add("Jane"); list.add("Sam"); // 返回迭代器 Iterator<String> iterator=list.iterator(); // hashNext能够判断是否还有元素 while(iterator.hasNext()){ //next()做用是返回当前指针指向的元素,以后将指针移向下个元素 System.out.println(iterator.next()); } }
固然也可使用for-each loop
方式遍历编程
for (String item : list) { System.out.println(item); }
可是实际上,这种写法在class文件中也是会转成迭代器形式,这只是一个语法糖。class文件以下:数组
public class IterableTest { public IterableTest() { } public static void main(String[] args) { iteratorHasNext(); } public static void iteratorHasNext() { List<String> list = new ArrayList(); list.add("Jam"); list.add("Jane"); list.add("Sam"); Iterator<String> iterator = list.iterator(); Iterator var2 = list.iterator(); while(var2.hasNext()) { String item = (String)var2.next(); System.out.println(item); } } }
须要注意的一点是,迭代遍历的时候,若是删除或者添加元素,都会抛出修改异常,这是因为快速失败【fast-fail】机制。安全
public static void iteratorHasNext(){ List<String> list=new ArrayList<String>(); list.add("Jam"); list.add("Jane"); list.add("Sam"); for (String item : list) { if(item.equals("Jam")){ list.remove(item); } System.out.println(item); } }
从下面的错误咱们能够看出,第一个元素是有被打印出来的,也就是remove操做是成功的,只是遍历到第二个元素的时候,迭代器检查,发现被改变了,因此抛出了异常。多线程
Jam Exception in thread "main" java.util.ConcurrentModificationException at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909) at java.util.ArrayList$Itr.next(ArrayList.java:859) at IterableTest.iteratorHasNext(IterableTest.java:15) at IterableTest.main(IterableTest.java:7)
其实就是把对每个元素的操做当成了一个对象传递进来,对每个元素进行处理。app
default void forEach(Consumer<? super T> action) { Objects.requireNonNull(action); for (T t : this) { action.accept(t); } } ```java 固然像ArrayList天然也是有本身的实现的,那咱们就可使用这样的写法,简洁优雅。forEach方法在java8中参数是`java.util.function.Consumer`,能够称为**消费行为**或者说**动做**类型。 ```java list.forEach(x -> System.out.print(x));
同时,咱们只要实现Consumer
接口,就能够自定义动做,若是不自定义,默认迭代顺序是按照元素的顺序。框架
public class ConsumerTest { public static void main(String[] args) { List<String> list=new ArrayList<String>(); list.add("Jam"); list.add("Jane"); list.add("Sam"); MyConsumer myConsumer = new MyConsumer(); Iterator<String> it = list.iterator(); list.forEach(myConsumer); } static class MyConsumer implements Consumer<Object> { @Override public void accept(Object t) { System.out.println("自定义打印:" + t); } } }
输出的结果:
自定义打印:Jam 自定义打印:Jane 自定义打印:Sam
这是一个为了并行遍历数据元素而设计的迭代方法,返回的是Spliterator
,是专门并行遍历的迭代器。以发挥多核时代的处理器性能,java默认在集合框架中提供了一个默认的Spliterator
实现,底层也就是Stream.isParallel()实现的,咱们能够看一下源码:
// stream使用的就是spliterator default Stream<E> stream() { return StreamSupport.stream(spliterator(), false); } default Spliterator<E> spliterator() { return Spliterators.spliterator(this, 0); } public static <T> Stream<T> stream(Spliterator<T> spliterator, boolean parallel) { Objects.requireNonNull(spliterator); return new ReferencePipeline.Head<>(spliterator, StreamOpFlag.fromCharacteristics(spliterator), parallel); }
使用的方法以下:
public static void spliterator(){ List<String> list = Arrays.asList("1", "2", "3","4","5","6","7","8","9","10"); // 获取可迭代器 Spliterator<String> spliterator = list.spliterator(); // 一个一个遍历 System.out.println("tryAdvance: "); spliterator.tryAdvance(item->System.out.print(item+" ")); spliterator.tryAdvance(item->System.out.print(item+" ")); System.out.println("\n-------------------------------------------"); // 依次遍历剩下的 System.out.println("forEachRemaining: "); spliterator.forEachRemaining(item->System.out.print(item+" ")); System.out.println("\n------------------------------------------"); // spliterator1:0~10 Spliterator<String> spliterator1 = list.spliterator(); // spliterator1:6~10 spliterator2:0~5 Spliterator<String> spliterator2 = spliterator1.trySplit(); // spliterator1:8~10 spliterator3:6~7 Spliterator<String> spliterator3 = spliterator1.trySplit(); System.out.println("spliterator1: "); spliterator1.forEachRemaining(item->System.out.print(item+" ")); System.out.println("\n------------------------------------------"); System.out.println("spliterator2: "); spliterator2.forEachRemaining(item->System.out.print(item+" ")); System.out.println("\n------------------------------------------"); System.out.println("spliterator3: "); spliterator3.forEachRemaining(item->System.out.print(item+" ")); }
结果以下:
tryAdvance: 1 2 ------------------------------------------- forEachRemaining: 3 4 5 6 7 8 9 10 ------------------------------------------ spliterator1: 8 9 10 ------------------------------------------ spliterator2: 1 2 3 4 5 ------------------------------------------ spliterator3: 6 7
还有一些其余的用法在这里就不列举了,主要是trySplit()以后,能够用于多线程遍历。理想的时候,能够平均分红两半,有利于并行计算,可是不是必定平分的。
Collection
接口能够算是集合类的一个根接口之一,通常不可以直接使用,只是定义了一个规范,定义了添加,删除等管理数据的方法。继承Collection
接口的有List
,Set
,Queue
,不过Queue
定义了本身的一些接口,相对来讲和其余的差别比较大。
源码以下:
boolean add(Object o) //添加元素 boolean remove(Object o) //移除元素 boolean addAll(Collection c) //批量添加 boolean removeAll(Collection c) //批量移除 void retainAll(Collection c) // 移除在c中不存在的元素 void clear() //清空集合 int size() //集合大小 boolean isEmpty() //是否为空 boolean contains(Object o) //是否包含在集合中 boolean containsAll(Collection c) //是否包含全部的元素 Iterator<E> iterator() // 获取迭代器 Object[] toArray() // 转成数组 default boolean removeIf(Predicate<? super E> filter) {} // 删除集合中复合条件的元素,删除成功返回true boolean equals(Object o) int hashCode() default Spliterator<E> spliterator() {} //获取可分割迭代器 default Stream<E> stream() {} //获取流 default Stream<E> parallelStream() {} //获取并行流
里面获取并行流的方法parallelStream()
,其实就是经过默认的ForkJoinPool(主要用来使用分治法(Divide-and-Conquer Algorithm)来解决问题),提升多线程任务的速度。咱们可使用ArrayList来演示一下平行处理能力。例以下面的例子,输出的顺序就不必定是1,2,3...,多是乱序的,这是由于任务会被分红多个小任务,任务执行是没有特定的顺序的。
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9); list.parallelStream() .forEach(out::println);
graph LR; Collection -->List-有顺序,可重复 List-有顺序,可重复 -->LinkedList-使用链表实现,线程不安全 List-有顺序,可重复 -->ArrayList-数组实现,线程不安全 List-有顺序,可重复 -->Vector-数组实现,线程安全 Vector-数组实现,线程安全 -->Stack-堆栈,先进后出 Collection-->Set-不可重复,内部排序 Set-不可重复,内部排序-->HashSet-hash表存储 HashSet-hash表存储-->LinkHashSet-链表维护插入顺序 Set-不可重复,内部排序-->TreeSet-二叉树实现,排序 Collection-->Queue-队列,先进先出
继承于Collection
接口,有顺序,取出的顺序与存入的顺序一致,有索引,能够根据索引获取数据,容许存储重复的元素,能够放入为null的元素。
最多见的三个实现类就是ArrayList
,Vector
,LinkedList
,ArrayList
和Vector
都是内部封装了对数组的操做,惟一不一样的是,Vector
是线程安全的,而ArrayList
不是,理论上ArrayList
操做的效率会比Vector
好一些。
里面是接口定义的方法:
int size(); //获取大小 boolean isEmpty(); //判断是否为空 boolean contains(Object o); //是否包含某个元素 Iterator<E> iterator(); //获取迭代器 Object[] toArray(); // 转化成为数组(对象) <T> T[] toArray(T[] a); // 转化为数组(特定位某个类) boolean add(E e); //添加 boolean remove(Object o); //移除元素 boolean containsAll(Collection<?> c); // 是否包含全部的元素 boolean addAll(Collection<? extends E> c); //批量添加 boolean addAll(int index, Collection<? extends E> c); //批量添加,指定开始的索引 boolean removeAll(Collection<?> c); //批量移除 boolean retainAll(Collection<?> c); //将c中不包含的元素移除 default void replaceAll(UnaryOperator<E> operator) {}//替换 default void sort(Comparator<? super E> c) {}// 排序 void clear();//清除全部的元素 boolean equals(Object o);//是否相等 int hashCode(); //计算获取hash值 E get(int index); //经过索引获取元素 E set(int index, E element);//修改元素 void add(int index, E element);//在指定位置插入元素 E remove(int index);//根据索引移除某个元素 int indexOf(Object o); //根据对象获取索引 int lastIndexOf(Object o); //获取对象元素的最后一个元素 ListIterator<E> listIterator(); // 获取List迭代器 ListIterator<E> listIterator(int index); // 根据索引获取当前的位置的迭代器 List<E> subList(int fromIndex, int toIndex); //截取某一段数据 default Spliterator<E> spliterator(){} //获取可切分迭代器
上面的方法都比较简单,值得一提的是里面出现了ListIterator
,这是一个功能更增强大的迭代器,继承于Iterator
,只能用于List
类型的访问,拓展功能例如:经过调用listIterator()
方法得到一个指向List开头的ListIterator
,也能够调用listIterator(n)
获取一个指定索引为n的元素的ListIterator
,这是一个能够双向移动的迭代器。
操做数组索引的时候须要注意,因为List的实现类底层不少都是数组,因此索引越界会报错IndexOutOfBoundsException
。
提及List的实现子类:
Set
接口,不容许放入重复的元素,也就是若是相同,则只存储其中一个。
下面是源码方法:
int size(); //获取大小 boolean isEmpty(); //是否为空 boolean contains(Object o); //是否包含某个元素 Iterator<E> iterator(); //获取迭代器 Object[] toArray(); //转化成为数组 <T> T[] toArray(T[] a); //转化为特定类的数组 boolean add(E e); //添加元素 boolean remove(Object o); //移除元素 boolean containsAll(Collection<?> c); //是否包含全部的元素 boolean addAll(Collection<? extends E> c); //批量添加 boolean retainAll(Collection<?> c); //移除全部不存在于c集合中的元素 boolean removeAll(Collection<?> c); //移除全部在c集合中存在的元素 void clear(); //清空集合 boolean equals(Object o); //是否相等 int hashCode(); //计算hashcode default Spliterator<E> spliterator() {} //获取可分割迭代器
主要的子类:
队列接口,在Collection接口的接触上添加了增删改查接口定义,通常默认是先进先出,即FIFO,除了优先队列和栈,优先队列是本身定义了排序的优先顺序,队列中不容许放入null元素。
下面是源码:
boolean add(E e); //插入一个元素到队列,失败时返回IllegalStateException (若是队列容量不够) boolean offer(E e); //插入一个元素到队列,失败时返回false E remove(); //移除队列头的元素并移除 E poll(); //返回并移除队列的头部元素,队列为空时返回null E element(); //返回队列头元素 E peek(); //返回队列头部的元素,队列为空时返回null
主要的子接口以及实现类有:
下面的源码的方法:
V put(K key, V value); // 添加元素 V remove(Object key); // 删除元素 void putAll(Map<? extends K, ? extends V> m); // 批量添加 void clear() // 移除全部元素 V get(Object key); // 经过key查询元素 int size(); // 查询集合大小 boolean isEmpty(); // 集合是否为空 boolean containsKey(Object key); // 是否包含某个key boolean containsValue(Object value); // 是否包含某个value Set<K> keySet(); // 获取全部key的set集合 Collection<V> values(); // 获取全部的value的set集合 Set<Map.Entry<K, V>> entrySet(); // 返回键值对的set,每个键值对是一个entry对象 boolean equals(Object o); // 用于比较的函数 int hashCode(); // 计算hashcode default V getOrDefault(Object key, V defaultValue) // 获取key对应的Value,没有则返回默认值() default void forEach(BiConsumer<? super K, ? super V> action) {} // 遍历 default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {} // 批量替换 // 缺乏这个key的时候才会添加进去 // 返回值是是key对应的value值,若是不存在,则返回的是刚刚放进去的value default V putIfAbsent(K key, V value) {} default boolean remove(Object key, Object value) {} // 移除元素 default boolean replace(K key, V oldValue, V newValue) {} // 替换 default V replace(K key, V value) {} //替换 // 和putIfAbsent有点像,只不过传进去的mappingFunction是映射函数,也就是若是不存在key对应的value,将会执行函数,函数返回值会被当成value添加进去,同时返回新的value值 default V computeIfAbsent(K key,Function<? super K, ? extends V> mappingFunction) {} // 和computeIfAbsent方法相反,只有key存在的时候,才会执行函数,而且返回 default V computeIfPresent(K key,BiFunction<? super K, ? super V, ? extends V> remappingFunction) {} // 无论如何都会执行映射函数,返回value default V compute(K key,BiFunction<? super K, ? super V, ? extends V> remappingFunction) {} default V merge(K key, V value,BiFunction<? super V, ? super V, ? extends V> remappingFunction) {}
值得注意的是,Map里面定义了一个Entry类,其实就是定义了一个存储数据的类型,一个entry就是一个<key,value>.
Map的经常使用的实现子类:
这些集合原始接口究竟是什么?为何须要?
我想,这些接口其实都是一种规则/规范的定义,若是不这么作也能够,全部的子类本身实现,可是从迭代以及维护的角度来讲,这就是一种抽象或者分类,好比定义了Iterator接口,某一些类就能够去继承或者实现,那就得遵照这个规范/契约。能够有所拓展,每一个子类的拓展不同,因此每一个类就各有所长,可是都有一个中心,就是原始的集合接口。好比实现Map接口的全部类的中心思想都不变,只是各有所长,各分千秋,造成了大千集合世界。
【做者简介】:
秦怀,公众号【秦怀杂货店】做者,技术之路不在一时,山高水长,纵使缓慢,驰而不息。我的写做方向:Java源码解析,JDBC,Mybatis,Spring,redis,分布式,剑指Offer,LeetCode等,认真写好每一篇文章,不喜欢标题党,不喜欢花里胡哨,大多写系列文章,不能保证我写的都彻底正确,可是我保证所写的均通过实践或者查找资料。遗漏或者错误之处,还望指正。
平日时间宝贵,只能使用晚上以及周末时间学习写做,关注我,咱们一块儿成长吧~