1.Collection接口

下面是Collection接口的大概示意图:
在这里插入图片描述
Collection接口,我们所熟悉的一些容器(集合)接口全部继承自它,比如List,Set,这些相对于我们之前用的数组,要厉害的多,但是这些集合有些底层也是用数组实现的。

​ 数组有下面的一些缺点:

​ ①长度固定

​ ②只能存储一种类型的对象

​ ③查询很快但是增删改很费劲

​ Collection接口规定了一些基本的对集合的操作,也就是说,这些最基本的操作,在之后所有的集合中都适用。在这里插入图片描述
我们这里只看一些常用的方法:
size:返回集合的目前的大小
isEmpty:集合事后为空
contains:集合中是否包含指定元素
add:向集合中添加一个元素
remove:从集合中移除指定元素
containsAll:指定集合是否包含另一集合
addAll:将另一集合添加到指定集合中
removeAll:将指定集合与另一集合的共同元素移除
retainAll:返回两个集合的交集元素
——————————————————————————————————

它有两个子接口:List<>,Set<>

List<>:它有三个实现类,Vector,ArrayList,LinkedList。ArrayList底层实现原理是数组,所以它适合查询,不适合增删改;LinkedList底层是双向链表,适合增删改,不适合查询。它们的线程都不安全;Vector底层是数组,线程安全。List的特点是,加入的元素有顺序,可以重复。

ArrayList:这是最常用的一个实现类,我们比较常用这个,因为日常操作查询较多,增删操作比较少,因为底层是数组实现,所以多了很多方法是根据索引完成的,Collection的方法它都有,特有的有:

​ get(int):获取指定索引的元素

​ set(int,E):将指定索引的元素改变为指定元素

​ indexOf(Object):获取指定字符第一次出现位置

​ lastIndexOf(Object):获取指定字符最后一次出现的位置

​ add(int, E):在指定位置加入指定字符

​ remove(int):移除指定位置的字符

LinkedList:这是一个底层使用链表实现的集合,所以增删改极其方便,查询比较慢,它有许多链表特有的方法
addFirst(E e):在该列表开头插入指定的元素。

addLast(E e):将指定的元素追加到此列表的末尾。

getFirst():返回此列表中的第一个元素。

getLast():返回此列表中的最后一个元素。

offer(E e):将指定的元素添加为此列表的尾部(最后一个元素)。
boolean offerFirst(E e):在此列表的前面插入指定的元素。
boolean offerLast(E e):在该列表的末尾插入指定的元素。
peek():检索但不删除此列表的头(第一个元素)。
peekFirst():检索但不删除此列表的第一个元素,如果此列表为空,则返回 null
peekLast():检索但不删除此列表的最后一个元素,如果此列表为空,则返回 null
poll():检索并删除此列表的头(第一个元素)。
pollFirst():检索并删除此列表的第一个元素,如果此列表为空,则返回 null 。
pollLast():检索并删除此列表的最后一个元素,如果此列表为空,则返回 null
pop():从此列表表示的堆栈中弹出一个元素。
void push(E e):将元素推送到由此列表表示的堆栈上。
remove():检索并删除此列表的头(第一个元素)。

Set<>:Set接口与List接口类似,但是你加入的元素没有顺序,就是你加入1,2,3;但是你打印出来不一定是1,2,3。并且你无法加入重复元素,且只允许加入的一个值为null。它的常用实现类为HashSet<>,TreeSet<>。

HashSet<>:HashSet的底层实现是一个HashMap,我们先看一小段源码

private transient HashMap<E,Object> map;

    // Dummy value to associate with an Object in the backing Map
    private static final Object PRESENT = new Object();

    /**
     * Constructs a new, empty set; the backing {@code HashMap} instance has
     * default initial capacity (16) and load factor (0.75).
     */
    public HashSet() {
        map = new HashMap<>();
    }

很明显它的底层是HashMap,将你想加入的值作为Key,而Value为new Object()。所以,Set中的值是不可重复的

下面我们来看它的常用方法,它的方法基本就和前面的一样了,记住它的无重复特性就好

TreeSet<>:TreeSet的底层是TreeMap实现的,我们都知道之前提到的HashSet是无序的,但是TreeMap默认从小到大给你排好序。

public static void main(String[] args) {
    	Set<Integer> s = new TreeSet<Integer>();
    	s.add(400);
    	s.add(100);
    	s.add(300);
    	s.add(200);
    	s.add(200);
    	System.out.println(s);
    }

