Java 集合详解、源码分析(1)

数组:

  1. 长度开始时必须指定,而且一旦指定,不能修改
  2. 保存的必须为同一类型的元素
  3. 使用数组进行增加/删除元素比较麻烦

集合:

  1. 可以动态保存任意多个对象,使用比较方便
  2. 提供了一系列方便操作对象的方法: add、remove、set、get
  3. 使用集合添加,删除新元素的代码简洁明了

集合框架体系

Collection体系图

image-20210927084556673

Map集合体系图

image-20210927085020479

Collection 接口和常用方法

Collection接口实现类的特点

public interface Collection<E> extends Iterable<E>
  1. collection实现子类可以存放多个元素,每个元素可以是Object
  2. 有些Collection的实现类,可以存放重复的元素,有些不可以
  3. 有些Collection的实现类,有些是有序的(List),有些不是有序的(Set)
  4. Collection接口没有直接的实现子类,是通过它的子接口Set和List来实现的

Collection接口常用方法

操作元素方法

  1. add:添加单个元素
  2. remove:删除指定元素
  3. contains:查找元素是否存在
  4. size:获取元素个数
  5. isEmpty:判断是否为空
  6. clear:清空
  7. addAll:添加多个元素
  8. containsAll:查找多个元素是否都存在
  9. removeAll:删除多个元素
/**
 *  Collection 常用方法举例
 *    以实现子类 ArrayList举例
 */
public class CollectionMethod {
    public static void main(String[] args) {
        List list = new ArrayList();
//        1. add:添加单个元素
        list.add("卡卡西");
        list.add(20);  // list.add(new Integer(10))
        list.add(true);
        System.out.println("list="+list);

//        2. remove:删除指定元素
        list.remove(0); // 按索引删除第一个元素
        list.remove(new Integer(20)); //指定删除某个元素
        System.out.println("list="+list);

//        3. contains:查找元素是否存在
        System.out.println(list.contains("卡卡西"));

//        4. size:获取元素个数
        System.out.println(list.size());

//        5. isEmpty:判断是否为空
        System.out.println(list.isEmpty());

//        6. clear:清空
        list.clear();
        System.out.println("list="+list);

//        7. addAll:添加多个元素
        ArrayList list2 = new ArrayList();
        list2.add("鸣人");
        list2.add("佐助");
        list2.add("博人");

        list.addAll(list2);
        System.out.println("list="+list);

//        8. containsAll:查找多个元素是否都存在
        System.out.println(list.containsAll(list2));

//        9. removeAll:删除多个元素
        list.add("大蛇丸");
        list.removeAll(list2);
        System.out.println("list="+list);
    }
}

image-20210927092853681

Iterator(迭代器)遍历元素

Iterator基本介绍

  1. Iterator对象称为迭代器,主要用于遍历Collection集合中的元素。
  2. 所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator接口的对象,即可以返回一个迭代器。
  3. Iterator 的结构
  4. Iterator 仅用于遍历集合,Iterator 本身并不存放对象。

Iterator迭代器执行原理

//得到一个集合的迭代器
Iterator iterator = coll.iterator();
//hasNext():判断是否还有下一个元素
while(iterator.hasNext()){
    //next(): 返回迭代中的下一个元素
    System.out.printIn(iterator.next());
}

Iterator接口方法

返回类型方法描述
booleanhasNext()如果迭代具有更多元素,则返回 true 。
Enext()返回迭代中的下一个元素。
default voidremove()从底层集合中删除此迭代器返回的最后一个元素(可选操作)。
default voidforEachRemaining(Consumer action)对每个剩余元素执行给定的操作,直到所有元素都被处理或动作引发异常。

注意:在调用it.next()方法之前必须调用it.hasNext()进行检测。若不调用,且下一条记录无效,直接调用it.next会抛出 NoSuchElementException异常。

public class CollectionIterator {
    public static void main(String[] args) {

        Collection col = new ArrayList();

        col.add(new book("三国演义","罗贯中",20.2));
        col.add(new book("红楼梦","曹雪芹",150.3));
        col.add(new book("小李飞刀","古龙",25.3));

        System.out.println(col);

//        遍历集合
//        1. 得到集合对应的迭代器
        Iterator iterator = col.iterator();
//        2. 使用while循环遍历
        while (iterator.hasNext()){ //判断是否还有数据
            // 返回下一个元素,类型是Object
            Object object = iterator.next();
            System.out.println("onject="+object);
        }
//        提示: 快速生成while循环快捷键 => itit
//        显示快捷提示键  => ctrl+J

    }
}
class book{
    public String name;
    public String author;
    public double price;

