Java集合整理1——深入理解Collection、List、Set

本文介绍了Java中的常用数据结构,如栈、队列、数组、链表(单向/双向/循环)、二叉树及其子类,以及Set和List接口及其实现类(如ArrayList、Vector、LinkedList)。重点讲解了这些数据结构的特点和适用场景,以及如何根据业务需求选择合适的类。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

常用集合

Java 中为了方便用户操作各个数据结构, 所以引入了类集的概念,有时候就可以把类集称为 java 对数据结构的实现。首先先简单介绍有哪些常用的数据结构:

常用的数据结构

栈(Stack)

栈(stac),又称堆栈, 栈(stack)是限定仅在表尾进行插入和删除操作的线性表。我们把允许插 入和删除的一端称为栈顶,另一端称为栈底,不含任何数据元素的栈称为空栈。栈又称为先进后出 的线性表 。

  • 先进后出:先进入栈的元素后取出。进栈存元素又叫压栈(push),取出元素又叫弹栈(pop)。
  • 栈的入口、出口的都是栈的顶端位置。

栈数据结构

队列(queue)

队列(queue),简称队, 队列是一种特殊的线性表,是运算受到限制的一种线性表,只允许在表的 一端进行插入,而在另一端进行删除元素的线性表。队尾(rear)是允许插入的一端。队头(front)是 允许删除的一端。空队列是不含元素的空表

  • 先进先出:先进入队列存储的元素先取出。
  • 队列的入口、出口各占一侧。
    队列数据结构
数组(array)

数组(Array)是有序的存放相同元素的元素序列,数组是在内存中开辟一段连续的空间,并在此空间存放元素。可以通过数组下标进行数据的存储和取出。

  • 查找元素快:数组通过索引(下标)可以快速访问指定元素。
  • 增删元素慢:指定索引增加或删除元素,需要创建一个新的数组,在指定位置增加元素或删除元素,把原数组根据索引复制到型数组对于索引的位置。

数组数据结构

链表(linked list)

链表(linked list),由一系列结点node(链表中每一个元素称为结点)组成,结点可以在运行时i动 态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的 指针域。我们常说的链表结构有单向链表与双向链表。

  • 增删元素快
  • 查找元素慢
单链表

单链表 (Linked List):由各个内存结构通过一个 Next 指针链接在一起组成,每一个内存结构都存在后继内存结构(链尾除外),内存结构由数据域和 Next 指针域组成。

链表数据结构

单向数据结构用代码表示如下:
    
public class Node {
	Object data; //存储的数据
	Node next; //存储的下一个节点的地址
}
双向链表

双向链表 [Double Linked List]:由各个内存结构通过指针 Next 和指针 Prev 链接在一 起组成,每一个内存结构都存在前驱内存结构和后继内存结构(链头没有前驱,链尾没有后 继),内存结构由数据域、Prev 指针域和 Next 指针域组成。

双向链表数据结构

链表数据结构用代码实现如下:
    
public class Node {
    Node pev; //存储的下一个节点的地址
	Object data; //存储的数据
	Node next; //存储的下一个节点的地址
}
循环链表

循环链表 单向循环链表 [Circular Linked List] : 由各个内存结构通过一个指针 Next 链接在一起 组成,每一个内存结构都存在后继内存结构,内存结构由数据域和 Next 指针域组成。

双向循环链表 [Double Circular Linked List] : 由各个内存结构通过指针 Next 和指针 Prev 链接在一起组成,每一个内存结构都存在前驱内存结构和后继内存结构,内存结构由 数据域、Prev 指针域和 Next 指针域组成。
循环链表数据结构

二叉树(tree)

二叉树:binary tree ,是每个结点不超过2的有序树(tree) 。二叉树是每个节点最多有两个子树的树结构。顶上的叫根结点,两边被称作“左子树”和“右子树”。
二叉树数据结构
二叉树又有平衡二叉树、红黑树等特殊的数据结构,在这里不做详细说明。

二叉树数据结构用代码实现:
class Node2{
	Object data; //存放数据
	Node2 leftNode; //存放左节点
	Node2 rightNode; //存放右节点
}

选择合适的数据结构进行数据存储能大大提升效率,为便于使用数据结构,java实现不同的数据结构,并引入类集 的概念,结构如下图。
类集结构

Collection

Collection 接口是在整个 Java 类集中保存单值最大操作父接口,里面每次操作的时候都只能保存一个对象的数据。此接口定义:

public interface Collection<E> extends Iterable<E> // 引入的范型,便于类集的更安全操作

定义的方法如下:

方法描述
``````int size();返回集合中元素的数量
boolean isEmpty();如果该集合不包含元素则返回true
boolean contains(Object o);如果集合包含指定的元素,返回true
Iterator iterator();返回集合的迭代器Iterator
Object[] toArray();将集合转换为数组,包含集合所有元素
T[] toArray(T[] a);将集合转换为指定类型的数组,包含集合所有元素
boolean add(E e);集合添加元素,添加成功返回true
boolean remove(Object o);从集合移除指定元素,成功返回true
boolean containsAll(Collection<?> c);如果集合包含指定集合全部元素则返回true
boolean addAll(Collection<? extends E> c);将指定集合中的所有元素添加到此集合,成功返回true
boolean removeAll(Collection<?> c);移除指定集合中的所有元素,成功返回true
boolean retainAll(Collection<?> c);仅保留指定集合中的所有元素,移除其他元素,成功返回true
void clear();清空集合
boolean equals(Object o);比较指定对象与此集合是否相等,相等返回true
int hashCode();返回集合的哈希值

在编写程序时一般不会直接使用Collection,而是使用其子接口:List和Set

List接口继承了Colelction
public interface List<E> extends Collection<E>
    
 Set接口继承了Collection
public interface Set<E> extends Collection<E>
List

List接口是Colletion接口的子类,继承了Collection所有方法,也根据自身特点(元素有序,元素可重复),也有一些特有的方法:

方法描述
boolean addAll(int index, Collection<? extends E> c);在指定位置插入指定集合的所有元素
void add(int index, E element);在指定位置插入元素
E get(int index);获取指定索引的元素
E set(int index, E element);将列表中指定位置的元素替换为指定的元素,返回被替换的元素
E remove(int index);删除指定索引的元素,返回被删除的元素
int indexOf(Object o);返回元素第一次出现的索引位置。不存在返回-1;
int lastIndexOf(Object o);返回元素最后一次出现的索引位置。不存在返回-1;
ListIterator listIterator();返回ListIterator实例
ListIterator listIterator(int index);从指定索引位置返回ListIterator实例
List subList(int fromIndex, int toIndex);返回指定返回的子集合(包含起始索引,不包含终点索引)

List为接口不能直接使用,需使用其实现类,常用实现类有:ArrayList、Vector、LinkedList,其中:ArrayList和Vector是数组实现,查找快,增删慢;LinkedList是链表实现,查找慢,增删快。需根据实际业务数据需求选择合适类。

ArrayList

根据源码,ArralList继承了 AbstractList 类。AbstractList 是 List 接口的子类。AbstractList 是个抽象类,适配器设计模式。暂不对设计模式进行说明。设计模式学习地址:Java设计模式:23种设计模式全面解析

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
构造方法描述
ArrayList();构造一个初始长度为10的空列表
ArrayList(int initialCapacity) ;构造一个指定大小的空列表
ArrayList(Collection<? extends E> c);构造一个包含指定集合元素的列表

这里有个问题,根据ArrayList无参构造源码,最初构造的ArrayList是一个空的数组,长度为0,为什么API描述是构造一个长度为10的空列表呢?因为构造的ArrayList第一次进行进行添加元素时进行了扩容,变成了默认长度10,结合源码理解。

例:
public class ArrayListTest {
	public static void main(String[] args) {
		ArrayList<Integer> arrayList = new ArrayList<>();  //初始容量为0
		arrayList.add(10); //第一次添加元素进行了扩容,变成了10
	}
}
初始源码:  
    
transient Object[] elementData;  
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;  
    }

源码解读:
    ArrayList底层是一个数组,初始构造的是一个为空的数组,容量为0
-------------------------------------------------------------------------------------
 public boolean add(E e) {
        modCount++;
        add(e, elementData, size);
        return true;
    }
源码解读:
    添加元素,返回true-------------------------------------------------------------------------------------
      private void add(E e, Object[] elementData, int s) {
        if (s == elementData.length)
            elementData = grow();
        elementData[s] = e;
        size = s + 1;
    }
源码解读:
    添加元素的算法:先进行容量判断,s == elementData.length ?  elementData = grow(): ;
	列表存满(底层数组存满)时需进行扩容(对于初始创建的列表,底层的数组长度为0,也需要进行扩容)。否则正常储存,size加1。(size是已储存元素的个数)
