集合:一个长度可变的容器
ArrayList、LinekdList :有序、可重复、有索引。
HashSet: 无序、不重复、无索引;
LinkedHashSet: 有序、不重复、无索引。
TreeSet:按照大小默认升序排序、不重复、无索引。
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索引
boolean hasNext() //询问当前位置是否有元素存在,存在返回true ,不存在返回false
E next() //获取当前位置的元素,并同时将迭代器对象移向下一个位置,注意防止取出越界。
//获取迭代器
Iterator<String> it = 集合对象.iterator();
//hashNext() : 判断集合中是否还有元素
while(it.hasNext()) {
//next() : 取出集合元素, 并将指针向后移动
String s = it.next();
System.out.println(s);
}
得益于JDK 8开始的新技术Lambda表达式,提供了一种更简单、更直接的遍历集合的方式。
底层原理也是迭代器。
c.forEach(new Consumer<Student>() {
@Override
public void accept(Student student) {
System.out.println(student);
}
});
void add(int index,E element) //在此集合中的指定位置插入指定的元素
E remove(int index) //删除指定索引处的元素,返回被删除的元素
E set(int index,E element) //修改指定索引处的元素,返回被修改的元素
E get(int index) //返回指定索引处的元素
普通for循环(因为List集合存在索引)
迭代器
增强for循环
forEach(Lambda表达式)
当迭代器在遍历的过程中 , 使用集合的添加或删除方法, 则出现并发修改异常
(迭代器对象, 集合对象) 同时操作, 就是并发修改
结论 : 迭代器遍历的过程中, 如果涉及到增删, 请使用迭代器自身的赠删方法
(addAll:将旧集合中的元素复制到新集合)
作用 : 对集合中的元素进行排序操作 (底层红黑树实现)
如果存储的是自定义对象, 还没有实现过 Comparable 接口,运行的时候, 将会出现 ClassCastException
类实现Comparable接口,重写compareTo 方法:
compareTo 方法的返回值:0 集合中只有第一个元素
compareTo 方法的返回值:1 集合中正序排序
compareTo 方法的返回值:-1 集合中倒序排序
@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;
}
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已经写好的类, 大多数都具有自然排序的规则, 这些规则放在源代码中, 我们无法修改,如果我们要实现的需求, 排序规则, 跟已经具备的自然排序不一样,这时候就要使用比较器排序.)
特点:去重
注意:HashSet集合,存储元素,想要去重,需要同时重写hashCode和equals方法
结论: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,就会进行将链表转换成红黑树
如果想要集合中的元素可重复
用ArrayList集合,基于数组的。(用的最多)
如果想要集合中的元素可重复,而且当前的增删操作明显多于查询
用LinkedList集合,基于链表的
如果想对集合中的元素去重
用HashSet集合,基于哈希表的。 (用的最多)
如果想对集合中的元素去重,而且保证存取顺序
用LinkedHashSet集合,基于哈希表和双链表,效率低于HashSet。
如果想对集合中的元素进行排序
用TreeSet集合,基于红黑树。后续也可以用List集合实现排序。
可变参数格式:数据类型… 变量名
本质:就是一个数组
public static void sort(List list)
//将集合中元素按照默认规则排序
public static void sort(List list,Comparator super T> c)
//将集合中元素按照指定规则排序
注意:
单列集合底层的数据结构,是依赖双列集合
HashSet —> HashMap
TreeSet —> TreeMap
LinkedHashSet —> LinkedHashMap
重要:双列集合中,数据结构都是针对于键有效.
Put:返回被覆盖掉的值(Value)
细节:这个方法不但能添加,还可以修改(键不存在,直接添加)(键存在,新值覆盖旧值)
根据键,删除集合中的键值对,返回被删除的值
通过键找值
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 super K,? super V> 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);
}
});
}
总结:
HashMap底层是哈希表结构的
依赖hashCode方法和equals方法保证键的唯一
如果键存储的是自定义对象,需要重写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方法