运行结果是:
在这里插入图片描述
我们可以看出来,结果是排好序的也是去重的。
那么问题来了,如果我们是一个自定义的对象,比如学生对象,我们想按成绩进行排序,怎么办呢??答案是:实现Comparable接口覆盖compareTo方法。

我们有这样一个需求:比较两个学生的成绩,成绩高的先输出,成绩相同的,id大的先输出。

public class Main{
    public static void main(String[] args) {
    	Set<Student> s = new HashSet<Student>();
    	s.add(new Student(96, 1));
    	s.add(new Student(90, 4));
    	s.add(new Student(90, 2));
    	s.add(new Student(100, 5));
    	s.add(new Student(93, 3));
    	for(Student stu:s) {
    		System.out.println("stu  "+stu.id+"------"+stu.grade);
    	}
    }
}
class Student implements Comparable<Student>{
	int grade;
	
	int id;
	
	public Student(int grade, int id) {
		super();
		this.grade = grade;
		this.id = id;
	}

	@Override
	public int compareTo(Student o) {//1:大于  0:等于  -1:小于
		if(this.grade>o.grade) {
			return 1;
		}else if(this.grade<o.grade){
			return -1;
		}else {
			if(this.id>o.id) {
				return 1;
			}
			return -1;
		}
	}
}

我们可以看到这样的输出结果:
在这里插入图片描述
我们完成了两个对象的比较。HashSet是无序的,如果你一定要集合里元素的顺序与你加入的顺序一样,LinkedHashSet是一个很好的选择。这里我们不讲只演示一下。

 public static void main(String[] args) {
    	Set<Integer> s = new LinkedHashSet<Integer>();
    	s.add(654);
    	s.add(200);
    	s.add(300);
    	System.out.println(s);
    }

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

※2. Map<>接口:Map常用的实现类,HashMap<K, V>,TreeMap<K, V>,LinkedHashMap<K, V>

HashMap<K, V>:底层是哈希表结构,它以键值对的形式存在一个Key对应一个Value,哈希表!!!十分重要!!!!!哈希表的数据结构是“数组+链表”,JDK8以后变为了“数组+链表+红黑树”哈希表将数组与链表的优势完美结合,查询快,增删也快,我们先来看一下它的主要方法

containsKey(Object key):如果此映射包含指定键的映射,则返回 true 。

get(Object key):返回到指定键所映射的值,或 null如果此映射包含该键的映射。

isEmpty():如果此地图不包含键值映射,则返回 true 。

keySet():返回此地图中包含的键的Set视图。

put(K key, V value) :将指定的值与此映射中的指定键相关联。

remove(Object key) :从该地图中删除指定键的映射(如果存在)。

replace(K key, V value) :只有当目标映射到某个值时,才能替换指定键的条目。

但是我们要知道,你加入HashMap的元素是无序的,我们下面来深入了解一下HashMap,在源代码中,有这样一段代码,这是一个Node类,包含了你加入的Key和Value,还有一个hash值以及一个指向下一个节点的指针

这就是Node链表的基本结构
在这里插入图片描述

static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;

        Node(int hash, K key, V value, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
 }

同时还有一个Node类型的数组,初始化长度为16,这个数组每一个节点里面,存的就是上面的链表(图略),只不过链表的长度是多少不确定

transient Node<K,V>[] table;

下面我们来说一说,为什么HashMap无序,对应下图

第一步当你put了一个Key时,会调用HashMap的hashcode方法得到一个哈希码,这时候通过hash函数计算得到一个值,这个hash()是什么呢,一般的常用的就是 hashcode%数组长度,这样我们得到的值一定是可以作为数组索引的值,计算出来是多少,就在对应数组索引处加入该对象,如果哈希函数算出来的值一样,那么就让上一个对象的next指针指向这个对象,于是就形成了HashMap的结构,数组里存储着链表,这就是为什么HashMap无序,因为你永远不知道hashcode算出来是多少,哈希函数计算出来的数组索引是多少,所以它的顺序不一定和你加入的顺序一样,如果要求一样,可以使用LinkedHashMap<>。JDK8以后,在每个数组中的链表长度超多8时,数据结构会由链表变为红黑树,也是提高查询性能的一种方法。
在这里插入图片描述
TreeMap<>: 就是你加入的Key输出时会为你从小到大排好序,如果是自定义对象依然要实现Comparable接口覆盖compareTo方法。才能实现对比自定义对象大小的功能。

如果一定要你加入的Key的顺序与你的输出顺序一致,可以使用LinkedHashMap。这里不做详解。

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