Java(6)集合

一、Java集合框架概述

1、什么是集合

  • 集合框架:用于存储数据的容器。
    • 数组、集合等存储数据的结构,叫Java容器
    • 此时的存储,是指内存层面的存储,不涉及持久化的存储。
  • 任何集合框架都包含三大块的内容:对外的接口、接口的实现、对集合运算的算法。

2、集合的特点

  • 数组的特点/缺点

    • 长度固定。一旦初始化,长度不能修改。
    • 类型确定。类型严格(算是一个好处),当然要想可以放多种类型的数据,也可以声明为Object类型。
    • 方法有限。添加、删除元素效率低;获取元素个数不方便。
    • 元素有序可重。对于无序、不重复的元素,不能满足。
  • 集合的特点/优点

    • 容量自增长。

3、集合的体系

  • Collection接口Map接口是所有集合框架的父接口。
  • Collection接口继承树

说明:

  1. List接口:存储有序、可重复的数据。“动态数组”
  2. Set接口:存储无序、不可重复的数据。“数学中的集合”
  • Map接口继承树

说明:

  1. Map接口:双列集合,用于存储成对的数据(key-value)。“数学中的函数”

二、Collection接口中的方法(15个)

1、说明

  • 因为Collection接口是List、Set、Queue接口的父接口,所以定义在Collection接口的中方法可以用于操作子接口的实现类的对象。

2、15个方法

方法 描述
add(Object obj) 添加元素
addAll(Collection c) 添加另一个集合中所以元素
size() 元素个数
clear() 清空集合
isEmpty() 是否为空
contains(Object obj) 包含某个元素
containsAll(Collection c) 包含某个集合中所有元素
remove(Object obj) 删除元素
removeAll(Collection c) 差集
retainAll(Collection c) 交集
equals(Object obj) 判断集合是否相等
toArray() 转换为数组
toArray(T[] a)
hashCode() 求集合的哈希值
iterator() 返回迭代器对象

注意事项:

  • 想Collection接口的实现类对象中添加数据obj时,要求obj所在类重写equals()

  • 数组—>集合:Arrays.asList()

    •   List arr1 = Arrays.asList(new int[]{123, 456});
        System.out.println(arr1.size());//1
      
    •   List arr2 = Arrays.asList(new Integer[]{123, 456});
        System.out.println(arr2.size());//2
      
    • asList中是整型时,用int,默认会把数组当场一个对象。

3、Iterator迭代器接口

  • Iterator是什么?
    • Iterator对象成为迭代器,是设计模式的一种。主要用于遍历集合中的元素。
    • 迭代器模式:提供一种方法访问一个容器(container)对象中各个元 素,而又不需暴露该对象的内部细节。迭代器模式,就是为容器而生。
  • Iterator接口中方法
    • hasNext()
    • next()
    • remove()遍历过程中移除某元素
      • Iterator可以删除集合的元素,但是是遍历过程中通过迭代器对象的 remove方 法,不是集合对象的remove方法。
      • 如果还未调用next()或在上一次调用 next 方法之后已经调用了 remove 方法, 再调用remove都会报IllegalStateException
  • 怎么用Iterator遍历集合
//用集合中的iterator方法得到迭代器对象
Iterator iterator = coll.iterator();
//遍历集合
while(iterator.hasNext()){
    System.out.println(iterator.next());
}

//当然我们也可以用foreach来遍历,其实foreach本质也是调用了迭代器。
  • Iterator原理

    • 得到的迭代器对象,默认指针指向第一个元素的前面
    • hasNext()来判断下一位是否有元素。(在调用it.next()方法之前必须要调用it.hasNext()进行检测。若不调用,且 下一条记录无效,直接调用it.next()会抛出NoSuchElementException异常)
    • next():两个作用:①指针下移②取出指针指向的元素
  • 注意

    • 集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合 的第一个元素之前。因此下面这种写法不正确
while (coll.iterator().hasNext()){
    System.out.println(coll.iterator().next());
}

三、List接口

