Java集合整理(Arraylist、Vector、Linklist)

1、类集概述

类集设置的目的(常用类库的重点):
对象数组有那些问题?普通的对象数组的最大问题在于数组中的元素个数是固定的,不能动态的扩充大小,所以最早的时候可以通过链表实现一个动态对象数组。但是这样做毕竟太复杂了,所以在Java中为了方便用户操作各个数据结构,所以引入了类集的概念,有时候就可以把类集称为java对数据结构的实现
在整个类集中的,这个概念是从JDK12 (Java2) 之后才正式引入的,最早也提供了很多的操作类,但是并没有完整的提出类集的完整概念。
类集中最大的几个操作接口: Collection、 Map、Iterator, 这三个接口为以后要使用的最重点的接口。 所有的类集操作的接口或类都在javautil包中。

Java类集结构图:.

  • Collection:用来存储单值
  • Map:用来存储双值
  • Iterator:迭代器可以用最优的方式来存取数据

在这里插入图片描述

2、链表

数组的动态扩容很费内存。主要表现在数组存储中不利于删除数据,删除数据后数据不连续,操作前移费内存。但链表就解决了这个问题。
链表 [Linked List]:链表是由一组不必相连(不必相连:可以连续也可以不连续) 的内存结构(节点) ,按特定的顺序链接在一起的抽象数据类型

补充:
抽象数据类型(Abstract Data Type [ADT]):表示数学中抽象出来的一些操作的集合。
内存结构:内存中的结构,如: struct、特殊内存块…等等之类;

数组和链表的区别和优缺点:

数组是一种连续存储线性结构,元素类型相同,大小相等。
数组的优点: 存取速度快
数组的缺点:
事先必须知道数组的长度
插入删除元素很慢
空间通常是有限制的
需要大块连续的内存块
插入删除元素的效率很低

链表是离散存储线性结构 n 个节点离散分配,彼此通过指针相连,每个节点只有一个前驱节点,每个节点只有一个后续节点,首节点没有前驱节点,尾节点没有后续节点。

链表优点:空间没有限制 插入删除元素很快
链表缺点:存取速度很慢

删除指针:后一个指针向前移!

2、链表共分几类?
链表常用的有 3 类: 单链表、双向链表、循环链表。
在这里插入图片描述
单链表:只知道下一数据的下标
双链表:既知道上一个数据的下标,也知道下一个数据的下标
循环链表:首尾相连
链表的核心操作集有 3 种:插入、删除、查找(遍历)

单链表

//链表节点
class Node{
    
    
	Object data;
	Node next;
}

单链表 [Linked List]:由各个内存结构通过一个 Next 指针链接在一起组成,每一个内存结构都存在后继内存结构(链尾除外) ,内存结构由数据域和 Next 指针域组成。
单链表实现图示:
在这里插入图片描述
解析:
Data 数据 + Next 指针,组成一个单链表的内存结构 ;
第一个内存结构称为 链头,最后一个内存结构称为 链尾;
链尾的 Next 指针设置为 NULL [指向空];
单链表的遍历方向单一(只能从链头一直遍历到链尾)
单链表操作集:
在这里插入图片描述

3、二叉树

二叉树是树的一种,每个节点最多可具有两个子树,即结点的度最大为 2(结点度:结点拥有的子树数)。

//二叉树节点
class Node{
    
    
	Object data;
	Node left;
	Node right;
}

在这里插入图片描述

先序遍历(根-左-右):1-2-4-8-9-5-10-3-6-7
中序遍历:(左-根-右):8-4-9-2-10-5-1-6-3-7
后序遍历(左-右-根):8-9-4-10-5-2-6-7-3-1

4、Collection接口(重点)

