一、集合的概述

  • 集合、数组都是用来存储、获取、操作数据的容器,此时的存储主要指的是内存层面的存储,不涉及持久化存储
  • 集合和数组的对比:
  • 数组:长度不可变;没有提供查看有效元素个数的方法
  • 集合:长度可以改变;可以存储任意类型的对象;只能存储对象(不能存储基本数据类型)

集合的分类:

java中集合为什么说是可以存储多种类型 集合只能存储对象吗_List

  • collection接口:单列数据,定义了存储一组对象的方法的集合
  • List:有序,可重复的集合,又称“动态”数组
  • Set:无序,不可重复的集合,类似高中讲的集合(无序、确定、互异性)
  • Map接口:双列数据,保存具有映射关系“k-v健值对”的集合,类似于高中的函数(y=f(x),支持多对一,不支持一对多)

二、Collection接口

Collection是List、Set、Queue接口的父接口,该接口中定义的方法既可以操作set集合,也可以操作List、Queue集合,常用的方法:

作用

方法


add(Object o)

addAll(Collection coll)


boolean remove(Object obj):通过equals判断是否是要删除的那个元素。只会删除找到的第一个元素

boolean removeAll(Collection coll):取当前集合的差集

判断是否包含某个元素

boolean contains(Object obj):通过equals方法判断

boolean containsAll(Collection c):通过equals方法,拿两个集合的元素挨个比较

判断是否为空集合

isEmpty()

获取有效元素的个数

int size()

清空集合

clear()

取两个集合的交集

boolean retainAll(Collection c):把交集的结果存在当前集合中,不影响c

集合—>数组

Object[] toArray()

数组—>集合:Arrays.asList(123,456)

集合是否相等

boolean equals(Object obj)

获取集合对象的哈希值

hashCode()

遍历

iterator():返回迭代器(设计模式的一种),用于集合遍历

Iterator迭代器:
主要方法:hastNext(),next()

ArrayList<Object> list = new ArrayList<>();
Iterator<Object> iterator = list.iterator();
//hasNext():判断是否还有下一个元素
while(iterator.hasNext()){
	//next():指针下移,将下移以后集合位置上的元素返回
    System.out.println(iterator.next());
}

1 、Collection之List

List:单列数据,有序、可以重复的,用来替换数组,又称“动态”数组

List中的常用方法:
list除了从Collection继承的方法外,还添加了一些根据索引来操作集合元素的方法

作用

方法


add(int index,Object o):在index位置插入元素

addAll(int index,Collection coll):从index位置开始将coll里的元素插入


E remove(int index):移除index位置的元素,并返回此元素


Object set(int index, Object ele):将index位置的元素改为ele


Object get(int index):获取指定index位置的元素

查2

int indexOf(Object obj):返回obj在集合中首次出现的位置

int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置

截取

List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的子集合

  • 实现类一:ArrayList
    List的主要实现类,线程不安全的,效率高。底层使用Object[] elementData(初始容量为10的数组),扩容时,扩大1.5倍
  • 实现类二:LinkedList
    底层使用双向链表,内部没有声明数组,而是定义了Node类型的first和last, 用于记录首末元素,对于频繁插入、删除的操作,效率比ArrayList高
    Node结构:
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;
    }
}

新增的方法:

作用

方法


void addFirst(Object obj)

void addLast(Object obj)


Object removeFirst()

Object removeLast()


Object getFirst()

Object getLast()

  • 实现类三:Vector
    List的古老实现类,线程安全(方法中加synchronized),效率低,底层使用Object[] elementData。扩容时扩大2倍
    新增的方法:

作用

方法


void addElement(Object obj)

void insertElementAt(Object obj,int index)


void removeElement(Object obj)

void removeAllElements()


void setElementAt(Object obj,int index)

常见面试题:
1、ArrayList和LinkedList的异同?
同:ArrayList和LinkedList都继承了List用来存储单列数据,二者都是线程不安全的,效率高
异:ArrayList底层是动态数组,当容量不够时,需要扩容。对随机访问List(get和set操作)效率高,因为LinkedList要移动指针从前往后依次查找。
LinkedList底层是双向链表,不存在扩容,对于新增和删除(add和remove操作)效率高,因为ArrayList需要移动数据

2、ArrayList和Vector的异同
同:底层数据结构都是数组
异:ArrayList是线程不安全的,效率高;扩容时,扩大为原来的1.5倍
Vector因为使用了synchronized,是线程安全的;效率低,扩容时,扩大为原来的2倍。

