JAVA集合框架库


基合框架库就是java对数据结构的实现。下面介绍集合中的几个接口。

一、Collection接口

Collectoion接口是Java集合框架库中最基本的接口,它存储的是单个对象(元素),包含 ListSetQueue 三个子接口。

  • List:存储一组 有序允许重复)的对象。
  • Set:存储一组 无序不允许重复)的对象。
  • Queue:首先要遵循队列先进先出的特点,提供了一个接口,提供了一些具体的实现类用来操作。(多使用优先级处理)
    在这里插入图片描述

Collection的简单使用

  1. 定义一个collection 的引用 Collection<Integer> coll = new ArrayList<>();(所有的类都基于Collection接口来使用)
  2. 在集合中添加元素coll.add(10);
  3. 移除一个元素coll.remove(10),返回true或者false
  4. 判断集合当中是否包含某一个元素coll.contains(10);
  5. 判断集合是否为空coll.isEmpty();
  6. 返回当前集合中有效元素个数coll.size();
  7. 添加当前集合coll.addAll();
  8. 返回当前迭代器对象 coll.iterator();,使用迭代器对象遍历当前集合,不需要了解底层的数据结构 (这也是迭代器存在的意义)。

迭代器使用

while (iterator.hasNext()){
//判断当前迭代对象ArrayList集合是否有下一个可迭代的元素
            Integer next = iterator.next();//获取下一个给可迭代的元素
            System.out.println(next);
            iterator.remove();//删除返回的元素
        }

1. List

List接口存储的元素是有序的(插入顺序与获取顺序是一样的),允许重复的,数据可以为null。

ArrayList

特点
  1. 数据插入有序
  2. 数据可以重复
  3. 存储的数据可以为null
  4. 数据结构为数组
  5. 集合可以自动扩大
使用
  1. 创建一个空的ArrayList对象,List用来存放String类型的数据
    ArrayList<String> list = new ArrayList();
    List<String> list = new ArrayList<>();
  2. 往list中添加元素
    list.add("ce");
  3. 获取List中的内容
    System.out.println(list); 内容能否获取,取决于对象所对应的类是否实现了toString方法(实现了才能打印)
  4. 获取某一个元素的索引位置list.indexOf("ce");
  5. 判断List是否为空 list.isEmpty();
  6. 获取list的大小list.size();
  7. 检查list是否包含某一元素list.contains("hy");
  8. 获取指定位置的元素list.get(1);
  9. 遍历ArrayList中的元素:三种方法
for(String s: list){                                                            
    System.out.println(s);                                                      
}//能够使用foreach循环,是因为Array实现了迭代器接口                                               

//使用迭代器                                                                             
Iterator<String> iterator = list.iterator();                                    
while(iterator.hasNext()){                                                      
    System.out.println(iterator.next());                                        
} 

for(int i=0; i<list.size(); i++){                                                                                                                               
}                                                                               
  1. 将ArrayList转换为Arraylist.toArray();
实现
  • ArrayList数组 的区别?
    1)数组一旦创建不能修改大小,ArrayList是一个”动态“的数组,本身会提供一些能够操作的方法。
    2)数组在内存中是连续存储的,所以它的索引速度比较快,但是插入/删除时比较麻烦,容易造成内存浪费或溢出。ArrayList可以解决以上问题。
  • 代码实现
class MyArrayList<T>{
	//属性
    private T[] elements ;// 数组->存储元素的容器
    private int size; //有效元素个数
    private static final int defaultCapactity = 5;
	//构造函数
    public MyArrayList(){
        this(defaultCapactity);
    }
	//给elements new一个空间
    public MyArrayList(int capacity){
        this.elements = (T[])new Object[capacity];
    }
	//添加
    public void add(T value){
        //判断是否需要扩容
        if(size == elements.length){
            elements = Arrays.copyOf(elements, elements.length*2);//以2倍的方式扩容
        }
        //添加元素
        elements[size++] = value;
    }
	//检查位置合法性
    public void checkIndex(int index){
        if(index < 0 || index > size-1){
            throw new UnsupportedOperationException("the index is illegal");//index不合法
        }
    }
	//删除
    public boolean remove(int index){
        try{
            checkIndex(index);
            System.arraycopy(elements, index+1, elements, index, size-1-index);
          /*数组拷贝(4种方法):
            Arrays.copyOf()
            System.arraycopy(原数组, 原数组的起始位置,目标数组, 目标数组的起始位置, 拷贝的长度)
            clone
            for循环 */
            elements[--size] = null;
            return true;
        } catch (UnsupportedOperationException e){
        	//若存在异常,打印给定信息
            System.out.println(e.getMessage());
            return false;
        }
    }
	//判断是否包含某一元素
    public boolean contains (T target){
        for(int i=0; i<size; i++){
            if(target.equals(elements[i])){//值相等,存在
                return true;
            }
        }
        return false;
    }
	//给定索引
    public T get(int index){
        try{
            checkIndex(index);
            return elements[index];
        } catch (UnsupportedOperationException e){
            System.out.println(e.getMessage());
            return null;
        }
    }
	//修改某一位置的值
    public boolean set(int index, T value){
        try{
            checkIndex(index);
            elements[index] = value;
            return true;
        } catch (UnsupportedOperationException e){
            System.out.println(e.getMessage());
            return false;
        }
    }
	//把集合当中的有效元素转换为一个String字符串返回
    public String toString(){
        StringBuilder strs = new StringBuilder();
        for(int i=0; i<size; i++){
            strs.append(elements[i]+" ");//"+"不高效,故使用StringBuilder
        }
        return strs.toString();//转换
    }
}
源码分析

