15.java-集合

集合

​ 集合:一个长度可变的容器

集合的分类

单列集合(Collection)
  • 一次添加一个元素
  • 所有的单列集合都是实现了一个接口:Collection
List接口
  • 存取有序
  • 有索引
  • 可以存储重复数据

ArrayList、LinekdList :有序、可重复、有索引。

Set接口
  • 存取无序
  • 没有索引
  • 不可以存储重复数据

HashSet: 无序、不重复、无索引;

LinkedHashSet: 有序、不重复、无索引。

TreeSet:按照大小默认升序排序、不重复、无索引。

双列集合(Map)
  • 一次添加两个元素
  • 所有的双列集合都是实现了一个接口:Map

Collectiond的使用

public boolean add(E e)	//把给定对象添加到当前集合中
public void clear()	//清空集合中所有元素
Public boolean remove(E e)	//把给定对象在当前集合中删除
Public boolean contains(Object obj)	//判断当前集合中是否包含给定对象
Public boolean isEmpty()	//判断当前集合是否为空
Public int size()	//返回集合中元素的个数/集合的长度

集合的遍历方式

迭代器
获取迭代器
Iterator iterator()	//返回集合中的迭代器对象,该迭代器对象默认指向当前集合的0索引
Iterator中的常用方法
boolean hasNext()	//询问当前位置是否有元素存在,存在返回true ,不存在返回false
E next()	//获取当前位置的元素,并同时将迭代器对象移向下一个位置,注意防止取出越界。
使用格式
//获取迭代器
Iterator<String> it = 集合对象.iterator();
//hashNext() : 判断集合中是否还有元素
while(it.hasNext()) {
	//next() : 取出集合元素, 并将指针向后移动
	String s = it.next();
    System.out.println(s);
 }
增强 for 循环
  • 增强for循环:既可以遍历集合也可以遍历数组。
  • 它是JDK5之后出现的,其内部原理就是一个Iterator迭代器,简化迭代器的代码书写
  • 实现Iterable接口的类才可以使用迭代器和增强for,Collection接口已经继承了Iterable接口。
forEach 方法

得益于JDK 8开始的新技术Lambda表达式,提供了一种更简单、更直接的遍历集合的方式。

​ 底层原理也是迭代器。

c.forEach(new Consumer<Student>() {
            @Override
            public void accept(Student student) {
                System.out.println(student);
            }
        });

List接口

List的实现类的底层原理
  • ArrayList底层是基于数组实现的:根据索引定位元素快,增删相对慢。
  • LinkedList底层基于双链表实现的:查询元素慢,增删首尾元素是非常快的。
List集合特有方法
void add(int index,E element)	//在此集合中的指定位置插入指定的元素
E remove(int index)	//删除指定索引处的元素,返回被删除的元素
E set(int index,E element)	//修改指定索引处的元素,返回被修改的元素
E get(int index)	//返回指定索引处的元素
List集合的遍历方式

普通for循环(因为List集合存在索引)

迭代器

增强for循环

forEach(Lambda表达式)

LinkedList的特点

  • 底层数据结构是双链表,查询慢,首尾操作的速度是极快的,所以多了很多首尾操作的特有API。
LinkedList集合的特有功能

15.java-集合_第1张图片

并发修改异常

当迭代器在遍历的过程中 , 使用集合的添加或删除方法, 则出现并发修改异常

(迭代器对象, 集合对象) 同时操作, 就是并发修改

结论 : 迭代器遍历的过程中, 如果涉及到增删, 请使用迭代器自身的赠删方法

TreeSet集合

(addAll:将旧集合中的元素复制到新集合)

作用 : 对集合中的元素进行排序操作 (底层红黑树实现)

注意

如果存储的是自定义对象, 还没有实现过 Comparable 接口,运行的时候, 将会出现 ClassCastException

TreeSet的两种排序方式
  • 自然排序
  • 比较器排序

类实现Comparable接口,重写compareTo 方法:

compareTo 方法的返回值:0 集合中只有第一个元素

compareTo 方法的返回值:1 集合中正序排序

compareTo 方法的返回值:-1 集合中倒序排序

自然排序
  • 类实现 Comparable 接口
  • 重写 compareTo 方法
  • 根据方法的返回值, 来组织排序规则
    • 负数 : 左边走
    • 正数 : 右边走
    • 0 : 不存
@Override
    public int compareTo(Student o) {
        //this:发起比较对象   o:被比较对象
        //目标:根据年龄做主要排序条件,根据姓名做次要排序条件,同姓名同年龄需要保存(降序)
        int ruage = o.age - this.age;
        int runame = ruage == 0 ? o.name.compareTo(this.name) : ruage;
        return runame == 0 ? 1 : runame;
    }
比较器排序
  • 在 TreeSet 的构造方法中, 传入 Compartor 接口的实现类对象
  • 重写 compare 方法
  • 根据方法的返回值, 来组织排序规则
    • 负数 : 左边走
    • 正数 : 右边走
    • 0 : 不存
  public static void main(String[] args) {
        //目标:优先根据字符串的长度进行排序(从大到校),如果长度相同,就按照内容进行排序
        TreeSet<String> s = new TreeSet<>(new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                int lengthResult = o2.length() - o1.length();
                return lengthResult == 0 ? o2.compareTo(o1) : lengthResult;
            }
        });
        s.add("aa");
        s.add("aaa");
        s.add("bb");
        s.add("d");
        s.add("cccc");
        System.out.println(s);
    }

重点: 如果同具备自然排序, 和比较器排序, 会优先按照比较器进行排序操作

(Java已经写好的类, 大多数都具有自然排序的规则, 这些规则放在源代码中, 我们无法修改,如果我们要实现的需求, 排序规则, 跟已经具备的自然排序不一样,这时候就要使用比较器排序.)

两种方式中,关于返回值的规则:
  • 如果认为第一个元素大于第二个元素返回正整数即可。
  • 如果认为第一个元素小于第二个元素返回负整数即可。
  • 如果认为第一个元素等于第二个元素返回0即可,此时Treeset集合只会保留一个元素,认为两者重复。

HashSet集合

  • 哈希集集合底层采取哈希表存储数据
  • 哈希表是一种对于增删改查数据性能都较好的结构

特点:去重

注意:HashSet集合,存储元素,想要去重,需要同时重写hashCode和equals方法

hashCode方法介绍
  • 哈希值:是根据某种规则算出来的int类型的整数
  • Public int hashCode():调用底层C++代码计算出的一个随机数(常被人称作地址值)
hashCode方法改造
  • HashCode方法返回的哈希值(int类型的整数)
  • 应该根据类的所有属性值,进行运算,从而降低哈希冲突的概率.
hasCode方法和equals方法的配合流程
  • 当添加对象的时候,会先调用对象的hasCode方法计算出一个应该存入的索引位置,查看该位置.上是否存在元素
    • 不存在:直接存
    • 存在:调用equals方法比较内容
      • false:不存
      • true:存

结论:hashCode方法在重写的时候,就算将对象的所有属性值都参与运算了,也会有相同的可能性

哈希值相同,又叫做哈希冲突
哈希冲突的时候,就会调用equals方法做内容的比较。

注意:

  • 如果只是重写了equals方法,没有重写hashCode方法,为什么没有去重?

    如果没有重写hashCode方法,调用的逻辑,就来自于Object类。
    Object类中的hashCode方法,底层是调用了c++的代码,计算出的随机数

    随机数不相同,也就不会调用equals方法做内容的比较了。

  • 调用对象的hashCode方法计算出应存入的索引位置,是如何计算的?

    1.调用对象的hashcode方法得到原始哈希值,再使用原始哈希值>>>16进行哈希扰动
    2.使用扰动后的哈希值,和原始哈希值做^操作,这叫做二次哈希(再哈希)

    3.使用二次哈希的结果%数组长度(16),计算出应存入的索引位置

    • 源码中并没有直接%数组长度,而是(数组长度-1)&二次哈希的结果
    • 原因:&的二进制操作,要比%的效率更高.
