集合中只能存放引用类型,数组中可以存放基本数据类型和引用类型

一、List(有序 不唯一)

1.ArrayList

1.1 ArrayList特点

  1. 底层是一个数组
  2. ArrayList根据索引取元素的效率比较高
  3. 有序,存储的元素都是有顺序的

ArrayList的基本使用:

  1. public static void main(String[] args) {
  2. List<Integer> list=new ArrayList<>();//多态
  3. ArrayList<Integer> list1=new ArrayList<>();
  4. list.add(123);//自动装箱
  5. list.add(234);
  6. list.add(345);
  7. //get()集合中取元素,括号中为下标
  8. Integer a=list.get(1);
  9. System.out.println(a);
  10. //size();为集合的长度
  11. Integer b=list.size();
  12. System.out.println(b);
  13. //集合的遍历
  14. for (int i = 0; i <list.size() ; i++) {
  15. System.out.println(list.get(i));
  16. }
  17. //集合foreach遍历
  18. for (Integer l:list) {
  19. System.out.println(l);
  20. }
  21. }

1.2 ArrayList的其他使用方法

  1. import java.util.ArrayList;
  2. import java.util.List;
  3. //ArrayList的其他使用
  4. public class ArrayListB {
  5. public static void main(String[] args) {
  6. List<Integer> list=new ArrayList<>();//多态
  7. ArrayList<Integer> list1=new ArrayList<>();
  8. list.add(123);
  9. list.add(0,12);//指定的位置插入
  10. list1.add(1);
  11. list.addAll(list1);//把list1追加到list中
  12. list.set(1,555);//修改list中索引为1的值为555
  13. Integer a=list.remove(0);//移除list中索引为0的元素,并将原值赋值给a
  14. Boolean b=list.remove(new Integer(555));//移除list中的第一个555,返回一个Boolean的值
  15. list.clear();//清空集合中所有元素
  16. }
  17. }

1.3 ArrayList中add底层源码理解

image.png
ArrayList成员变量和构造方法的源码分析

  1. public class ArrayList<E> extends AbstractList<E> implements List<E> {ArrayList实现了List的接口
  2. private static final long serialVersionUID = 8683452581122892189L;
  3. private static final int DEFAULT_CAPACITY = 10;定义默认的长度为10并赋值给DEFAULT_CAPACITY
  4. private static final Object[] EMPTY_ELEMENTDATA = new Object[0];声明空数组,并赋值地址
  5. private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = new Object[0];声明空数组,并赋值地址
  6. transient Object[] elementData;数组elementData是一个空对象,其地址为null,用途为数据传递
  7. private int size;定义私有化的size变量
  8. private static final int MAX_ARRAY_SIZE = 2147483639;数组最大长度
  9. ----------------------------------- ArrayList的有参构造方法---------------------------------------------
  10. public ArrayList(int initialCapacity) { ArrayList的有参构造方法
  11. if (initialCapacity > 0) { 如果初始容量是大于0
  12. this.elementData = new Object[initialCapacity]; elementData10】,数组容量变为10
  13. } else {
  14. if (initialCapacity != 0) {
  15. throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);若是初始容量小于0则抛出异常
  16. }
  17. this.elementData = EMPTY_ELEMENTDATA; 如果初始容量等于0,则将空数组的地址赋值给
  18. }
  19. }
  20. ----------------------------------- ArrayList的无参构造方法---------------------------------------------
  21. public ArrayList() {
  22. this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; 将空数组地址赋值给当前的elementData数组
  23. }