Collection 接口是在整个 Java 类集中保存单值的最大操作父接口, 里面每次操作的时候都只能保存一个对象的数据。
此接口定义在 java.util 包中。
No. 方法名称 类型 描述1 public boolean add(E e) 普通 向集合中插入一个元素2 public boolean addAll(Collection<? extends E> c) 普通 向集合中插入一组元素3 public void clear() 普通 清空集合中的元素4 public boolean contains(Object o) 普通 查找一个元素是否存在5 public boolean containsAll(Collection<?> c) 普通 查找一组元素是否存在6 public boolean isEmpty() 普通 判断集合是否为空7 public Iterator iterator() 普通 为 Iterator 接口实例化8 public boolean remove(Object o) 普通 从集合中删除一个对象9 boolean removeAll(Collection<?> c) 普通 从集合中删除一组对象10 boolean retainAll(Collection<?> c) 普通 判断是否没有指定的集合11 public int size() 普通 求出集合中元素的个数12 public Object[] toArray() 普通 以对象数组的形式返回集合中的全部内容13  T[] toArray(T[] a) 普通 指定操作的泛型类型, 并把内容返回14 public boolean equals(Object o) 普通 从 Object 类中覆写而来15 public int hashCode() 普通 从 Object 类中覆写而来
本接口中一共定义了 15 个方法, 那么此接口的全部子类或子接口就将全部继承以上接口中的方法。
但是, 在开发中不会直接使用 Collection 接口。 而使用其操作的子接口: List、 Set。
之所以有这样的明文规定, 也是在 JDK 1.2 之后才有的。 一开始在 EJB 中的最早模型中全部都是使用 Collection 操作的, 所以很早之前开发代码都是以 Collection 为准, 但是后来为了更加清楚的区分, 集合中是否允许有重复元素所以, SUN在其开源项目 —— PetShop(宠物商店) 中就开始推广 List (允许重复)和 Set (不允许重复)的使用。

5、List接口(重点)

在整个集合中 List 是 Collection 的子接口, 里面的所有内容都是允许重复的。
List 子接口的定义:

public interface List<E> extends Collection<E>

此接口上依然使用了泛型技术。 此接口对于 Collection 接口来讲有如下的扩充方法:
No. 方法名称 类型 描述1 public void add(int index,E element) 普通 在指定位置处增加元素2 boolean addAll(int index,Collection<? extends E> c) 普通 在指定位置处增加一组元素3 public E get(int index) 普通 根据索引位置取出每一个元素4 public int indexOf(Object o) 普通 根据对象查找指定的位置, 找不到返回-15 public int lastIndexOf(Object o) 普通 从后面向前查找位置, 找不到返回-16 public ListIterator listIterator() 普通 返回 ListIterator 接口的实例7 public ListIterator listIterator(int index) 普通 返回从指定位置的 ListIterator 接口的实例8 public E remove(int index) 普通 删除指定位置的内容9 public E set(int index,E element) 普通 修改指定位置的内容10 List subList(int fromIndex,int toIndex) 普通 返回子集合
在 List 接口中有以上 10 个方法是对已有的 Collection 接口进行的扩充。
所以, 证明, List 接口拥有比 Collection 接口更多的操作方法。
了解了 List 接口之后, 那么该如何使用该接口呢? 需要找到此接口的实现类, 常用的实现类有如下几个:
· ArrayList(95%) 、 Vector(4%) 、 LinkedList(1%)

5.1、ArrayList(重点)

ArrayList 是 List 接口的子类, 此类的定义如下:

public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, Serializable

此类继承了 AbstractList 类。 AbstractList 是 List 接口的子类。 AbstractList 是个抽象类, 适配器设计模式。

范例: 增加及取得元素
package org.listdemo.arraylistdemo;
import java.util.ArrayList;
import java.util.List;
public class ArrayListDemo01 {
    
    
	public static void main(String[] args) {
    
    
		List<String> all = new ArrayList<String>(); // 实例化List对象, 并指定泛型类型
		all.add("hello "); // 增加内容, 此方法从Collection接口继承而来
		all.add(0, "LAMP ");// 增加内容, 此方法是List接口单独定义的
		all.add("world"); // 增加内容, 此方法从Collection接口继承而来
		System.out.println(all); // 打印all对象调用toString()方法
	}
}

ArrayList的构造方法

构造器 描述
ArrayList() 构造一个初始容量为10的空列表。
ArrayList​(int initialCapacity) 构造具有指定初始容量的空列表。
ArrayList​(Collection<? extends E> c) 按照集合的迭代器返回的顺序构造一个包含指定集合元素的列表。

