java中的集合包简要分析
1.集合包
集合包是java中最常用的包,它主要包括Collection和Map两类接口的实现。
对于Collection的实现类需要重点掌握以下几点:
1)Collection用什么数据结构实现?
2)Collection的创建、添加对象、删除对象、获取对象、遍历、判断是否存在、排序等操作的原理,及优缺点。
1.1.Collection
Collection存放的是多个单对象。Collection又分为两类接口,List和Set。
1.1.1.List
List支持放入重复的对象。List的实现类主要包括:ArrayList、LinkedList、Vector、Stack。
ArrayList
1)从ArrayList的构造方法可以看出,他使用数组作为底层数据结构。
public ArrayList( int initialCapacity) super (); if (initialCapacity 0 ) throw new IllegalArgumentException( "Illegal + initialCapacity); this .elementData new Object[initialCapacity]; } |
默认的initialCapacity为10。
2)添加对象
由于是定容量的数组存储对象,总有数组满的时候,此时需要我们进行扩容。
public void ensureCapacity( int minCapacity) //minCapacity为目前需要的最小容量 modCount++; int oldCapacity //当前list的最大存储容量 if (minCapacity Object int newCapacity 3 )/ 2 + 1 ; //扩展容量1.5倍 if (newCapacity newCapacity // elementData //复制、扩容 } } |
其中Arrays.copyOf可能会比较陌生,他的具体实现如下:
public static <T,U> int newLength, extends T[]> T[] class ) ? new Object[newLength] : //创建一个新数组,该数组的类型和之前ArrayList中元素的类型一致。 System.arraycopy(original, 0 , 0 , Math.min(original.length, //System return copy; } |
添加对象还有一种方法,add(int index, E element),在指定位置插入一个对象。
public void add( int index, if (index 0 ) throw new IndexOutOfBoundsException( "Index: +index+ ", +size); ensureCapacity(size+ 1 ); // System.arraycopy(elementData, 1 , size elementData[index] size++; } |
指定的位置必须是存在的(0到size之间),由于ArrayList是数组实现,因此需要将插入位置之后的元素进行后移一个位置,腾出空间给新元素。因此这个方法多了一次数组复制的工作。
于此同时还有一个修改对象的方法,set(int index, E element),将制定位置的对象替换掉。
public E int index, RangeCheck(index); E elementData[index] return oldValue; } |
好奇他的范围检索只对“上界”检查,不对“下界”检查。
private void RangeCheck( int index) if (index throw new IndexOutOfBoundsException( "Index: +index+ ", +size); } |
3)删除对象
删除指定位置的对象remove(int index)。
public E int index) RangeCheck(index); //检查范围 modCount++; E int numMoved 1 ; //计算移动几个元素 if (numMoved 0 ) System.arraycopy(elementData, 1 , numMoved); elementData[--size] null ; // return oldValue; } |
remove(int index)和add(int index , E element)类似,需要通过数组的复制覆盖或腾出空间。
删除指定对象remove(E element)
public boolean remove(Object if (o null ) for ( int index 0 ; if (elementData[index] null ) fastRemove(index); return true ; } } else { for ( int index 0 ; if (o.equals(elementData[index])) fastRemove(index); return true ; } } return false ; } |
删除指定的对象,需要对删除对象是否为null区别对待。如果为null,则遍历数组中的元素,并比较是否为null(==null),如果为null则调用fastRemove删除。如果不为null,则遍历数组中的元素,并用equals比较是否相等,相等则调用fastRemove删除。
private void fastRemove( int index) modCount++; int numMoved 1 ; if (numMoved 0 ) System.arraycopy(elementData, 1 , numMoved); elementData[--size] null ; // } |
fastRemove是简化的remove(int index),不需要进行范围检查。
还有removeRange(int fromIndex, int toIndex)意思差不多,不予赘述!
4)获取单个对象
get(int index),传入的参数的为数组元素的位置。
public E int index) RangeCheck(index); return (E) } |
获取指定对象的位置,indexOf(Object o)。
public int indexOf(Object if (o null ) for ( int i 0 ; if (elementData[i]== null ) return i; } else { for ( int i 0 ; if (o.equals(elementData[i])) return i; } return - 1 ; } |
其实remove(Object o)可以改成:
public boolean remove(Object int index if (index>= 0 ){ fastRemove(index); return true ; } else return false ; } |
有了indexOf,contains(Object E)就简单了。
public boolean contains(Object return indexOf(o) 0 ; } |
5)遍历
iterator有ArrayList的父类AbstractList实现,调用iterator会创建一个内部类Itr的实例(class Itr implements Iterator<E>)。主要关注hasNext、next方法。
public boolean hasNext() return cursor } |
比较当前指向数组的位置是否和数组中已有元素的个数相等。
public E checkForComodification(); try { E lastRet return next; } catch (IndexOutOfBoundsException checkForComodification(); throw new NoSuchElementException(); } } |
checkForComodification
final void checkForComodification() if (modCount throw new ConcurrentModificationException(); } |
调用next的时候要比较当前的modCount和创建iterator时的modCount是否相等。如果不相等,则说明对集合大小产生了影响,此时抛出ConcurrentModificationException。
相等则调用get方法,此时有可能抛出IndexOutOfBoundsException,在捕获IndexOutOfBoundException后,检查modCount(checkForComodification),如果modCount不相等,抛出ConcurrentModificationException,
如果相等则抛出NoSuchElementException。
LinkedList
LinkedList是基于双向链表机制,在LinkedList中,Entry类来代表集合中的元素。
private static class Entry<E> E Entry<E> Entry<E> Entry(E this .element this .next this .previous } } |
元素的值赋给element,previous指向前一个元素,next指向后一个元素,通过previous、next将多个独立的Entry串起来形成链表,因为它有两个方向的关联,所以称为双向链表。
1)创建LinkedList
private transient Entry<E> new Entry<E>( null , null , null ); private transient int size 0 ; /** * */ public LinkedList() header.next } |
创建一个Entry对象,将其previous、nest全部指向自己(header),形成一个闭环。
2)添加元素
add(E e)实际调用了addBefore。(addBefore(e, header);)
private Entry<E> Entry<E> new Entry<E>(e, newEntry.previous.next newEntry.next.previous size++; modCount++; return newEntry; } |
这个地方稍微有点绕,新建一个Entry对象,并将next指向header,previous指向header.previous,实际header.previous都是指向最后一个元素(为添加之前最后一个元素)。
将前一元素的next指向自己,前一元素为header.previous,即为添加前最后一个元素。
将自己的next元素,即header元素的previous指向自己,这样也始终保持了header.previous都是指向最后一个元素。
3)删除元素
remove(Object o)
public boolean remove(Object if (o null ) for (Entry<E> if (e.element null ) remove(e); return true ; } } } else { for (Entry<E> if (o.equals(e.element)) remove(e); return true ; } } } return false ; } |
先遍历找到对应的Entry,然后在调用remove(Entry e)。
private E if (e throw new NoSuchElementException(); E e.previous.next e.next.previous e.next null ; e.element null ; size--; modCount++; return result; } |
要删除指定的Entry e比较简单,让e的前一个元素的next指向e的next(e.previous.next = e.next),让e的后一个元素的previous指向e的previous(e.next.previous = e.previous)。
然后将e的element、next和previous置为null,此时gc应该有机会将删除的e消灭掉。
4)获取指定位置的元素
get(int index)有entry(int index)实现。
private Entry<E> int index) if (index 0 || throw new IndexOutOfBoundsException( "Index: + ", + Entry<E> if (index 1 )) for ( int i 0 ; e } else { for ( int i e } return e; } |
这里有个小小的优化,如果index<size/2,则从前往后遍历,否则从后往前遍历链表。
5)遍历
iterator会创建一个AbstractList的内部类ListItr。
这里的类结构有必要说明一下。Iterator接口就定义了三个方法:hasNext、next、remove。
ListIterator接口,继承Iterator接口,又定义了:add、hasPrevious、previous、set、previousIndex、nextIndex等方法。
Ite作为AbstractList的内部类,实现了Iterator接口,主要用于ArrayList的遍历。
ListIte作为AbstractList的内部类,实现了ListIterator接口、同时继承了Ite类,主要用于LinkedList的遍历。
看看ListIte的previous方法:
public E checkForComodification(); try { int i 1 ; E lastRet return previous; } catch (IndexOutOfBoundsException checkForComodification(); throw new NoSuchElementException(); } } |
因此LinkedList可以向前、向后遍历。
6)其他一些方法
offer、peek、poll、pop、push。
offer和add类似(offerFirst、offerLast)
peek和get类似(peekFirst、peekLast)
poll和remove类似(pollFirst、pollLast)
pop等价removeFirst
push等价addFirst
Vector
Vector和ArrayList一样,也是基于数组的方式来实现的。
Vector是基于synchronized实现的线程安全的ArrayList,因此很多方法都和ArrayList的类似,只是添加了synchronized关键字。
除此之外,还有扩容方面稍有差别。
private void ensureCapacityHelper( int minCapacity) int oldCapacity if (minCapacity Object[] int newCapacity 0 ) (oldCapacity 2 ); if (newCapacity newCapacity } elementData } } |
他这里直接就是翻倍,而ArrayList是1.5倍。为什么还搞个这样的区别呢?
Stack
Stack是继承Vector,实现了LIFO的栈操作。主要由push、pop、peek方法。
public E addElement(item); return item; } public synchronized E E int len obj removeElementAt(len 1 ); return obj; } public synchronized E int len if (len 0 ) throw new EmptyStackException(); return elementAt(len 1 ); } |
ArrayList是基于数组的,get很快,但添加、删除操作需要移动元素,效率较低。且不是线程安全的。
LinkedList是基于双向链表的,添加、删除不需要移动元素,仅仅只要改变元素的previous、next,效率较高。get需要从前或从后开始遍历,效率较低。同样不是线程安全的。
Vector是线程安全的ArrayList实现,Stack在继承Vector的基础上实现了栈的操作。
通常我们会在外部对线程安全进行控制而选用ArrayList而非Vector。
1.1.2.Set
Set不支持放入重复的对象。Set的实现类主要包括:HashSet、TreeSet。
(先看Map部分)
HashSet
HashSet是基于HashMap来实现的。
TreeSet
TreeSet是基于TreeMap来实现的。
1.2.Map
Map存放Key-Value形式的键值对。Map的实现类主要包括:HashMap、TreeMap。
HashMap
1)创建
public HashMap() this .loadFactor threshold int )(DEFAULT_INITIAL_CAPACITY table new Entry[DEFAULT_INITIAL_CAPACITY]; init(); } |
默认loadFactor为0.75,threshold为12,创建一个大小为16的Entry对象数组。(大小为2的4次方)
public HashMap( int initialCapacity, float loadFactor) if (initialCapacity 0 ) throw new IllegalArgumentException( "Illegal + initialCapacity); if (initialCapacity initialCapacity if (loadFactor 0 || throw new IllegalArgumentException( "Illegal + loadFactor); // int capacity 1 ; while (capacity capacity 1 ; this .loadFactor threshold int )(capacity table new Entry[capacity]; init(); } |
指定initialCapacity、loadFactor,capacity为大于initialCapacity的最小的2的n次方。capacity为Entry数组的大小。
2)添加
put(Object key , Object value)
public V if (key null ) return putForNullKey(value); int hash int i for (Entry<K,V> null ; Object if (e.hash V e.value e.recordAccess( this ); return oldValue; } } modCount++; addEntry(hash, //key不存在 return null ; } |
indexFor是根据capacity做取余操作。
static int indexFor( int h, int length) return h 1 ); } |
当key不存在的时候,也许会冲突,这个交由addEntry处理。
void addEntry( int hash, int bucketIndex) Entry<K,V> table[bucketIndex] new Entry<K,V>(hash, if (size++ resize( 2 * } |
addEntry不对冲突进行特殊处理,都会将新加的k-v作为一个Entry加入到每个列表的头部。
当size大于等于threshod时,需要进行扩容。扩容是一个比较繁琐的过程,需要对当前Entry对象数组中的元素重新hash,并填充数组,最后重新设置threshold值。
void resize( int newCapacity) Entry[] int oldCapacity if (oldCapacity threshold return ; } Entry[] new Entry[newCapacity]; transfer(newTable); table threshold int )(newCapacity } |
void transfer(Entry[] Entry[] int newCapacity for ( int j 0 ; Entry<K,V> if (e null ) src[j] null ; do { Entry<K,V> int i //重新hash e.next newTable[i] e } while (e null ); } } } |
这块内容也比较复杂,有兴趣的同学最好跟下代码。
如果我们预知需要存入很多k-v,还调用默认无参构造map,那么就会面临很多次不必要的扩容操作。因此最好选用public HashMap(int initialCapacity)构造方法。(或者:public HashMap(int initialCapacity, float loadFactor) )
3)获取
get(Object key)
public V if (key null ) return getForNullKey(); int hash for (Entry<K,V> e null ; e Object if (e.hash return e.value; } return null ; } |
对于key为null的情况,直接获取数组中的第一个Entry对象,并基于next属性进行遍历,寻找key为null的Entry,如果找到了则返回该Entry的value,没有找到返回null。
如果key不为null,则对key进行hash,然后取余获取其的存储位置。然后获取该位置上的Entry,并基于next属性进行遍历,寻找key为null的Entry,如果找到了则返回该Entry的value,没有找到返回null。
4)删除
remove(Object key)
具体的删除过程如下:
final Entry<K,V> int hash null ) 0 : int i Entry<K,V> Entry<K,V> while (e null ) Entry<K,V> Object if (e.hash ((k null && modCount++; size--; if (prev table[i] else prev.next e.recordRemoval( this ); return e; } prev e } return e; } |
remove和get类似,也是先找到key对应的存储位置,然后遍历找到key删除entry。这里使用单向链表解决冲突。
5)包含
containKey(Object key)
public boolean containsKey(Object return getEntry(key) null ; } /** * * * */ final Entry<K,V> int hash null ) 0 : for (Entry<K,V> e null ; e Object if (e.hash ((k null && return e; } return null ; } |
getEntry和get方法类似,getEntry返回Entry对象,get返回Entry的value。
HashMap参考:
http://www.cnblogs.com/huangfox/archive/2012/07/06/2579614.html
TreeMap
TreeMap是支持排序的Map实现,可以自己指定Comparator参数。
1)创建
public TreeMap(Comparator<? super K> this .comparator } |
2)添加
put(Object key , Object value)
public V Entry<K,V> if (t null ) // // // // // root new Entry<K,V>(key, null ); size 1 ; modCount++; return null ; } int cmp; Entry<K,V> // Comparator<? super K> if (cpr null ) do { parent cmp if (cmp 0 ) t else if (cmp 0 ) t else return t.setValue(value); } while (t null ); } else { if (key null ) throw new NullPointerException(); Comparable<? super K> super K>) do { parent cmp if (cmp 0 ) t else if (cmp 0 ) t else return t.setValue(value); } while (t null ); } Entry<K,V> new Entry<K,V>(key, if (cmp 0 ) parent.left else parent.right fixAfterInsertion(e); size++; modCount++; return null ; } |
先判断root是否为null,如果是则新建一个Entry对象,并赋值给root。
如果root不为null,首先判断是否指定了Comparator,如果已经传入,则基于红黑树的方式遍历,通过比较结果选择左树或者右树(左小右大)。
如果找到相等的key则直接替换vlaue,并返回结束put操作。
如果遍历结束都没有找到相等的key,则根据最后一次比较结果在遍历的最后一个节点添加一个左结点或右结点,依据依然是左小右大。
如果没有指定Comparator(Comparator == null),则需要根据key来创建一个比较器(Comparable<? super K> k = (Comparable<? super K>) key;),操作过程和上面相似。
2)获取
get(Object key)
final Entry<K,V> // if (comparator null ) return getEntryUsingComparator(key); if (key null ) throw new NullPointerException(); Comparable<? super K> super K>) Entry<K,V> while (p null ) int cmp if (cmp 0 ) p else if (cmp 0 ) p else return p; } return null ; } |
3)删除
remove(Object key)
public V Entry<K,V> if (p null ) return null ; V deleteEntry(p); return oldValue; } |
首先通过getEntry获取entry对象,如果不为null将此entry从红黑树上删除,并重新调整树的相关节点。
这个过程比较复杂,可以参考红黑树的相关知识。
java中的集合包简要分析的更多相关文章
- Java中的集合概述
Java中的集合类有两个重要的分支,分别是接口Collection(包括List,Set等)和接口Map. 由于HashSet的内部实现原理使用了HashMap,所以我们先来了解Map集合类. 1.H ...
- Java 中的集合接口——List、Set、Map
Java 中的集合接口——List.Set.Map 什么叫集合:集合就是Java API所提供的一系列类的实例,可以用于动态存放多个对象.这跟我们学过的数组差不多,那为什么我们还要学集合,我们看看数组 ...
- Java 常用List集合使用场景分析
Java 常用List集合使用场景分析 过年前的最后一篇,本章通过介绍ArrayList,LinkedList,Vector,CopyOnWriteArrayList 底层实现原理和四个集合的区别.让 ...
- Java中的集合框架-Collection(二)
上一篇<Java中的集合框架-Collection(一)>把Java集合框架中的Collection与List及其常用实现类的功能大致记录了一下,本篇接着记录Collection的另一个子 ...
- Java中的集合和常用类
Java中的常用类: ▪ Object类 ▪ Math类 ▪ String类和StringBuffer类(字符串) ▪ 8种基本类型所对应的包装类 ▪ java.util包中的类——Date类 Obj ...
- Java中的集合类型体系(一)
Java中的集合类型体系(一) 提问:为什么需要集合? 通常情况下,程序需要根据运行时才知道创建了多少对象.若非程序运行时,而在开发阶段,我们并不知道创建了多少对象,甚至不知道对象的准确类型,为了满足 ...
- 万字长文深入理解java中的集合-附PDF下载
目录 1. 前言 2. List 2.1 fail-safe fail-fast知多少 2.1.1 Fail-fast Iterator 2.1.2 Fail-fast 的原理 2.1.3 Fail- ...
- 实现java 中 list集合中有几十万条数据,每100条为一组取出
解决"java 中 list集合中有几十万条数据,每100条为一组取出来如何实现,求代码!!!"的问题. 具体解决方案如下: /** * 实现java 中 list集合中有几十万条 ...
- java中对集合对象list的几种循环访问
java中对集合对象list的几种循环访问的总结如下 1 经典的for循环 public static void main(String[] args) { List<String> li ...
- 菜鸟日记之 java中的集合框架
java中的集合框架图 如图所示:java中的集合分为两种Collection和Map两种接口 可分为Collection是单列集合和Map的双列集合 Collection单列集合:继承了Iterat ...
随机推荐
- 解决使用filter: blur时图片四周泛白的问题
发现问题 在使用filter: blur(15px)模糊背景图时,发现图片周围会泛白. 解决问题 查了好多办法: 1.使用StackBlur.js处理图片模糊. 2.改变background-size ...
- c# RSA加密解密,与java代码互通问题
RSA加密解密原本是公开算法,但是和一个java的小伙伴对接却出现了点问题,现在记录一下 首先,RSA的公钥私钥,有2种: 1.pem格式. 2.xml格式. 文章底部有pem格式和对应的xml样本数 ...
- node: /lib64/libm.so.6: version `GLIBC_2.27‘ not found问题解决方案
场景 centos7服务器使用nvm安装的node之后,只要使用npm或者node,均会出现以下问题. npm -v node: /lib64/libm.so.6: version `GLIBC_2. ...
- SQL Server – Soft Delete
前言 Soft Delete 中文叫 "逻辑删", "软删除". 对比的自然就是 Hard Delete. 这篇想聊一聊它的好与坏, 什么时候可以考虑用它. H ...
- Maya 2019.2 Mtoa 无法正常加载并报错
事件起因: 在开始安装 Maya2019.2 时自动安装的 Mtoa 的版本为 5.3.1,但是在插件管理器里无法启用插件,于是乎去网上下了一个低的版本 5.1.1,虽然可以使用但是渲染出来的东西不能 ...
- AD域下,没有登录服务器处理登录请求
原因: IP地址配置有问题 或者 DNS : 解决办法: 重新设置 IP地址 和 DNS : 此案例中, 切换到 test 账户(域管理员)后发现 , 未配置 IP地址 和 DNS :
- 10款好用的开源 HarmonyOS 工具库
大家好,我是 V 哥,今天给大家分享10款好用的 HarmonyOS的工具库,在开发鸿蒙应用时可以用下,好用的工具可以简化代码,让你写出优雅的应用来.废话不多说,马上开整. 1. efTool efT ...
- ARM64中的ASID地址空间标识符
1. 从ARM32到ARM64 从ARM32到ARM64不止将处理器从32位升级到了64位,还有许多性能的技术也得到了极大的提升,光是个头长了可不行啊!能耐也得跟着长啊!哈哈哈 1.1 ARM32的T ...
- C# 中的数组使用
· // 数组 /// 数组是一组相同类型的数据(ps:js中的数组可以不同类型) 访问通过索引访问数组元素 /// 数组的声明 要使用 new 使用 {} 来初始化数组元素 还需要指定数组的大小 / ...
- jQuery的$(document).ready(function(){}) 和 原生 js 的load 等待加载事件有什么不同
jQuery 的 $(function (){}) 函数入口需要等待 DOM 结构绘制完成才会执行 , 不用等待外部资源加载完毕 和原生js 的 DOMContentLoaded 类似 , 2 者 ...