Java学习笔记——集合

集合框架

1.集合、数组都是对多个数据进行存储操作的结构,简称Java容器

​ 说明:此时的存储,主要指的是内存层面的存储,不涉及持久化的存储(.txt,.jpg……)

2.数组在存储多个数据方面的特点

  • ​ 一旦初始化以后,其长度就确定了
  • 数组一旦定义好,其元素的类型也就确定了,只能操作指定类型的数据。String[]、int[]……

3.数组在存储多个数据方面的缺点

  • 一旦初始化之后,长度不能修改
  • 数组提供的方法有限,对于添加删除插入数据等操作效率不高不方便
  • 获取数组中实际元素的个数的需求,数组没有直接的属性和方法(length是数组的长度,不是元素的长度)
  • 数组存储数据的特点:有序、可重复,对于无序、不可重复的需求不能满足
    Java学习笔记——集合_第1张图片
    Java学习笔记——集合_第2张图片

|—Collection

	|---List:动态数组

​	|---Set:集合

|—Map:函数y=f(x)

概念

在java.util包中,可以存放对象的容器

  • 集合中只能存放对象

  • 集合中存放的是多个对象的引用,对象本身还是存放在堆内存中

  • 集合中可以存放不同类型、不限数量【size():int】的数据类型的数据,如果不使用泛型约束存储数据的类型,则默认Object

  • 集合类是由两个接口派生出来的:Collection接口和Map接口

    • Collection:为了存储一个元素合集
    • Map:为了存储键值对

Iterator接口

用于遍历集合的工具,集合对象可以通过Iterator去遍历访问集合中的所有元素

List有一个ListIterator专门遍历List,提供双向遍历功能

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接口继承于Iterator接口,要求所有的Collection接口的实现类中提供Iterator接口的实现。

ArrayList中的Iterator方法的实现

    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;//实现快死异常,不允许遍历集合的同时,有其他线程修改集合结构

fast-fail异常

//不能一边遍历一边对元素进行修改
Set<String> set = new HashSet<>();
for(int i =0; i< 10; i++){
    set.add("set-"+i);
}
Iterator<String> it = set.iterator();
while(it.hasNext()){
    String tmp = it.next;
    sout(tmp);
//  set.remove(tmp);//ConcurrentModificationException--modCount
    it.remove();//删除刚访问过的元素
}

如何解决

  1. 使用juc包中提供的CopyOnWriterArrayList
  2. 使用迭代器中提供的remove

总结

  • Iterator只能单向移动 next
  • Iterator中提供的remove方法是唯一安全的可以在迭代访问的同时修改集合

ListIterator接口

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

Collction体系集合

Collection父接口

方法

  • boolean add(Object obj)//添加一个对象
    
  • boolean addAll(Collection c)//将一个集合中的所有对象添加到此集合
    
  • void clear()//清空此集合中的所有对象
    
  • boolean contains(Object o)//检查此集合中是否包含o对象
    
  • boolean equals(Object o)//比较此集合是否与指定对象相等
    
  • boolean isEmpty()//判断此集合是否为空
    
  • boolean remove(Object o)//在此集合中移除o对象
    
  • int size()//返回此集合中的元素个数
    
  • Object[] toArray()//将此集合转换成数组
    
  • Iterator iterator()//迭代集合中的所有元素
    
  • //遍历集合中的所有元素
    for(Object tmp:collection){}
    
  • //使用迭代器
    Iterator it = collection.iterator();
    while(it.hasNext()){
        Object tmp = it.next();
    }
    

List接口与实现类

List子接口

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

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

方法

  • sort(Comparator<? super E> c)//用于针对集合中的元素按照c比较器进行排序,底层实现是通过Arrays.sort(a,(Comparator) c);
    
  • void add(int index, Object o)//在index位置上插入对象o
    
  • boolean addAll(int index, Collection c)//将一个集合中的元素添加到集合中的index位置
    
  • Object get(int index)//返回集合中指定位置的元素
    
  • List subList(int fromIndex, int toIndex)//返回fromIndex和toIndex之间的集合元素
    
  • Object remove(int index)//删除指定位置上的元素,并返回删除的数据
    
  • Object set(int index, Object obj)//修改指定位置上的元素,并返回原始数据
    
  • boolean addAll(Collection c)//将一个集合中的所有对象添加到此集合
    
  • int indexOf(Object obj)//查找元素的下标位置
    

