(一) java汇合分类
之前大略分为三种,Set
,List
,Map
三种,JDK5之后,减少Queue
.次要由Collection
和Map
两个接口衍生进去,同时Collection
接口继承Iterable
接口,所以咱们也能够说java外面的汇合类次要是由Iterable
和Map
两个接口以及他们的子接口或者其实现类组成。咱们能够认为Collection
接口定义了单列汇合的标准,每次只能存储一个元素,而Map
接口定义了双列汇合的标准,每次能存储一对元素。
Iterable
接口:次要是实现遍历性能Collection
接口: 容许反复Set
接口:无序,元素不可反复,拜访元素只能通过元素自身来拜访。List
接口:有序且可反复,能够依据元素的索引来拜访汇合中的元素。Queue
接口:队列,个别先进先出,可反复
Map
接口:映射关系,简略了解为键值对<Key,Value>,Key不可反复,与Collection
接口关系不大,只是个别函数应用到。
整个接口框架关系如下(来自百度百科):
(1) Iterable接口
1. 外部定义的办法
java汇合 最源头的接口,实现这个接口的作用次要是汇合对象能够通过迭代器去遍历每一个元素。
源码如下:
// 返回一个外部元素为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
关键字标识的办法,其子类能够不必实现,这也是接口标准发生变化的一点。
上面咱们别离展现三个接口的调用:
1.1 iterator办法
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操作是胜利的,只是遍历到第二个元素的时候,迭代器查看,发现被扭转了,所以抛出了异样。
JamException 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)
1.2 forEach办法
其实就是把对每一个元素的操作当成了一个对象传递进来,对每一个元素进行解决。
default void forEach(Consumer<? super T> action) { Objects.requireNonNull(action); for (T t : this) { action.accept(t); } }
当然像ArrayList
天然也是有本人的实现的,那咱们就能够应用这样的写法,简洁优雅。forEach()
在java8中参数是java.util.function.Consumer
,能够称为消费行为或者说动作类型。
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
1.3 spliterator办法
这是一个为了并行遍历数据元素而设计的迭代办法,返回的是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()
一个一个元素进行遍历forEachRemaining()
程序地分块遍历trySplit()
进行分区造成另外的Spliterator
,应用在并行操作中,分进去的是后面一半,就是一直把后面一部分分进去
后果如下:
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()
之后,能够用于多线程遍历。现实的时候,能够均匀分成两半,有利于并行计算,然而不是肯定平分的。
2. Collection接口 extend Iterable<E>
Collection
接口能够算是汇合类的一个根接口之一,个别不可能间接应用,只是定义了一个标准,定义了增加,删除等治理数据的办法。继承Collection
接口的有List
,Set
,Queue
,不过Queue
定义了本人的一些接口,相对来说和其余的差别比拟大。
2.1 外部定义的办法
源码如下:
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) {} // 删除汇合中复合条件的元素,删除胜利返回trueboolean 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);
2.2 继承Collection的次要接口
graph LR;Collection -->List-有程序,可反复 List-有程序,可反复 -->LinkedList-应用链表实现,线程不平安List-有程序,可反复 -->ArrayList-数组实现,线程不平安List-有程序,可反复 -->Vector-数组实现,线程平安Vector-数组实现,线程平安 -->Stack-堆栈,先进后出Collection-->Set-不可反复,外部排序Set-不可反复,外部排序-->HashSet-hash表存储HashSet-hash表存储-->LinkHashSet-链表保护插入程序Set-不可反复,外部排序-->TreeSet-二叉树实现,排序Collection-->Queue-队列,先进先出
2.2.1 List extend Collection<E>
继承于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的实现子类:
ArrayList
:底层存储构造是数组构造,减少删除比较慢,查找比拟快,是最罕用的List汇合。线程不平安。LinkedList
:底层是链表构造,减少删除比拟快,然而查找比较慢。线程不平安。Vector
:和ArrayList差不多,然而是线程平安的,即同步。
2.2.2 Set extend Collection<E>
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(); //计算hashcodedefault Spliterator<E> spliterator() {} //获取可宰割迭代器
次要的子类:
HashSet
- 容许空值
- 通过
HashCode
办法计算获取hash
值,确定存储地位,无序。
LinkedHashSet
HashSet
的子类- 有程序
TreeSet
- 如果无参数构建
Set
,则须要实现Comparable
办法。 - 亦能够创立时传入比拟办法,用于排序。
- 如果无参数构建
2.2.3 Queue extend Collection<E>
队列接口,在Collection
接口的接触上增加了增删改查接口定义,个别默认是先进先出,即FIFO
,除了优先队列和栈,优先队列是本人定义了排序的优先程序,队列中不容许放入null元素。
上面是源码:
boolean add(E e); //插入一个元素到队列,失败时返回IllegalStateException (如果队列容量不够)boolean offer(E e); //插入一个元素到队列,失败时返回falseE remove(); //移除队列头的元素并移除E poll(); //返回并移除队列的头部元素,队列为空时返回nullE element(); //返回队列头元素E peek(); //返回队列头部的元素,队列为空时返回null
次要的子接口以及实现类有:
Deque(接口):Queue的子接口,双向队列,能够从两边存取
- ArrayDeque:Deque的实现类,底层用数组实现,数据存贮在数组中
AbstractQueue:Queue的子接口,仅实现了add、remove和element三个办法
- PriorityQueue:依照默认或者本人定义的程序来排序元素,底层应用堆(齐全二叉树)实现,应用动静数组实现,
- BlockingQueue: 在java.util.concurrent包中,阻塞队列,满足以后无奈解决的操作。
(2) Map接口
- 定义双列汇合的标准
Map<K,V>
,每次存储一对元素,即key
和value
。 key
的类型能够和value
的类型雷同,也能够不同,任意的援用类型都能够。key
是不容许反复的,然而value
是能够反复的,所谓反复是指计算的hash
值。
上面的源码的办法:
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); // 是否蕴含某个valueSet<K> keySet(); // 获取所有key的set汇合Collection<V> values(); // 获取所有的value的set汇合Set<Map.Entry<K, V>> entrySet(); // 返回键值对的set,每一个键值对是一个entry对象boolean equals(Object o); // 用于比拟的函数int hashCode(); // 计算hashcodedefault 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值,如果不存在,则返回的是刚刚放进去的valuedefault 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的罕用的实现子类:
HashMap
:由数组和链表组成,线程不平安,无序。LinkedHashMap
:如果咱们须要是有序的,那么就须要它,工夫和空间效率没有HashMap
那么高,底层是保护一条双向链表,保障了插入的程序。ConcurrentHashMap
:线程平安,1.7JDK应用锁拆散,每一段Segment都有本人的独立锁,相对来说效率也比拟高。JDK1.8摈弃了Segment,应用Node数组+链表和红黑树实现,在线程安全控制上应用Synchronize
和CAS
,能够认为是优化的线程平安的HashMap
。HashTable
:比照与HashMap
次要是应用关键字synchronize
,加上同步锁,线程平安。
(二)总结
这些汇合原始接口到底是什么?为什么须要?
我想,这些接口其实都是一种规定/标准的定义,如果不这么做也能够,所有的子类本人实现,然而从迭代以及保护的角度来说,这就是一种形象或者分类,比方定义了Iterator
接口,某一些类就能够去继承或者实现,那就得恪守这个标准/契约。能够有所拓展,每个子类的拓展不一样,所以每个类就各有千秋,然而都有一个核心,就是原始的汇合接口。比方实现Map
接口的所有类的中心思想都不变,都是<key,value>
只是各有千秋,各分千秋,造成了大千汇合世界。
此文章仅代表本人(本菜鸟)学习积攒记录,或者学习笔记,如有侵权,请分割作者删除。人无完人,文章也一样,文笔稚嫩,在下不才,勿喷,如果有谬误之处,还望指出,感激不尽~
技术之路不在一时,山高水长,纵使迟缓,驰而不息。
公众号:秦怀杂货店