    public book(String name, String author, double price) {
        this.name = name;
        this.author = author;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "book{" +
                "name='" + name + '\'' +
                ", author='" + author + '\'' +
                ", price=" + price +
                '}';
    }
}

增强 for 循环遍历集合元素

增强for循环,可以替代iterator迭代器,特点:增强for就是简化版的iterator,本质一样。只能用于遍历集合或数组。

基本语法

for(元素类型 元素名 : 集合名或数组名){
    访问元素
}
public class CollectionFor {
    public static void main(String[] args) {
        Collection col = new ArrayList();

        col.add(new book("三国演义","罗贯中",20.2));
        col.add(new book("红楼梦","曹雪芹",150.3));
        col.add(new book("小李飞刀","古龙",25.3));

        System.out.println(col);

//        使用增强for循环遍历
//        增强for循环底层依然是迭代器
//        提示:快捷键 =>  col.for
        for(Object book : col){
            System.out.println("book="+book);
        }
//        增强for也可以用在数组上
        int[] nums = {1,25,13,8,5};
        for(int i : nums){
            System.out.println("i="+i);
        }

    }
}

List 接口和常用方法

List 接口基本结束

List接口是 Collection 接口的子接口

  1. List集合类中元素有序(即添加顺序和取出顺序一致)、且可重复。
  2. List集合中的每个元素都有其对应的顺序索引,即支持索引。
  3. List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素。
  4. List接口常用的实现类有:ArrayList、LinkedList 和 Vector。

List 接口常用方法

方法描述
void add ( int index , Object ele)在index位置插入ele元素
boolean addAll ( int index , Collection eles)从index位置开始将eles中的所有元素添加进来
Object get ( int index )获取指定index位置的元素
int indexOf ( Object obj)返回obj在集合中首次出现的位置
int lastIndexOf ( Object obj)返回obj在当前集合中末次出现的位置
Object remove ( int index)移除指定index位置的元素,并返回此元素
Object set ( int index , Object ele)设置指定index位置的元素为ele,相当于是替换
List subList ( int fromeIndex , int toIndex)返回从fromIndex 到 toIndex 位置的子集合
public class ListMethod {
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add("卡卡西");
        list.add("鸣人");

//        void add (int index,Object ele)在index位置插入ele元素
        list.add(1,"佐助");
        System.out.println(list);

//        boolean addAll(int index,Collection eles)从index位置开始将eles中的所有元素添加进来
        List list2 = new ArrayList();
        list2.add("jack");
        list2.add("tom");

        list.addAll(1,list2);
        System.out.println(list);

//        Object get(int index)获取指定index位置的元素
        Object o = list.get(1);
        System.out.println(o);

//        int indexOf(Object obj)返回obj在集合中首次出现的位置
        System.out.println(list.indexOf("tom"));

//        int lastIndexOf(Object obj)返回obj在当前集合中末次出现的位置
        list.add("jack");
        System.out.println(list.lastIndexOf("jack"));

//        Object remove(int index)移除指定index位置的元素,并返回此元素
        Object remove = list.remove(5);
        System.out.println(remove);

//        Object set(int index,Object ele)设置指定index位置的元素为ele,相当于是替换
        list.set(1,"mary");
        System.out.println(list);

//        List subList(int fromeIndex,int toIndex)返回从fromIndex 到 toIndex 位置的子集合
//        子集合范围为前闭后开   fromeIndex <= subList  <  toIndex
        List subList = list.subList(2, 4);
        System.out.println(subList);

    }
}

image-20210927113416808

List的三种遍历方式

  1. 使用iterator
  2. 使用增强for
  3. 使用普通for