List实现类

ArrayList

底层实现是一个可变长数组【java中的数组都是定长的】,允许插入null值,默认初始化容积为10,默认采用的是延迟加载的方式进行初始化数组操作,随着元素的不断增加,集合会调用grow()进行扩容处理,增长扩容比例为50%。

  • 实现了RandomAccess接口,支持随机访问功能
  • 不是一个线程安全的容器
  • 具体存储数据使用的是transient Object[] elementData
  • 具体元素的移动和拷贝都是通过System.arrayCopy实现的
  • size、isEmpty、get、set操作都是O(1)
  • add操作由于可能会涉及扩容处理,但是考虑到分摊固定运行时间和数组拷贝使用System.arrayCopy方法,所以也可以近似认为O(1)
构造器

ArrayList()内部存储数据的数组为空数组

  • elementData保存数据

  • capacity容器 ArrayList(int) 默认容积10

     private static final int DEFAULT_CAPACITY = 10;
    
  • modcout快速失败

无参构造器

new ArrayList(),JDK1.8采用的是延迟数组初始化操作elementData={},这是一种针对内存消耗的优化处理

带参构造器

new ArrayList(18) 初始化容积 capacity和size

add方法

添加元素时首先仅从范围检查,然后和建议分配的大小值进行比较,如果大于默认大小则进行扩容。扩容时首先将原始数组的大小提升到1.5倍,称为新数组大小,然后进行元素的拷贝

remove方法

删除元素,但是没有变小容积的处理

线程安全的替代方案:Collections.sychronizedList CopyOnWriteArrayList

get方法

首先进行索引序号[0,size()-1]检查,然后判定是否有并发修改[快速失败处理modcount],最后按照下标从数组中获取元素

LinkedList

底层实现是一个双向链表,没有长度限制【int size()】

不能随机访问,当通过下标访问时会判断具体first和last的远近,从而决定是从头还是从尾开始逐一访问[p=p.next p=p.prev]

  • 实现了Deque接口,双向队列
  • 没有容积的限制,所以无限制追加元素[size():int]
transient int size = 0;
transient Node<E> first;
transient Node<E> last;

删除remove(Object)虽然删除操作时间复杂度O(1),但是定位元素的时间复杂度为O(n)

Vector

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

Stack

stack继承于Vector,实现了一个后进先出的堆栈,基本方法:push()压栈和pop()弹栈,建议使用Deque(双向队列的方式取代)

ArrayList LinkedList Vector
实现方式 底层实现是数组 底层实现是双向链表 底层实现是数组
查询增删 擅长随机访问get和set,非同步 擅长增删add和remove,非同步 查询快,增删慢
线程安全 线程不安全、没有同步处理,并发访问效率高 线程不安全、没有同步处理,并发访问效率高 线程安全、同步处理,并发访问效率较低

Set接口和实现类

Set子接口

方法

  • 全部继承Collection中的方法,没有新方法,对add、equals和hashCode方法添加了限制

  • 当添加的两个对象类型元素的hashCode值相等时,才会调用equals判定相等

  • 如果hashCode值不相等,则不会调用equals方法

  • 允许使用null元素

  • 具体实现类有HashSet、LinkedHashSet和TreeSet

    • Set–>HashSet–>LinkedHashSet
    • SortedSet–>TreeSet

Set实现类

HashSet散列集

  1. 底层是HashMap实现的【利用key特性,value值是一个常量值】,无序

    private transient HashMap<E,Object> map;//具体存储数据的实现
    private static final Object PRESENT == new Object();//向HashSet中存储数据实际上是利用HashMap存储Key-Value的方式进行存储,其中key是HashSet中存储的数据,value是一个常量对象PRESENT
    
  2. HashSet采hash算法来存储集合中的元素,因此具有良好的读写性能

  3. 调用add方法向HashSet中添加元素时,首先针对对象的hashCode值进行比较。HashCode值相等——>继续调用equals方法。

  4. 定义类时,如果equals为true,则hashCode必须相等,反之不一定。

  5. HashSet方法没有同步约束,所以线程不安全

  6. HashSet允许添加null元素

