集合
集合继承体系
- Collection(接口) 单列"集合"
- List(接口) (列表) 有序可重复
- ArrayList 数组
- LinkList 链表
- Set (接口) 无序不可重复
- HashSet 数组+链表 或 数组+链表+红黑树
- ThreeSet 红黑树
- List(接口) (列表) 有序可重复
- Map(接口) 双列"集合" 键值对 键不可重复
- HashMap 数组+链表 或 数组+链表+红黑树
- ThreeMap 红黑树
该"集合"这是汉语中的语义的统称 具体该为对应单词所表示的东西
常见数据结构的特点
栈:先进后出
队列:先进先出
数组:查找快增删慢
链表:增删快查找慢
树:->树状结构
二叉树:任意一个节点的度要小于等于2 (度: 每一个节点的子节点数量)
查找树(排序树):
- 每一个节点上最多有两个子节点
- 左子树上所有节点的值都小于根节点的值
- 右子树上所有节点的值都大于根节点的值
平衡二叉树:
- 二叉树左右两个子树的高度差不超过1
- 任意节点的左右两个子树都是一颗平衡二叉树
红黑树:
红黑规则
- 每一个节点或是红色的,或者是黑色的
- 根节点必须是黑色
- 如果一个节点没有子节点或者父节点,则该节点相应的指针属性值为Nil,这些Nil 视为叶节点,每个叶节点(Nil)是黑色的
- 如果某一个节点是红色,那么它的子节点必须是黑色(不能出现两个红色节点相连 的情况)
- 对每一个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点
如何实现红黑规则
根节点位置:
直接变为黑色
非根节点位置:
父节点为黑色:
不需要任何操作,默认红色即可
父节点为红色:
叔叔节点为红色:
将"父节点"设为黑色,将"叔叔节点"设为黑色
将"祖父节点"设为红色
如果"祖父节点"为根节点,则将根节点再次变成黑色
叔叔节点为黑色:
将"父节点"设为黑色
将"祖父节点"设为红色
以"祖父节点"为支点进行旋转
Collection中的方法()
返回只类型 | 方法(参数列表) | 用法 |
---|---|---|
boolean | add(E e) | 往集合中添加元素 |
boolean | remove(Object o) | 从该集合中删除指定元素 |
boolean | clear() | 从该集合中删除全部元素 |
default boolean | removeIf(Predicate<? super E> filter) | 删除满足条件的元素 |
int | size() | 返回此集合中的元素数 |
boolean | contains(Object o) | 如果此集合包含指定的元素,则返回 true |
Iterator |
iterator() | 获得迭代器对象 |
default void | forEach(Consumer<? super T> action) | 从迭代器对象中获得的方法(接口中有方法体) |
1.Collection是接口创建对象 需要创建子类对象
2.removeIf()的使用
实现一个 断言的接口 该接口对返回值为真的 元素进行删除
collection.removeIf(new Predicate<Student>() {
@Override
public boolean test(Student student) {
boolean a = student.getName().startsWith("a");
return a;
}
});
3.forEach()的使用
实现一个 消费者的接口 该接口 对接受的 每一个元素进行操作 一般进行遍历 增删会导致并发修改异常
collection.forEach(new Consumer<Student>() {
@Override
public void accept(Student student) {
System.out.println(student);
}
});
4.手写迭代器
https://www.cnblogs.com/acman-mr-lee/p/16413241.html
List中的方法
特点:
- 有序 放入的数据顺序和取出数据顺序的顺序一致
- 有索引 有下标可以直接锁定单一的元素
- 可以放重复的元素 这里的重复指的是 对象封装的信息的相同
方法名 | 描述 |
---|---|
void add(int index,E element) | 在此集合中的指定位置插入指定的元素 |
E remove(int index) | 删除指定索引处的元素,返回被删除的元素 |
E set(int index,E element) | 修改指定索引处的元素,返回被修改的元素 |
E get(int index) | 返回指定索引处的元素 |
ArrayList
底层:数组结构 查询快 增删慢
LinkList
底层:链表结构 增删快 查询慢
特有方法:
方法名 | 说明 |
---|---|
public void addFirst(E e) | 在该列表开头插入指定的元素 |
public void addLast(E e) | 将指定的元素追加到此列表的末尾 |
public E getFirst() | 返回此列表中的第一个元素 |
public E getLast() | 返回此列表中的最后一个元素 |
public E removeFirst() | 从此列表中删除并返回第一个元素 |
public E removeLast() | 从此列表中删除并返回最后一个元素 |
区别:(来自百度)
1、数据结构不同
- ArrayList是Array(动态数组)的数据结构,LinkedList是Link(链表)的数据结构。
2、效率不同
- 当随机访问List (get和set操作)时,ArrayList比LinkedList的效率更高,因为LinkedList是线性的数据存储方式,所以需要移动指针从前往后依次查找。
当对数据进行增加和删除的操作(add和remove操作)时,LinkedList比ArrayList的效率更高,因为ArrayList是数组,所以在其中进行增删操作时,会对操作点之后所有数据的下标索引造成影响,需要进行数据的移动。
3、自由性不同
- ArrayList自由性较低,因为它需要手动的设置固定大小的容量,但是它的使用比较方便,只需要创建,然后添加数据,通过调用下标进行使用;而LinkedList自由性较高,能够动态的随数据量的变化而变化,但是它不便于使用。
4、主要控件开销不同
- ArrayList主要控件开销在于需要在List列表预留一定空间;而LinkList主要控件开销在于需要存储节点信息以及节点指针信息
自己实现对 对象的排序 实现对List的排序
Set中的方法
特点:
-
无序 放入取出的数据 顺序不一致
-
没有索引 不可以用普通for 遍历
-
不可以重复 封装的信息内容不可以重复
HashSet 通过 哈希值+ equals实现 不重
ThreeSet 通过 比较(自身可比,或构造 传 比较器 )实现
方法: 常用无特有方法 和 Collection中的一样
HashSet
方法: 常用无特有方法 和 Collection中的一样
话术:
-
HashSet 底层的数据结构是 数组+链表 ,
-
存放规则是: 底层在添加add时会先比较哈希值 不同则会添加元素 哈希值相同 则比较equals 再根据equals 结果,选择性存放;
-
之所以要比较两次是因为 ,我们为了去除业务上认为的 相同 要重写hashCode()和equals ()方法,
额外使用hash值的原因是为了提高比较的效率,因为如果用equals是需要比较每各属性值的,重写的hashCode()产生的哈希值 将不再是唯一,如果不要equals()方法比较 而直接放弃存储,是按自己的意愿 想强制改变底层 比较流程,
总结:
-
HashSet 底层数据结构是 数组+链表结构(JDK1.7) 数组+链表+红黑树(jdk1.8)当链表 长度大于8时链表会转为红黑树
放元素时是 先根据 哈希值 对 数组长度 模运算 来分组 存放元素 模值相同的元素以链表结构存在
当数组中存放的 链表结构(节点(Node)对象) 的数目为 数组长度的0.75倍时 数组扩容 -
存放规则
去重依赖:重写hashCode()和重写eqalus()
默认情况下 底层在添加add时会先比较哈希值 不同则会添加元素 哈希值相同 则比较equals 再根据equals 结果判断
-
为啥写重写两个---- 为了满足 业务逻辑(我们认为的一样)
不重写hashCode写重写equls 根据添加流程的控制 hash值不同 是要添加元素的
只重写hashCode 不重写eqalus 重写后的哈希值不再唯一 -
为啥要有哈希值呢 为了提高比较的效率
分析:
- HashSet 底层数据结构是 数组+链表结构 (JDK1.7)
- 放元素时是 先根据 哈希值 对 数组长度 模运算 来分组 存放元素 模值相同的元素以链表结构存在
- 当数组中存放的 链表结构(节点(Node)对象) 的数目为 数组长度的0.75倍时 数组扩容
- 添加规则
- 当模运算的结果相同时 通过元素的equals方法比较 相同则不放 不同则放入// (这是自己定义的添加规则和jdk的一样)
补充:默认情况下 底层在添加add时会先比较哈希值值相同 则比较equals 不同则会添加元素 (添加数组null 或已有的节点中)
- HashSet存在自定义类型时 要求属性一样认为是重复
JDK:中的
- 没有重写 haseCode() 是调用父类 该方法是个本地方法 计算时是基于对象的堆地址计算 哈希值的 该值具有唯一性
- 重写 haseCode() 计算基于对象的 属性计算 哈希值 自己写的 不一定唯一
- 没有重写 equals() 是比较 对象的堆内存地址值
- 重写 equals() 比较是对象的 属性进行比较
- 不重写hashCode 不重写 equals
每次new一次 得到一个新的堆内存地址 每次hash值不同都可以放入到HashSet 中 - 重写hashCode 不重写 equals
可能不同属性的 两个对象的哈希值一样 ,而根据添加规则 这样的对象会被添加进去;
(equals 比地址值不同,不同类new 两次)
如果是相同属性的 两个对象 哈希值也一样,而根据添加规则 这样的对象也会被添加进去;
(equals 比地址值不同,相同类new 两次)
- 不重写hashCode 重写 equals
hash值肯定不同 肯定会加 重写 equals 的没有起作用 - 重写hashCode 重写 equals (hasCode重写后不唯一 为了Java的扩展性+equals)
hash值相同的一定放在一组中 equals比较的是内容 相同则 不放入
加equals方法 是为了防止 自己重写的hashCode产生值一样 而不equals 直接不放而产生<误判><-----流程不进行equals
ThreeSet
方法:
和Collection的常用方法一样
新特性:
可排序
利用排序:
1.要么自身可比 即 类本身实现Comparable接口
2.要么再创建集合时传入 比较器对象( Comparator的实现类对象)
推荐使用:比较器的方式, 应为我们用别人的jar 包是.class文件 我们交付的 也是.class文件 自身可比 需要改源码
排序规则
实现Comparable接口重写compareTo() 方法 返回值 int ;实现Comparator接口重写compare()方法 返回值int
- 如果返回值为负数,表示当前存入的元素是较小值,存左边
- 如果返回值为0,表示当前存入的元素跟集合中元素重复了,添加新值返回旧值 看起来没有变
- 如果返回值为正数,表示当前存入的元素是较大值,存右边
add执行
new TreeSet 的对象时 底层用的就是TreeMap对象 add方法就是map对象的add方法;add方法中有这样一个流程 如果 构造中传入了 比较器对象 就用比较器对象的compare()获得比较结果 然后根据结果 添加元素, 如果构造器 对象为 null 就将传入的对象 转为 Comparable 类型 用Comparable 的compareTo() 方法 获得比较结果.
例子: 书写自己的 Comparable 和Comparator 接口实现排序
https://www.cnblogs.com/acman-mr-lee/p/16407524.html
Map
Map的特点
1.双列集合,一个键对应一个值
2.键不可以重复,值可以重复
Map的常用方法
方法名 | 说明 |
---|---|
V put(K key,V value) | 添加元素 增 改 |
V remove(Object key) | 根据键删除键值对元素 删 |
void clear() | 移除所有的键值对元素 |
boolean containsKey(Object key) | 判断集合是否包含指定的键 |
boolean containsValue(Object value) | 判断集合是否包含指定的值 |
boolean isEmpty() | 判断集合是否为空 |
int size() | 集合的长度,也就是集合中键值对的个数 |
V get(Object key) | 根据键获取值 查 |
Set |
获取所有键的集合 查 |
Collection |
获取所有值的集合 查 |
Set<Map.Entry<K,V>> entrySet() | 获取所有键值对对象的集合 查 |
Map的遍历
public class HashMapDemo {
public static void main(String[] args) {
HashMap <Integer,String> address= new HashMap<>();
address.put(101,"阿三面馆");
address.put(102,"阿四粥馆");
address.put(103,"阿五米馆");
address.put(104,"阿三快递");
// set<k>
Set<Integer> keySet = address.keySet();
Iterator<Integer> it = keySet.iterator();
while (it.hasNext()){
Integer key=it.next();
System.out.println(key+"--"+address.get(key));
}
// EntrySet
System.out.println("----------");
Set<Map.Entry<Integer, String>> keyValues = address.entrySet();
for (Map.Entry<Integer, String> entry : keyValues) {
System.out.println(entry.getKey()+"--"+entry.getValue());
}
// 利用不可变集合 实现批量添加
Map<String, String> ssMap = Map.ofEntries(
Map.entry("张三","123"),
Map.entry("李四","125"),
Map.entry("王五","123"));
//ssMap 自身不可以增加 删除 修改
//ssMap.put("张三","111");//不支持的操作异常
Map<String, String> map = new HashMap<>(ssMap);
map.put("赵六","126");
System.out.println(map);
}
}
HashMap
注意事项:
- HashMap底层是哈希表结构的
- 依赖hashCode方法和equals方法保证键的唯一
- 如果键要存储的是自定义对象,需要重写hashCode和equals方法
常用方法: 和Map一样
ThreeMap
注意事项:
- TreeMap底层是红黑树结构
- 依赖自然排序或者比较器排序,对键进行排序
- 如果键存储的是自定义对象,需要实现Comparable接口或者在创建TreeMap对象时候给出比较器排序规则
常用方法: 和Map一样
不可变集合
用途: 结合集合的带参构造,实现集合的批量添加
- 在List、Set、Map接口中,都存在of方法,可以创建一个不可变的集合
- 这个集合不能添加,不能删除,不能修改
- 但是可以结合集合的带参构造,实现集合的批量添加
- 在Map接口中,还有一个ofEntries方法可以提高代码的阅读性
- 首先会把键值对封装成一个Entry对象,再把这个Entry对象添加到集合当中
- Arrays.asList() 返回的是一个不可变List集合可以修改值
Arrays.asList() 详解 超链
https://www.cnblogs.com/acman-mr-lee/p/16407474.html
可变参数
语法:
修饰符 返回值类型 方法名(数据类型… 变量名) { }
本质:
- 这里的变量其实是一个引用类型的数组
- 如果一个方法有多个参数,包含可变参数,可变参数要放在最后
- 如果是可变参:调用者可以传入一个元素, 或者不传(得到是一个长度为0的数组,数组不是null), 或者传入多个元素, 或者传入数组都行