public class ListFor {
    public static void main(String[] args) {

        List list = new ArrayList();
        list.add("jack");
        list.add("tom");
        list.add("卡卡西");
        list.add("博人");
        
//        遍历
        System.out.println("=======迭代器遍历");
//        1. 迭代器
        Iterator iterator = list.iterator();
        while (iterator.hasNext()) {
            Object obj =  iterator.next();
            System.out.println(obj);
        }

        System.out.println("=======增强for循环");
//      2.  增强for循环
        for (Object o : list) {
            System.out.println("o="+o);
        }

        System.out.println("=======普通for循环");
//        3. 普通for循环
        for (int i = 0; i < list.size(); i++) {
            System.out.println("对象="+list.get(i));
        }



    }
}

ArrayList 底层结构和源码分析

ArrayList 注意事项

  1. ArrayList 可以加入null,并且多个
  2. ArrayList是由数组来实现的
  3. ArrayList基本等同于Vector,除了 ArrayList是线程不安全(执行效率高),在多线程情况下,不建议使用ArrrayList。
    // ArrayList 是线程不安全,源码 没有 synchronized
        public boolean add(E e) {
            ensureCapacityInternal(size + 1);  // Increments modCount!!
            elementData[size++] = e;
            return true;
        }
    

ArrayList 的底层操作机制源码分析(重点,难点)

  1. ArrayList 中维护了一个 Object类型的数组 elementData。
    transient Object[] elementData; // transient 标识瞬间,短暂的,表示该属性不会被序列化
  2. 当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData 容量为0,第一次添加,则扩容elementData为10,如需再次扩容,则扩容elementData为1.5倍。
  3. 如果使用的是指定大小的构造器,则初始elementData容量为指定大小,如果需要扩容,则直接扩容elementData为1.5倍。

源程序:

// 关闭警告
@SuppressWarnings({"all"})
public class ArrayListSource {
    public static void main(String[] args) {

//        源码分析

//        使用无参构造器创建ArrayList对象
        ArrayList list = new ArrayList();

//        使用for循环给list集合添加 1-10 数据
        for (int i = 0; i <= 10; i++) {
            list.add(i);
        }

//        使用for循环给list集合添加 11-15 数据
        for (int i = 11; i<=15;i++){
            list.add(i);
        }
        list.add(100);
        list.add(200);
        list.add(null);

        for (Object o : list) {
            System.out.println(o);
        }
    }
}

image-20210928101741070

  • 在 ArrayList list = new ArrayList(); 处添加断点

  • debug -- step Into 到 ArrayList.java的ArrarList()构造方法

    /**
         * Constructs an empty list with an initial capacity of ten.
         */
        public ArrayList() {
            this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
        }
    

    查询DEFAULTCAPACITY_EMPTY_ELEMENTDATA可发现 默认为空数组

    /**
         * Shared empty array instance used for default sized empty instances. We
         * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
         * first element is added.
         */
        private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    
  • 第一次for循环,程序先进入Integer valueOf() 方法对数据进行装箱

    public static Integer valueOf(int i) {
            if (i >= IntegerCache.low && i <= IntegerCache.high)
                return IntegerCache.cache[i + (-IntegerCache.low)];
            return new Integer(i);
        }
    

    然后执行 list 的boolean add(E e)方法

    /**
         * Appends the specified element to the end of this list.
         *
         * @param e element to be appended to this list
         * @return <tt>true</tt> (as specified by {@link Collection#add})
         */
        public boolean add(E e) {
            ensureCapacityInternal(size + 1);  // Increments modCount!!
            elementData[size++] = e;
            return true;
        }
    

    boolean add(E e)方法中,先执行ensureCapacityInternal(size + 1)方法确定是否要扩容,然后再执行赋值。

    private void ensureCapacityInternal(int minCapacity) {
            ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
        }
    

    calculateCapacity()方法中 先确定elementData是否为空数组,如果为空数组,返回DEFAULT_CAPACITY(默认为10) 和 minCapacity(第一次为1) 中的最大值,

    private static int calculateCapacity(Object[] elementData, int minCapacity) {
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
                return Math.max(DEFAULT_CAPACITY, minCapacity);
            }
            return minCapacity;
        }
    

    ensureExplicitCapacity(int minCapacity)方法中确定是否真的扩容

    modCount++ :记录集合修改次数

    minCapacity - elementData.length > 0 :如果数组所需最小容量 - 数组当前实际大小 大于 0 则执行扩容

    private void ensureExplicitCapacity(int minCapacity) {
            modCount++;
    
            // overflow-conscious code
            if (minCapacity - elementData.length > 0)
                grow(minCapacity);
        }
    

    grow()方法执行扩容

    1. 将elementData.length 记录到 oldCapacity中,第一次值为0
    2. newCapacity = oldCapacity + (oldCapacity >> 1); 执行扩容,扩容大小为 数组当前容量+数组当前大小右移1位(除以2),即扩容1.5倍
    3. 因为第一次扩容oldCapacity 为0 所有newCapacity 也为0,执行
      if (newCapacity - minCapacity < 0) newCapacity = minCapacity;
      此时newCapacity 为 10,所以第一次扩容大小为 10
    4. elementData = Arrays.copyOf(elementData, newCapacity);
      Arrays.copyOf()方法可保留原先数据扩容
      执行Arrays.copyOf()方法进行扩容,第一次执行完elementData 中有10个空数据
    /**
         * Increases the capacity to ensure that it can hold at least the
         * number of elements specified by the minimum capacity argument.
         *
         * @param minCapacity the desired minimum capacity
         */
        private void grow(int minCapacity) {
            // overflow-conscious code
            int oldCapacity = elementData.length;
            int newCapacity = oldCapacity + (oldCapacity >> 1);
            if (newCapacity - minCapacity < 0)
                newCapacity = minCapacity;
            if (newCapacity - MAX_ARRAY_SIZE > 0)
                newCapacity = hugeCapacity(minCapacity);
            // minCapacity is usually close to size, so this is a win:
            elementData = Arrays.copyOf(elementData, newCapacity);
        }
    

    扩容完成后,继续执行add()方法,将数据添加到elementData数组中

