详谈集合框架
集合概述
集合:集合相当于是一种用来存储数据的容器
集合和数组的区别:
1.集合的长度可以改变,数组的长度是固定的
2.数组只能存储同一种类型的元素,集合可以存储的是对象,所以基本数据类型要转换为包装类
这张图简直就是核心 一定要记在脑子里
Collection接口的常用方法
Collection接口是所有单列集合的父接口
public boolean add(E e)
: 把给定的对象添加到当前集合中 。public void clear()
:清空集合中所有的元素。public boolean remove(E e)
: 把给定的对象在当前集合中删除。public boolean contains(E e)
: 判断当前集合中是否包含给定的对象。public boolean isEmpty()
: 判断当前集合是否为空。public int size()
: 返回集合中元素的个数。public Object[] toArray()
: 把集合中的元素,存储到数组中。
List接口介绍
有三个实现类:ArrayList,Vector,LinkedList
list接口的特点:
1.是一个元素存取有序的集合
2.是一个有索引的集合
3.集合中可以有重复元素
List作为Collection接口的子接口增加了一些方法
List作为Collection集合的子接口,不但继承了Collection接口中的全部方法,而且还增加了一些根据元素索引来操作集合的特有方法,如下:
public void add(int index, E element)
: 将指定的元素,添加到该集合中的指定位置上。public E get(int index)
:返回集合中指定位置的元素。public E remove(int index)
: 移除列表中指定位置的元素, 返回的是被移除的元素。public E set(int index, E element)
:用指定元素替换集合中指定位置的元素,返回值的更新前的元素。
List的子类
ArrayList
ArrayList是最常用的List实现类,内部是通过数组实现的,允许对元素进行快速随机访问,数组的缺点就是每个元素不能出现间隔,当数组大小不满足时需要添加增加存储能力,就要将已经有数组的数据复制到新的存储空间中,当从ArrayList的中间位置插入或者元素时,需要对数组进行复制,移动,代价比较高,因此适合查找不适合插入和删除
LinkedList
LinkedList是用链表结构存储数据的,很适合数据的动态插入和删除,随机访问和遍历速度比较慢 ,另外,还提供List接口没有定义的方法,专门用于操作表头和表尾元素,可以当做堆栈,队列,双向队列使用
-
public void addFirst(E e)
:将指定元素插入此列表的开头。 -
public void addLast(E e)
:将指定元素添加到此列表的结尾。 -
public E getFirst()
:返回此列表的第一个元素。 -
public E getLast()
:返回此列表的最后一个元素。 -
public E removeFirst()
:移除并返回此列表的第一个元素。 -
public E removeLast()
:移除并返回此列表的最后一个元素。 -
public E pop()
:从此列表所表示的堆栈处弹出一个元素。 -
public void push(E e)
:将元素推入此列表所表示的堆栈。 -
public boolean isEmpty()
:如果列表不包含元素,则返回true。public class Demo02LinkedList { public static void main(String[] args) { show03(); } private static void show03() { // 创建linkedList对象 LinkedList<String> linked = new LinkedList<>(); linked.add("a"); linked.add("b"); linked.add("c"); System.out.println(linked); // String first = linked.removeFirst(); //`public E pop()`:从此列表所表示的堆栈处弹出一个元素。 String pop = linked.pop(); String last = linked.removeLast(); System.out.println(linked); } private static void show02() { // 创建linkedList对象 LinkedList<String> linked = new LinkedList<>(); linked.add("a"); linked.add("b"); linked.add("c"); // linked.clear(); // NoSuchElementException if (!linked.isEmpty()){ //`public E getFirst()`:返回此列表的第一个元素。 // `public E getLast()`:返回此列表的最后一个元素。 System.out.println(linked.getFirst()); System.out.println(linked.getLast()); } } private static void show01() { // 创建linkedList对象 LinkedList<String> linked = new LinkedList<>(); linked.add("a"); linked.add("b"); linked.add("c"); System.out.println(linked); // public void addFirst(E e)`:将指定元素插入此列表的开头。 // linked.addFirst("www"); // System.out.println(linked); // `public void push(E e)`:将元素推入此列表所表示的堆栈。 linked.push("www"); System.out.println(linked);// [www, a, b, c] // `public void addLast(E e)`:将指定元素添加到此列表的结尾。 linked.addLast("com"); System.out.println(linked); // } }
Vector
Vector和ArrayList一样,也是通过数组实现的,不同的是它支持线程的同步,即某一时刻只有一个线程能够访问Vector,避免多线程同时写而引起的不一致性,但实现同步需要很高的花费,因此,访问它比ArrayList慢。
Set的子类
与List一样同样是继承了Collection接口,与Collection接口中的方法基本一致,知识比Collection接口更加严格了,与List接口不同,Set接口中元素无序,并且不重复
对象的相等性本质是对象hashCode值(java根据对象的内存地址计算出的此序号)判断的,如果想要让两个不同的对象视为相等,就必须覆盖Object的hashCode方法和equals方法
HashSet
底层是Hash表,哈希表存放的是哈希值,在JDK1.8之前,哈希表底层采用数组+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里。但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低。而JDK1.8中,哈希表存储采用数组+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。HashSet存储元素的顺序并不是按照存入时的顺序,而是按照哈希值来存的所以取数据也是按照哈希值取得的,元素的哈希值是通过元素的hashCode方法来获取的,HashSet首先判断两个元素的哈希值,如果哈希值一样,接着就会比较equals方法如果equals结果为true,HashSet就视为同一个元素,如果equals为false就不是同一个元素
哈希值相同equals为false的元素存储是在同样的哈希值下顺延(可以认为哈希值相同的元素放在一个哈希桶中),哈希值一样的存一列。
HashSet通过hashCode值来确定元素在内存中的位置,一个hashCode位置上可以存放多个元素
public class Person extends Object {
// 重写hashCode
@Override
public int hashCode() {
return 1;
}
}
import java.util.Objects;
public class Demo03Person {
private String name;
private int 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;
}
public Demo03Person(String name, int age) {
this.name = name;
this.age = age;
}
public Demo03Person() {
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Demo03Person that = (Demo03Person) o;
return age == that.age &&
Objects.equals(name, that.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public String toString() {
return "Demo03Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
TreeSet
TreeSet是使用二叉树的原理对新add()的对象按照指定的顺序排序(升序,降序),每增加一个对象都会进行排序,将对象插入的二叉树指定的位置
Integer和String对象都可以进行默认的TreeSet排序,而自定义类的对象是不可以的,自定义的类必须实现Comparable接口,并且覆写相应的compareTo()函数,才可以正常使用
在覆写compare()函数时,要返回相应的值才能使TreeSet按照一定的顺序来排序
比较此对象与指定对象的顺序,如果该对象小于,等于或大于指定对象,则分别返回负整数,零或者正整数。
LinkedHashSet
如果想让元素放进去保证是有序的可以用,它继承了HashSet,又基LinkedHashMap实现的,LinkedHashSet底层使用LinkedHashMap来保存所有元素,它继承HashSet
其所有的方法操作上又与HashSet相同,因此非常简单,直接调用父类HashSet的方法即可
/**
* @author:chenliuyiding
* @date 2020/6/29 13: 56:
* @description
* 利用Set去重
* 将整形Integer[] nums = { 33, 20, 20, 33, 6, 8, 8, 7, 11, 12, 12 };数组去重
**/
public class QuChong {
public static void main(String[] args) {
Integer[] nums = { 33, 20, 20, 33, 6, 8, 8, 7, 11, 12, 12 };
List<Integer> list = Arrays.asList(nums);
Set<Integer> set = new HashSet<>(list);
List<Integer> list2 = new ArrayList<>(set);
list2.stream().forEach(s -> System.out.println(s));
System.out.println(list2);
}
}
Map
也是非常重要的一张图片
Map中的集合,元素是成对存在的,每个元素由键与值两部分组成,通过键可以找出所对应的值
Map接口中的常用方法
Map接口中定义了很多方法,常用的如下:
public V put(K key, V value)
: 把指定的键与指定的值添加到Map集合中。public V remove(Object key)
: 把指定的键 所对应的键值对元素 在Map集合中删除,返回被删除元素的值。public V get(Object key)
根据指定的键,在Map集合中获取对应的值。boolean containsKey(Object key)
判断集合中是否包含指定的键。public Set<K> keySet()
: 获取Map集合中所有的键,存储到Set集合中。public Set<Map.Entry<K,V>> entrySet()
: 获取到Map集合中所有的键值对对象的集合(Set集合)。
HashMap
1.8以后底层为数组+链表+红黑树,HashMap根据键的hashCode值存储数据,大多数情况下可以直接定位到它的值因而具有很快的访问速度,但遍历顺序是不确定的,HashMap最多只允许一条记录的键位null,值可以多为null,如果需要满足线程安全,可以用Collections的sychronizedMap方法是HashMap具有线程安全的能力,或者使CurrentHashMap
HashMap因为是面试必考要知道源码与底层所以我会出一篇文章专门讲解自己的看法
ConcurrentHashMap
ConcurrentHashMap和HashMap思路差不多,但是因为它支持并发操作,所以稍微复杂一点,整个ConcurrentHashMap是由一个个Segment(代表部分)组成,简单理解ConcurrentHashMap是一个Segment数组,Segment通过继承ReentrantLock来进行加锁,所以每次每次需要加锁的操作锁住的是一个Segment,这样只要保证每个Segment是线程安全的,也就实现了全局的线程安全
HashTable
HashTable是一个遗留类,很多映射功能与HashMap相似,不同的是它继承Dictionary类,并且线程是安全的,并发性不如CurrentHashMap,不建议使用
TreeMap
TreeMap实现SortedMap接口,能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器。在使用 TreeMap 时,key 必须实现 Comparable 接口或者在构造 TreeMap 传入自定义的Comparator,否则会在运行时抛出 java.lang.ClassCastException 类型的异常。
LinkedHashMap
shTable是一个遗留类,很多映射功能与HashMap相似,不同的是它继承Dictionary类,并且线程是安全的,并发性不如CurrentHashMap,不建议使用
TreeMap
TreeMap实现SortedMap接口,能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器。在使用 TreeMap 时,key 必须实现 Comparable 接口或者在构造 TreeMap 传入自定义的Comparator,否则会在运行时抛出 java.lang.ClassCastException 类型的异常。
LinkedHashMap
LinkedHashMap 是 HashMap 的一个子类,保存了记录的插入顺序,在用 Iterator 遍历。使用LinkedHashMap 时,先得到的记录肯定是先插入的,也可以在构造时带参数,按照访问次序排序