++## 集合容器概述
集合的特点
集合的特点主要有如下两点:
- 对象封装数据,对象多了也需要存储。集合用于存储对象。
- 对象的个数确定可以使用数组,对象的个数不确定的可以用集合。因为集合是可变长度的。
集合和数组的区别
- 数组是固定长度的;集合可变长度的。
- 数组可以存储基本数据类型,也可以存储引用数据类型;集合只能存储引用数据类型。
- 数组存储的元素必须是同一个数据类型;集合存储的对象可以是不同数据类型。
- 数组采用连续存储空间,删除和添加效率太低
- 数组无直接保存映射关系**(key ----value ;key只能以0—n数字,不能以其他为key用作对应关系)**
- 数组缺乏封装,操作繁琐
使用集合框架的好处
- 容量自增长;
- 提供了高性能的数据结构和算法,使编码更轻松,提高了程序速度和质量;
- 允许不同 API 之间的互操作,API之间可以来回传递集合;
- 可以方便地扩展或改写集合,提高代码复用性和可操作性。
- 通过使用JDK自带的集合类,可以降低代码维护和学习新API成本。
常用的集合类有哪些?
Map接口和Collection接口是所有集合框架的父接口:
* Collection接口的子接口包括:Set接口和List接口
* Map接口的实现类主要有:HashMap、TreeMap、Hashtable、ConcurrentHashMap以及Properties等
* Set接口的实现类主要有:HashSet、TreeSet、LinkedHashSet等
* List接口的实现类主要有:ArrayList、LinkedList、Stack以及Vector等
list ,set,map,三者区别?
list ,set ,map 是否继承自Collection接口?
list ,map,set 三个接口存取元素,各有什么特点?
Java 容器分为 Collection 和 Map 两大类,Collection集合的子接口有Set、List、Queue三种子接口。我们比较常用的是Set、List,Map接口不是collection的子接口。
Collection集合主要有List和Set两大接口
List:有序不唯一
- 一个有序(元素存入集合的顺序和取出的顺序一致)容器,元素可以重复,可以插入多个null元素,元素都有索引。常用的实现类有 ArrayList、LinkedList 和 Vector。
Set:无序唯一
- 一个无序(存入和取出顺序有可能不一致)容器,不可以存储重复元素,只允许存入一个null元素,必须保证元素唯一性。Set 接口常用实现类是 HashSet、LinkedHashSet 以及 TreeSet。
Map:
- 是一个键值对集合,存储键、值和之间的映射。
- Key无序,唯一;
- value 不要求有序,允许重复。
- Map没有继承于Collection接口,从Map集合中检索元素时,只要给出键对象,就会返回对应的值对象。
- Map 的常用实现类:HashMap、TreeMap、HashTable、LinkedHashMap、ConcurrentHashMap
集合框架底层数据结构?
Collection
1. List
Arraylist: Object数组
LinkedList: 双向循环链表
Vector: Object数组
2. Set
HashSet(无序,唯一):基于 HashMap 实现的,底层采用HashMap 来保存元素
LinkedHashSet: LinkedHashSet 继承与 HashSet,并且其内部是通过 LinkedHashMap 来实现的。有点类似于我们之前说的LinkedHashMap 其内部是基于 Hashmap 实现一样,不过还是有一点点区别的。
TreeSet(有序,唯一): 红黑树(自平衡的排序二叉树。)
3. Map
HashMap: JDK1.8之前HashMap由数组+链表组成的,
数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突).
JDK1.8以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间
LinkedHashMap:LinkedHashMap 继承自 HashMap,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外,LinkedHashMap 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。
HashTable: 数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的
TreeMap: 红黑树(自平衡的排序二叉树)
collection
存入的数据可以是Object类型,引用数据类型等存放的数据太杂,存进去容易但取出来不好处理需要强转相应的类型才能用其中的方法;
所以从JDK1.5之后启用了泛型来规范存进去数据类型的一致性。
Collection c =new ArrayList();//创建容器对象
//向容器中添加元素
c.add("sdsk");
c.add(1332);//自动装箱
c.add(true);
collection 集合系列的根接口,用于存放无序,不唯一的元素
方法
add(E e)添加指定元素
addAll();
clear()
contains();
equals()
remove();
size();
泛型
泛型接口:实现该接口重写该类型的方法
1.当实现类实现接口时,泛型的类型确定
泛型类:继承这个类或者,创建这个对象时 new
泛型方法:
1.当前类是泛型类
2.当前方法返回值类型是泛型
public <T> test(T t){
syso
}
泛型的类型必须是引用数据类型,不能使用基本数据类型
List
用于存放有序不唯一的元素,也是一个接口,常用的实现类有:
ArrayList
/*
* 测试ArrayList,有一个班所有的学生的成绩需要统计
* 使用集合存储使用ArrayList:有序(索引顺序),不唯一
* ArrayList的特点:
* 1.底层是一个Object 数组
* 2.默认长度是10;
* 3.扩容每次扩充原来的1.5倍
*
* 4.提供一个内部类:private class Itr implements Iterator<E>{}
* 在Iterator()中实际上就是返回了Itr对象:
* public Iterator<E> iterator(){
* return new Itr();
* }
*/
public class TestArrayList {
public static void main(String[] args) {
// 创建集合1.7版本
List<Integer> list = new ArrayList<>();
// 向集合中添加若干元素
list.add(100);
list.add(89);
List<Integer> list2 = new ArrayList<>();
list2.add(85);
list2.add(63);
list.addAll(list2);
System.out.println(list);
System.out.println("集合中元素的个数" + list.size());
// 数组:length 属性
// 字符串:length 方法
// 集合:size()方法
// 集合的遍历:
// 方式1:for循环
for (int i = 0; i < list.size(); i++) {
Integer integer = list.get(i);
System.out.println(integer);
}
System.out.println("================================");
// 方式二:foreach
for (Integer score : list) {
System.out.println(score);
}
System.out.println("=================================");
// 方式三迭代器iterator
Iterator<Integer> it = list.iterator();
while (it.hasNext()) {
// 取出元素
Integer next = it.next();
System.out.println(next);
}
}
}
ArrayList常用方法
/**
* List中的其他方法
* 增:add ,addAll,addAll(index, collection),addAll(index,element)
* 删:clear()删除全部,
* remove(index):list 方法返回被删除的元素
* remove(Object):是collection中的方法,返回boolean
* 删除指定元素或包装类型对象,
* removeAll()
* 改:set(),list 中的方法,修改指定位置的元素
* 查:get() iterator(),size()
* 其他功能:
*isEmpty()
*contains(60)
containsAll();
*/
public class TestArrayList2 {
public static void main(String[] args) {
//创建ArrayList对象
List<Integer> list = new ArrayList<>();
//添加元素
list.add(67);
list.add(98);
//在指定的索引位置插入添加的数据,
list.add(0, 90);
//list.addAll(index, collection);
System.out.println(list);
//删除方法
//list.clear();
list.remove(new Integer(90));//remove(Object)从collection中继承的方法:删除后索引重新排序,返回boolean
list.remove(1);//remove(index);list 中的方法:返回值为int 类型:所删除的元素
//修改方法
list.set(0, 60);//set(),list 中的方法,修改指定位置的元素
//查询方法
//方法一:for循环
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
//方法二:iterator 迭代器
Iterator<Integer> it = list.iterator();
while(it.hasNext()){
Integer next = it.next();
System.out.println(next);
}
//判断方法
System.out.println("判断集合是否为空"+list.isEmpty());
System.out.println("判断集合中是否包含某个元素"+list.contains(60));//containsAll()
System.out.println(list);
}
}
小练习,保存学生信息
public class StudentList {
public static void main(String[] args) {
List<Student> list = new ArrayList<>();
Student s = new Student("小明",18);
Student s1 = new Student("嚣张",28);
Student s2 = new Student("小王",19);
Student s3 = new Student("小丽",16);
list.add(s);
list.add(s1);
list.add(s2);
list.add(s3);
Student s4=new Student("张大帅",23);
list.add(1, s4);
list.remove(2);
Iterator<Student> it = list.iterator();
while(it.hasNext()){
Student student = it.next();
System.out.println(student);
}
//修改第三个学生的年龄
/*Student s6 =new Student("小丽",23);
list.set(3,s6);*/
list.get(3).setAge(43);//引用数据类型传递的是地址
System.out.println(list);
}
}
ArrayList:底层—对象数组
在内存中分配连续的空间,实现了长度可变的数组
优点:遍历和随机访问元素效率比较高
缺点:添加和删除需大量移动元素效率低,按照内容查询效率底下
LinkedList
采用链表存储方式
缺点:遍历和 随机访问元素效率低下
优点:插入删除头尾数据效率较高 (其他位置前提也是必须要先进行低效率的查询)
* 测试LinkedList 将ArrayList修改为LinkedList后:
* 相同点:
* 方法都不需要修改;结果也一样
* 不同点:ArrayList数据结构采用的是数组
* LinkedList数据结构采用的是链表
Set集合
set 继承collection
用于存储,无序,唯一的数据
set 有三种实现类:
HashSet
/**
* 存放课程信息:java ,php,Andoid,css ,html
* 没有顺序要求,唯一,推荐使用Set集合
* 增:add(e),addAll(collection);
*/
public class TestSet {
public static void main(String[] args) {
Set<String> set=new HashSet<>();
//添加元素
set.add("Java");
set.add("PHP");
Set<String> set1=new HashSet<>();
set1.add("Andoid");
set1.add("css");
set1.add("html");
set.addAll(set1);
//遍历set集合
//set集合不能使用for循环
//foreach
/*for(String s: set){
System.out.println(s);
}*/
//iterator迭代器
set.remove("html");
Iterator<String> iterator = set.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
System.out.println(set);
System.out.println(set.size());
}
LinkedHashSet
public static void main(String[] args) {
//Set<String> set=new HashSet<>();//无序,唯一
//Set<String> set=new LinkedHashSet<>();//有序(插入顺序),唯一
Set<String> set=new TreeSet<>();//有序(自然顺序),唯一
//添加元素
set.add("Java");
set.add("PHP");
List<String> set1=new ArrayList<>();//有序,不唯一
set1.add("Andoid");
set1.add("css");
set1.add("html");
set.addAll(set1);
//遍历set集合
//set集合不能使用for循环
//foreach
/*for(String s: set){
System.out.println(s);
}*/
//iterator迭代器
//set.remove("html");
Iterator<String> iterator = set.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
System.out.println(set);
System.out.println(set.size());
}
}
TreeSet
/**
* 1.使用Hashset,LinkedHashSet时,为什么不能保证唯一呢?
* 因为存放对象key是String类型是唯一的String重写了equals和hashCode对象
* 需要存放对象实例,重写equals方法和hashCode方法
* hashcode 方法用于计算元素应该存放在哪个位置
* equals方法用于比较已有的元素和当前存放的元素是否相同
* 2.Treeset时:抛出异常:因为存储对象没有实现comparable接口重写compareTo方法
* Treeset 是有序的,自然顺序,需要进行比较大小
* TreeSet 是通过二叉树结构保证唯一性和有序性的,要求放进的元素必须实现comparable 接口重写CompareTo方法
*/
public class StudentSet {
public static void main(String[] args) {
//Set<Student> set=new HashSet<>();//无序,唯一(需要重写hashcode ,equals方法)
Set<Student> set=new TreeSet<>();
Student s = new Student("张红",16);
Student s1 = new Student("张三",18);
Student s2 = new Student("张二",19);
Student s3 = new Student("张都市封神",19);
Student s4 = new Student("张二",19);
set.add(s);
set.add(s1);
set.add(s2);
set.add(s3);
set.add(s4);
for (Student stu : set) {
System.out.println(stu);
}
}
}
复习
1.常用的List有哪些?各有什么特点(比较一下)?
ArrayList:
- 底层是数组实现
- 查询遍历效率高
- 插入删除效率较低,大量的移动元素
LinkedList:
- 链表
- 查询效率低
- 删除插入效率高
2.常用的set有哪些?各自有什么特征?
HashSet:唯一, 无序
treeSet:唯一,有序(自然顺序)
linkedHashSet:唯一,有序(插入顺序)
3.list 和Set 做一下比较。
list 有序 不唯一:数组或链表,for循环
set 无序 唯一:哈希表,链表,二叉树,不能for循环
TreeSet外部比较器
Set set=new TreeSet<>(comparator);
Map
key-value 映射(键值对)
HashMap:
- key:唯一 ,无序(hashset)
- value:不唯一,无序(collection)
LinkedHashMap:(有序:插入顺序)
TreeMap:(有序:自然顺序)
public static void main(String[] args) {
//Map <String, String> map=new HashMap<>();//无序, key唯一,value不唯一
//Map <String, String> map=new LinkedHashMap<>();//有序(插入顺序),key唯一,value 不唯一
Map <String, String> map=new TreeMap<>();//有序(key 自然排序)
//向map集合中添加元素
map.put("US","American");
map.put("CN","China");
map.put("us","American");
map.put("US","美国");
map.put("IT","ADSAK");
//打印输出
System.out.println("map的大小"+map.size());
System.out.println(map);
//map集合的遍历
//思路1,先把所有的key取出来,然后遍历所有的key,根据遍历所有的key,根据每一个key取出对应的value
Set<String> keys=map.keySet();
for(String k:keys){
System.out.println(k+"="+ map.get(k));
}
System.out.println("======================================");
//思路二:先把所有key- value都取出来,然后遍历所有的key-value。EntrySet
Set<Entry<String,String>> entrySet = map.entrySet();
Iterator<Entry<String, String>> it = entrySet.iterator();
while(it.hasNext()){
Entry<String, String> next = it.next();
System.out.println(next);
System.out.println("key:"+next.getKey());
System.out.println("value:"+next.getValue());
}
}
Set和map 的关系:
1.Set底层使用的是Map
2.Set只用了Map的key ,存放时将元素放到Map的key位置,value 上放了一个new Object()
map中常用的方法
/**
* 定义一个学生类:,属性:学号,姓名,年龄
* 要求存储学号和学生的映射关系:
* 分析:
* 1.学号能重复吗?不能
* 2.存放到HashMap,linkedHashMap时,需要重写HashCode和equals吗?
* 不需要,定义的学号,引用数据类型Integer实现了hashcode,equals
* 3.存放到TreeMap时,需要实现comparable接口?不需要
* 查看帮助文档:增: 删:改: 查:
*/
public class TestStudentMap {
public static void main(String[] args) {
HashMap<Integer, Student> hashMap = new HashMap<>();
Student s = new Student(1, "张三", 18);
Student s1 = new Student(2, "李四", 19);
Student s2 = new Student(3, "王五", 16);
//向集合中添加元素
hashMap.put(s.getSno(), s);
hashMap.put(s1.getSno(), s1);
hashMap.put(s2.getSno(), s2);
//map集合中常用的方法
System.out.println("map的大小"+hashMap.size());
//查询
System.out.println("字号为2的学生信息"+hashMap.get(2));
//keyset 查询所有学生
System.out.println("查询所有学生的学号:"+hashMap.keySet());
System.out.println("查询所有学生的信息:"+hashMap.values());
System.out.println("entrySet"+hashMap.entrySet());
//删除
//remove 可以删除,也可以查出所要删除了信息
Student remove = hashMap.remove(2);
System.out.println("指定删除内容"+remove);
System.out.println(hashMap);
//hashMap.clear();
//修改 replace ,put
hashMap.replace(3, new Student(4,"圣诞节",15));
System.out.println(hashMap);
//判断方法
System.out.println(hashMap.isEmpty());
System.out.println("判断是否存在某个key"+hashMap.containsKey(1));
System.out.println("判断是否存在某个value"+hashMap.containsValue(s));
}
}
Map的泛型的写法:Map<K,V>
Iterator 迭代器
* 迭代器:
* 1.Iterator专门用于对集合进行遍历使用的:
* 2.哪些集合可以使用迭代器进行遍历?
* Collection :可以
* List:可以
* Set:可以
* Map:不可以*map-->set:map.keyset();
* 3.Iterator :接口:调用iterator()方法得到该接口类型的对象
* 在集合定义中有实现iterator接口的内部类的集合,可以使用迭代器
* 实现了iterable接口的集合可以使用迭代器
* 4. Iterator(接口)迭代器,用于对集合(collection)进行遍历
* Iterable(接口),有一个抽象方法iterator()返回结果是一个Iterator对象(前提是在集合中定义内部类,实现了iterator接口)
* iterator():源头是Iterable接口,返回一个迭代器对象
* 5.foreach,增强for循环,jdk1.5之后可以使用
* foreach循环底层使用了迭代器的方式
* 6,foreach 和迭代器Iterator 的区别:
* foreach可以遍历数组,迭代器不能遍历数组
*
* 7,迭代器中的方法
* hashNext():用于判断迭代器中是否含有下一个元素
* next()用于获取下一个元素
* remove():删除元素
* 前提是必须先调用next方法,而且remove()方法只能在next方法后被调用一次,否则抛出异常
* 同一个迭代器 只能使用一次指针已经指向末尾了在调用hashnext()就返回false
*/
public class TestIterator {
public static void main(String[] args) {
List<String> list=new ArrayList<>();
list.add("java");
list.add("php");
list.add("c");
list.add("c++");
System.out.println(list);
Iterator<String> it = list.iterator();
while(it.hasNext()){
System.out.println(it.next());
it.remove();
}
System.out.println(list);
}
}
/*
* ConcurrentModificationException:在迭代器集合的过程中向集合中添加元素时,发生异常(并发修改异常)
* ListIterator和Iterator的联系与区别
* 联系:Iterator是ListIterator的父类接口:继承
* 区别:1.Iterator给所有的collection 使用;ListIterator只能在list及其实现类中使用
* 2.Iterator 中只有三种方法:hashNext,next,和remove;listIterator中提供了其他很多方法
* 3.Iterator 只能对集合进行正序遍历,lIstIterator可以正序和逆序遍历集合
* 4.Iterator只能实现删除 元素的功能,在修改元素可能会发生并发修改异常;ListIterator可以实现在遍历同时进行增删改操作
* 5,Iterator 中不能获取元素的索引,ListIterator能获取 元素的索引
*/
public class TestIterator1 {
public static void main(String[] args) {
List<Integer> list=new ArrayList<>();
list.add(12);
list.add(15);
list.add(125);
list.add(124);
list.add(121);
System.out.println(list);
ListIterator<Integer> lit = list.listIterator();
//正序遍历(顺序遍历)
while(lit.hasNext()){
System.out.println("上一个索引值"+lit.previousIndex());
System.out.println("下一个索引值"+lit.nextIndex());
Integer num = lit.next();
System.out.println(num);
//修改
//lit.set(12);
//添加
lit.add(0);
}
System.out.println("================================");
//逆序遍历
while(lit.hasPrevious()){
System.out.println("上一个索引值"+lit.previousIndex());
System.out.println("下一个索引值"+lit.nextIndex());
Integer num = lit.previous();
if(0==num){
//去除0
lit.remove();
}
System.out.println(num);
}
System.out.println(list);
}
}
Collections工具类
用于程序员对Collection进行简化操作和maths, Arrays拥有私有构造器,不能创建对象,提供一些静态方法
public static void main(String[] args) {
List<String> list=new ArrayList<String>();
//[1]一次性向集合中添加若干元素
Collections.addAll(list, "java","hsd","sdsdjjs");
System.out.println(list);
//[2]排序(自然顺序)
Collections.sort(list);
//定义使用外部比较器
//Collections.sort(list, new StringLenComparator());
//定义使用匿名内部类
Collections.sort(list, new Comparator<String>() {
@Override
public int compare(String s, String s1) {
return s.length()-s1.length();
}
});
System.out.println(list);
//[3]二分,搜索,前提是必须先有序,如果找不到,返回负数
int index = Collections.binarySearch(list,"hsd");
System.out.println(index);
//[4]MAX 和min
String max = Collections.max(list);
String min = Collections.min(list);
System.out.println(max);
System.out.println(min);
//[5]fill,快速填充,使用指定的元素填充 一个集合
//Collections.fill(list, "*");
//System.out.println(list);
//[6]reverse ,反转集合中的元素
Collections.reverse(list);
System.out.println(list);
//Arrays中asList方法和Collection中的toArray方法
//Array--->List(长度是按照数组中元素大小而定的,不能再增加)
List<Integer> asList = Arrays.asList(23,232,12,32);
System.out.println(asList);
//List-->array
String [] array=list.toArray(new String[0]);//如果给定的数组范围够用,则返回带有集合元素的数组,若是不够用,则创建一个数组返回(包含所有集合元素)
System.out.println("数组"+Arrays.toString(array));
}
Collection接口
List 接口
迭代Iterator是什么?
- Iterator 接口提供遍历任何 Collection 的接口。我们可以从一个 Collection 中使用迭代器方法来获取迭代器实例。迭代器取代了 Java 集合框架中的 Enumeration,迭代器允许调用者在迭代过程中移除元素。
Itertor 怎么使用?有什么特点
- Iterator 的特点是只能单向遍历,但是更加安全,因为它可以确保,在当前遍历的集合元素被更改的时候,就会抛出 ConcurrentModificationException 异常。
Iterator和ListIterator 有什么区别?
- Iterator 可以遍历 Set 和 List 集合,而 ListIterator 只能遍历 List。
- Iterator 只能单向遍历,而 ListIterator 可以双向遍历(向前/后遍历)。
- ListIterator 实现 Iterator 接口,然后添加了一些额外的功能,比如添加一个元素、替换一个元素、获取前面或后面元素的索引位置。
Set接口
说一下 HashSet 的实现原理?
HashSet如何检查重复?HashSet是如何保证数据不可重复的?
HashSet与HashMap的区别
Queue
BlockingQueue是什么?
在 Queue 中 poll()和 remove()有什么区别?
相同点:都是返回第一个元素,并在队列中删除返回的对象。
不同点:如果没有元素 poll()会返回 null,而 remove()会直接抛出 NoSuchElementException 异常。
Map接口
说一下 HashMap 的实现原理?
HashMap概述: HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。
HashMap的数据结构: 在Java编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),所有的数据结构都可以用这两个基本结构来构造的,HashMap也不例外。HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。
HashMap 基于 Hash 算法实现的
当我们往Hashmap中put元素时,利用key的hashCode重新hash计算出当前对象的元素在数组中的下标
存储时,如果出现hash值相同的key,此时有两种情况。
(1)如果key相同,则覆盖原始值;
(2)如果key不同(出现冲突),则将当前的key-value放入链表中
获取时,直接找到hash值对应的下标,在进一步判断key是否相同,从而找到对应值。
理解了以上过程就不难明白HashMap是如何解决hash冲突的问题,核心就是使用了数组的存储方式,然后将冲突的key的对象放入链表中,一旦发现冲突就在链表中做进一步的对比。
需要注意Jdk 1.8中对HashMap的实现做了优化,当链表中的节点数据超过八个之后,该链表会转为红黑树来提高查询效率,从原来的O(n)到O(logn)
HashMap的put方法的具体流程?
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
//实现Map.put和相关方法
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
// 步骤①:tab为空则创建
// table未初始化或者长度为0,进行扩容
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// 步骤②:计算index,并对null做处理
// (n - 1) & hash 确定元素存放在哪个桶中,桶为空,新生成结点放入桶中(此时,这个结点是放在数组中)
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
// 桶中已经存在元素
else {
Node<K,V> e; K k;
// 步骤③:节点key存在,直接覆盖value
// 比较桶中第一个元素(数组中的结点)的hash值相等,key相等
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
// 将第一个元素赋值给e,用e来记录
e = p;
// 步骤④:判断该链为红黑树
// hash值不相等,即key不相等;为红黑树结点
// 如果当前元素类型为TreeNode,表示为红黑树,putTreeVal返回待存放的node, e可能为null
else if (p instanceof TreeNode)
// 放入树中
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
// 步骤⑤:该链为链表
// 为链表结点
else {
// 在链表最末插入结点
for (int binCount = 0; ; ++binCount) {
// 到达链表的尾
//判断该链表尾部指针是不是空的
if ((e = p.next) == null) {
// 在尾部插入新结点
p.next = newNode(hash, key, value, null);
//判断链表的长度是否达到转化红黑树的临界值,临界值为8
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
//链表结构转树形结构
treeifyBin(tab, hash);
// 跳出循环
break;
}
// 判断链表中结点的key值与插入的元素的key值是否相等
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
// 相等,跳出循环
break;
// 用于遍历桶中的链表,与前面的e = p.next组合,可以遍历链表
p = e;
}
}
//判断当前的key已经存在的情况下,再来一个相同的hash值、key值时,返回新来的value这个值
if (e != null) {
// 记录e的value
V oldValue = e.value;
// onlyIfAbsent为false或者旧值为null
if (!onlyIfAbsent || oldValue == null)
//用新值替换旧值
e.value = value;
// 访问后回调
afterNodeAccess(e);
// 返回旧值
return oldValue;
}
}
// 结构性修改
++modCount;
// 步骤⑥:超过最大容量就扩容
// 实际大小大于阈值则扩容
if (++size > threshold)
resize();
// 插入后回调
afterNodeInsertion(evict);
return null;
}
①.判断键值对数组table[i]是否为空或为null,否则执行resize()进行扩容;
②.根据键值key计算hash值得到插入的数组索引i,如果table[i]==null,直接新建节点添加,转向⑥,如果table[i]不为空,转向③;
③.判断table[i]的首个元素是否和key一样,如果相同直接覆盖value,否则转向④,这里的相同指的是hashCode以及equals;
④.判断table[i] 是否为treeNode,即table[i] 是否是红黑树,如果是红黑树,则直接在树中插入键值对,否则转向⑤;
⑤.遍历table[i],判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操作,否则进行链表的插入操作;遍历过程中若发现key已经存在直接覆盖value即可;
⑥.插入成功后,判断实际存在的键值对数量size是否超多了最大容量threshold,如果超过,进行扩容。
HashMap是怎么解决哈希冲突的?
什么是哈希?
这就是把任意长度的输入通过散列算法,变换成固定长度的输出,该输出就是散列值(哈希值)
什么是哈希冲突?
当两个不同的输入值,根据同一散列函数计算出相同的散列值的现象,我们就把它叫做碰撞(哈希碰撞)。
JDK1.8新增红黑树
降低复杂度O(logn)
总结
能否使用任何类作为 Map 的 key?
如果类重写了 equals() 方法,也应该重写 hashCode() 方法。
类的所有实例需要遵循与 equals() 和 hashCode() 相关的规则。
如果一个类没有使用 equals(),不应该在 hashCode() 中使用它。
用户自定义 Key 类最佳实践是使之为不可变的,这样 hashCode() 值可以被缓存起来,拥有更好的性能。不可变的类也可以确保 hashCode() 和 equals() 在未来不会改变,这样就会解决与可变相关的问题了。
为什么HashMap中String、Integer这样的包装类适合作为K?
都是final类型,即不可变性,保证key的不可更改性,不会存在获取hash值不同的情况
内部已重写了equals()、hashCode()等方法实现了Comparable接口
如果使用Object作为HashMap的Key,应该怎么办呢?
1.重写hashCode()是因为需要计算存储数据的存储位置,需要注意不要试图从散列码计算中排除掉一个对象的关键部分来提高性能,这样虽然能更快但可能会导致更多的Hash碰撞;
2.重写equals()方法,需要遵守自反性、对称性、传递性、一致性以及对于任何非null的引用值x,x.equals(null)必须返回false的这几个特性,目的是为了保证key在哈希表中的唯一性;
HashMap 与 HashTable 有什么区别?
线程安全:
HashMap 是非线程安全的,
HashTable 是线程安全的;
HashTable 内部的方法基本都经过 synchronized 修饰(如果你要保证线程安全的话就使用ConcurrentHashMap );
效率:
因为线程安全的问题,HashMap 要比 HashTable 效率高一点。另外,HashTable 基本被淘汰,不要在代码中使用它;
对Null key 和Null value的支持: HashMap 中,null 可以作为键,这样的键只有一个,可以有一个或多个键所对应的值为 null。但是在 HashTable 中 put 进的键值只要有一个 null,直接抛NullPointerException。
**初始容量大小和每次扩充容量大小的不同 **:
①创建时如果不指定容量初始值,Hashtable 默认的初始大小为11,之后每次扩充,容量变为原来的2n+1。HashMap 默认的初始化大小为16。之后每次扩充,容量变为原来的2倍。
②创建时如果给定了容量初始值,那么 Hashtable 会直接使用你给定的大小,而 HashMap 会将其扩充为2的幂次方大小。也就是说 HashMap 总是使用2的幂作为哈希表的大小,后面会介绍到为什么是2的幂次方。
底层数据结构: JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。Hashtable 没有这样的机制。
推荐使用:在 Hashtable 的类注释可以看到,Hashtable 是保留类不建议使用,推荐在单线程环境下使用 HashMap 替代,如果需要多线程使用则用 ConcurrentHashMap 替代。
如何决定使用 HashMap 还是 TreeMap?
对于在Map中插入、删除和定位元素这类操作,HashMap是最好的选择。然而,假如你需要对一个有序的key集合进行遍历,TreeMap是更好的选择。基于你的collection的大小,也许向HashMap中添加元素会更快,将map换为TreeMap进行有序key的遍历。
ConcurrentHashMap 和 Hashtable 的区别?
ConcurrentHashMap 结合了 HashMap 和 HashTable 二者的优势。
HashMap 没有考虑同步,
HashTable 考虑了同步的问题。
但是 HashTable 在每次同步执行时都要锁住整个结构。
ConcurrentHashMap 锁的方式是稍微细粒度的。
ConcurrentHashMap 底层具体实现知道吗?实现原理是什么?
JDK1.7
首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问。
在JDK1.8中,放弃了Segment臃肿的设计,取而代之的是采用Node + CAS + Synchronized来保证并发安全进行实现,synchronized只锁定当前链表或红黑二叉树的首节点,这样只要hash不冲突,就不会产生并发,效率又提升N倍。
Array 和 ArrayList 有何区别?
Array 可以存储基本数据类型和对象,ArrayList 只能存储对象。
Array 是指定固定大小的,而 ArrayList 大小是自动扩展的。
Array 内置方法没有 ArrayList 多,比如 addAll、removeAll、iterator 等方法只有
如何实现 Array 和 List 之间的转换?
Array 转 List: Arrays. asList(array) ;
List 转 Array:List 的 toArray() 方法
comparable 和 comparator的区别?
comparable是出自java.lang包, compareTo(Object obj)方法用来排序
comparator是出自 java.util 包,compare(Object obj1, Object obj2)方法用来排序
Collection 和 Collections 有什么区别?
java.util.Collection 是一个集合接口(集合类的一个顶级接口)
Collections则是集合类的一个工具类/帮助类
TreeMap 和 TreeSet 在排序时如何比较元素?
1.TreeMap 和 TreeSet在存储对象,所存储的对象必须实现Comparable接口,
2.该接口提供了比较元素的compareTo()方法;
3.TreeMap要求存放的键值对映射的键必须实现comparable接口从而对元素进行排序
Collections 工具类中的 sort()方法:传入list进行排序
//[2]排序(自然顺序)
Collections.sort(list);
//定义使用外部比较器
//Collections.sort(list, new StringLenComparator());
//定义使用匿名内部类
Collections.sort(list, new Comparator<String>() {
@Override
public int compare(String s, String s1) {
return s.length()-s1.length();
}
});