Vector 底层结构和源码分析

Vector 基本介绍

image-20210928110321822

  1. Vector类的定义

  2. Vector 底层也是一个对象数组,protected Object[] elementData;

  3. Vector 是线程同步的,即线程安全,Vector 类的操作方法带有 ==synchronized==

        public synchronized boolean add(E e) {
            modCount++;
            ensureCapacityHelper(elementCount + 1);
            elementData[elementCount++] = e;
            return true;
        }
    
  4. 在开发中,需要线程同步安全时,考虑使用Vector.

Vector 和 ArrayList的比较

底层结构版本线程安全(同步)效率扩容倍数
ArrayList可变数组jdk1.2不安全,效率高如果有参构造1.5倍,如果是无参 1. 第一次10 2. 从第二次开始按1.5倍扩容
Vector可变数组jdk1.0安全,效率不高如果是无参,默认10,满后,就按2倍扩容。如果指定大小,则每次直接按2倍扩容

Vector 源码分析

创建Vector对象,并循环添加

 @SuppressWarnings({"all"})
public class Vector_ {

    public static void main(String[] args) {
//        无参构造 创建对象
        Vector vector = new Vector();
        for (int i = 0; i < 10; i++) {
            vector.add(i);
        }
    }
}

image-20210929092112608

  • 创建Vector 对象,首先执行无参构造器

        public Vector() {
            this(10);
        }
    
       public Vector(int initialCapacity) {
            this(initialCapacity, 0);
        }
    

    所以 new Vector() 会默认创建 容量为10的 对象

  • add 方法添加

       public synchronized boolean add(E e) {
            modCount++;
            ensureCapacityHelper(elementCount + 1);
            elementData[elementCount++] = e;
            return true;
        }
    

    首先执行ensureCapacityHelper() 方法判断是否要扩容。

    如果数组所需最小容量大于当前数组容量,执行grow()方法扩容。

        private void ensureCapacityHelper(int minCapacity) {
            // overflow-conscious code
            if (minCapacity - elementData.length > 0)
                grow(minCapacity);
        }
    
  • grow()方法源码:

    默认扩容两倍大小。

    private void grow(int minCapacity) {
            // overflow-conscious code
            int oldCapacity = elementData.length;
            int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                             capacityIncrement : oldCapacity);
            if (newCapacity - minCapacity < 0)
                newCapacity = minCapacity;
            if (newCapacity - MAX_ARRAY_SIZE > 0)
                newCapacity = hugeCapacity(minCapacity);
            elementData = Arrays.copyOf(elementData, newCapacity);
        }
    

    也可指定扩容大小。在vector带参构造器中指定扩容大小

        public Vector(int initialCapacity, int capacityIncrement) {
            super();
            if (initialCapacity < 0)
                throw new IllegalArgumentException("Illegal Capacity: "+
                                                   initialCapacity);
            this.elementData = new Object[initialCapacity];
            this.capacityIncrement = capacityIncrement;
        }
    