当用ArrayList() 创建一个长度为10的数组,当存储到11个数据时,会扩容1.5倍,知道扩容到足够位置,如果存储大量的数据,便会导致大量的内存空间被浪费,因此初次创建时需要创建大量的数据时,因使用ArrayList​(int initialCapacity)的一参构造方法,指定存储的数量。
ArrayList通过无参构造器构造的集合在开始时长度是多少?
为0.,然后当存入数据后,会比较发现存不下,在将容量扩容到10

package com.java.arrayListDemo;
import java.util.ArrayList;
public class Demo1 {
    
    
    public static void main(String[] args) {
    
    
        ArrayList<Integer> arr = new ArrayList();
        arr.add(100);
    }
}

进入add方法的中:

    public boolean add(E e) {
    
    
        modCount++;
        add(e, elementData, size);//add(要往集合中添加的内容,存数据的数组,数组的长度)
        return true;//只要调用add方法,一定返回True,必定为true
    }

再看下add算法

    private void add(E e, Object[] elementData, int s) {
    
    
        if (s == elementData.length)//目前存的数据跟数据长度一致,说明已经存满了 
            elementData = grow();//存满的话,调用扩容算法,扩容后将新的数组赋值给elementData 
        elementData[s] = e;//正常存
        size = s + 1;//存完size(数组长度)+1
    }

再看下add算法下的grow()算法

    private Object[] grow() {
    
    
        return grow(size + 1);//目前已存的数据量跟数组总长度是一致的,已经存满了,最少需要加一个
    }

再看grow()算法的方法

	//minCapacity已经加1了
    private Object[] grow(int minCapacity) {
    
    
    //将一个旧数组和新长度产生(copy)一个新数组
        return elementData = Arrays.copyOf(elementData,//旧数组
                                           newCapacity(minCapacity));//新长度
                                           //旧数组的数据会给到新数组
    }

再看下新长度如何产生

    private int newCapacity(int minCapacity) {
    
    
        // overflow-conscious code
        int oldCapacity = elementData.length;//旧数组的长度
        //旧数组的长度+其右移一位(二进制,旧长度的0.5倍)
        //新的   =  1.5倍的旧  =  1倍的旧  +  0.5倍的旧
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //扩容后的长度-加一后的长度<=0(当旧的=0、1、一组时)
        if (newCapacity - minCapacity <= 0) {
    
    //扩容后的长度小于数组加一,说明新扩容后的长度不够用
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
            //如果 数组=默认数组(即只有第一次增加数组时)
            	// private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
                return Math.max(DEFAULT_CAPACITY, minCapacity);//返回最大值
                //private static final int DEFAULT_CAPACITY = 10;
            if (minCapacity < 0) // overflow内存溢出,符号改变
                throw new OutOfMemoryError();
            return minCapacity;//需要多少,给多少
        }//没进入if,新扩容后的长度够用
        //新的长度<int类型的最大值-8
        private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
        return (newCapacity - MAX_ARRAY_SIZE <= 0)
            ? newCapacity
            : hugeCapacity(minCapacity);
    }
    private static int hugeCapacity(int minCapacity) {
    
    
	    if (minCapacity < 0) // overflow
	        throw new OutOfMemoryError();
	    return (minCapacity > MAX_ARRAY_SIZE)//在这8个数的范围之中
	    //private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
	        ? Integer.MAX_VALUE//返回int类型最大值,
	        : MAX_ARRAY_SIZE;//返回int类型的最大值-8
}

add存,get取

package com.java.arrayListDemo;
import java.util.ArrayList;
public class Demo1 {
    
    
    public static void main(String[] args) {
    
    
        ArrayList<Integer> arr = new ArrayList();
        arr.add(100);
        arr.add(200);
        System.out.println(arr.get(0));
        System.out.println(arr.get(1));
        System.out.println(arr.get(2));
    }
}

运行结果:在这里插入图片描述

5.2、Vector(重点)

与 ArrayList 一样, Vector 本身也属于 List 接口的子类, 此类的定义如下:

public class Vector<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, Serializable