-------------------------------------------------------------------------------------
   private Object[] grow() {
        return grow(size + 1);
    }
  
  private Object[] grow(int minCapacity) {
        return elementData = Arrays.copyOf(elementData,
                                           newCapacity(minCapacity));
    }

 源码解读:
     所需最小的长度为当前长度size+1,用newCapacity(minCapacity)方法计算新的长度。调用Arrays.copyOf(elementData,newCapacity(minCapacity))进行底层数组扩容。
 ------------------------------------------------------------------------------------ 	
     private static final int DEFAULT_CAPACITY = 10;

     private int newCapacity(int minCapacity) {
      // overflow-conscious code
      int oldCapacity = elementData.length;
      int newCapacity = oldCapacity + (oldCapacity >> 1);
      if (newCapacity - minCapacity <= 0) {
          if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
                return Math.max(DEFAULT_CAPACITY, minCapacity);
          if (minCapacity < 0) // overflow
                throw new OutOfMemoryError();
          return minCapacity;
        }
      return (newCapacity - MAX_ARRAY_SIZE <= 0) ?
          								newCapacity : 
     									hugeCapacity(minCapacity);
    }
源码解读:
    oldCapacity为列表原来的长度,newCapacity为原来列表长度的 1.5 倍
    如果新长度小于等于就长度,判断原来的数组是否为0(是否为新创建),是再返回DEFAULT_CAPACITY=10
和minCapacity=1的最大值即是DEFAULT_CAPACITY=10。
    不为0则判断是否容量是否溢出。

应用举例:

public class ArrayListDemo02 {
	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接口继承而来
		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

Vector也是实现了List接口,用法与ArrayList基本一样

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

应用举例:

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接口单独定义的
		}
	}
}

ArrayList与Vector的区别:ArrayList是线程不安全的,Vector是线程安全的。

LinkedList

LinkedList底层是双向链表的实现,内部实现了双向链表结构:

   private static class Node<E> {
        E item;
        Node<E> next;
        Node<E> prev;

        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }

因此出来实现了List的方法外,还有大量操作收尾元素的方法,可用于实现列表、栈等数据结构。

方法描述
public E getFirst();返回列表中的第一个元素,如果为null,抛出异常NoSuchElementException()
public E getLast();返回列表中的最后一个元素,如果为null,抛出异常NoSuchElementException()
public E removeFirst();移除并返回列表中的第一个元素,如果为null,抛出异常NoSuchElementException()
public E removeLast();移除并返回列表中的最后一个元素,如果为null,抛出异常NoSuchElementException()
public void addFirst(E e)在列表的开头插入指定的元素
public void addLast(E e)将指定的元素追加到列表的末尾
public E peek();返回列表中的第一个元素,如果为null,返回null
public E element();返回列表中的第一个元素,如果为null,抛出异常NoSuchElementException()
public E poll();移除并返回列表中的第一个元素,如果为null,返回null
public E remove();移除并返回列表中的第一个元素,如果为null,抛出异常NoSuchElementException()
public boolean offer(E e);添加指定的元素作为这个列表的尾部(最后一个元素),返回true
public boolean offerFirst(E e);在列表的前面插入指定的元素,返回true
public boolean offerLast(E e);在列表的末尾插入指定的元素,发回true
public E peekFirst();返回列表中的第一个元素,如果为null,返回null
public E peekLast();返回列表中的最后一个元素,如果为null,返回null
public E pollFirst();移除并返回列表中的第一个元素,如果为null,返回null
public E pollLast();移除并返回列表中的最后一个元素,如果为null,返回null
public void push(E e);将元素插入到列表的最前面
public E pop() ;移除并返回列表中的第一个元素,如果为null,抛出异常NoSuchElementException()
Set

Set 接口也是 Collection 的子接口,与 List 接口最大的不同在于,Set 接口里面的内容是不允许重复的。 Set 接口并没有对 Collection 接口进行扩充,基本上还是与 Collection 接口保持一致。

Set为接口,常使用其实现类HashSet、TreeSet、LinkedHashSet(为HashSet的子类)。

HashSet

HashSet属于散列的存放类集,里面的内容是无序存放的。是底层是哈希表(HashMap)实现。

  public HashSet() {
        map = new HashMap<>();
    }

HashSet不能存储重复元素,是否为重复元素:先比较hashcoed,hashcode不一样则是不重复;hashcode一样,则进行equals比较内容,equals不一样,则不重复;一样则重复。

add方法源码: 
public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

   private static final Object PRESENT = new Object();

源码解读:调用map.put进行添加,添加的元素e为map的key,而PRESENT为默认创建的对象。

HashSet是无序存放的,因此也没有索引,那如何进行遍历呢?

**方法1:**调用toArray方法转换成数组,在对数组进行循环遍历

**方法2:**使用迭代器进行遍历

**方法3:**使用增强for循环