LinkedList 底层结构

LinkedList 基本介绍

  1. LinkedList底层实现了双向链表和双端队列特点
  2. 可以添加任意元素(元素可以重复),包括null
  3. 线程不安全,没有实现同步

LinkedList 的底层操作机制

image-20210929101626433

  1. LinkedList 底层维护了一个双向链表

  2. LinkedList中维护了两个属性 first 和 last 分别指向 首节点和尾节点

  3. 每个节点(Node对象),里面又维护了prev、next、item、三个属性,其中通过prev指向前一个,通过next指向后一个节点。最终实现双向链表。

  4. LinkedList的元素的添加和删除,不是通过数组完成的,相对来说效率较高。

LinkedList 源码分析

  • LinkedList的增删改查案例

    @SuppressWarnings({"all"})
    public class LinkedListCRUD {
        public static void main(String[] args) {
    
            LinkedList linkedList = new LinkedList();
            linkedList.add(1);
            linkedList.add(2);
            linkedList.add(3);
    
            System.out.println("linkedList="+linkedList);
    
    //        删除节点,默认删除首节点
            linkedList.remove();
            System.out.println("linkedList="+linkedList);
    
    //        修改某个节点对象
            linkedList.set(0,666);
            System.out.println("linklist="+linkedList);
    
    //        得到某个节点对象
    //        get(1)为第二个对象
            Object o = linkedList.get(1);
            System.out.println(o);
    
    
    //        LinkedList 实现 List 接口 遍历可是迭代器
            System.out.println("====linkedList迭代器遍历");
            Iterator iterator = linkedList.iterator();
            while (iterator.hasNext()) {
                Object next =  iterator.next();
                System.out.println(next);
            }
    
            System.out.println("====增强for循环遍历");
            for (Object o1 : linkedList) {
                System.out.println(o1);
            }
    
            System.out.println("====传统for循环");
            for (int i = 0; i < linkedList.size(); i++) {
                System.out.println(linkedList.get(i));
            }
    
        }
    }
    
    

LinkedList 添加元素

image-20210929104848943

  1. 创建集合对象 LinkedList linkedList = new LinkedList();

        /**
         * Constructs an empty list.
         */
        public LinkedList() {
        }
    

    初始化双向链表

  • 执行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;
            if (l == null)
                first = newNode;
            else
                l.next = newNode;
            size++;
            modCount++;
        }
    

LinkedList 删除元素

//        删除节点,默认删除首节点
        linkedList.remove();
        System.out.println("linkedList="+linkedList);
    public E remove() {
        return removeFirst();
    }
  • 首先让 f 指向 首节点,判断首节点是否为空;

    如果为空,抛出异常;

    如果不为空,执行删除操作;

     public E removeFirst() {
            final Node<E> f = first;
            if (f == null)
                throw new NoSuchElementException();
            return unlinkFirst(f);
        }
    
  • 执行删除操作

    将 首节点置空,first指向下一个节点,下一个节点的prev指向空,即将下一个节点调整为首节点,原首节点有GC算法回收。

    private E unlinkFirst(Node<E> f) {
            // assert f == first && f != null;
            final E element = f.item;
            final Node<E> next = f.next;
            f.item = null;
            f.next = null; // help GC
            first = next;
            if (next == null)
                last = null;
            else
                next.prev = null;
            size--;
            modCount++;
            return element;
        }
    
    

ArrayList 和 LinkedList 比较

底层结构增删效率改查效率
ArrayList可变数组较低,数组扩容较高
LinkedList双向链表较高,通过链表追加较低

如何选择ArrayList和LinkedList:

  1. 如果改查的操作较多,选择ArrayList
  2. 如果增删的操作较多,选择LinkedList
  3. 一般来说,程序中,80%-90%都是查询,因此大部分情况下选择ArrayList
  4. 在一个项目中,根据业务灵活选择,也可能这样,一个模块使用的是ArrayList,另外一个模块是LinkedList。根据业务合理选择。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值