集合框架学习总结

集合框架总结

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LHqYM2Hz-1651461861232)(C:\Users\阿白\AppData\Roaming\Typora\typora-user-images\image-20220502105358300.png)]
所有的集合类位于java.util包下【注意juc包】,集合类主要是由两个接口派生出来的,Collection和Map接口是整个框架的根接口

  • 6个集合接口,用于表示不同的集合类型

  • 5个抽象类,对集合接口的部分实现

  • 8个实现类,是对接口的具体实现

  • Collection接口:无序、允许重复。包含了集合的基本操作,可以分为List和Set两大分支

    • List有序,每个元素都有一个下标索引,从0开始,具体实现LinkedList、ArrayList、Vector
    • Set不允许重复,具体实现HashSet和TreeSet,特殊的时LinkedHashSet
  • Set接口继承Collection接口:无序(不维护插入数据的顺序)、不允许重复

  • List接口继承Collection接口:有序(维护插入数据的顺序)、允许重复

  • Map接口存储key-value对数据,与Collection接口无关

    • 具体实现HashMap、Hashtable、TreeMap、LinkedHashMap
  • Iterator接口是用于遍历集合的工具,Collection接口继承于Iterable接口,要求所有的Collection接口的实现类中提供Iterator接口的实现。针对List有一个ListIterator专门遍历List,提供双向遍历功能,注意fail-fast问题

  • Enumeration是JDK1.0引入的抽象类,作用和Iterator一致,用于实现集合的遍历,已过时

可以将List、Set和Map看作集合的三大类

  • List有序,允许重复,访问集合中的元素可以根据元素的索引来访问

    • Iterable和Iterator foreach结构
    • for
  • Set无序,不可以重复,访问集合中的元素只能根据元素本身进行访问

    • Iterable和Iterator foreach结构
  • Map存储key-value对元素,访问时可以通过key访问value

    • keySet
    • values
    • entrySet

两个特殊的工具类

  • Arrays是操作数组的工具类

  • Collections是操作集合的工具类

Collection接口

Collection接口继承于Iterable接口

  • size():int 获取元素个数

  • add(E e):boolean 新增元素

  • remove(E e):boolean 删除指定元素

  • iterator():Iterator 获取迭代器对象

  • contains(E e):boolean 判断集合中是否包含指定元素

  • clear():void 清空集合

  • toArray():Object[]/toArray(T[]):T[]

List接口

集合中的每个元素都有一个顺序索引,允许通过索引访问指定位置上的集合元素

List接口继承于Collection接口,同时提供了关于索引相关的操作方法

  • sort(Comparator<? super E> c)用于针对集合中的所有元素按照c比较器进行排序,具体的底层实现是通过 Arrays.sort(a, (Comparator) c);

  • E get(int index); 根据下标获取指定位置上的元素

  • E set(int index, E element); 修改指定位置上的元素为element

  • void add(int index, E element); 向指定位置上添加元素element

  • E remove(int index); 删除指定位置上的元素,并返回删除的元素

  • int indexOf(Object o);int lastIndexOf(Object o); 查找指定元素o的下标位置

  • ListIterator<E> listIterator(); 提供双向遍历的具体实现类对象

  • List<E> subList(int fromIndex, int toIndex) 获取指定范围的子集合

具体实现类有ArrayList、LinkedList、Vector、Stack(Deque)

ArrayList

底层实现是一个可变长数组【java中的数组都是定长的】,允许插入null值,默认初始化容积为10,默认采用的是延迟加载的方式进行初始化数组操作,随着元素的不管增加,集合会调用grow方法自动进行扩容处理,增长扩容比例为50%。如果可以评估的化最好能够指定一个合理的初始化容积值,尽量避免过多的扩容操作而浪费时间

  • size、isEmpty、get、set操作都是O(1)

  • add操作由于可能会涉及扩容处理,但是考虑到分摊固定运行时间和数组拷贝使用System.arrayCopy方法,所以也可以近似认为O(1)

ArrayList擅长随机访问,非同步

LinkedList

底层实现是一个双向链表,没有容积的概念【int size()】,不能随机访问,当通过下标访问元素时会判断具体first和last的远近,从而决定从头还是从尾开始逐一访问[p=p.next p=p.prev]

由于链表中插入和删除数据没有数据移动问题,所以增删的效率较高,但是注意:定位位置的时间复杂度还是O(n)