ArrayList中add方法的源码分析

  1. ArrayList<Integer> list1=new ArrayList<>(); 创建list1对象
  2. list.add(123);
  3. list.add(234);
  4. ---------------------list.add(123);分析过程A---------------------------
  5. public static Integer valueOf(int i) {
  6. return i >= -128 && i <= Integer.IntegerCache.high ? Integer.IntegerCache.cache[i + 128] : new Integer(i);
  7. } 实现自动装箱的过程,将int类型的数值转换为Integer
  8. ---------------------list.add(123);分析过程B---------------------------
  9. public boolean add(E e) { 返回Boolean类型的值
  10. ++this.modCount;
  11. this.add(e, this.elementData, this.size); 调用add的方法,传入当前的值123,空数组,0
  12. return true;返回true
  13. }
  14. ---------------------list.add(123);分析过程C---------------------------
  15. private void add(E e, Object[] elementData, int s) { 123 空数组 0
  16. if (s == elementData.length) { 如果数组elementData的长度等于0
  17. elementData = this.grow(); grow中继续调用grow的有参方法,此时此刻数组elementData的大小为1
  18. }
  19. elementData[s] = e;将123赋值给elementData[0]
  20. this.size = s + 1;size1 size当前的值为1
  21. }
  22. private int newCapacity(int minCapacity) { 通过grow进行的调用,数组扩容的方法
  23. int oldCapacity = this.elementData.length; 旧容量等于当前数组的长度
  24. int newCapacity = oldCapacity + (oldCapacity >> 1); 新容量等于旧的容量加上旧的容量的一半
  25. if (newCapacity - minCapacity <= 0) { 如果新容量和传过来的容量(1)的差小于等于0,也就是容量不够
  26. if (this.elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
  27. return Math.max(10, minCapacity); 如果数组为空,则返回容量为10
  28. } else if (minCapacity < 0) {
  29. throw new OutOfMemoryError(); 如果传入的值小于0则抛出异常
  30. } else {
  31. return minCapacity; 如果数组的容量大于传过来的值,则返回传来的值
  32. }
  33. } else {
  34. return newCapacity - 2147483639 <= 0 ? newCapacity : hugeCapacity(minCapacity);
  35. 如果传来的minCapacity容量小于新容量,则进行扩容,返回新容量,等于原来容量的1.5
  36. }
  37. }
  1. list.add(123);
  2. list.add(234);
  3. ---------------------list.add(234);分析过程A---------------------------
  4. public static Integer valueOf(int i) { 实现自动装箱的过程
  5. return i >= -128 && i <= Integer.IntegerCache.high ? Integer.IntegerCache.cache[i + 128] : new Integer(i);
  6. }
  7. ---------------------list.add(234);分析过程B---------------------------
  8. public boolean add(E e) {
  9. ++this.modCount;
  10. this.add(e, this.elementData, this.size); 调用add方法,传入234elementData数组,1
  11. return true;
  12. }
  13. ---------------------list.add(234);分析过程C---------------------------
  14. private void add(E e, Object[] elementData, int s) {传入234elementData数组,1
  15. if (s == elementData.length) { 如果s和数组长度相等则执行,本次中s==1不成立,继续往下执行,跳出if
  16. elementData = this.grow();
  17. }
  18. elementData[s] = e; elementData1】=234
  19. this.size = s + 1; size=1+1=2
  20. }

2.LinkedList

2.1 LinkedList特点

  1. 有序
  2. 插入、删除的时候比较快
  3. 底层为双向链表

get方法中元素获取:
链表的下标和数组的下标含义不同
数组通过下标取值是计算出来的,速度比较快
链表的下标指代的是链表的自然顺序,根据下标取值是数出来的效率较低

  1. import java.util.LinkedList;
  2. public class TestLinkedList {
  3. public static void main(String[] args) {
  4. LinkedList<Integer> list=new LinkedList<>();
  5. list.add(123);//追加
  6. list.add(234);
  7. list.add(345);
  8. Integer a=list.get(0);//元素获取
  9. int b=list.size();//长度
  10. for (int i = 0; i <list.size() ; i++) {//遍历
  11. System.out.println(list.get(i));
  12. }
  13. for (Integer m:list) {
  14. System.out.println(m);
  15. }
  16. System.out.println(list);
  17. }
  18. }

2.2 LinkedList相较于ArrayList的特有方法

addFirst(); addLast(); 头部/尾部追加元素
getFirst(); getLast(); 获取头部/尾部的元素
removeFirst(); removeLast(); 移除头部/尾部的元素

  1. import java.util.LinkedList;
  2. public class LinkedListB {
  3. public static void main(String[] args) {
  4. LinkedList<Integer> list=new LinkedList<>();
  5. list.add(123);//末尾追加元素
  6. list.addFirst(12);//在起始位置追加元素
  7. list.addLast(13);//在末尾位置追加元素
  8. System.out.println(list.getFirst());//获取起始位置元素的值
  9. System.out.println(list.getLast());//获取末尾位置元素的值
  10. System.out.println(list);
  11. list.removeFirst();//移除起始位置的元素
  12. list.removeLast();//移除末尾位置的值
  13. System.out.println(list);
  14. }
  15. }

2.3 LinkedList底层源码理解

image.png
LinkedList成员变量和构造方法的源码分析

