目录

集合


集合

1 概念

1.1 集合是一个容器

  • 是一个用来装对象的容器

1.2 数据结构

  • 1、物理结构

    • 数组也是一个容器

      • 缺点

        • (1)长度固定
        • (2)无法直接获取有效元素的个数
      • 在实际开发中,基本数据类型一般用数组,引用数据类型一般用集合

      • 数组是依据“数组名+下标”来确定某个元素,数组名中存储的是数组的首地址

    • 链表

      • 不仅仅存储数据,还有存储前/后元素的引用
  • 2、逻辑结构

    • 动态数组

      • 底层是数组,可以通过扩容的方式实现动态数组
    • 链表

      • 结合Node

        • 双向链表

          • class Node{ Node pre; Object data; Node next; }
        • 单向链表

          • class Node{ Object data; Node next; }
      • 经典的代表

        • 二叉树

          • class Node{ Node left; Object data; Node right; }
      • 先进后出
      • 添加的顺序
      • 出栈的顺序
    • 队列

      • 先进先出
      • 添加的顺序
      • 出队列的顺序
    • ….

2 Collection

2.1 java.util.Collection是一个接口,是个根接口

2.2 Collection没有直接的实现类,它有两个子接口

  • java.util.List

    请描述List的各种实现类的区别

    ArrayList:动态数组(JDK1.2),每次扩容为原来的1.5倍,支持Iterator和foreach迭代,线程不安全的

    Vector:旧版的动态数组(JDK1.0),默认每次扩容为原来的2倍,支持旧版Enumeration,还支持Iterator和foreach迭代,线程安全的

    LinkedList:双向链表,在添加和删除时 效率比较高,不需要移动大量的元素,只需要修改前后元素引用关系

    Stack:Stack是Vector的子类,具有后进先出的特点

    • 有序的(添加顺序),可重复的

      • java.util.Vector动态数组

        • JDK1.0就有,最早

        • 支持Enumeration迭代方式

          • 当然也支持Iterator,foreach
        • 线程安全的

        • 扩容算法

          • 如果没有指定扩容参数,那么默认扩大为原来的2倍

            • 默认初始容量是10
          • 如果指定了扩容参数,那么就按照指定参数值进行扩容

      • java.util.ArrayList动态数组

        • 相对Vector来说新一点

        • 只支持Iterator,foreach

        • 线程不安全的

        • 扩容算法

          • 扩大为原来的1.5倍
      • java.util.LinkedList双向链表

        • 相对于动态数组来说的优势

          • 在插入和删除操作比较频繁时,链表的方式效率更高
        • 相对于动态数组来说的劣势

          • 如果根据索引信息来查找的话,每次都要现统计
      • java.util.Stack

        • 是Vector的子类

        • 特征的方法

          • peek()

            • 查看栈顶的元素,但不移除
          • pop()

            • 获取栈顶的元素,并移除
          • push()

            • 压入栈,添加的位置在栈顶
          • search(Object)

            • 返回位置,以 1 为基数
        • 逻辑结构

        • 底层

          • 数组
          • 每次添加到后面,栈顶是数组的后面[size-1]号元素,栈底是数组的[0]元素
    • 列表,序列

    • 补充Collection的方法

      • 和index相关的方法

        • (1)添加

          • add(int index, E element)
          • addAll(int index, Collection<? extends E> c)
        • (2)删除

          • remove(int index)
        • (3)查找

          • indexOf(Object o)
          • lastIndexOf(Object o)
          • get(int index)
        • (4)替换

          • set(int index, E element)
        • (5)截取

          • subList(int fromIndex, int toIndex)
  • java.util.Set

    5、请描述Set的各种实现类的区别

    HashSet:无序的,不可重复的,依据元素的hashCode()和equals方法

      HashSet底层实现是HashMap,它的value是一个Object的常量对象
    

    TreeSet:不保证添加顺序,但是会按照元素的“大小”顺序进行排列,不可重复,

      依据元素的自然排序Comparable(compareTo())或定制排序Comparator(compare())规则进行排序
    
      TreeSet底层实现是TreeMap,它的value是一个Object的常量对象
    

    LinkedHashSet:是HashSet的子类,除了有HashSet的特点,它还要在HashSet基础上维护元素的添加的顺序,所以在添加和删除时比HashSet慢一点

      LinkedHashSet的底层实现是LinkedHashMap,它的value是一个Object的常量对象
    
    • 无序的(添加顺序),不可重复的

      • java.util.HashSet

        • 无序,不可重复

        • 依赖于元素的hashCode和equals方法

        • equals和hashCode

          • hash值不同,这两个对象一定不同,可以不调用equals
          • equals如果相同,hashCode一定相同
          • hash值相同,这两个对象不一定相等,所以一定要调用equals方法进行确认
      • java.util.TreeSet

        • 不按添加顺序,但是按照元素“大小”顺序存储,不可重复

        • 不可重复,依据元素是否“大小相等”

          • 调用元素的compareTo或定制比较器的compare
        • 添加到TreeSet的元素一定要支持可比较大小,可排序

          • 自然排序

            • 要求元素类型本身要实现java.lang.Comparable接口,并重写int compareTo(Object)方法
          • 定制排序

            • 要为TreeSet指定一个定制比较器对象

              • TreeSet set = new TreeSet(定制比较器对象);
      • java.util.LinkedHashSet

        • 比较HashSet多了一个顺序维护,所以在添加和删除时,比HashSet效率低,遍历查找时效率高
        • 继承HashSet
    • 底层实现

      • HashSet

        • HashMap
      • TreeSet

        • TreeMap
      • LinkedHashSet

        • LinkedHashMap
    • Set的元素其实也是一对,只不过它的value是共享同一个常量对象Object对象