LinkedList擅长增删,非同步

Vector

底层实现是一个可变长数组,但是大部分方法上都进行synchronized同步处理,所以线程安全。因为同步处理会影响并发访问性能还有加锁的代价问题,所以已经不再推荐使用。默认初始化容积为10,默认立即执行数组的初始化操作。扩容增长比例为100%

Stack

Stack继承于Vector,实现了一个后进先出的堆栈,基本方法就是push压栈和pop弹栈,建议使用Deque

总结List
  • ArrayList变长数组,线程不安全,有扩容处理,随机访问速度快,增加删除数据速度慢

  • LinkedList双向链表,线程不安全,没有扩容处理,根据下标索引访问速度慢,增加删除数据速度快

  • Vector变长数组,线程安全,基本上已经被ArrayList所替代

  • Stack是堆栈的实现,继承Vector,建议通过双向队列的方式替代

Set接口

不包含重复元素的集合,它维持自己内部的顺序,无序,随机访问没有任何意义;传入set集合中的元素不能相等【equals为true或者compareTo为0】。具体的实现类HashSet散列集、LinkedHashSet和TreeSet

HashSet类

没有重复元素的集合,底层实现是HashMap【利用key特性,value值是一个常量值】,不保证元素的顺序,允许null值,非同步的,HashSet利用hash算法存储集合中的元素,所以具有很好的存取和查找性能

  • hashset允许存放null值

  • hashset存储数据是无序的

  • 小心操作可变对象mutable object

LinkedHashSet

LinkedHashSet继承于HashSet,底层是基于LinkedHashMap实现的,有序【插入序和访问序】,非同步;集合中同样是根据元素的hashCode值决定对应的存储位置;在HashMap的基础上附加了一个链表,用于维护元素的访问次序。

TreeSet

TreeSet底层是通过TreeMap实现的,具体存储采用的是红黑树

  • TreeSet集合不是通过hashCode和equals函数来实现的,是通过comparaTo或者compare判断大小和排序,和hashCode和equals方法无关
强调

存储数据到HashSet中要求数据对应的类型必须重写了equals就需要重写hashCode

规则:要求equals为true时hashCode必须相等

实际上这只是一条规范,如果不这样做程序也可以执行,只不过会隐藏bug。一般一个类的对象如果会存储在Hashtable\HashSet\HashMap等散列存储结构中,那么重写equals后最好也重写hashCode,否则会导致存储数据的不唯一性(存储了两个equals相等的数据)。而如果确定不会存储在这些散列结构中,则可以不重写hashCode。

但是加建议还是重写比较好一点,谁能保证后期不会存储在这些结构中呢,况且重写了hashCode也不会降低性能,一般重写又是可以通过IDE工具生成,因为在线性结构(如ArrayList)中是不会调用hashCode,所以重写了也不要紧,也为后期的修改打了补丁。

equals和==的对比?

==针对简单类型是比较具体的值,针对复杂类型比较的是引用值,在Java中不允许重载运算符,所以没有办法重新定义

equals针对引用类型可以按照业务规则自定义比较内容,如果不定义则从Object类中可以继承到equals方法,这个方法默认和==等价,java中允许覆盖定义Object类中继承到equals方法

Map接口

Map用于存储key-value对的集合,提供了key到value的映射。不允许key值重复,添加数据时如果key值重复,则后盖前;但是针对value没有任何特殊要求

具体的实现类HashMap【ConcurrentHashMap】、Hashtable、LinkedHashMap、TreeMap

HashMap

面试重点

HashMap底层实现采用的是拉链法的哈希表结构,具体实现 JDK1.7采用【数组+链表】,JDK1.8采用【数组+链表+红黑树】。是为了快速查找而设计的,内部定义了一个Node[]数组用于存储数据,元素会通过哈希函数将key转换为数组中存放的索引值,如果有冲突【key值不相同但是映射位置相同】则采用单向链表存储数据;如果单向链表长度过长则会影响查询效率,所以大于树化阈值,可以将链表转换为红黑树;红黑树的平衡处理比较繁琐,所以当红黑树中节点个数小于退化阈值时会自动转换为单向链。

默认初始化数组长度为16,加载因子为0.75【减少hash碰撞的概率】,树化阈值为8和树化总节点数64,退化阈值6。当元素个数大于【容积*加载因子】时会进行扩容,扩容比例为增加100%。如果设置初始化容积值会自动转换为2的n次方【大于等于初始值】,数组扩容会涉及rehash计算。插入数据时采用的是尾插法