此类与 ArrayList 类一样, 都是 AbstractList 的子类。 所以, 此时的操作只要是 List 接口的子类就都按照 List 进行操作。

package org.listdemo.vectordemo;
import java.util.List;
import java.util.Vector;
public class VectorDemo01 {
    
    
	public static void main(String[] args) {
    
    
		List<String> all = new Vector<String>(); // 实例化List对象, 并指定泛型类型
		all.add("hello "); // 增加内容, 此方法从Collection接口继承而来
		all.add(0, "LAMP ");// 增加内容, 此方法是List接口单独定义的
		all.add("world"); // 增加内容, 此方法从Collection接口继承而来
		all.remove(1); // 根据索引删除内容, 此方法是List接口单独定义的
		all.remove("world");// 删除指定的对象
		System.out.print("集合中的内容是: ");
		for (int x = 0; x < all.size(); x++) {
    
     // size()方法从Collection接口继承而来
		System.out.print(all.get(x) + "、 "); // 此方法是List接口单独定义的
}
}
}
构造器 描述
Vector() 构造一个空向量,使其内部数据数组的大小为 10 ,其标准容量增量为零。(ArrayLisy每次扩容1.5倍)
Vector​(int initialCapacity) 构造一个具有指定初始容量且容量增量等于零的空向量。
Vector​(int initialCapacity, int capacityIncrement) 构造具有指定初始容量和容量增量的空向量。
Vector​(Collection<? extends E> c) 按照集合的迭代器返回的顺序构造一个包含指定集合元素的向量。
package com.java.arrayListDemo;

import java.util.Vector;

public class VectorDemo {
    
    
    public static void main(String[] args) {
    
    
        Vector<Integer> v = new Vector<Integer>();
        //Vector :使用的是数组结构,对于增加删除慢,查找快.
        v.add(10);
        v.add(20);
        System.out.println(v.get(1));
    }
}

5.3、(重点)LinkedList

LinkedList可以当成栈和队列。

package com.java.arrayListDemo;

import java.util.LinkedList;

public class LinkListDemo {
    
    
    public static void main(String[] args) {
    
    
        //LinkedList :使用的是双向链表结构,对于增加删除快,查找慢.
        LinkedList<Integer> data = new LinkedList<Integer>();
        data.add(100);
        data.add(200);
        data.add(300);
        data.add(400);
        Integer num1 = data.removeFirst();//删除并返回第一个元素
        System.out.println(num1);
        Integer num2 = data.removeLast();//删除并返回最后一个元素
        System.out.println(num2);
        int num3 = data.size();//返回此列表中的元素数
        System.out.println(num3);//2个
        Object[] arr = data.toArray();//以适当的顺序(从第一个元素到最后一个元素)返回包含此列表中所有元素的数组。 
        for(int i=0;i<arr.length;i++){
    
    
            System.out.println(arr[i]);
        }
    }
}
输出结果:
100
400
2
200
300

package com.java.arrayListDemo;

import java.util.LinkedList;

public class LinkListDemo2 {
    
    
    public static void main(String[] args) {
    
    
        //LinkedList :使用的是双向链表结构,对于增加删除快,查找慢.
        LinkedList<Integer> data = new LinkedList<Integer>();
        //压栈
        data.push(100);
        data.push(200);
        data.push(300);
        //将元素推送到此列表所表示的堆栈上。
        //弹栈
        Integer num1 = data.pop();//弹出此列表所代表的堆栈中的元素。换句话说,删除并返回此列表的第一个元素。
        System.out.println(num1);
    }
}
输出结果:300

6、迭代器Iterator与ListIterator

package com.java.arrayListDemo;

import java.util.ArrayList;
import java.util.Iterator;

public class IteratorDemo {
    
    
    public static void main(String[] args) {
    
    
        ArrayList<Integer> data =new ArrayList<>();
        data.add(1);
        data.add(2);
        data.add(3);
        data.add(4);
        data.add(5);
        data.add(6);
        Iterator<Integer> iterator = data.iterator();
        while(iterator.hasNext()){
    
    
            Integer i = iterator.next();//返回迭代中的下一个元素。 
            System.out.println(i);
        }
    }
}
1
2
3
4
5
6
package com.java.arrayListDemo;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.ListIterator;