HashSet常见方法方法
  • boolean contains(Object)

  • Object[] toArray():放回包含所有元素的数组;如果需要指定返回数组的类型,可以使用toArray(类型[])

  • boolean add(Object)

  • boolean remove(Object)

常见问题:hashset是如何保证数据不重复的

HashSet的底层实际上就是HashMap,只不过 HashSet是实现了Set接口,并且把数据作为key值,而具体的value值一直使用一个相同的虚值来实现的 private static final Object PRESENT = new Object();

public boolean add(E e){
    return map.put(e,PRESENT)==null;//调用hashmap中的put方法添加数据,其中添加的元素是key值,具体的value是hashset中定义一个常量
}

由于HashMap的key值不允许重复,并且在HashMap中如果出现key值相同时,会使用新的value覆盖旧有的value,然后返回旧有的value值,那么在hashset中执行这句话就会返回一个false,表示插入失败,这样就能保证了数据的不可重复性

LinkedHashSet链式散列集

LinkedHashSet继承于HashSet,底层基于LinkedHashMap实现的,底层采用的是链表和哈希表的算法,使用链表记录元素的添加位置,使用哈希表保证元素的唯一性

LinkedHashSet也是根据hashCode值来决定元素的具体存储位置,但是同时它引入了一个链表可以维护元素的插入顺序

有序【插入序和访问序】

使用HashSet存储数据,并遍历访问
Java学习笔记——集合_第3张图片

使用LinkedHashSet存储数据,并遍历访问
Java学习笔记——集合_第4张图片

SortedSet接口

TreeSet树形集

  • 基于排列顺序实现元素不重复

  • 实现了SortedSet接口,对集合元素自动排序

  • 元素对象的类型必须实现Comparable接口,指定排序规则。TreeSet是通过CompareTo方法或compare判断大小和排序,确定是否为重复元素与hashCode和equals无关

  • TreeSet是一种排序的Set集合

  • 底层实际上就是TreeMap实现的,本质上是一个红黑树原理

HashSet LinkedHashSet TreeSet
无序(不仅不能保证元素的插入顺序,而且元素以后的顺序也可能发生改变),底层采用的是哈希算法,查询效率高 LinkedHashSet是HashSet的子类,底层采用的是哈希算法以及链表实现,既能保证元素的添加顺序,同时也保证了查询性能。但是由于需要额外维护链表结构,所以整体性能要略低于HashSet TreeSet不保证元素的添加顺序,但是会对集合中的元素进行排序。底层采用的是红黑树的实现,但是由于需要定位存储位置,所以增删的效率较低。
存储的元素不允许重复(equals和hashCode) 如果访问的顺序和插入的顺序一致,使用LinkedHashSet Comparable接口或者自定义Comparator
是否用到hashCode()和equals() ×
要求存入HashSet中的元素必须覆盖定义equals()和hashCode()两个方法,判断元素相等需要依赖这两个方法 要求存入HashSet中的元素必须覆盖定义equals()和hashCode()两个方法,判断元素相等需要依赖这两个方法 使用的是compareTo和compare进行比较大小和排序
性能分析 HashSet和TreeSet是Set集合中使用最多的集合,HashSet的性能总是比TreeSet集合的性能好 LinkedHashSet需要额外维护链表以实现记录元素的插入顺序,因此在插入时性能比HashSet低

Set的典型问题

1.使用Set集合可以剔除集合中的重复元素

Random r = new Random();
List<Integer> list = new ArrayList<Integer>();
for (int i = 0; i < 10; i++) {
	list.add(r.nextInt(20));
}
System.out.println(list);
		
Set<Integer> set = new LinkedHashSet<>(list);
System.out.println(set);
image-20220314181028482

2.读取一个英文文档,获取其中有哪些字符(去重复)

  • 读取文本文件可以使用BIO中的字符文件流
  • 每次获取一个字符 String提供了方法charAt(int):char
  • 存储数据可以使用set,以达到去重复的目的