如何提高查询性能
  • 扩容数组

    • 扩容数组的条件

      A:当数组中的元素个数到达了数组长度*加载因子(16*0.75(加载因子)=12)

      • (在第13次添加的时候)扩容成原数组2倍的大小

      B:链表挂载的元素超过了8(阈值)个,并且数组长度没有超过64

  • 链表转红黑树

    某一条链表的节点个数超过了8(阈值)个,并且数组长度到达了64

面试题

HasMap集合,Put方法的添加过程?

​ 首先HashMap底层是哈希表结构,哈希表结构是数组+链表+红黑树的结合.
​ 当我们调用Put方法进行添加操作的时候,会调用对象的hashCode方法来计算出一个应存入的索引位置。
​ 看索引位置上是否为null

  • 是:直接存储
  • 不是:说明该位置上已经存在元素,就会调用equals方法比较内容
    • false:内容不相同,就存储,jdk 8版本是尾插法
    • true:内容相同,就不存

存入的索引位置的计算:

​ 会调用对象的hashcode方法得到原始哈希值,对原始哈希值>>>16进行哈希扰动
​ 使用扰动后的哈希值,和原始哈希值进行^操作,这叫做二次哈希
​ 使用二次哈希后的结果%数组的长度,来计算应存入的索引位置
但是源码中,并不是直接%数组长度,而是(数组长度-1)&二次哈希的结果.(&的二进制操作,要比%的效率更高.)

某一条链表,节点的个数超过了阈值,也就是8,会进行优化.

  • 判断数组长度是否超过了64,
    • 如果没有超过64,会选择扩容数组来进行优化
    • 如果超过了64,就会进行将链表转换成红黑树

LinkedHashSet集合

  • 有序,不重复,无索引
  • 这里的有序指的是保证存储和取出的元素顺序一致
  • 原理:(基于哈希表和双链表)底层数据结构依旧是哈希表,只是每个元素又额外的多了一个双链表的机制记录存储的顺序.

总结

  1. 如果想要集合中的元素可重复

    用ArrayList集合,基于数组的。(用的最多)

  2. 如果想要集合中的元素可重复,而且当前的增删操作明显多于查询

    用LinkedList集合,基于链表的

  3. 如果想对集合中的元素去重

    用HashSet集合,基于哈希表的。 (用的最多)

  4. 如果想对集合中的元素去重,而且保证存取顺序

    用LinkedHashSet集合,基于哈希表和双链表,效率低于HashSet。

  5. 如果想对集合中的元素进行排序

    用TreeSet集合,基于红黑树。后续也可以用List集合实现排序。

Collections集合工具类

  • java.utils.Collections:是集合工具类
  • 作用:Collections并不属于集合,是用来操作集合的工具类
可变参数

可变参数格式:数据类型… 变量名

本质:就是一个数组

注意事项:
  • 如果方法声明了可变参数之外,还有其他的参数,可变参数一定要放在最后。
  • 一个方法中,只能声明一个可变参数
Collections常用方法

15.java-集合_第2张图片

Collections 排序相关API
  • 使用范围:只能对于List集合的排序
排序方式1

public static void sort(List list)

//将集合中元素按照默认规则排序

排序方式2

public static void sort(List list,Comparator c)

//将集合中元素按照指定规则排序

Map接口

  • Map是双列集合的顶层接口,它的功能是全部双列集合都可以继承使用的
  • Map集合是一种双列集合,每个元素包含两个数据
  • Map集合的每个元素的格式key=value(键值对元素)
    • key(键):不允许重复)
    • value(值):允许重复)
      键和值是一 一对应的,每个键只能找到自己对应的值
  • 键+值这个整体,我们称之为“键值对”或者“键值对对象”
    在Java中使用Entry对象表示