public class ListIteratorDemo {
    
    
    public static void main(String[] args) {
    
    
        ArrayList<Integer> data =new ArrayList<>();
        data.add(1);
        data.add(2);
        data.add(3);
        data.add(4);
        data.add(5);
        data.add(6);
        ListIterator<Integer> iterator = data.listIterator();
        iterator.next();
        iterator.next();//返回列表中的下一个元素并前进光标位置。
        iterator.set(25);//指定的元素替换 next()或 previous()返回的最后一个元素(可选操作)。
        iterator.previous();//返回列表中的上一个元素并向后移动光标位置。
        iterator.previous();//返回两次回到光标为0
        iterator.remove();//从列表中删除 next()或 previous() (可选操作)返回的最后一个元素。删除了0光标位置数组
        while(iterator.hasNext()){
    
    
            Integer num = iterator.next();
            System.out.println(num);
        }
    }
}
25
3
4
5
6

7、forEach(增强for循环)

迭代数组或集合(collection)

package com.java.arrayListDemo;

import java.util.ArrayList;

public class ForEachDemo {
    
    
    public static void main(String[] args) {
    
    
        int arr[] = {
    
    1,2,5,8,10};
        /*for(int i=0;i<arr.length;i++){
            System.out.println(arr[i]);
        }*/
        //ctrl+shift+/:注释
        //注释玩用shift+enter换行

        //forEach :增强For循环 ,最早出现在C#中 .
        //用于迭代数组或集合(collection)
        //语法:
        //for(数据类型 变量名:集合或数组名){}
        for(int data:arr){
    
    
            System.out.println(data);
        }
        System.out.println("-----------------------");
        ArrayList<String> data = new ArrayList<>();
        data.add("锄禾日当午");
        data.add("汗滴禾下土");
        for (String s:data) {
    
    
            System.out.println(s);
        }
    }
}
1
2
5
8
10
-----------------------
锄禾日当午
汗滴禾下土




8、Set

包含重复元素的集合。 更正式地说,集合不包含元素对e1和e2 ,使得e1.equals(e2)和最多一个null元素。 正如其名称所暗示的,此接口模拟数学 集 抽象。
该Set接口放置额外的约定,超过从继承Collection接口,所有构造函数的合同,而位于该合同add , equals和hashCode方法。 为方便起见,此处还包括其他继承方法的声明。 (这些声明附带的规范是针对Set接口定制的,但它们不包含任何其他规定。)

对构造函数的额外规定,毫不奇怪,所有构造函数必须创建一个不包含重复元素的集合(如上所定义)。

注意:如果将可变对象用作set元素,则必须非常小心。 如果在对象是集合中的元素时以影响equals比较的方式更改对象的值,则不指定集合的行为。 这种禁令的一个特例是,不允许将一个集合作为一个元素包含在内。

8.1、HashSet

此类实现Set接口,由哈希表(实际上是HashMap实例)支持

package com.java.arrayListDemo;

import java.util.HashSet;
import java.util.Iterator;
import java.util.ListIterator;

public class HashSetDemo {
    
    
    public static void main(String[] args) {
    
    
        //HashSet:散列存放(哈希表在学习HashMap集合时讲解)
        HashSet<String> set = new HashSet<>();
        boolean flag1 = set.add("锄禾日当午");
        set.add("汗滴禾下土");
        set.add("谁知盘中餐");
        set.add("粒粒皆辛苦");
        boolean flag2 = set.add("锄禾日当午");
        System.out.println(flag1);
        System.out.println(flag2);//不能添加重复元素1,所以返回值为添加失败
        Iterator<String> iterator = set.iterator();
       /* while(iterator.hasNext()){
            String str = iterator.next();
            System.out.println(str);
        }*/
        for (String s:set) {
    
    
            System.out.println(s);
        }
    }
}
true
false
汗滴禾下土
谁知盘中餐
锄禾日当午
粒粒皆辛苦

因为HasSet是散列存放的,所以看到输出将结果就是无序的。

8.2、TreeSet与Comparable