类的继承关系

  • 继承了一个AbstractList,它介于List接口和ArrayList之间一个抽象类的实现,实现了ListRandomAccess(随机访问)、CloneableSerializable这四个接口。
  • ArrayList和Vector之间的区别和联系?
    ArrayList是非线程安全的、Vector是线程安全的

构造函数

  • 如果初始化List对象调用无参构造函数,当前第一次调用add方法才会给底层数组初始化,每次调用add
    方法都会获取一个最大容量(length, size+1), 判断是否需要扩容,扩容以1.5倍方式进行扩容
  • 如果初始化List对象调用带参构造函数,每次调用add方法都会获取一个最大容量(length, size+1),
    判断是否需要扩容,扩容以1.5倍方式进行扩容
ArrayList 应用场景

在(数据有序/可重复/可存储null值)的前提下,查询较高的场景。

迭代器实现
  1. 定义一个返回迭代器对象的方法
  2. 调用迭代器对象的 hasNext和 next方法去返回当前的迭代对象
    实现 hasNext和next方法 ->在Iterator接口 -> 自定义一个迭代器的类实现 Iteraator接口,重写hasNext和 next方法。
class MyArrayList<T>{
	//在上面实现部分的类中实现
	//迭代elemennts
    public Iterator<T> iterator(){
        return new MyItr();//返回迭代器对象
    }

    class MyItr implements Iterator{
        int cursor; //定义一个游标 迭代元素返回位置
        @Override
        public boolean hasNext() {
            //判断是否还有下一个可迭代元素
            //elemtents  0~size-1
            return cursor < size;//有可迭代的元素
        }
        @Override
        public Object next() {
            //返回下一个可迭代元素
            T result = elements[cursor];
            cursor++;
            return result;
        }
    }
}
快速失败机制

从迭代器源码中modCount了解Java集合的 fast-fail机制

  • Java中非线程安全的集合:ArrayList、HashMap,经常可以在一些修改集合结构的操作中看到实例变量modCount++,以此来统计集合的修改次数。
  • fast-fail机制:它能够立刻报告任何可能导致失败的错误检测机制。
  • 当使用迭代器时(当构造迭代器对象时),起初有一个expectedModCount = modCount,在迭代的过程会判断两者之间的关系,如果modCount与期望值不符合,就说明在迭代的过程中集合结构发生了修改,便会抛出ConcurrentModificationException的异常。

LinkedList

LinkedList底层通过链表实现。

使用
  1. 创建一个LinkedList 基于双向链表 链表/队列
    LinkedList<String> list = new LinkedList<>();
    List<String> list = new LinkedList<>;
  2. 添加到尾节点list.add("zs");
    添加元素到头list.addFirst("ls");
    添加元素到尾list.addLast("ww");
  3. 删除头节点list.remove();
    删除指定元素list.remove("zs");
    删除指定位置元素list.remove(0);
  4. 打印System.out.println(list);
  5. 迭代器遍历
  Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()){
            System.out.println(iterator.next());
        }
        //foreach
        for(String s:list){
            System.out.println(s);
        }
  1. 修改指定索引位置元素list.set(0,"xh");
  2. 获取指定位置的元素list.get(0);
  3. 判空list.isEmpty();
  4. 获取元素个数list.size();
  5. 判断是否包含某元素list.contains("zs");
实现
class MyLinkedList<T>{
    private Node<T> head; //指向头节点
    private Node<T> tail; //指向尾节点
    private int size; //有效元素的个数
    
    class Node<T>{
        private T data; //数据域
        private Node<T> prev; //指向当前节点的前一个节点
        private Node<T> next; //指向当前节点的后一个节点
        //构造函数
        public Node(T data, Node<T> prev, Node<T> next){
            this.data = data;
            this.prev = prev;
            this.next = next;
        }
    }
	//尾插法添加一个元素
    public void add(T value){
        Node<T> newNode = new Node<>(value, tail, null);
        //第一次插入要特殊处理:判断是否是空链表
        if(head == null){
            head = newNode;
        }else{
            //正常情况下
            tail.next = newNode;
        }
        tail = newNode;//尾巴向后挪
        size++;
    }
	//指定位置添加一个元素
    public void add(int index, T value){
        if(index < 0 || index > size){
            return;
        }
        if(index == size){//index是尾部
            add(value);//调用尾插法
        }else{
            //根据index找到该位置的节点
            Node<T> succ = findNodeByIndex(index);
            Node<T> succPrev = succ.prev;
            Node<T> newNode = new Node(value, succPrev ,succ);
            succ.prev = newNode;
            if(succPrev == null){
                //将当前节点插到至第一个位置
                head = newNode;
            }else{
                succPrev.next = newNode;
            }
        }
        size++;
    }
	//获取index位置的节点
    public Node<T> findNodeByIndex(int index){
        Node<T> tmp = head;//临时引用tmp从头开始
        for(int i=0; i<index; i++){
            tmp = tmp.next;
        }//i=index,跳出循环
        return tmp;
    }
    //删除元素所在的节点
    public boolean remove(T value){
        Node<T> succ = findNodeByValue(value);
        if(succ == null){//没找到
            return false;
        }
        //特殊情况:删除第一个节点和最后一个节点
        Node<T> succPrev = succ.prev;
        Node<T> succNext = succ.next;
        if(succPrev == null){//删除第一个节点
            head = succNext;
        }else{//正常情况
            succPrev.next = succNext;
            succ.prev = null;
        }
        
        if(succNext == null){ //删除最后一个节点
            tail = succPrev;
        }else{
            //正常情况
            succNext.prev = succPrev;
            succ.next = null;
        }
        succ.data = null; //方便垃圾回收
        size--;
        return true;
    }
	//通过value获取当前节点
    public Node<T> findNodeByValue(T value){
        for(Node<T> tmp=head; tmp != null; tmp=tmp.next){
            if(tmp.data.equals(value)){//找到节点
                return tmp;
            }
        }
        return null;
    }
	//修改指定位置的元素
    public T set(int index, T newValue){
        if(index < 0 || index >= size){
            return null;
        }
        Node<T> succ = findNodeByIndex(index);
        T result = succ.data;
        succ.data = newValue;
        return result;
    }
	//获取某一位置的元素
    public T get(int index){
        if(index < 0 || index >= size){
            return null;
        }
        return findNodeByIndex(index).data;
    }

    public String toString(){
        StringBuilder strs = new StringBuilder();
        Node<T> tmp = head;
        for(int i=0; i<size; i++){
            strs.append(tmp.data+" ");
            tmp = tmp.next;
        }
        return strs.toString();
    }
}
源码

1.类的继承关系

  • 相比与ArrayList,多实现了一个类,实现了Deque接口,是双端队列的一个接口,实现它可以将LinkedList当作一个队列来使用。
  • 继承了AbstractSequentialList接口以及与ArrayList相同的其他几个接口。

2.类的属性

transient int size = 0;//有效元素个数
transient Node<E> first;//第一个节点
transient Node<E> last;//最后一个节点

3.构造函数
4.数据结构
底层采用双向链表
5. 扩容机制
4.常用方法

  • add方法
public boolean add(E e) {
        linkLast(e);//尾部添加元素
        return true;
    }
	void linkLast(E e) {
        final Node<E> l = last;//获取当前尾节点的引用
        final Node<E> newNode = new Node<>(l, e, null);//封装当前节点
        last = newNode;//last往后走
        if (l == null)//第一次插入
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }    
    //在指定位置添加元素
    public void add(int index, E element) {
        checkPositionIndex(index);
        if (index == size)//要往最后一个位置插入
            linkLast(element);
        else
            linkBefore(element, node(index));
    }
  • remove调用了一个ublink方法
E unlink(Node<E> x) {
        // assert x != null;
        final E element = x.item;
        final Node<E> next = x.next;
        final Node<E> prev = x.prev;

        if (prev == null) {//删除头节点
            first = next;
        } else {
            prev.next = next;
            x.prev = null;
        }
        
        if (next == null) {//删除尾节点
            last = prev;
        } else {
            next.prev = prev;
            x.next = null;
        }

        x.item = null;
        size--;
        modCount++;
        return element;
    }
  • get 方法
应用场景

在(数据有序/可重复/可存储null值)的前提下,在修改比较高的场景下。
LinkedList 也支持 fsat-fail(快速失败机制)。

ArrayList和LinkedList的区别?