3、谈谈你的理解?ArrayList底层 是什么?扩容机制?

2、Collection之Set

set接口是collection的子接口,set接口没有提供额外的方法,使用的都是Collection定义的方法
特点是:无序、不可重复,类似于高中讲的集合(确定性、互异性、无序性)

无序性:并不等于随机性,指的是添加数据时数组在底层存储不是按照索引的位置依次存储,而是根据hash值经过一系列的算法决定存储的位置
不可重复性:先通过hashCode()方法看存储位置是否有元素,如果有再通过equals()比较,如果没有元素则直接添加,所以重写equals()方法的同时需要重写hashCode()保证相同的对象具有相同的散列码

  • Set的实现类一:HashSet
    线程不安全的,可以存null健null值。底层是数组+链表的结构

HashSet的添加过程:
当我们向HashSet中添加元素a,先通过a所在的类的hashCode方法计算元素a的哈希值,然后哈希值通过一些列算法得到元素a在HashSet中存储位置,判断数组此位置上是否已经存在元素,如果此位置上没有其他元素,则元素a直接添加成功,如果此位置上有其他元素b(或者以链表形式存在多个元素)则比较元素a与元素b的哈希值,如果哈希值不相同,则元素a以链表形式添加成功,如果哈希值相同,则调用元素a所在的类的equals方法,返回true则添加失败,返回false则以链表形式添加成功(链表:七上八下)

  • HashSet的实现类二:LinkedHashSet
    LinkedHashSet是HashSet的子类,它在HashSet的数据结构上使用链表来维护添加的顺序,所以遍历时可以按照添加的顺序遍历。这样设计的目的是对于频繁的遍历操作LinkedHashSet的效率要高于HashSet。
  • Set的实现类三:TreeSet
    底层使用红黑树存储(二叉树的一种),要求放入TreeSet类中的数据是同一个类的对象,可以按照对象的某些属性进行排序

排序方式:自然排序(实现Comparable接口)和定制排序(Comparator)
判断对象是否是否相同:自然排序中是使用compareTo()是否返回0,定制排序中使用compare()方法,都不再使用equals方法

小葵花妈妈开课了:
问题一:为什么重写equals方法时需要重写hashCode方法?
自定义的类如果没有重写hashCode方法,则使用的是Object类的hashCode方法,Object中的hashCodeI方法使用的是本地类库,相当于每次new一个对象,就随机的分配一个哈希值,所以如果一个类有自己特有的逻辑相等的概念(即重写equals方法),需要重写hashCode方法。

问题二:使用idea等编译工具重写的hashCode方法中为什么要乘31
例如:自定义User类,里面有name和age属性,重写的hashCode方法如下:

public int hashCode() {
    int result = name != null ? name.hashCode() : 0;
    result = 31*result + age;
    return result;
}

首先,我们认为当User的name和age相同时,就是同一个对象,即哈希值相同。用简单的思路可以将hashCode方法写为hash=name.hashCode()+age,但如果name.hashCode()=20,age=12和name.hashCode()=12,age=20的两个对象其实并不是相同的对象,但他们的哈希值相同,就会存在哈希冲突的情况,冲突之后,就会接着通过equals方法比较,返回false时就会以链表的形式添加,虽然也能存进去,但我们希望他存在数组中
第二步,就考虑到将某个数乘以一个系数放大,那么这个系数就要尽量大,因为计算出来的哈希值越大,所谓的 “冲突”就越少,查找起来效率也会提高。(减少冲突)。但也不能太大,太大可能造成内存溢出。为了效率考虑,我们首先考虑到2的幂次方且为一个素数,31=(2<<5)-1

三、Map接口

双列数据,保存具有映射关系“k-v健值对”的集合,类似于高中的函数(y=f(x),支持多对一,不支持一对多)

  • HashMap
    作为Map的主要实现类,线程不安全的,效率高,可以存储键值对为null的key和value
    LinkedHashMap在HashMap的基础上,底层添加了一对指针指向前一个元素和后一个元素,保证了在遍历时,可以按照添加的顺序遍历。
  • HashTable
    作为Map的古老实现类,线程安全,效率低,不能存储null的key和value。
    Properties用来处理配置文件,key和value都是String类型
  • TreeMap
    底层使用红黑树,按照添加的key-value对其排序,实现排序遍历。key的自然排序和定制排序

面试题:
1、HashMap的底层原理?

2、HashMap 和 Hashtable的异同

3、CurrentHashMap 和 Hashtable的异同