SortedSet s = Collections.synchronizedSortedSet(new TreeSet(…)); 此类的iterator方法返回的迭代器是快速失败的:如果在创建迭代器之后的任何时间修改集合,除了通过迭代器自己的remove方法之外,迭代器将抛出ConcurrentModificationException 。 因此,在并发修改的情况下,迭代器快速而干净地失败,而不是在未来的未确定时间冒任意,非确定性行为的风险。

请注意,迭代器的快速失败行为无法得到保证,因为一般来说,在存在不同步的并发修改时,不可能做出任何硬性保证。 失败快速迭代器在尽力而为的基础上抛出ConcurrentModificationException。 因此,编写依赖于此异常的程序以确保其正确性是错误的: 迭代器的快速失败行为应该仅用于检测错误。

此类是Java Collections Framework的成员。

package com.java.arrayListDemo;

import java.util.TreeSet;

public class TreeSetDemo {
    
    
        //TreeSet:
        public static void main(String[] args) {
    
    
        TreeSet<String> data = new TreeSet<>();
        data.add("B");
        data.add("C");
        data.add("A");
        data.add("D");
        for (String s:data) {
    
    
        System.out.println(s);
        }
        }
}
A
B
C
D
这里的有序是按系统顺序排的
package com.java.arrayListDemo;

import java.util.Objects;
import java.util.TreeSet;

public class TreeSetDemo2 {
    
    
    public static void main(String[] args) {
    
    
        TreeSet<Person> data = new TreeSet<>();
        Person p1 = new Person("张三",18);
        Person p2 = new Person("李四",19);
        Person p3 = new Person("王五",19);//一样大的话就存储失败

        data.add(p1);
        data.add(p2);
        data.add(p3);
        for (Person p:data) {
    
    
            System.out.println(p);
        }
    }
}
//实现Comparable接口进行自定义排序
class Person implements Comparable<Person>{
    
    
    private String name;
    private  int age;
    @Override
    public int compareTo(Person o) {
    
    
        //this 与0比较
        //返回的数据:负数this小/零一样大/正数this大
        if(this.age>o.age){
    
    
            return 1 ;
        }else if(this.age==o.age){
    
    
            return 0;
        }
        return -1 ;
    }
    @Override
    public boolean equals(Object o) {
    
    
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age &&
                Objects.equals(name, person.name);
    }

    @Override
    public int hashCode() {
    
    
        return Objects.hash(name, age);
    }

    @Override
    public String toString() {
    
    
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    public Person() {
    
    
    }

    public Person(String name, int age) {
    
    
        this.name = name;
        this.age = age;
    }

    public String getName() {
    
    
        return name;
    }

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

    public int getAge() {
    
    
        return age;
    }
    public void setAge(int age) {
    
    
        this.age = age;
    }
}

9、Map

set集合使用map集合的单值存储,因此只用了map集合键值对中的键来存储数据。
将键映射到值的对象。 MAP不能包含重复的键; 每个键最多可以映射一个值。
此接口取代了Dictionary类,它是一个完全抽象的类而不是接口。
Map:Mapping映射

在这里插入图片描述

哈希桶中的数据量大于8时,从链表转换为红黑二叉树。
当哈希桶中的数据量减少到6时,从红黑二叉树转换为链表。
哈希桶中的数据量为7时,减少到6时,要从红黑二叉树转换为链表吗?
答:要分情况如果,如果存储情况本来就是链表,则不用
初始桶数量16,散列因子0. 75,如果大于散列因子0. 75就会扩容到32,存储十九会变为对32取余运算。

9、Map集合各子类区别分析

HashMap、Hashtable、ConcurrentHashMap的区别在于多线程,线程安全与否

HashMap :线程不安全,效率高。同时操作
Hashtable:线程安全,效率低。队列机制
ConcurrentHashMap:采用分段锁机制,保证线程安全,效率又比较高。操作时锁定集合内部的操作的哈希桶。

散列因子给的越高,存储的数据越多,越节省空间, 查询效率就越低
散列因子给的越低,存储的数据越少,越浪费空间, 查询效率就越高

猜你喜欢

转载自blog.csdn.net/qq_38960155/article/details/108618405