1、List接口概述

  • List接口是Collection的子接口
  • List被称为“动态数组”,是因为数组的局限性而代替数组的一种容器。
  • 存储的元素有序、可重复。每个元素都有对应的顺序索引
  • 实现类有三个:ArrayList、LinkedList、Vector

2、ArrayList、LinkedList、Vector的异同(面试题)

    • 三个类都是List接口的实现类,因此存储数据都是有序可重复的。
实现类 地位 since 线程安全否 底层 应用场景
ArrayList 主要实现类 1.2 线程不安全、效率高 Object[] 遍历、查找
LinkedList 1.2 双向链表 频繁插入、删除
Vector 古老实现类 1.0 线程安全、效率低 Object[]

3、源码分析(加分项)

  • ArrayList源码分析—–JDK7

    • 创建:调用空参构造器时,底层创建一个长度为10的Object[]数组elementData
    • 扩容:默认扩容为原来的1.5倍(使用移位操作),并将原来数组中的数据复制到新的数组中
    • 结论:开发中使用带参构造器指定容量:ArrayList list = new ArrayList(int capacity)
  • ArrayList源码分析—–JDK8

    • 创建:调用空参构造器时,底层创建一个Object[]数组elementData,初始化为空{}
    • 扩容:同JDK7
    • 对比:jdk7中的ArrayList的对象的创建类似于单例的饿汉式,而jdk8中的ArrayList的对象的创建类似于单例的懒汉式,延迟了数组的创建,节省内存
  • LinkedList源码分析

    • 创建:调用空参构造器时,内部声明了Node类型的first和last属性,默认值为null
    • 添加:将数据封装在Node中,创建Node对象
  • Vector源码分析

    • 创建:调用空参构造器时,底层创建一个长度为10的Object[]数组elementData
    • 扩容:默认扩容为原来的2倍,并将原来数组中的数据复制到新的数组中

4、List常用方法

  • List除了从Collection集合继承的方法外,还有一些独有的、根据索引来操作集合元素的方法

  • 总结的简化版(常用的、便于记忆的总结)

常用方法的作用 方法名
add(Object obj)
remove(Object obj)、remove(int index)
set(int index, Object obj)
get(int index)
add(int index, Object obj)
长度 size()
遍历 ①、Iterator迭代器
②、增强for循环
③、普通for循环
  • 区分List中的remove方法
    • list.remove(2):删除索引为2的,因为有remove(int index)的方法,直接匹配上不用自动装箱就能匹配,name何必自动装箱呢?
    • list.remove(new Integer(2)):删除数据2,匹配的是remove(Object obj)的方法

四、Set接口

1、Set接口概述

  • Set接口是Collection的子接口。
  • Set中没有额外的方法
  • Set中的元素不可重复,判断是否相等用的是equals()方法
  • 实现类有HashSet、LinkedHashSet、TreeSet

2、HashSet、LinkedHashSet、TreeSet的异同

  • HashSet:是Set接口的主要实现类;线程不安全;可以存储null值;
  • LinkedHashSet:是HashSet的子类;可以按照添加顺序遍历,遍历效率高;
  • TreeSet:可以按照添加对象的指定属性排序
    • 要求1:添加的对象类型相同
    • 要求2:对象的类要实现Comparable接口来进行自然排序,或者,实现Comparator接口来进行定制排序
      • 自然排序中,比较两个对象是否相同的标准为(是否可添加“相同对象”):compareTo()返回0.不再是equals().
      • 定制排序中,比较两个对象是否相同的标准为:compare()返回0.不再是equals().

3、理解无序、可重复

  • 无序
    • 指的是存储在底层数组的数据,并不是按照数组索引的顺序添加的,而是根据数据的哈希值决定的。
    • 无序性,不等于随机性
  • 不可重复
    • 相同的元素只能添加一个。元素按照equals()判断,不能返回true。