注意:

  • 单列集合底层的数据结构,是依赖双列集合

    HashSet —> HashMap
    TreeSet —> TreeMap
    LinkedHashSet —> LinkedHashMap
    重要:双列集合中,数据结构都是针对于键有效.

Map的常见API

  • V put(K key,V value)

​ Put:返回被覆盖掉的值(Value)
​ 细节:这个方法不但能添加,还可以修改(键不存在,直接添加)(键存在,新值覆盖旧值)

  • V remove(Object key)

​ 根据键,删除集合中的键值对,返回被删除的值

15.java-集合_第3张图片

Map集合的遍历方式

  • 通过键找值

    V get(Object key)	//根据键查找对应的值
    
    Set   keySet	//获得Map集合中的所有键
    
public static void main(String[] args) {
        HashMap<Student, String> map = new HashMap<>();
        map.put(new Student("张三",23),"北京");
        map.put(new Student("李四",24),"上海");
        map.put(new Student("王五",25),"广州");
        //1.获取map集合中的所有键
        Set<Student> keySet = map.keySet();
        //遍历set集合,获取每一个键
        for (Student students : keySet) {
            //遍历的过程中调用get方法,根据键找值
            String value = map.get(students);
            System.out.println(students + "----" + value);
        }
    }
  • 通过键值对对象获取键和值

    Set> entrySet()	//获取集合中所有键值对对象
    

    案例解析:

     public static void main(String[] args) {
            HashMap<Student, String> map = new HashMap<>();
            map.put(new Student("张三",23),"北京");
            map.put(new Student("李四",24),"上海");
            map.put(new Student("王五",25),"广州");
            //1.通过enterySet()获取所有键值对对象
            Set<Map.Entry<Student, String>> entrySet = map.entrySet();
            //2.遍历Set集合,获取每一个键值对对象
            for (Map.Entry<Student, String> entery : entrySet) {
                //3.通过键值对对象获取键和值
                Student key = entery.getKey();
                String value = entery.getValue();
                System.out.println(key+"---------"+value);
            }
        }
    
  • 通过foreach方法遍历

    default void forEach(BiConsumer action)	//遍历Map集合, 获取键和值
    

    案例解析:

     public static void main(String[] args) {
     LinkedHashMap<Student, String> lhm = new LinkedHashMap<>();
            lhm.put(new Student("张三",23),"北京");
            lhm.put(new Student("李四",24),"上海");
            lhm.put(new Student("王五",25),"广州");
            lhm.forEach(new BiConsumer<Student, String>() {
                @Override
                public void accept(Student student, String s) {
                    System.out.println(student + "-------" + s);
                }
            });
        }
    
Map接口
  • 双列集合的数据结构,都只针对于键有效,和值没有关系
  • TreeMap : 键(红黑树)
  • HashMap : 键(哈希表)
  • LinkedHashMap : 键(哈希表 + 双向链表)

总结:

  1. HashMap底层是哈希表结构的

  2. 依赖hashCode方法和equals方法保证键的唯一

  3. 如果存储的是自定义对象,需要重写hashCode和equals方法

    如果存储的是自定义对象,不需要重写hashCode和equals方法

() {
@Override
public void accept(Student student, String s) {
System.out.println(student + “-------” + s);
}
});
}


#### Map接口

* 双列集合的数据结构,都只针对于键有效,和值没有关系
* TreeMap : 键(红黑树)
* HashMap : 键(哈希表)
* LinkedHashMap : 键(哈希表 + 双向链表)

总结:

1. HashMap底层是哈希表结构的

2. 依赖hashCode方法和equals方法保证键的唯一

3. 如果**键**存储的是自定义对象,需要重写hashCode和equals方法

 如果**值**存储的是自定义对象,不需要重写hashCode和equals方法

你可能感兴趣的:(java,开发语言)