BufferedReader br = new BufferedReader(new FileReader("abc.txt"));
Set<Character> set = new HashSet<Character>();
String str = null;
while ((str=br.readLine())!=null) {
	for (int i = 0; i < str.length(); i++) {
		char cc = str.charAt(i);
		set.add(cc);
	}
}
System.out.println(set);

Map接口与实现类

Map接口与Collection接口没有任何关系,也是一个顶级接口

Map结构

数组+链表+红黑树

Map父接口

存储一对数据(Key+Value)

public interface Map<K,V>{}
  • 定义map对象时建议指定key和value对应的类型,key和value要求必须是复杂类型,不能采用int之类的简单类型

  • map接口中有一个内部接口Entry,每个Entry对象用于封装一对key/value,value允许修改,但是key不允许修改

  • key值不允许重复,value值没有要求。添加数据时key值重复则后盖前

方法

  • V put(K key,V value)//将对象存入到集合中,关联键值。key重复则覆盖原值
    
  • Object get(Object key)//根据键获取对应的值
    
  • int size()
    
  • Object get(Object key)//根据键获取对应的值
    

Map集合的实现类

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
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>,Cloneable,Serializable
  • 如果不指定泛型,则默认key-value的类型都是Obejct

具体的内部数据存储方式

transient Node<K,V>[] table;//哈希表的本质就是一个数组,数组中的每一个元素称为一个桶,桶里存放的是一个key-value组成的链表或者红黑树

LinkedHashMap

LinkedHashMap是HashMap的子类,存储方式与HashMap一致,引入一个双向链表记录数据的顺序

顺序分为【插入序和访问序】

允许null键和null值

非同步线程不安全

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

特别适合作为缓存的实现

TreeMap

内部实现是红黑树,存储元素时会根据key值进行排序【自然排序和定制排序】

因为会排序,所以要key值实现Comparable接口或者自定义

判断key值相等使用的是compareTo或者compare方法,不是equals

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

Hashtable

基于【数组+链表】实现数据存储,没有要求初始化容积值必须为2^n,默认初始化容积值为11,负载因子:0.75,扩容后容器为【旧有容积*2+1】

HashMap LinkedHashMap TreeMap Hashtable
key、value==null? key、value可以为null,但key作为null只能出现一次 key、value可以为null,但key作为null只能出现一次 key、value不允许为null(要不无法排序啊) key、value不允许为null
安全问题 非同步线程不安全(在多线程并发操作中会出现rehash操作出现死循环、脏读导致的数据丢失和size值不精确) 非同步线程不安全 非同步线程不安全 同步处理,线程安全
总结 基于拉链法实现的散列表,一般用于单线程 在HashMap存储数据的基础上添加了额外的双向链表记录数据的访问顺序 要求按照key值进行排序,底层是红黑树 不推荐使用

两个工具类

Arrays:操作数组的工具类

Collections:操作集合的工具类

Collection和Collections

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

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

Tips

  • 有关集合的学习:记接口 记接口中的方法 记实现类的区别

  • Collection接口 List接口 Set接口 Map接口
    无序、无下标、元素可以重复 有序(维护插入数据的顺序)、有下标(通过索引访问集合元素)、元素可以重复 无序(不维护插入数据的顺序),无下标(根据元素本身访问)、元素不可重复 无序、无下标、键不可重复、值可重复
    实现类 ArrayList、LinkedList、Vector HashSet、LinkedHashSet、TreeSet HashMap、LinkedHashMap、TreeMap、Hashtable
  • 数组和集合的比较

    数组 集合
    定长 变长存储数据的容器,容量可以动态改变
    不是面向对象的 弥补了数组的缺陷,比数组更灵活
    存放简单类型和引用类型的数据 存放的都是对象的引用
    无法判断存储的元素个数,length:容器的大小 size:元素的个数
    仅采用顺序表的方式 有多种不同的实现方式和不同的适用场景
    以类的形式存在,具有三大特征

你可能感兴趣的:(Java学习,java,学习,开发语言)