2.3 遍历

  • (1)直接foreach

    • 语法结构

      • for(集合的元素类型 element : 集合名){ }
    • 在遍历时,效率高,但是不适用于遍历的同时对集合进行修改,特别是影响集合元素个数的操作

  • (2)Iterator迭代器

    • 语法结构

      • Iterator iter = 集合对象.iterator();
      • while(iter.hasNext()){ Object element = iter.next(); //可以使用iter.remove()进行移除 }
    • Iterator是一个接口

      • 在每一类集合中,都有自己的实现类,通过内部类的形式来实现Iterator接口

2.4 继承关系图

2.5 Collection的常用方法

  • (1)添加

    • (1)add(Object obj)

      • 添加一个元素到集合中
    • (2)addAll(Collection other)

      • 把other集合中的元素一一添加到当前集合中,一次添加多个
  • (2)删除

    • remove(Object obj)

      • 删除一个元素
    • removeAll(Collection other)

      • 从当前集合中删除它俩的交集

        • this - this ∩ other
  • (3)查找

    • contains(Object obj)

      • 在当前集合中查找一个元素
    • containsAll(Collection c)

      • 判断c是否是当前集合的子集
  • (4)其他

    • size()

      • 获取有效元素的个数
    • retainsAll(Collection other)

      • 把this ∩ other赋值给当前集合

        • this = this ∩ other
  • (5)遍历

    • Iterator iterator()
    • Object[] toArray()

3 Map

3.1 Map的特点

  • Map的元素,即存储的映射关系(key,value),其类型是Entry类型,它是Map的内部子接口,在各种Map的实现类中,都用内部类的方式来实现Entry接口
  • Map的key不可重复,而且一旦添加到map中,key不建议修改,特别是参与hashCode和equals方法的属性,或参与compareTo或compare方法比较的属性

3.2 Map的常用方法

  • 常用的方法

    • 1、添加

      • put(key,value)
      • putAll(Map)
    • 2、有效键值对数

      • size
    • 3、根据key获取value

      • get(key)
    • 4、是否包含某个key/value

      • containsKey()
      • containsValue()
    • 5、删除

      • remove(key)
    • 6、和迭代相关

      • 遍历所有的key

        • Set keySet()
      • 遍历所有的value

        • Collection values()
      • 遍历所有的映射关系

        • Set entrySet()
        • Set的元素是Entry类型

3.3 Map的常见实现类

  • Hashtable

    • JDK1.0就有的,属于旧版HashMap,线程安全的
  • HashMap

    • 它的key不可重复的,依据key的hashCode()和equals()方法,线程不安全的 JDK1.7时底层实现是数组+链表 JDK1.8时底层实现是数组+链表+红黑树
  • TreeMap

    • 它的key不可重复的,按照key的“大小”顺序进行排列, 依据key的自然排序Comparable(compareTo())或定制排序Comparator(compare())规则进行排序
  • LinkedHashMap

    • 它是HashMap的子类,在HashMap的基础上同时要维护添加的顺序
  • Properties

    • Properties是Hashtable的子类,它的key和value的类型都是String类型

3.4 高频面试题:HashMap的底层实现过程

  • JDK1.7时底层实现是数组+链表

    •   当我们要添加一个新的映射关系时,
      
      (1)先取出key,算出它的hash值 (2)如果数组是空的,会先建立一个长度为16的数组table (3)如果数组不为空,这个时候要判断数组的长度是否达到临界点(数组长度*0.75),如果已经达到临界点,应该先对数组进行扩容,扩大为2倍 一旦扩容,要重头开始(以前元素要重新排序位置,对新添加的映射关系也要重写计算key的hash值,和index) (4)会根据key的hash值与table数组的长度做一个“按位与&”的运算,计算出要存储的下标index (5)先判断table[index]是否为空,如果为空,直接放进去,放进去之前会构建一个Entry类型对象 (6)如果table[index]不是空的,那么调用key的equals方法与table[index]的key做比较,如果table[index]下面还有链表, 可能需要与table[index]下面的链表的元素一一比较,直到遇到了equals为true或都不相同 (7)如果有一个equals返回true,那么就把value给替换 (8)如果equals都不相等,那么把当前映射关系构建的Entry对象,放在此链表的表头,把原来的对象作为我的next
  • JDK1.8时底层实现是数组+链表+红黑树

    •   当我们要添加一个新的映射关系时,
      
      (1)先取出key,算出它的hash值 (2)如果数组是空的,会先建立一个长度为16的数组table (3)如果数组不为空,这个时候要判断数组的长度是否达到临界点(数组长度*0.75),如果已经达到临界点,应该先对数组进行扩容,扩大为2倍 一旦扩容,要重头开始(以前元素要重新排序位置,对新添加的映射关系也要重写计算key的hash值,和index) (4)会根据key的hash值与table数组的长度做一个“按位与&”的运算,计算出要存储的下标index (5)先判断table[index]是否为空,如果为空,直接放进去,放进去之前会构建一个Entry类型对象 (6)如果table[index]不是空的,那么调用key的equals方法与table[index]的key做比较,如果table[index]下面有树或者链表, 可能需要与table[index]下面的链表或树的元素一一比较,直到遇到了equals为true或都不相同 (7)如果有一个equals返回true,那么就把value给替换 (8)如果都不相等,如果现在已经是树,就直接添加到该树的叶子节点上。 (9)如果都不相等,如果现在不是树,而是链表,看当前链表的长度是否达到8个,如果没有达到8个,直接添加到链表的尾部 (10)如果已经达到8个,此时要检查数组table的长度是否达到64,如果没有达到64,先扩容,一旦扩容,一切从头开始 (11)如果达到64,把该链表变成一颗红黑树
    •   什么时候树会变回链表?
      
      每次进行resize(),会检查树的叶子节点的总数是否<6个,如果<6个,会把这个红黑树变回链表

4 集合框架图

5 集合工具类

5.1 java.util.Collections

  • 操作集合的各种静态方法

    • 1、Collections.addAll(Collection, T… elements)

    • 2、binarySearch(List, T target)

      • 对List的元素类型有要求,必须支持可比较大小
    • 3、max/min(Collection)

      • 对Collection的元素类型有要求,必须支持可比较大小
    • 4、sort(List)

      • 元素必须实现Comparable
    • 5、sort(List,Comparator)

      • 按照指定比较器进行排序
    • 6、如果想要获得线程安全的集合对象

      • synchronizedXXX(集合)
    • 7、。。。。