JAVA集合框架

集合体系结构

  • Collection(单列集合)
  • Map(双列集合)

Collection

常用方法

1
2
3
4
5
6
7
8
9
10
// 1. public boolean add(E e):添加元素,添加成功返回true
// 2. public void clear():清空集合的元素
// 3. public boolean isEmpty():判断集合是否为空
// 4. public int size():获取集合的大小
// 5. public boolean contains(Object obj):判断集合中是否包含某个元素
// 6. public boolean remove(E e):删除某个元素:如果有多个重复元素默认删除前面的第一个
// 7. public Object[] toArray():把集合转成数组
c.toArray();
c.toArray(new String[c.size()]);
// 8. c1.addAll(c2):将c2集合的全部数据添加到c1

遍历方式

迭代器

Collection集合获取迭代器的方法

方法名称 说明
Iterator<E> iterator() 返回集合中的迭代器对象,该迭代器对象默认指向当前集合的第一个元素

迭代器中的常用方法方法名称

方法名称 说明
boolean hasNext() 询问当前位置是否有元素存在,存在返回true,不存在返回false
E next() 获取当前位置的元素,并同时将迭代器对象指向下一个元素处
注:可能会出现索引越界问题

增强for

本质是迭代器遍历集合的简化写法

for(元素的数据类型 变量名 : 数组或集合){}

lambda表达式

需要使用Collection的如下方法来完成

方法名称 说明
default void forEach(Consumer<? super T> action) 结合lambda遍历集合
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Collection<String> c = new ArrayList<>();
c.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});

// forEach源码
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}

// Consumer接口中找到了函数式注解,说明匿名内部类可以用lambda简化
c.forEach(s -> System.out.println(s));
c.forEach(System.out::println);

List

常用方法

方法名称 说明
void add(int index,E element) 在此集合中的指定位置插入指定的元素
E remove(int index) 删除指定索引处的元素,返回被删除的元素
E set(int index,E element) 修改指定索引处的元素,返回被修改的元素
E get(int index) 返回指定索引处的元素

遍历方式

for循环(因为List集合有索引)
1
2
3
4
for(int i=0; i < list.size(); i++){
String s = list.get(i);
System.out.println(s);
}
Collection集合的遍历方式

迭代器,增强for循环,lambda表达式

ArrayList

底层原理
  • 基于数组实现
  • 查询速度快(根据索引)
  • 删除效率低(需要把后面的数据前移)
  • 添加效率极低(可能需要把后面很多的数据后移,再添加元素;或者也可能需要进行数组的扩容)
  1. 利用无参构造器创建的集合,会在底层创建一个默认长度为0的数组
  2. 添加第一个元素时,底层会创建一个新的长度为10的数组
  3. 存满时,会扩容1.5倍。将原集合的数据迁移
  4. 如果一次添加多个元素,1.5倍还放不下,则新创建数组的长度以实际为准
适用场景
  1. ArrayList适合:根据索引查询数据比如根据随机索引取数据(高效)或者数据量不是很大时
  2. ArrayList不适合:数据量大的同时又要频繁的进行增删操作

LinkedList

底层原理
  • 基于双链表实现

  • 查询慢,增删相对较快,但对首尾元素进行增删改查的速度是极快的

  • 新增了很多首尾操作的特有方法

    方法名称 说明
    public void addFirst(E e) 在该列表开头插入指定的元素
    public void addLast(E e) 将指定的元素追加到此列表的末尾
    public E getFirst() 返回此列表中的第一个元素
    public E getLast() 返回此列表中的最后一个元素
    public E removeFirst() 从此列表中删除并返回第一个元素
    public E removeLast() 从此列表中删除并返回最后一个元素
适用场景

可以用来设计队列、栈

Set

HashSet

底层原理
  • 基于哈希表实现

  • 哈希表是一种增删改查数据,性能都较好的数据结构

    • JDK8之前,哈希表=数组+链表

      1. 创建一个默认长度16的数组,默认加载因子为0.75,数组名table(扩容两倍)-> JDK8开始,当链表长度超过8,且数组长度>=64时,自动将链表转成红黑树
      2. 使用元素的哈希值数组的长度求余计算出应存入的位置
      3. 判断当前位置是否为null,如果是null直接存入
      4. 如果不为null,表示有元素,则调用equals方法比较(相等,则不存;不相等,则存入数组)
        • JDK8之前,新元素存入数组,占老元素位置,老元素挂下面
        • JDK8开始之后,新元素直接挂在老元素下面

      1. 如果数组快占满了,会出什么问题? 链表会过长,导致查询性能降低 -> 扩容
    • JDK8之后,哈希表=数组+链表+红黑树(自平衡的二叉树)

    HashSet去重复

    Hashset集合默认不能对内容一样的两个不同对象去重复
    比如内容一样的两个学生对象存入到Hashset集合中去,Hashset集合是不能去重复的

    如何让Hashset集合能够实现对内容一样的两个不同对象也能去重复?

    重写对象的hashCode()和equals()方法