从结果来看HashSet也能说明无序存储)

public class HashSetTest {
	public static void main(String[] args) {
		HashSet<Character> hashSet = new HashSet<>();
		hashSet.add('a');
		hashSet.add('v');
		hashSet.add('b');
		hashSet.add('d');
		hashSet.add('e');
		//法1:转换成数组进行遍历
		Object[] nums = hashSet.toArray();
		for (int i = 0; i < nums.length; i++) {
			System.out.println(nums[i]);
		}
		System.out.println("----------------------分割线-----------------------");
		//法2:使用迭代器进行遍历
		Iterator<Character> iterator = hashSet.iterator();
		while (iterator.hasNext()) {
			System.out.println(iterator.next());
		}
		System.out.println("----------------------分割线-----------------------");
		//法3:使用增强for循环
		for (Character character : hashSet) {
			System.out.println(character);
		}
	}
}

结果

LinkedHashSet

LinkedHashSet是HashSet的子类,其内部实现是LinkedHashMap,是哈希表和链表实现,具有可预测的迭代顺序。

public class LinkedHashSet<E>
    extends HashSet<E>
    implements Set<E>, Cloneable, java.io.Serializable {

将以上案例用LinkedHashSet实现,可以看到遍历打印输出的结果和添加的顺序保持一致。这就是具有可预测的迭代顺序。

public class HashSetTest {
	public static void main(String[] args) {
		HashSet<Character> hashSet = new LinkedHashSet<>();
		hashSet.add('a');
		hashSet.add('v');
		hashSet.add('b');
		hashSet.add('d');
		hashSet.add('e');
		//法1:转换成数组进行遍历
		Object[] nums = hashSet.toArray();
		for (int i = 0; i < nums.length; i++) {
			System.out.println(nums[i]);
		}
		System.out.println("----------------------分割线-----------------------");
		//法2:使用迭代器进行遍历
		Iterator<Character> iterator = hashSet.iterator();
		while (iterator.hasNext()) {
			System.out.println(iterator.next());
		}
		System.out.println("----------------------分割线-----------------------");
		//法3:使用增强for循环
		for (Character character : hashSet) {
			System.out.println(character);
		}
	}
}

结果

TreeSet

TreeSet存储的内容是具有自然顺序的,对于存储自定义的对象是,需实现Comparable接口。TreeSet底层是实现TreeMap。

 TreeSet(NavigableMap<E,Object> m) {
        this.m = m;

public TreeSet() {
        this(new TreeMap<>());
    }
源码解读:
 class TreeMap<K,V> implements NavigableMap<K,V>
 interface NavigableMap<K,V> extends SortedMap<K,V>
 
SortedMap<K,V>接口中定义了Comparator<? super E> comparator()方法,保证了存储数据是有顺序的。

对于具有自然顺序的数字,字母等对象,可以直接存储;对于自定义的对象如Person,需实现Comparable接口,定义对象的比较规则。

**Comparable接口与Comparator接口比较:

Comparable:强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序,类的 compareTo方法被称为它的自然比较方法。只能在类中实现compareTo()一次,不能经常修改类的代码 实现自己想要的排序。实现此接口的对象列表(和数组)可以通过Collections.sort(和Arrays.sort)进 行自动排序,对象可以用作有序映射中的键或有序集合中的元素,无需指定比较器。

Comparator:强行对某个对象进行整体排序。可以将Comparator 传递给sort方法(如Collections.sort 或 Arrays.sort),从而允许在排序顺序上实现精确控制。还可以使用Comparator来控制某些数据结构 (如有序set或有序映射)的顺序,或者为那些没有自然顺序的对象collection提供排序。

将以上案例用TreeSet实现,通过输出可以看到是具有自然顺序的。

public class HashSetTest {
	public static void main(String[] args) {
		TreeSet<Character> treeSet = new TreeSet<>();
		treeSet.add('a');
		treeSet.add('v');
		treeSet.add('b');
		treeSet.add('d');
		treeSet.add('e');
		//法1:转换成数组进行遍历
		Object[] nums = treeSet.toArray();
		for (int i = 0; i < nums.length; i++) {
			System.out.println(nums[i]);
		}
		System.out.println("--------------------------分割线--------------------------");
		//法2:使用迭代器进行遍历
		Iterator<Character> iterator = treeSet.iterator();
		while (iterator.hasNext()) {
			System.out.println(iterator.next());
		}
		System.out.println("--------------------------分割线--------------------------");
		//法3:使用增强for循环
		for (Character character : treeSet) {
			System.out.println(character);
		}
	}
}

结果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值