LinkedList底层为双链表
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, Serializable {
    transient int size;  链表的容量
    transient LinkedList.Node<E> first;  头节点
    transient LinkedList.Node<E> last;   尾节点
    private static final long serialVersionUID = 876323262645176354L;

    public LinkedList() { 无参构造
        this.size = 0; 
    }

    public LinkedList(Collection<? extends E> c) { 有参构造
        this();
        this.addAll(c);
    }

    -----------------Node源码-----------------
    private static class Node<E> { Node类
        E item;  item为节点值
        LinkedList.Node<E> next;  下一个节点
        LinkedList.Node<E> prev;  上一个节点

        Node(LinkedList.Node<E> prev, E element, LinkedList.Node<E> next) {  有参构造
            this.item = element;  
            this.next = next;
            this.prev = prev;
        }
    }
   ------------------list.add(123);分析过程A------------------------------- 
        public static Integer valueOf(int i) { 自动装箱
        return i >= -128 && i <= Integer.IntegerCache.high ? Integer.IntegerCache.cache[i + 128] : new Integer(i);
    }
   ------------------list.add(123);分析过程B------------------------------- 
       public boolean add(E e) { 返回值为布尔类型,e为123
        this.linkLast(e);调用linkLast方法,传入参数e
        return true; 返回true
    }

        void linkLast(E e) { e为123
        LinkedList.Node<E> l = this.last; 当前last为null,将地址赋值给节点l
        创建一个节点对象,将null,123,null赋值给newNode     oX222
        LinkedList.Node<E> newNode = new LinkedList.Node(l, e, (LinkedList.Node)null);
        this.last = newNode; 此时last为新节点的地址 0X222
        if (l == null) {  因为l的值为null,所以在当前情况下null==null
            this.first = newNode;  将新节点的地址赋值给前驱节点,frist的地址也为 0X222                 0X22 123 0X22
        } else {
            l.next = newNode;
        }

        ++this.size;  当前size从0变为1
        ++this.modCount;
    }
 ------------------list.add(234);分析过程A------------------------------- 
     public static Integer valueOf(int i) {  自动装箱
        return i >= -128 && i <= Integer.IntegerCache.high ? Integer.IntegerCache.cache[i + 128] : new Integer(i);
    }
        public boolean add(E e) { 此时此刻 e为234
        this.linkLast(e);调用linkLast方法
        return true;
    }

    void linkLast(E e) { 234
        LinkedList.Node<E> l = this.last;  将尾节点的地址(0X222)赋值给节点l
        LinkedList.Node<E> newNode = new LinkedList.Node(l, e, (LinkedList.Node)null);  0X222 234 null
        this.last = newNode;  尾节点被赋值成新节点的地址0X333,为下个节点(0X444)的引用做准备
        if (l == null) {                                                0X222!=null 执行else
            this.first = newNode;   将新节点的地址指向上一节点
        } else {
            l.next = newNode;     将地址为0X222的尾节点变成0X333指向0X333的地址
        }

        ++this.size; size从1变成2
        ++this.modCount;
    }


二、Set(无序 唯一)

1. Set集合的方法

set:唯一和无序

1.1 HashSet

1.2 TreeSet

1.3 LinkedHashSet

09_集合(List、Set) - 图3

public class SetTest {
    public static void main(String[] args) {
        Set<String> set=new HashSet<>();
        Set<String> set1=new TreeSet<>();
        Set<String> set2=new LinkedHashSet<>();
        //元素添加
        set.add("A");
        set.add("D");
        set.add("C");
        System.out.println(set);
        //输出集合长度
        System.out.println(set.size());
        //只可以通过增强for循环进行取值
        //注意:不可以通过普通for循环进行取值,set集合没有索引
        for (String s:set) {
            System.out.println(s);
        }
        set.clear();//清空集合元素
        System.out.println(set);
    }
}

2.栈

  1. 特点:先进后出
  2. 栈的基本方法

    public class StackTest {
     public static void main(String[] args) {
         Stack<String> stack=new Stack<>();
         //入栈/压栈
         stack.push("A");
         stack.push("B");
         //输出栈顶元素
         System.out.println(stack.peek());
         //出栈/弹栈
         stack.pop();
         System.out.println(stack);
     }
    }
    

    3.队列

  3. 分类:单端队列(先进先出)和双端队列

  4. 单端队列方法

    public class QueueTest {
     public static void main(String[] args) {
         Queue queue=new LinkedList();//LinkedList为Queue的子类
         //进入单端队列
         queue.offer("A");
         queue.offer("D");
         queue.offer("C");
         System.out.println(queue);
         //出队
         queue.poll();
         System.out.println(queue);
     }
    }
    
  5. 双端队列方法

    public class DequeTest {
     public static void main(String[] args) {
         //Deque<E> extends Queue<E>
         Deque<String> deque=new LinkedList<>();
         System.out.println("-----------------把Deque当作双端队列使用--------------------");
         deque.offerFirst("123");//从首端进入
         deque.offerLast("345");//从尾端进入
         deque.pollFirst();//从首端出去
         deque.pollLast();//从尾端出去
         System.out.println("-----------------把Deque当作单端队列使用--------------------");
         deque.offer("123");
         deque.poll();
         System.out.println("-----------------把Deque当作栈使用--------------------------");
         deque.push("");
         deque.pop();
     }
    }
    

    4. 栈溢出、堆溢出

    09_集合(List、Set) - 图4

    1.堆溢出

    创建对象时如果没有可以分配的堆内存,JVM就会抛出OutOfMemoryError:java heap space异常。
    堆溢出实例:

    public class heapOverflow {
     public static void main(String[] args) {
         List<byte[]> list = new ArrayList<>();
         int i=0;
         while(true){
             list.add(new byte[1024*1024*1024]);
             System.out.println("分配次数:"+(++i));
         }
     }
     }
    

    2.栈溢出

    栈空间不足时,需要分下面两种情况处理:

  • 线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError
  • 虚拟机在扩展栈深度时无法申请到足够的内存空间,将抛出OutOfMemberError

附:当前大部分的虚拟机栈都是可动态扩展的。

public class StackOverflow {
    public static void main(String[] args) {
        dy(20);
    }
    static int dy(int a){
        return a*dy(a-1);
    }
}