LinkedHashSet

底层原理
  • 依然是基于哈希表(数组、链表、红黑树)实现的
  • 但是,它的每个元素都额外的多了一个双链表的机制记录它前后元素的位置

TreeSet

底层原理
  • 基于红黑树实现

注意

  1. 对于数值类型:Integer,Double,默认按照数值本身的大小进行升序排序
  2. 对于字符串类型:默认按照首字符的编号升序排序
  3. 对于自定义类型如Student对象,Treeset默认是无法直接排序的
自定义排序规则
  1. 让自定义的类(如学生类)实现Comparable接口,重写里面的compareTo方法来指定比较规则
  2. 通过调用Treeset集合有参数构造器,可以设置Comparator对象(比较器对象,用于指定比较规则)

如果类本身有实现Comparable接口,Treeset集合同时也自带比较器,默认使用集合自带的比较器排序

适用场景总结

  1. 如果希望记住元素的添加顺序,需要存储重复的元素,又要频繁的根据索引查询数据?
    • 用ArrayList集合(有序、可重复、有索引),底层基于数组的(常用)
  2. 如果希望记住元素的添加顺序,且增删首尾数据的情况较多?
    • 用LinkedList集合(有序、可重复、有索引),底层基于双链表实现的
  3. 如果不在意元素顺序,也没有重复元素需要存储,只希望增删改查都快?
    • 用Hashset集合(无序,不重复,无索引),底层基于哈希表实现的(常用)
  4. 如果希望记住元素的添加顺序,也没有重复元素需要存储,且希望增删改查都快?
    • 用LinkedHashset集合(有序,不重复,无索引),底层基于哈希表和双链表
  5. 如果要对元素进行排序,也没有重复元素需要存储?且希望增删改查都快?
    • 用Treeset集合,基于红黑树实现

集合的并发修改异常

  • 使用迭代器遍历集合时,又同时在删除集合中的数据,程序就会出现并发修改异常的错误
  • 由于增强for循环遍历集合就是迭代器遍历集合的简化写法,因此,使用增强for循环遍历集合,又在同时删除集合中的数据时,程序也会出现并发修改异常的错误

使用for循环时,注意i–

使用迭代器时,注意使用迭代器的remove方法,而不是list的

可变参数

  • 就是一种特殊形参,定义在方法、构造器的形参列表里,格式是:数据类型...参数名称;
  • 特点:可以不传数据给它;可以传一个或者同时传多个数据给它;也可以传一个数组给它
  • 好处:常常用来灵活的接收数据
  • 可变参数的注意事项
    • 可变参数在方法内部就是一个数组
    • 一个形参列表中可变参数只能有一个
    • 可变参数必须放在形参列表的最后面

Collections

是一个用来操作集合的工具类