4、添加元素的过程(以HashSet为例)

  • 添加过程并不会单独考察,但是对于理解HashMap有很大帮助

  • 我们向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置(即为:索引位置),判断数组此位置上是否已经有元素:

    • 如果此位置上没有其他元素,则元素a添加成功。 —>情况1
    • 如果此位置上有其他元素b(或以链表形式存在的多个元素),则比较元素a与元素b的hash值:
      • 如果hash值不相同,则元素a添加成功。—>情况2
      • 如果hash值相同,进而需要调用元素a所在类的equals()方法:
        • equals()返回true,元素a添加失败
        • equals()返回false,则元素a添加成功。—>情况3
  • 对于添加成功的情况2和情况3而言:元素a 与已经存在指定索引位置上数据以链表的方式存储

    • jdk 7 :元素a放到数组中,指向原来的元素。
    • jdk 8 :原来的元素在数组中,指向元素a
    • 总结:七上八下
  • HashSet底层:数组+链表的结构。

5、重写hashCode()和equals()方法

  • 要求:向Set(主要指:HashSet、LinkedHashSet)中添加的数据,其所在的类一定要重写hashCode()和equals()

  • 重写要求:重写的hashCode()和equals()尽可能保持一致性:相等的对象必须具有相等的散列码

  • 重写两个方法的小技巧:对象中用作 equals() 方法比较的 Field,都应该用来计算 hashCode 值。

6、面试题

  • 在List内去除重复数字值,要求尽量简单

    • 可以用Set实现
  •   //程序的输出结果
      HashSet set = new HashSet();
      Person p1 = new Person(1001,"AA");
      Person p2 = new Person(1002,"BB");
      
      set.add(p1); 
      set.add(p2);
      p1.name = "CC";
      set.remove(p1); 
      System.out.println(set);//[Person{id=1002, name='BB'}, Person{id=1001, name='CC'}]
      /*
      存放p1、p2时,根据p1和p2的属性算出了hashCode来确定存放位置
      p1的name改为CC后,再删除p1--->根据此时p1的属性算hashCode来找set中和p1相同的元素,此时算出来的hashCode大概率不是p1的位置,相当于没有找到,所以删除并没有成功。
      */
      
      set.add(new Person(1001,"CC"));
      System.out.println(set); //[Person{id=1002, name='BB'}, Person{id=1001, name='CC'}, Person{id=1001, name='CC'}]
      /*
      存放新建的p,是根据1001和CC来算hashCode,此时数组中这个位置空的,所以存放成功
      */
      set.add(new Person(1001,"AA"));
      System.out.println(set);//[Person{id=1002, name='BB'}, Person{id=1001, name='CC'}, Person{id=1001, name='CC'}, Person{id=1001, name='AA'}]
      /*
      存放新建的p,先hashCode后和p1是一样的,再equals发现不一样,加到链表上,存放成功
      */
    

五、Map接口

1、Map接口概述

  • Map接口继承树

  • Map接口:
    • 存储key-value的键值对,类似于数学中的函数
  • Map实现类对比
Map实现类 底层实现 线程 地位 特点
HashMap JDK7及以前:数组+链表
JDK8:数组+链表+红黑树
线程不安全 主要实现类 可以存储null的k-v
LinkedHashMap 同上 同上 同上 可按照添加顺序遍历
TreeMap 可排序遍历
Hashtable 线程安全 古老实现类 不可存储null的k-v
Properties 常用来处理配置文件

2、面试题

  •     HashMap的底层实现原理★★★★★(见下面第四点)
    
  •     HashMap和Hashtable的异同
    
  •     CurrentHashMap和Hashtable的异同(暂时不学)
    

3、Map结构的理解

  • Map中的key:无序的、不可重复的,使用Set存储所有的key —> key所在的类要重写equals()和hashCode() (以HashMap为例);当然如果是LinkedHashMap还要实现排序接口

  • Map中的value:无序的、可重复的,使用Collection存储所有的value —>value所在的类要重写equals()

  • 一个键值对:key-value构成了一个Entry对象。

  • Map中的entry:无序的、不可重复的,使用Set存储所有的entry

四、 HashMap的底层实现原理★★★★★(高频面试题)

