JavaLearn#(15)集合提升训练:手写ArrayList单链表LinkedListHashMapHashSet新一代并发集合类

Posted LRcoding

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaLearn#(15)集合提升训练:手写ArrayList单链表LinkedListHashMapHashSet新一代并发集合类相关的知识,希望对你有一定的参考价值。

1. 手写ArrayList

1.1 ArrayList 底层原理细节

  • 底层结构是一个长度可以动态增长数组(顺序表)

    // 指向数组的引用
    transient Object[] elementData; // transient修饰的变量,不参与序列化
    

    数组特点:在内存中分配连续的空间只存储数据,不存储地址信息

    • 节省存储空间(无需存储地址和数据间的关系)、索引查询效率高(其它地址可以通过首地址计算得到)
    • 插入和删除效率低,必须提前分配固定数量的空间,可能会导致空闲浪费,按照内容查询效率低
  • 通过无参构造方法,创建对象时,JDK1.7初始长度10,JDK1.8初始长度为0,在第一次添加元素时就进行扩容(每次扩容1.5倍)

  • 提供了一个内部类 Itr,实现了 Iterator 接口,用来对 ArrrayList 进行遍历

1.2 自定义:List、Iterator、异常处理

List

public interface List 
    /**
     * 返回线性表的大小,即数据元素的个数
     * @return
     */
    int size();

    /**
     * 返回线性表中序号为 i 的数据元素
     * @param i
     * @return
     */
    Object get(int i);

    /**
     * 如果线性表为空返回true,否则返回false
     * @return
     */
    boolean isEmpty();

    /**
     * 判断线性表中是否包含数据元素 e
     * @param e
     * @return
     */
    boolean contains(Object e);

    /**
     * 返回数据元素 e 在线性表中的序号
     * @param e
     * @return
     */
    int indexOf(Object e);

    /**
     * 将数据元素 e 插入到线性表中 i 号的位置
     * @param i
     * @param e
     */
    void add(int i, Object e);

    /**
     * 将数据元素 e 插入到线性表的末尾
     * @param e
     */
    void add(Object e);

    /**
     * 删除线性表中序号为 i 的元素,并返回值
     * @param i
     * @return
     */
    Object remove(int i);

    /**
     * 删除线性表中第一个与 e 相同的元素
     * @param e
     * @return
     */
    boolean remove(Object e);

    /**
     * 替换线性表中序号为 i 的数据元素为 e,返回原数据元素
     * @param i
     * @param e
     * @return
     */
    Object replace(int i, Object e);

    /**
     * 迭代List
     * @return
     */
    Iterator iterator();

Iterator

public interface Iterator<T> 
    /**
     * 是否还有下一个元素
     * @return
     */
    boolean hasNext();

    /**
     * 下一个返回的元素值
     * @return
     */
    T next();

IndexOutOfBoundsException

public class IndexOutOfBoundsException extends RuntimeException
    public IndexOutOfBoundsException() 
    

    public IndexOutOfBoundsException(String message) 
        super(message);
    

1.3 自定义:测试类

public class TestArrayList 
    public static void main(String[] args) 
        List list = new ArrayList();

        list.add("1111");
        list.add("aaaa");
        list.add("bbbb");
        list.add("3333");
        list.add("2222");
        list.add("1111");
        list.add("aaaa");
        list.add("bbbb");
        list.add("3333");
        list.add("2222");
        // 添加第 11 个元素时,注意扩容
        list.add("1111");
        // 添加到指定位置,注意移动元素
        list.add(3, "AAAA");

        System.out.println(list.size());
        System.out.println(list.isEmpty());
        System.out.println(list.get(2));
        System.out.println(list.contains("4444"));
        System.out.println(list.indexOf("2222"));
        // 注意判断查找 null 时
        System.out.println(list.indexOf(null));
		System.out.println(list.toString());

        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()) 
            System.out.println(iterator.next());
        
        
        // 光标已经走到最后面,还往后走,进行报错
        System.out.println(iterator.next());
    

1.4 自定义:ArrayList

最重要的扩容底层

ArrayList类

package com.lwclick.list;

import java.util.Arrays;

/**
 * @author LIUWEI
 * @className ArrayList
 * @date 2021/9/27 22:42
 * @description 手写的ArrayList
 */
public class ArrayList implements List
    /**
     * ArrayList底层是一个【长度可以动态增长】的数组,elementData是数组的引用
     */
    private transient Object[] elementData;

    /**
     * 集合中存储了元素的个数,不是数组空间的容量length
     * 增加、删除元素时,size的值都要发生变化
     */
    private int size;

    public ArrayList() 
        this(10);
    

    public ArrayList(int initialCapacity) 
        elementData = new Object[initialCapacity];
    

    @Override
    public int size() 
        return size;
    

    @Override
    public Object get(int i) 
        if (i >= size || i < 0) 
            throw new IndexOutOfBoundsException("数组索引越界" + i);
        
        return elementData[i];
    

    @Override
    public boolean isEmpty() 
        return size == 0;
    

    @Override
    public boolean contains(Object e) 
        return indexOf(e) >= 0;
    

    @Override
    public int indexOf(Object e) 
        int index = -1;
        if (e == null)  // 为空则比较引用
            for (int i = 0; i < size; i++) 
                if (e == elementData[i]) 
                    return i;
                
            
         else  // 不为空则比较内容
            for (int i = 0; i < size; i++) 
                if (e.equals(elementData[i])) 
                    return i;
                
            
        
        return index;
    

    @Override
    public void add(int index, Object e) 
        if (elementData.length == size) 
            grow();
        
        
        // 添加到指定位置时,一定要移动元素,不然会覆盖
        for (int i = size; i > index; i--) 
            elementData[i] = elementData[i - 1];
        
        
        elementData[index] = e;
        size++;
    

    @Override
    public void add(Object e) 
        // 如果数组已满,需要先扩容
        if (elementData.length == size) 
            grow();
        
        
        // 添加元素到最后:初始elementData为空的,size的值也为0,所以就是 size的位置
        elementData[size] = e;
        // 存储的元素进行 ++
        size++;
    

    private void grow() 
        // 1.新创建一个更大容量的数组
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1); // 使用右移,是为了不出现小数(右移时注意优先级)
        Object[] newArr = new Object[newCapacity];

        // 2.将原来数组的元素,拷贝到新数组中,索引一一对应
        for (int i = 0; i < oldCapacity; i++) 
            newArr[i] = elementData[i];
        

        // 3.成员变量elementData指向扩容后的新数组
        elementData = newArr;

        // 可以使用一句话实现
        // elementData = Arrays.copyOf(elementData, newCapacity);
    

    @Override
    public Object remove(int index) 
        if (index >= size || index < 0) 
            throw new IndexOutOfBoundsException("数组索引越界" + index);
        
        Object e = elementData[index];

        // 移动元素
        int numMoved = size - index - 1;
        if (numMoved > 0) 
            // System.arraycopy(源数组, 从源数组的起始位置开始, 拷贝到目标数组, 到目标数组的开始位置, 复制的个数)
            System.arraycopy(elementData, index + 1, elementData, index, numMoved);
        

        // 最后的元素指向 null,交给GC
        elementData[--size] = null;

        return e;
    

    @Override
    public boolean remove(Object e) 
        if (e == null)  // 如果为空,比较地址
            for (int i = 0; i < size; i++) 
                if (e == elementData[i]) 
                    fastRemove(i);
                    return true;
                
            
         else 
            for (int i = 0; i < size; i++) 
                if (e.equals(elementData[i])) 
                    fastRemove(i);
                    return true;
                
            
        
        return false;
    

    private void fastRemove(int index) 
        int numMoved = size - index - 1;
        if (numMoved > 0) 
            System.arraycopy(elementData, index + 1, elementData, index, numMoved);
        

        elementData[--size] = null;
    

    @Override
    public Object replace(int index, Object e) 
        if (index >= size || index < 0) 
            throw new IndexOutOfBoundsException("数组索引越界" + index);
        
        if (e == null) 
            if (e != elementData[index]) 
                Object oldValue = elementData[index];
                elementData[index] = e;
                return oldValue;
            
         else 
            if (!e.equals(elementData[index])) 
                Object oldValue = elementData[index];
                elementData[index] = e;
                return oldValue;
            
        
        return this;
    

    @Override
    public Iterator iterator() 
        return new Itr();
    

    @Override
    public String toString() 
        StringBuilder builder = new StringBuilder("[");
        for (int i = 0; i < size; i++) 
            builder.append(elementData[i] + ",");
        
        if (size > 0) 
            builder.deleteCharAt(builder.length() - 1);
        
        builder.append("]");
        return builder.toString();
    

    private class Itr<T> implements Iterator<T> 
        // 定义一个光标依次指向数组
        int cursor = 0;
        @Override
        public boolean hasNext() 
            return cursor != size;
        

        @Override
        public T next() 
            if (cursor >= size) 
                throw new RuntimeException("元素已经遍历结束");
            
            return (T)elementData[cursor++];
        
    