不同点ArrayListLinkedList
底层实现动态数组双向链表
内存上地址空间连续非连续
初始化时默认容量10null
扩容1.5倍(需要进行判满操作)/
随机访问index(效率更高)next / prev
随机插入和删除移动目标节点后面的节点(System.arraycopy 方法移动)O(N)修改目标节点 next / prev 属性O(1)(效率更高)
顺序插入和删除不需要移动节点(效率更高)/
/存在空间浪费/

vector类

2.Set

HashSet

特点: HashSet底层基于HashMap,当前元素作为key,key不允许重复,具有HashMap所有的特点

如何通过HashMap实现HashSet?
——HashMap中存储key-value键值对,Set接口的实现类都是存储单种类型的元素,Set的实现种使用了HashMap中的Key来存储元素,Value值给定的是一个Object对象填充。

应用场景:对元素去重场景

使用
  • HashSet中添加元素也是调用add方法,且只能用这一个方法,set集合不能指定位置添加元素。可以调用size() 方法获取元素个数,remove() 删除元素,isEmpty() 判空,contais() 判断是否存在。
  • 没有获取元素和修改元素的方法。(因为底层基于HashMap实现,无法确定位置)
  • 遍历可以使用迭代器foreach循环。
  • 在Set中添加和删除不会出现异常报错,增删成功返回 true,失败返回 false

LinkHashSet

特点:

  1. LinkedHashSet是基于LinkedHashMap实现的(是set接口的实现,基于哈希表链表,哈希表用来保证元素的唯一性,链表保证元素的插入顺序
  2. 元素是不能重复的
  3. 可以存储null值
  4. 数据是有序的(插入和访问有序,相比于HashSet,节点类型多了beforeafter引用,通过这两个引用决定插入的先后顺序)

应用场景:数据去重且数据有序

HashSet和LinkedHashSet的异同点?

不同点:

  • HashSet
    继承自 AbstractSet
    不能保证元素的排列顺序,顺序有可能发生变化。
    不是同步的。
    集合元素可以是null,但只能放入一个null。
    内部使用HashMap来存储数据,数据存储在HashMap的key中,value都是同一个默认值。
  • LinkedHashSet
    继承自 HashSet
    内部使用LinkHashMap存储数据 -> 元素顺序一定,遍历和插入顺序一致。

相同点:
都不是线程安全的。如果想要保证线程安全,可以使用Collections.synchornizedSet()方法。

TreeSet

也是Set接口的实现,底层基于哈希表红黑树实现,哈希表用来保证元素的唯一性,红黑树保证元素的有序(自然顺序、比较器接口所对应的顺序)。

3.Queue

二、Map接口

Map接口是哈希表基本接口,存储的是key-value键值对对象。主要包含下图几个接口。
在这里插入图片描述

HashMap

详细介绍在 HashMap总结 中。

LinkedHashMap

特点

  1. 底层数据结构是哈希表
  2. 继承自HashMap:具有HashMap的所有特点(数据有序:插入有序、访问有序)

如何做到数据有序?
——维护一个双向的链表接口。
应用场景:数据统计且数据有序(统计数据出现的次数且如果数据次数相等,按照插入顺序)

HashTable

特点

  1. 底层数据结构是哈希表
  2. 数据(key)不能重复
  3. 元素(key和value)都不可以为null
  4. 数据无序
  5. 是线程安全的
  6. 哈希表结构默认大小是11
  7. 2倍+1的关系进行扩容

HashMap 和 HashTable 和 ConcurrentHashMap 的区别?

HashMap 提供可供应用迭代的键的集合,是快速失败的。

HashMapHashTableConcurrentHashMap
数组+链表实现数组+链表实现分段的数组+链表实现
继承于AbstractMap继承于Dictionary
key、value允许为null不允许key、value为null不允许key、value为null
线程不安全线程安全线程安全
初始 size =16初始 size =11初始 size =16
扩容:2n(size为2的n次幂)扩容:2n+1扩容因子:0.75
不同步同步的
适合单线程环境适合多线程环境

WeakHashMap

特点:数据随着实践的推移会消失或减少

Java中的四种引用

  1. 强引用(strong):所作用的对象在内存不足抛出OOM的问题时都不会被回收
  2. 软引用(soft):当内存不足时,在发生GC操作时,软引用所作用的对象会倍回收
  3. 弱引用(weak):只要发生GC操作,无论内存是否充足,弱引用所作用对象会被回收
  4. 虚引用(phantom):和对象的生命周期无关,主要是来提醒对象被回收

应用场景:在内存紧张情况下,将非关键信息存放在该集合中

TreeMap

特点:

  1. 数据会按照属性的特征进行排序
  2. 底层数据结构:使用红黑树

应用场景:数据统计并且数据按照数据大小排序

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值