线程不安全,在多线程并发操作中会出现rehash操作出现死循环、脏读导致的数据丢失和size值不精确等问题

存储数据允许null值和null键,但是作为key时null只能出现一次

遍历数据:

  • keySet

  • values

  • entrySet

解决方案为:Collections.synchronizedMap或者ConcurrentHashMap

LinkedHashMap

LinkedHashMap是HashMap的子类,存储方法和HashMap一致,但是引入一个额外的双向链表记录数据的顺序,顺序可以分为2种:插入序(默认)和访问序,特别适合作为缓存的实现。

LinkedHashMap<String,Integer> map=new LinkedHashMap<>(16,0.75f,true);
map.put(null,111); 
map.put("aaa", 999); //ConcurrentModificationException 
for(String tmp:map.keySet()) 
    System.out.println(tmp+"-->"+map.get(tmp));

允许null键和null值

非同步线程不安全

由于需要维护元素的顺序,所以性能略低于hashMap性能,但是采用插入序迭代访问全部元素时性能较好

TreeMap

TreeMap内部实现是红黑树,存储元素时会根据key值进行排序,其中排序方法有自然排序和定制排序两种

TreeMap一般要求key必须实现Comparable接口,从而实现自然排序;如果key没有实现Comparable接口或者需要自定义比较器则应该构建TreeMap时指定比较器

TreeMap判断key值相等不是使用equals方法,而是使用compareTo或者自定义比较器中比较方法

自定义类充当key值一般要求实现Comparable接口,并定义equals。要求两个key值equals为true时,必须compareTo返回为零,避免二义性

TreeMap不允许null键和null值,如果希望使用null键则需要自定义比较规则

非同步线程不安全

Hashtable

古老的实现,基于【数组+链表】实现数据存储,没有要求初始化容积值必须为2的n次方,默认初始化容积值为11,负载因子为0.75,不允许null键和null值,扩容后的容器为【旧有容积*2+1】

同步处理,线程安全

总结

HashMap基于拉链法实现的散列表,一般用于单线程编程中

Hashtable基于拉链法实现的散列表,线程安全,使用中性能低下,不建议使用

TreeMap实现了SortedMap接口,要求按照key值进行排序,底层采用红黑树

LinkedHashMap在HashMap存储数据的基础上添加了额外的双向链表记录数据的访问顺序

Iterator和ListIterator接口

Iterator接口

Iterator是一个集合的迭代访问接口,集合对象可以通过Iterator去遍历访问集合中的所有元素

public interface Iterator<E> { 
    boolean hasNext(); //判断集合中是否存储下一个元素,如果有返回true,否则false 
    E next(); //获取集合中的下一个元素,同时指针后移 
    default void remove() { //删除集合中上一次next方法返回的元素 
        throw new UnsupportedOperationException("remove"); 
    }
    default void forEachRemaining(Consumer<? super E> action) { //用于针对 lambda表达式的方式访问集合中的元素 
        Objects.requireNonNull(action); 
        while (hasNext()) 
            action.accept(next()); 
    } 
}

由于Collection接口继承于Iterable接口,所以要求Collection的实现类中必须提供一个Iterator的实现ArrayList中的iterator方法的实现