2. 单链表

2.1 单链表技能点

  • 存储空间不连续
  • 每个结点对应一个数据元素,结点由指针域和数据域组成
  • 元素的逻辑关系通过结点间的链接关系体现,逻辑上相邻,物理上不一定相邻
  • 按索引查询慢,存储空间大
  • 插入、删除速度快
  • 有元素才会分配空间,不会有闲置的结点

注:为了对空表非空表的情况以及首元结点进行统一处理,常增加一个头结点

2.2 手写单链表

测试代码

public static void main(String[] args) 
    //创建线性顺序表
    List list = new SingleLinkedList();
    
    //向末尾添加元素
    list.add("11111");
    list.add("aaaaa");
    list.add("bbbbb");
    list.add("33333");
    list.add("22222");
    
    // 添加到指定位置
    list.add(3, "AAAAA");
    
    //进行各种操作验证添加
    System.out.println(list.size());
    System.out.println(list.isEmpty());
    System.out.println(list.get(2));
    System.out.println(list.contains("44444"));
    System.out.println(list.indexOf("22222"));
    System.out.println(list.toString());

add 操作及 get 操作的底层思想

List类

public interface List 
    // 返回线性表的大小,即数据元素的个数。
    int size();
    // 返回线性表中序号为 i 的数据元素
    Object get(int i);
    // 如果线性表为空返回 true,否则返回 false。
    boolean isEmpty();
    // 判断线性表是否包含数据元素 e
    boolean contains(Object e);
    // 返回数据元素 e 在线性表中的序号
    int indexOf(Object e);
    // 将数据元素 e 插入到线性表中 i 号位置
    void add(int i, Object e);
    // 将数据元素 e 插入到线性表末尾
    void add(Object e);
    // 将数据元素 e 插入到元素 obj 之前
    boolean addBefore(Object obj, Object e);
    // 将数据元素 e 插入到元素 obj 之后
    boolean addAfter(Object obj, Object e);
    // 删除线性表中序号为 i 的元素,并返回之
    Object remove(int i);
    // 删除线性表中第一个与 e 相同的元素
    boolean remove(Object e);
    // 替换线性表中序号为 i 的数据元素为 e,返回原数据元素
    Object replace(int i, 以上是关于JavaLearn#(15)集合提升训练:手写ArrayList单链表LinkedListHashMapHashSet新一代并发集合类的主要内容,如果未能解决你的问题,请参考以下文章

JavaLearn#(16)多线程提升训练:生产者和消费者问题Lock锁ReadWriteLockBlockingQueuevolatile线程池线程同步练习

JavaLearn#(16)多线程提升训练:生产者和消费者问题Lock锁ReadWriteLockBlockingQueuevolatile线程池线程同步练习

JavaLearn # (10)集合List栈队列Set外部比较器哈希表

JavaLearn#(11)MapIterator迭代器Collections集合总结泛型

15 手写数字识别-小数据集

15手写数字识别-小数据集