方法名称 说明
public static <T> boolean addAll(Collection<? super T> c, T···elements 给集合批量添加元素
public static void shuffle(List<?> list) 打乱List集合中的元素顺序
public static <T> void sort(List<T> list) 对List集合中的元素进行升序排序
public static <T> void sort(List<T> list, Comparator<? super T> c) 对List集合中元素,按照比较器对象指定的规则进行排序

Map

  • Map集合称为双列集合,格式:(key1=value1,key2=value2,key3=value3,…,一次需要存一对数据做为一个元素
  • Map集合的每个元素“key=value”称为一个键值对/键值对对象/一个Entry对象,Map集合也被叫做键值对集合
  • Map集合的所有键是不允许重复的,但值可以重复,键和值是一一对应的,每一个键只能找到自己对应的值

Map集合体系的特点
注意:Map系列集合的特点都是由键决定的,值只是一个附属品,值是不做要求的

  • HashMap(由键决定特点):无序、不重复、无索引;(用的最多)
  • LinkedHashMap (由键决定特点):有序、不重复、无索引
  • TreeMap (由键决定特点):按照大小默认升序排序、不重复、无索引

常用方法

方法名称 说明
public V put(K key,V value) 添加元素
public int size() 获取集合的大小
public void clear() 清空集合
public boolean isEmpty() 判断集合是否为空,为空返回true,反之
public V get(Object key) 根据键获取对应值
public V remove(Object key) 根据键删除整个元素
public boolean containsKey(Object key) 判断是否包含某个键
public boolean containsValue(Object value) 判断是否包含某个值
public Set<K> keySet() 获取全部键的集合
public Collection<V> values() 获取Map集合的全部值
public void putAll(Map<? extends K, ? extends V> m) 把其它Map集合中的数据放到自己集合中

遍历方式

键找值

先获取Map集合全部的键,再通过遍历键来找值

方法名称 说明
public Set<K> keySet() 获取所有键的集合
public V get(Object key) 根据键获取其对应的值

键值对

把“键值对”看成一个整体进行遍历

1
2
3
4
5
6
7
// Set<Map.Entry<K,V>> entrySet() 获取所有键值对的集合
Set<Map.Entry<String,Double>> entries = map.entrySet();
for(Map.Entry<String,Double> entry : entries){
String key = entry.getKey();
double value = entry.getValue();
System.out.println(key + "====>" + value);
}

Lambda

方法名称 说明
default void forEach(BiConsumer<? super K, ? super V> action) 结合lambda遍历Map集合
1
map.forEach((k,v) -> {System.out.println(k + "---->" + v);});

HashMap

  • 基于哈希表实现(Set系列集合的底层就是基于Map实现的,只是Set集合中的元素只要键数据,不要值数据)
  • HashMap的键依赖hashcode方法和equals方法保证键的唯一

LinkedHashMap

  • 底层数据结构依然是基于哈希表实现的,只是每个键值对元素又额外的多了一个双链表的机制记录元素顺序(保证有序)(LinkedHashset集合的底层原理就是LinkedHashMap)

TreeMap

  • 基于红黑树实现

Stream

  • 也叫Stream流,是Jdk8开始新增的一套APl(java.util.stream.*),可以用于操作集合或者数组的数据
  • 优势:Stream流大量的结合了Lambda的语法风格来编程,提供了一种更加强大,更加简单的方式操作集合或者数组中的数据,代码更简洁,可读性更好
1
2
List<String> names = new ArrayList<>();
List<String> list = names.stream().filter(s->s,startsWith("张")).filter(a->a,length()==3).collect(Collectors.toList());

Stream流的创建

  • 获取集合的Stream流

    Collection提供的如下方法 说明
    default Stream<E> stream() 获取当前集合对象的stream流
  • 获取数组的Stream流

    Arrays类提供的如下方法 说明
    public static <T> Stream<T> stream(T[] array) 获取当前数组的stream流
    Stream类提供的如下方法 说明
    public static<T> Stream<T> of(T... values) 获取当前接收数据的stream流

常见的中间方法

Stream提供的常用中间方法 说明
Stream<T> filter(Predicate<? super T> predicate) 用于对流中的数据进行过滤
Stream<T> sorted() 对元素进行升序排序
Stream<T> sorted(Comparator<? super T> comparator 按照指定规则排序
Stream<T> limit(long maxSize) 获取前几个元素
Stream<T> skip(long n) 跳过前几个元素
Stream<T> distinct() 去除流中重复的元素
<R> Stream<R> map(Function<? super T,? extends R> mapper) 对元素进行加工,并返回对应的新流
static <T> Stream<T> concat(Stream a,Stream b) 合并a和b两个流为一个流

常见的终结方法

Stream提供的常用终结方法 说明
void forEach(Consumer action) 对此流运算后的元素执行遍历
long count() 统计此流运算后的元素个数
Optional<T> max(Comparator<? super T> comparator 获取此流运算后的最大值元素
Optional<T> min(Comparator<? super T> comparator 获取此流运算后的最小值元素

调用max或min时后面跟上get()方法可以将数据取出来

  • 收集Stream流:把Stream流操作后的结果转回到集合或者数组中去返回
  • Stream流:方便操作集合/数组的手段;集合/数组:才是开发中的目的
Stream提供的常用终结方法 说明
R collect(Collector collector) 把流处理后的结果收集到一个指定的集合中去
Object[] toArray() 把流处理后的结果收集到一个数组中去

collector可以是Collectors.toList(),Collectors.toSet(),Collectors.toMap()

注意Stream只能收集一遍

toArray示例

1
2
Object[] arr = students.stream().filter(a -> a.getHeight() > 170).toArray();
Student[] arr = students.stream().filter(a -> a.getHeight() > 170).toArray(len -> new Student[len]);