public Iterator<E> iterator() { 
    return new Itr(); 
}
private class Itr implements Iterator<E> { 
    int cursor; // index of next element to return 
    int lastRet = -1; // index of last element returned; -1 if no such 
    int expectedModCount = modCount;//实现快死异常,不允许遍历集合的同时,有其它线程修改了集合结构,例如add新增元素,则集合结构发生变化

基本使用

ArrayList<String> arr=new ArrayList<>(); 
arr.add("abc"); 
arr.add("bbb"); 
Iterator<String> it=arr.iterator(); 
while(it.hasNext()){ 
    String tmp=it.next(); 
    System.out.println(tmp); 
}

fail-fast异常

ArrayList<String> arr = new ArrayList<>(); 
arr.add("abc"); 
arr.add("bbb"); 
arr.add("ccc"); 
Iterator<String> it = arr.iterator(); 
while (it.hasNext()) { 
    String tmp = it.next(); 
    if("abc".equals(tmp)) 
        arr.remove("abc"); //ConcurrentModificationException--- modCount 
    System.out.println(tmp); 
}

如何解决?

解决方案1:使用juc包中提供的CopyOnWriteArrayList

解决方案2:使用迭代器中提供的remove

ArrayList<String> arr = new ArrayList<>(); 
arr.add("abc"); 
arr.add("bbb"); 
arr.add("ccc"); 
Iterator<String> it = arr.iterator(); 
while (it.hasNext()) { 
    String tmp = it.next(); 
    if("abc".equals(tmp)) 
        it.remove(); 
    System.out.println(tmp); 
}
System.out.println(arr.size());

总结:

Iterator只能单向移动 next

Iterator中提供的remove方法是唯一安全的可以在迭代访问的同时修改集合

ListIterator接口

ListIterator继承于Iterator接口,支持双向迭代访问,主要是针对List实现可以通过调用listIterator方法获取

接口定义

public interface ListIterator<E> extends Iterator<E> {
    boolean hasNext();//判断集合中是否存储下一个元素,如果有返回true,否则false 
    E next();//获取集合中的下一个元素,同时指针后移 
    boolean hasPrevious();//判断集合中是否存储上一个元素,如果有返回true,否则false 
    E previous();//获取集合中的上一个元素,同时指针前移 
    int nextIndex(); //获取当前指针所指向的元素的下一个索引值 
    int previousIndex();//获取当前指针所指向的元素的上一个索引值 
    void remove();//删除当前元素 
    void set(E e);//修改当前元素 
    void add(E e);//在next之前或者previous之后插入一个元素 
}

可以双向移动【向前或者向后】

可以获取前一个后者后一个元素的索引值

基本使用

ArrayList<String> arr = new ArrayList<>(); 
arr.add("aaa"); 
arr.add("bbb"); 
arr.add("ccc"); 
ListIterator<String> lit = arr.listIterator(); // 从前向后遍历所有元素 
while (lit.hasNext()) { 
    String tmp = lit.next(); 
    int pindex = lit.previousIndex(); 
    int nindex = lit.nextIndex();
    System.out.println(tmp+"\tpreindex:"+pindex+"\tnextIndex:"+nindex);
}// 从后向前遍历所有元素 
while (lit.hasPrevious()) { 
    System.out.println(lit.previous()+":"+lit.nextIndex()); 
}
lit = arr.listIterator(1); 
while (lit.hasNext()) { 
    String str=lit.next(); 
    System.out.println(str); 
    if("ccc".equals(str)){ 
        lit.set("ppp"); 
    }else
        lit.add("kkk"); 
}
System.out.println(arr);

常见的比较

ArrayList和LinkedList

ArrayList基于动态数组、LinkedList基于链表

随机访问get和set,ArrayList

新增和删除add和remove,LinkedList

如果针对单条数据插入或者删除ArrayList优于LinkedList,如果插入删除LinkedList优于ArrayList

Hashtable和HashMap

都是实现了Map、Cloneable、Serializable接口

都是存储key-value对的散列表,都是采用链表法解决哈希冲突

  • 历史原因

  • 同步

  • null值

  • 遍历方式:hashtable支持Iterator迭代器和Enumeration枚举器两种遍历方式,但是hashmap只有iterator

Collection和Collections

java.util.Collection是一个集合接口,提供了对集合对象进行基本操作的通用接口方法。Collection接口在java类库种有很多的具体的实现,Collection接口的作用就是为所有具体实现提供了最大化的统一操作方式,直接子类常见的有List、Set和Queue

st,如果插入删除LinkedList优于ArrayList

Hashtable和HashMap

都是实现了Map、Cloneable、Serializable接口

都是存储key-value对的散列表,都是采用链表法解决哈希冲突

  • 历史原因

  • 同步

  • null值

  • 遍历方式:hashtable支持Iterator迭代器和Enumeration枚举器两种遍历方式,但是hashmap只有iterator

Collection和Collections

java.util.Collection是一个集合接口,提供了对集合对象进行基本操作的通用接口方法。Collection接口在java类库种有很多的具体的实现,Collection接口的作用就是为所有具体实现提供了最大化的统一操作方式,直接子类常见的有List、Set和Queue

java.util.Collections是一个包装类(工具类、帮助类),包含了各种有关集合操作的静态多态方法,类不能实例化,用于实现对集合种的元素进行排序、搜索以及线程线程安全等各种操作

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值