以下内容从网上摘抄整理而来,仅用于本人知识积累
Java 集合,也称作容器,主要是由两大接口 (Interface) 派生出来的:Collection
和 Map
顾名思义,容器就是用来存放数据的。
那么这两大接口的不同之处在于:
- Collection 存放单一元素,由List、Queue、Set三个接口区分不同的集合特征,然后由下面的具体的类来实现对应的功能。
- Map 存放 key-value 键值对,可以用键对象来查找值。
特点
-
对象封装数据,对象多了也需要存储。集合用于存储对象。
-
对象的个数确定可以使用数组,对象的个数不确定的可以用集合。因为集合是可变长度的。
集合和数组的区别
- 数组是固定长度的;集合可变长度的。
- 数组可以存储基本数据类型,也可以存储引用数据类型;集合只能存储引用数据类型。
- 数组存储的元素必须是同一个数据类型;集合存储的对象可以是不同数据类型。
数据结构:就是容器中存储数据的方式。
对于集合容器,有很多种。因为每一个容器的自身特点不同,其实原理在于每个容器的内部数据结构不同。
集合容器在不断向上抽取过程中,出现了集合体系。使用一个体系的原则:参阅顶层内容。建立底层对象。
使用集合框架的好处
- 容量自增长;
- 提供了高性能的数据结构和算法,使编码更轻松,提高了程序速度和质量;
- 允许不同 API 之间的互操作,API之间可以来回传递集合;
- 可以方便地扩展或改写集合,提高代码复用性和可操作性。
- 通过使用JDK自带的集合类,可以降低代码维护和学习新API成本。
Iterator接口
Iterator接口,用于遍历集合元素的接口。
在Iterator接口中定义了三个方法:
修饰与类型 | 方法与描述 |
---|---|
boolean | hasNext() 如果仍有元素可以迭代,则返回true。 |
E | next() 返回迭代的下一个元素。 |
void | remove() 从迭代器指向的 collection 中移除迭代器返回的最后一个元素(可选操作)。 |
每一个集合都有自己的数据结构(就是容器中存储数据的方式),都有特定的取出自己内部元素的方式。为了便于操作所有的容器,取出元素。将容器内部的取出方式按照一个统一的规则向外提供,这个规则就是Iterator接口,使得对容器的遍历操作与其具体的底层实现相隔离,达到解耦的效果。
也就说,只要通过该接口就可以取出Collection集合中的元素,至于每一个具体的容器依据自己的数据结构,如何实现的具体取出细节,这个不用关心,这样就降低了取出元素和具体集合的耦合性。
使用迭代器遍历集合元素
public static void main(String[] args) {
List<String> list1 = new ArrayList<>();
list1.add("abc0");
list1.add("abc1");
list1.add("abc2");
// while循环方式遍历
Iterator it1 = list1.iterator();
while (it1.hasNext()) {
System.out.println(it1.next());
}
// for循环方式遍历
for (Iterator it2 = list1.iterator(); it2.hasNext(); ) {
System.out.println(it2.next());
}
}
使用Iterator迭代器进行删除集合元素,则不会出现并发修改异常。
因为:在执行remove操作时,同样先执行checkForComodification(),然后会执行ArrayList的remove()方法,该方法会将modCount值加1,这里我们将expectedModCount=modCount,使之保持统一。
注意:
(1)Iterator只能单向移动。
(2)Iterator.remove()是 唯一安全 的方式来在迭代过程中修改集合;如果在迭代过程中以任何其它的方式修改了基本集合将会产生未知的行为。而且每调用一次next()方法,remove()
方法 只能被调用一次,如果违反这个规则将抛出一个异常。
ListIterator接口
ListIterator是一个功能更加强大的迭代器, 它继承于Iterator接口,只能用于各种List类型的访问。可以通过调用 listIterator()
方法产生一个指向List开始处的ListIterator, 还可以调用 listIterator(n)
方法创建一个一开始就指向列表索引为n的元素处的ListIterator。
特点
- 允许我们向前、向后两个方向遍历 List;
- 在遍历时修改 List 的元素;
- 遍历时获取迭代器当前游标所在位置。
常用API
修饰与类型 | 方法与描述 |
---|---|
void | add(E e) 将指定的元素插入到列表 (可选操作)。 |
boolean | hasNext() 如果此列表迭代器在前进方向还有更多的元素时,返回 true 。 |
boolean | hasPrevious() 如果此列表迭代器在相反方向还有更多的元素时,返回 true 。 |
E | next() 返回列表中的下一个元素和光标的位置向后推进。 |
int | nextIndex() 返回调用 next() 后返回的元素索引。 |
E | previous() 返回列表中的上一个元素和光标的位置向前移动。 |
int | previousIndex() 返回调用 previous() 后返回的元素索引 。 |
void | remove() 删除列表中调用 next() 或 previous() 的返回最后一个元素。 |
void | set(E e) 用指定元素替换列表中调用 next() 或 previous() 的返回最后一个元素。 |
import java.util.*;
public class TestListIterator{
public static void main(String[] args) {
ArrayList<String> a = new ArrayList<String>();
a.add("aaa");
a.add("bbb");
a.add("ccc");
System.out.println("Before iterate : " + a);
ListIterator<String> it = a.listIterator()
while (it.hasNext()) {
System.out.println(it.next() + ", " + it.previousIndex() + ", " + it.nextIndex());
}
while (it.hasPrevious()) { //向前遍历
System.out.print(it.previous() + " ");
}
System.out.println();
it = a.listIterator(1);//调用listIterator(n)方法创建一个一开始就指向列表索引为n的元素处的ListIterator。
while (it.hasNext()) {
String t = it.next();
System.out.println(t);
if ("ccc".equals(t)) {
it.set("nnn");
} else {
it.add("kkk");
}
}
System.out.println("After iterate : " + a);
}
}
(1)双向移动(向前/向后遍历).
(2)产生相对于迭代器在列表中指向的当前位置的前一个和后一个元素的索引.
(3)可以使用set()方法替换它访问过的最后一个元素.
(4)可以使用add()方法在next()方法返回的元素之前或previous()方法返回的元素之后插入一个元素.
Iterator和ListIterator区别
- ListIterator有add()方法,可以向List中添加对象,而Iterator不能
- ListIterator和Iterator都有hasNext()和next()方法,可以实现顺序向后遍历,但是ListIterator有hasPrevious()和previous()方法,可以实现逆向(顺序向前)遍历。Iterator就不可以。
- ListIterator可以定位当前的索引位置,nextIndex()和previousIndex()可以实现。Iterator没有此功能。
- 都可实现删除对象,但是ListIterator可以实现对象的修改,set()方法可以实现。Iierator仅能遍历,不能修改。
Collection接口
所有集合类都位于java.util包下。Java的集合类主要由两个接口派生而出:Collection和Map,Collection和Map是Java集合框架的根接口,这两个接口又包含了一些子接口或实现类。
-
Collection一次存一个元素,是单列集合;
-
Map一次存一对元素,是双列集合。Map存储的一对元素:键–值,键(key)与值(value)间有对应(映射)关系。
单列集合继承关系图
Collection集合主要有List和Set两大接口
-
List:有序(元素存入集合的顺序和取出的顺序一致),元素都有索引。元素可以重复。
-
Set:无序(存入和取出顺序有可能不一致),不可以存储重复元素。必须保证元素唯一性。
List集合
List是元素有序并且可以重复的集合。
List的主要实现:
- Arraylist: 使用
Object[]
数组 实现 List - LinkedList: 使用双向链表(JDK1.6 之前为循环链表,JDK1.7 取消了循环)实现 List
- Vector:使用
Object[]
数组 实现 List,是一个已经被弃用的类,因为他是线程同步的,而我们平时使用的时候都是非同步的,使用同步的坏处就是会在一个记录上加锁,防止多个程序访问同一条数据导致数据不同步。这样会导致访问速度变慢。 - Stack :通过类名可以知道该类实现了栈的功能,Stack继承自Vector,因此也是由数组来实现,并且线程安全。是满足 “后进先出” 规则的容器(想象一下羽毛球筒),注意 LinkedList 可以实现所有的栈功能。(已弃用)
补充
双向链表
双向链表属于链表的一种,也叫双链表。双向即是说它的链接方向是双向的,它由若干个节点组成,每个节点都包含下一个节点和上一个节点的指针,所以从双向链表的任意节点开始,都能很方便访问他的前驱结点和后继节点。
双链表特点
- 创建双链表时无需指定链表的长度。
- 比起单链表,双链表需要多一个指针用于指向前驱节点,所以需要存储空间比单链表多一点。
- 双链表的插入和删除需要同时维护 next 和 prev 两个指针。
- 双链表中的元素访问需要通过顺序访问,即要通过遍历的方式来寻找元素。
双向循环链表
前面的双向链表的 head 节点和链尾没有连接关系,所以如果要访问最后一个节点的话需要从头开始遍历,直到最后一个节点。在双向链表基础上改进一下,把 header 节点的 prev 指针指向最后一个节点,而最后一个节点的 next 指针指向 header 节点,于是便构成双向循环链表。
双向链表: 包含两个指针,一个 prev 指向前一个节点,一个 next 指向后一个节点。
双向循环链表: 最后一个节点的 next 指向 head,而 head 的 prev 指向最后一个节点,构成一个环。
ArrayList
ArrayList是一个可以动态增长的数组。
我们都知道Java中的数组一旦指定了长度就不可以改变,如果我们在业务中需要使用动态的数组,就可以使用ArrayList
ArrayList默认的长度是10,如果我们插入的数据超过了10,ArrayList会不断的自我增长,这一原理的实现我们会在以后介绍
ArrayList由于底层是使用数组实现的,所以随机访问速度快,插入删除较慢
toArray:把LinkedList转化为Array
LinkedList
LinkedList是使用链表实现的容器。
在列表中插入和删除速度快,但是查找需要遍历整个链表,速度较慢
使用LinkedList可以实现很多队列、栈的数据结构,并且有很多方法很类似,但是有细小的差别
- getFirst和element都返回列表的头,但是不删除它,如果列表为空,抛出异常
- peek实现的功能一样,但是列表为空时返回null
- removeFirst和remove都是删除并返回列表的头,如果列表为空抛出异常
- pool实现的功能一样,但是列表为空时返回null
使用LinkedList可以实现一个栈的功能,下面让我们写一个属于自己的栈
public class MyStack<T> {
private LinkedList<T> storage = new LinkedList<T>();
/*
* 进栈
*/
public void push(T v){
storage.addFirst(v);
}
/**
* 窥视栈顶
* @return
*/
public T peek(){
return storage.getFirst();
}
/**
* 出栈
* @return
*/
public T pop(){
return storage.removeFirst();
}
public boolean empty(){
return storage.isEmpty();
}
public String toString(){
return storage.toString();
}
}
Queue
队列是一个满足“先进先出”的数据结构。
LinkedList提供了方法支持队列操作,并且实现了Queue接口,所以LinkedList是队列的一种实现,可以通过LinkedList向上转型为Queue
Queue 的方法官网都总结好了,它有两组 API,基本功能是一样的,一组是会抛异常的;另一组会返回一个特殊值。
功能 | 抛异常 | 返回值 |
---|---|---|
增 | add(e) | offer(e) |
删 | remove() | poll() |
获取 | element() | peek() |
- add 将一个元素插入队尾,禁止添加
null
,在添加失败(比如队列已满)时会报 一些运行时错误 - offer:将一个元素插入队尾,禁止添加
null
,添加失败返回false
- poll:移除并返回队头,队列为空返回null
- remove:移除并返回头部,队列为空时报
NoSuchElementException
- peek:获取但不删除,队列为空返回null
- element:获取但不删除,队列为空报错
- 虽然 LinkedList 没有禁止添加 null,但是一般情况下 Queue 的实现类都不允许添加 null 元素,为啥呢?因为 poll(), peek() 方法在异常的时候会返回 null,你添加了 null 以后,当获取时不好分辨究竟是否正确返回。
- Queue 一般都是 FIFO (头部添加,尾部删除) 的,但是也有例外,比如优先队列 priority queue(它的顺序是根据自然排序或者自定义 comparator 的);再比如 LIFO 的队列(跟栈一样,后来进去的先出去)。
不论进入、出去的先后顺序是怎样的,使用 remove(),poll() 方法操作的都是 头部 的元素;而插入的位置则不一定是在队尾了,不同的 queue 会有不同的插入逻辑。
Deque
Deque 是两端都可以进出的,那自然是有针对 First 端的操作和对 Last 端的操作,那每端都有两组,一组抛异常,一组返回特殊值:
功能 | 抛异常 | 返回值 |
---|---|---|
增 | addFirst(e)/ addLast(e) | offerFirst(e)/ offerLast(e) |
删 | removeFirst()/ removeLast() | pollFirst()/ pollLast() |
瞧 | getFirst()/ getLast() | peekFirst()/ peekLast() |
堆
堆其实就是一种特殊的队列——优先队列。
普通的队列游戏规则很简单:就是先进先出;但这种优先队列搞特殊,不是按照进队列的时间顺序,而是按照每个元素的优先级来比拼,优先级高的在堆顶。
这里要区别于操作系统里的那个“堆”,这两个虽然都叫堆,但是没有半毛钱关系,都是借用了 Heap 这个英文单词而已。
ArrayList、LinkedList、Vector 的区别
ArrayList | LinkedList | Vector | |
---|---|---|---|
底层实现 | 数组 | 双向链表 | 数组 |
同步性及效率 | 不同步,非线程安全,效率高,支持随机访问 | 不同步,非线程安全,效率高 | 同步,线程安全,效率低 |
特点 | 查询快,增删慢 | 查询慢,增删快 | 查询快,增删慢 |
默认容量 | 10 | / | 10 |
内存空间占用 | ArrayList 的空 间浪费主要体现在在 list 列表的结尾会预留一定的容量空间 | LinkedList 的空间花费则体现在它的每一个元素都需要消耗比 ArrayList 更多的空间(因为要存放直接后继和直接前驱以及数据) | 同ArrayList |
遍历时操作元素
遍历集合时,同时操作集合中的元素(增删等)
[a,b,b,c,d,e]
/**
* Description: for循环遍历
* 输出结果:
* [a, b, c, d, e]
* 由结果可知,第二个元素b并未删除,原因是当第一个元素b被删除后,它后面所有的元素都向前移动了一个单位,循环时导致第二个元素b漏掉了
*/
public static void remove(List<String> list) {
for (int i = 0; i < list.size(); i++) {
String s = list.get(i);
if (s.equals("b")) {
list.remove(s);
}
}
}
/**
* Description: foreach循环遍历
*
* 会报错:java.util.ConcurrentModificationException。
* 这是因为在这里,foreach循环遍历容器本质上是使用迭代器进行遍历的,会对修改次数modCount进行检查,不允许集合进行更改操作
*/
public static void remove2(List<String> list) {
for (String s : list) {
if (s.equals("b")) {
list.remove(s);
}
System.out.println(s);
}
}
/**
* Description: 使用迭代器遍历
*/
public static void remove3(List<String> list) {
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String s = it.next();
if (s.equals("b")) {
it.remove();
}
}
}
Set集合
set代表的是数学上的集合的意思,所以Set集合元素无序(存入和取出的顺序不一定一致),并且没有重复对象。
Set的主要实现类:
- HashSet(无序,唯一):基于 HashMap 实现的,底层采用 HashMap 来保存元素。主要特点是无序的,基本操作都是 O(1) 的时间复杂度,很快。
- TreeSet(有序,唯一): 红黑树(自平衡的排序二叉树),特点是可以有序,可以用自然排序或者自定义比较器来排序;缺点就是查询速度没有 HashSet 快。
- LinkedHashSet: 这个是一个 HashSet + LinkedList 的结构,特点就是既拥有了 O(1) 的时间复杂度,又能够保留插入的顺序。
那每个 Set 的底层实现其实就是对应的 Map:
数值放在 map 中的 key 上,value 上放了个 PRESENT,是一个静态的 Object,相当于 place holder(占位符),每个 key 都指向这个 object。
Set常用方法
HashSet、TreeSet、LinkedHashSet的区别
HashSet | TreeSet | LinkedHashSet | |
---|---|---|---|
底层实现 | HashMap | 红黑树 | LinkedHashMap |
重复性 | 不允许重复 | 不允许重复 | 不允许重复 |
有无序 | 无序 | 有序,支持两种排序方式,自然排序和定制排序,其中自然排序为默认的排序方式。 | 有序,以元素插入的顺序来维护集合的链接表 |
时间复杂度 | add(),remove(),contains()方法的时间复杂度是O(1) | add(),remove(),contains()方法的时间复杂度是O(logn) | LinkedHashSet在迭代访问Set中的全部元素时,性能比HashSet好,但是插入时性能稍微逊色于HashSet,时间复杂度是 O(1)。 |
同步性 | 不同步,线程不安全 | 不同步,线程不安全 | 不同步,线程不安全 |
null值 | 允许null值 | 不支持null值,会抛出 java.lang.NullPointerException 异常。因为TreeSet应用 compareTo() 方法于各个元素来比较他们,当比较null值时会抛出 NullPointerException异常。 | 允许null值 |
比较 | equals() | compareTo() | equals() |
HashSet如何检查重复
当你把对象加入HashSet时,HashSet会先计算对象的hashcode值来判断对象加入的位置,同时也会与其他加入的对象的hashcode值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同hashcode值的对象,这时会调用equals()方法来检查hashcode相等的对象是否真的相同。如果两者相同,HashSet就不会让加入操作成功。 ---- 《Head fist java》 第二版
hashCode()与equals()的相关规定:
- 如果两个对象相等,则hashcode一定也是相同的
- 两个对象相等,equals方法返回true
- 两个对象有相同的hashcode值,它们也不一定是相等的
- 综上,equals方法被覆盖过,则hashCode方法也必须被覆盖
- hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)。
总结:
HashSet是一个通用功能的Set,而LinkedHashSet 提供元素插入顺序保证,TreeSet是一个SortedSet(有序集合)实现,由Comparator 或者 Comparable指定的元素顺序存储元素。
comparable 和 Comparator 的区别
- comparable 接口实际上是出自
java.lang
包 它有一个compareTo(Object obj)
方法用来排序 - comparator接口实际上是出自
java.util
包它有一个compare(Object obj1, Object obj2)
方法用来排序
一般我们需要对一个集合使用自定义排序时,我们就要重写 compareTo()
方法或 compare()
方法,当我们需要对某一个集合实现两种排序方式,比如一个 song 对象中的歌名和歌手名分别采用一种排序方法的话,我们可以重写 compareTo()
方法和使用自制的 Comparator
方法或者以两个 Comparator
来实现歌名排序和歌星名排序,第二种代表我们只能使用两个参数版的 Collections.sort().
Comparator 定制排序
ArrayList<Integer> arrayList = new ArrayList<Integer>();
arrayList.add(-1);
arrayList.add(3);
arrayList.add(3);
arrayList.add(-5);
arrayList.add(7);
arrayList.add(4);
arrayList.add(-9);
arrayList.add(-7);
System.out.println("原始数组:");
System.out.println(arrayList);
// void reverse(List list):反转
Collections.reverse(arrayList);
System.out.println("Collections.reverse(arrayList):");
System.out.println(arrayList);
// void sort(List list),按自然排序的升序排序
Collections.sort(arrayList);
System.out.println("Collections.sort(arrayList):");
System.out.println(arrayList);
// 定制排序的用法
Collections.sort(arrayList, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2.compareTo(o1);
}
});
System.out.println("定制排序后:");
System.out.println(arrayList);
原始数组:
[-1, 3, 3, -5, 7, 4, -9, -7]
Collections.reverse(arrayList):
[-7, -9, 4, 7, -5, 3, 3, -1]
Collections.sort(arrayList):
[-9, -7, -5, -1, 3, 3, 4, 7]
定制排序后:
[7, 4, 3, 3, -1, -5, -7, -9]
重写 compareTo 方法实现按年龄来排序
// person对象没有实现Comparable接口,所以必须实现,这样才不会出错,才可以使treemap中的数据按顺序排列
// 前面一个例子的String类已经默认实现了Comparable接口,详细可以查看String类的API文档,另外其他
// 像Integer类等都已经实现了Comparable接口,所以不需要另外实现了
public class Person implements Comparable<Person> {
private String name;
private int age;
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
/**
* T重写compareTo方法实现按年龄来排序
*/
@Override
public int compareTo(Person o) {
if (this.age > o.getAge()) {
return 1;
}
if (this.age < o.getAge()) {
return -1;
}
return 0;
}
}
public static void main(String[] args) {
TreeMap<Person, String> pdata = new TreeMap<Person, String>();
pdata.put(new Person("张三", 30), "zhangsan");
pdata.put(new Person("李四", 20), "lisi");
pdata.put(new Person("王五", 10), "wangwu");
pdata.put(new Person("小红", 5), "xiaohong");
// 得到key的值的同时得到key所对应的值
Set<Person> keys = pdata.keySet();
for (Person key : keys) {
System.out.println(key.getAge() + "-" + key.getName());
}
}
5-小红
10-王五
20-李四
30-张三
无序性和不可重复性的含义是什么
1、什么是无序性?无序性不等于随机性 ,无序性是指存储的数据在底层数组中并非按照数组索引的顺序添加 ,而是根据数据的哈希值决定的。
2、什么是不可重复性?不可重复性是指添加的元素按照 equals()判断时 ,返回 false,需要同时重写 equals()方法和 HashCode()方法。
Map接口
Map 是一种把键对象和值对象映射的集合,它的每一个元素都包含一对键对象和值对象。
Map没有继承于Collection接口,从Map集合中检索元素时,只要给出键对象,就会返回对应的值对象。
Map的键值对都可以为null
Map可以多维扩展。例如一个人拥有多个宠物,你可以这样定义:Map< Person, List< pet>>
Map 的常用实现类:HashMap、TreeMap、HashTable、LinkedHashMap、ConcurrentHashMap
双列集合继承关系图
Map常见方法
- Object put(Object key, Object value):放进一个键值对,返回值是被替换的值
- Object remove(Object key)
- void putAll(Map mapping)
- void clear()
- boolean containsKey(Object key)是否包含某个键
- boolean containsValue(Object value)是否包含某个值
在Map中比较重要的是处理键值对的集合
- public Set keySet():返回这个Map的所有键的集合,因为Map中键是唯一的,所以返回使用一个set
- public Collection values():返回这个Map的所有值的集合,因为值可能重复,所以返回一个Collection
- public Set entrySet():返回一个实现Map.Entry接口对象集合,使用这个方法可以遍历每一条记录。
//遍历Map示范代码:
for(Map.Entry<String, String> file : films.entrySet()){
String title = file.getKey();
String videoUrl = file.getValue();
}
HashMap、HashTable、TreeMap的区别
- TreeMap:基于红黑树实现。
- HashMap:基于哈希表实现。
- HashTable:和 HashMap 类似,但它是线程安全的。现在可以使用 ConcurrentHashMap 来支持线程安全,并且 ConcurrentHashMap 的效率会更高,因为 ConcurrentHashMap 引入了分段锁。
- LinkedHashMap:使用双向链表来维护元素的顺序,顺序为插入顺序或者 最近最少使用(LRU)顺序。
HashMap | HashTable | TreeMap | |
---|---|---|---|
底层实现 | 哈希表(数组+链表) | 哈希表(数组+链表) | 红黑树 |
同步性 | 线程不同步 | 同步 | 线程不同步 |
null值 | 允许 key 和 Vale 是 null,但 null 作为键只能有一个,null 作为值可以有多个,且这个元素存放在哈希表 0 角标位置 | 不允许key、value 是 null | value允许为null。 当未实现 Comparator 接口时,key 不可以为null当实现 Comparator 接口时,若未对 null 情况进行判断,则可能抛 NullPointerException 异常。如果针对null情况实现了存入,就不能正常使用get()访问,只能通过遍历去访问。 |
hash | 使用hash(Object key)扰动函数对 key 的 hashCode 进行扰动后作为 hash 值 | 直接使用 key 的 hashCode() 返回值作为 hash 值 | |
容量 | 容量为 2^4 且容量一定是 2^n | 默认容量是11,不一定是 2^n | |
扩容 | 两倍,且哈希桶的下标使用 &运算代替了取模 | 2倍+1,取哈希桶下标是直接用模运算 |
HashMap在JDK1.7和JDK1.8中有哪些不同
不同 | JDK 1.7 | JDK 1.8 |
---|---|---|
存储结构 | 数组 + 链表 | 数组 + 链表 + 红黑树 |
初始化方式 | 单独函数:inflateTable() | 直接集成到了扩容函数resize()中 |
hash值计算方式 | 扰动处理 = 9次扰动 = 4次位运算 + 5次异或运算 | 扰动处理 = 2次扰动 = 1次位运算 + 1次异或运算 |
存放数据的规则 | 无冲突时,存放数组;冲突时,存放链表 | 无冲突时,存放数组;冲突 & 链表长度 < 8:存放单链表;冲突 & 链表长度 > 8:树化并存放红黑树 |
插入数据方式 | 头插法(先将原位置的数据移到后1位,再插入数据到该位置) | 尾插法(直接插入到链表尾部/红黑树) |
扩容后存储位置的计算方式 | 全部按照原来方法进行计算(即hashCode ->> 扰动函数 ->> (h&length-1)) | 按照扩容后的规律计算(即扩容后的位置=原位置 or 原位置 + 旧容量) |
HashMap 和 HashSet 区别
如果你看过 HashSet
源码的话就应该知道:HashSet
底层就是基于 HashMap
实现的。(HashSet
的源码非常非常少,因为除了 clone()、writeObject()、readObject()
是 HashSet
自己不得不实现之外,其他方法都是直接调用 HashMap
中的方法。
HashMap | HashSet |
---|---|
实现了 Map 接口 | 实现 Set 接口 |
存储键值对 | 仅存储对象 |
调用 put()向 map 中添加元素 | 调用 add()方法向 Set 中添加元素 |
HashMap 使用键(Key)计算 hashcode | HashSet 使用成员对象来计算 hashcode 值,对于两个对象来说 hashcode 可能相同,所以equals()方法用来判断对象的相等性 |
LinkedHashMap
LinkedHashMap 继承自 HashMap,在 HashMap 基础上,通过维护一条双向链表,解决了 HashMap 不能随时保持遍历顺序和插入顺序一致的问题。除此之外,LinkedHashMap 对访问顺序也提供了相关支持。在一些场景下,该特性很有用,比如缓存。在实现上,LinkedHashMap 很多方法直接继承自 HashMap,仅为维护双向链表覆写了部分方法。所以,要看懂 LinkedHashMap 的源码,需要先看懂 HashMap 的源码。
HashMap的底层是基于拉链式散列结构。该结构由数组和链表或红黑树组成,结构示意图大致如下:
LinkedHashMap 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。其结构可能如下图:
上图中,淡蓝色的箭头表示前驱引用,红色箭头表示后继引用。每当有新键值对节点插入,新节点最终会接在 tail 引用指向的节点后面。而 tail 引用则会移动到新的节点上,这样一个双向链表就建立起来了。
集合工具类Collections
Collections:集合工具类,方便对集合的操作。这个类不需要创建对象,内部提供的都是静态方法。
静态方法:
Collections.sort(list);//list集合进行元素的自然顺序排序。
Collections.sort(list,new ComparatorByLen());//按指定的比较器方法排序。
class ComparatorByLen implements Comparator<String>{
public int compare(String s1,String s2){
int temp = s1.length()-s2.length();
return temp==0?s1.compareTo(s2):temp;
}
}
Collections.max(list);//返回list中字典顺序最大的元素。
int index = Collections.binarySearch(list,"zz");//二分查找,返回角标。
Collections.reverseOrder();//逆向反转排序。
Collections.shuffle(list);//随机对list中的元素进行位置的置换。
//将非同步集合转成同步集合的方法:Collections中的 XXX synchronizedXXX(XXX);
//原理:定义一个类,将集合所有的方法加同一把锁后返回。
List synchronizedList(list);
Map synchronizedMap(map);
Collection 和 Collections的区别
Collections是个java.util下的类,是针对集合类的一个工具类,提供一系列静态方法,实现对集合的查找、排序、替换、线程安全化(将非同步的集合转换成同步的)等操作。
Collection是个java.util下的接口,它是各种集合结构的父接口,继承于它的接口主要有Set和List,提供了关于集合的一些操作,如插入、删除、判断一个元素是否其成员、遍历等。
数组工具类 Arrays
用于操作数组对象的工具类,里面都是静态方法。
数组 -> 集合:asList方法,将数组转换成list集合。
String[] arr ={"abc","kk","qq"};
List<String> list =Arrays.asList(arr);//将arr数组转成list集合。
将数组转换成集合,有什么好处呢?
- 可以通过list集合中的方法来操作数组中的元素:isEmpty()、contains、indexOf、set;
注意(局限性):数组是固定长度,不可以使用集合对象增加或者删除等,会改变数组长度的功能方法。比如add、remove、clear。(会报不支持操作异常UnsupportedOperationException);
如果数组中存储的引用数据类型,直接作为集合的元素可以直接用集合方法操作。
如果数组中存储的是基本数据类型,asList会将数组实体作为集合元素存在。
集合 -> 数组:用的是Collection接口中的toArray()方法;
- 如果给toArray传递的指定类型的数据长度小于了集合的size,那么toArray方法,会自定再创建一个该类型的数据,长度为集合的size。
- 如果传递的指定的类型的数组的长度大于了集合的size,那么toArray方法,就不会创建新数组,直接使用该数组即可,并将集合中的元素存储到数组中,其他未存储元素的位置默认值null。
所以,在传递指定类型数组时,最好的方式就是指定的长度和size相等的数组。
将集合变成数组后有什么好处?
限定了对集合中的元素进行增删操作,只要获取这些元素即可。
用基本数据类型的数组转换ArrayList,ArrayList的size有问题
public static void main(String[] args) {
int[] arr1 = { 1, 2, 3, 4, 5 };
List<int[]> intList = Arrays.asList(arr1);
// intList size: 1
System.out.println(String.format("intList size: %s", intList.size()));
Integer[] arr2 = { 1, 2, 3, 4, 5 };
List<Integer> integerList = Arrays.asList(arr2);
// integerList size: 5
System.out.println(String.format("integerList size:%s", integerList.size()));
}
asList方法接受的参数是一个泛型的变长参数,我们知道基本数据类型是无法泛型化的,也就是说基本类型是无法作为asList方法的参数的, 要想作为泛型参数就必须使用其所对应的包装类型。但是这个实例中为什么没有出错呢?因为该实例是将int 类型的数组当做其参数,而在Java中数组是一个对象,它是可以泛型化的。所以该例子是不会产生错误的。既然例子是将整个int 类型的数组当做泛型参数,那么经过asList转换就只有一个int 的列表了.
结论:在使用asList()时尽量不要将基本数据类型数组转List.
asList转换得到的ArrayList不是java.util.ArrayList
public static void main(String[] args) {
String[] arr = {"abc", "kk", "qq"};
List<String> list = Arrays.asList(arr);
// 添加一个元素,抛出异常UnsupportedOperationException
list.add("bb");
}
原因:
此处ArrayList是Arrays的内部类,并没有add方法,add方法是父类AbstractList的,但是没有具体实现,
而是直接抛出UnsupportedOperationException异常.
正确操作
public static void main(String[] args) {
String[] arr = {"abc", "kk", "qq"};
// 使用new ArrayList包裹一层
List<String> list = new ArrayList<>(Arrays.asList(arr));
list.add("bb");
}
如何选用集合?
主要根据集合的特点来选用,比如我们需要根据键值获取到元素值时就选用Map接口下的集合,需要排序时选择TreeMap,不需要排序时就选择HashMap,需要保证线程安全就选用ConcurrentHashMap.当我们只需要存放元素值时,就选择实现Collection接口的集合,需要保证元素唯一时选择实现Set接口的集合比如TreeSet或HashSet,不需要就选择实现List接口的比如ArrayList或LinkedList,然后再根据实现这些接口的集合的特点来选用。