1、JDK7

  • HashMap map = new HashMap():底层创建一个长度为16的数组Entry[] table。

  • map.put(key1, value1)

    • 调用key1所在类的hashCode()方法计算key1的哈希值,用哈希值在计算得到在Entry[]数组中的存放位置
      • 如果此位置为空,添加成功——>情况1
      • 如果此位置不为空,比较key1和已经存在的数据的哈希值
        • 如果key1的哈希值和已经存在的数据的哈希值不同,添加成功——>情况2
        • 如果key1的哈希值和已经存在的某一个数据(key2,value2)的哈希值相同,继续调用key1所在类的equals()方法,传入参数key2进行比较
          • 如果equals()返回值为false,添加成功——>情况3
          • 如果equals()返回值为true,用value1替换value2
  • 扩容:(当超出临界值且要添加数据的位置不为空时)默认扩容为原来的2倍,并复制数据到新数组

2、JDK8与JDK7的不同之处

  • HashMap map = new HashMap()时,底层没有直接创建数组,而是在首次put()时创建
  • 数组是Node[]类型,而非Entry[]类型
  • JDK7底层是:数组+链表;而JDK8底层是:数组+链表+红黑树
    • 形成链表时,七上八下(jdk7:新的元素指向旧的元素。jdk8:旧的元素指向新的元素)
    • 当数组的某一个索引位置上的元素以链表形式存在的数据个数 > 8 且当前数组的长度 > 64时,此时此索引位置上的所数据改为使用红黑树存储。

3、LinkedHashMap底层实现原理

static class Entry<K,V> extends HashMap.Node<K,V> {
    Entry<K,V> before, after;//能够记录添加的元素的先后顺序
    Entry(int hash, K key, V value, Node<K,V> next) {
        super(hash, key, value, next);
    }
}

六、Map接口常用方法

1、添加、删除、修改操作:

  • Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中

    void putAll(Map m):将m中的所有key-value对存放到当前map中

  • Object remove(Object key):移除指定key的key-value对,并返回value
    void clear():清空当前map中的所有数据

2、 元素查询的操作:

  • Object get(Object key):获取指定key对应的value

  • boolean containsKey(Object key):是否包含指定的key

    boolean containsValue(Object value):是否包含指定的value

  • int size():返回map中key-value对的个数

  • boolean isEmpty():判断当前map是否为空

  • boolean equals(Object obj):判断当前map和参数对象obj是否相等

3、元视图操作的方法:

  • Set keySet():返回所有key构成的Set集合
  • Collection values():返回所有value构成的Collection集合
  • Set entrySet():返回所有key-value对构成的Set集合

4、总结常用方法:

  • 添加:put(Object key,Object value)
  • 删除:remove(Object key)
  • 修改:put(Object key,Object value)
  • 查询:get(Object key)
  • 长度:size()
  • 遍历:keySet() / values() / entrySet()

七、Properties

1、配置文件

  • 默认存放路径为当前工程下

  • 创建配置文件的方式

    • 右键工程名—>new—>File:这时要写上 .properties,如 jdbc.properties
    • 右键工程名—>new—>Resource Bundle:这时秩序写名称就会自动补全后缀
  • 存取数据时,建议使用setProperty(String key,String value)方法和 getProperty(String key)方法

八、Collections工具类

1、Collections概述

  • Collections是一个操作Set、List、Map等集合的工具类
  • Collections 中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作, 还提供了对集合对象设置不可变、对集合对象实现同步控制等方法

2、Collections常用方法

排序操作

  • 排序操作都是针对List来讲的,因为Map本身无序,何谈排序
  • 返回值都是void,说明对List本身做了修改
方法 解释
reverse(List) 反转List中的元素
shuffle(List) 随机排序
sort(List) 自然排序
Sort(List, Comparator) 定制排序
swap(List, int i, int j) 交换

查询、替换

方法 解释
Object max/min(Collection) 返回自然排序的最大、小值
Object max/min(Collection, Comparator) 返回定制排序的最大、小值
int frequency(Collection, Object) 返回某集合中某元素的出现次数
void copy(List dest, List src) 复制
boolean replaceAll(List list, Object oldVal, Object newVal) 替换
  • void copy(List dest, List src)中,新List容量不能小于旧List,因此要用 List dest = Arrays.asList(new Object[list.size()]);来创建

3、Collection和Collections的区别(面试题)

Tags: