集合和数组都是对多个数据进行存储操作,简称Java容器
存储主要是指内存层面的存储,不涉及持久化的存储(txt,jpg)
这里没截完除开iterator和toArray()总共13个方法一个一个试
试看:
1、添加
add(Object obj)
addAll(Collection coll)
2、获取有效元素的个数
int size()
3、清空集合
void clear()
4、是否是空集合
boolean isEmpty()
5、是否包含某个元素
boolean contains(Object obj):是通过元素的equals方法来判断是否是同一个对象
boolean containsAll(Collection c):也是调用元素的equals方法来比较的。拿两个集合的元素挨个比较。
6、删除
boolean remove(Object obj) :通过元素的equals方法判断是否是要删除的那个元素。只会删除找到的第一个元素
boolean removeAll(Collection coll):取当前集合的差集
7、取两个集合的交集
boolean retainAll(Collection c):把交集的结果存在当前集合中,不影响c
8、集合是否相等
boolean equals(Object obj)
9、转成对象数组
Object[] toArray()
10、获取集合对象的哈希值
hashCode()
11、遍历
iterator():返回迭代器对象,用于集合遍历
Collection coll = new ArrayList();
//add填进去的是object
coll.add("a");
coll.add(123);
coll.add(new Date());
//size()添加元素的个数
coll.size();
//addAll()
coll.addAll(coll1);
//isEmpty()当前集合是否为空
coll.isEmpty();
//clear()清空集合元素
coll.clear();
//contains()
boolean coll = coll.contains("a");
集合主要类型:
集合类型 | 描述 |
---|---|
ArrayList | 可以动态增长和缩减的序列,查找效率高,添加效率低 |
LinkedList | 可以在任意位置进行高效的插入和删除类似链表优点 |
HashSet | 没有重复元素并且无序,只能存一个null |
TreeSet | 有序集 |
EunmSet | 包含枚举类型的值 |
LinkedHashSet | 可以记住插入顺序的集 |
PriorityQueue | 允许高效删除最小元素的集合 |
ArrayDeque | 用循环数组实现双端队列 |
HashMap | 存储键值映射表 |
TreeMap | 根据键有序的排列映射表 |
EunmMap | 键值属于枚举类型的映射表 |
LinkedHashMap | 可以记住键值对添加顺序的映射表 |
WeekHashMap | 一种其值不再被使用时可以被垃圾回收的映射表 |
IdentityHashMap | 一种使用==而不是用equals比较键值是否相等的映射表 |
List 是⼀个有序序列,且可重复,有3种类型的 List:
java.util.List 接⼝中主要的⽅法:
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
list.add("D");
System.out.println(list.toString());
list.remove(1);
注意这里remove()里面是可以传两只 index 和 object
可以看到remove的遍历范围,找到一个满足就返回这个区别于removeall
关于list的扩展内容:
首先需要知道的知识点:
short s1 = 1;
short s2 = 2;
Set<Short> set = new HashSet<>();
set.add.(s1);
set.add.(s2);
set.remove( s1+1);
System.out.println(set);//[1,2]
set.remove((short)s1+1);
System.out.println(set);//[1]
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
list.add("D");
System.out.println(list.toString());
list.remove(1);
Iterator<String> it = list.iterator();
while (it.hasNext()){
System.out.println(it.next());
}
我们打开iterator之后可以看到以下几个接口:
Iterator<String> it = list.iterator();
while (it.hasNext()){
System.out.println(it.next());
}
System.out.println(it.hasNext());//false
E next(); //访问下一个对象如果跑到尾部弹出NoSuchElementException
注意事项:
唯一安全的方式来在迭代过程中修改集合;如果在迭代过程中以任何其它的方式修改了基本集合将会产生未知的行为。而且每调用一次next()方法,remove()方法只能被调用一次
default void remove() {
throw new UnsupportedOperationException(“remove”);
}
这个iterator是通过Iterator接口来实现
跌迭代器对序列在操作中途不能够对列表结构进行操作,会抛出Exception in thread “main” java.util.ConcurrentModificationException
Collection<Node> c = new ArrayList<>();
c.add( new Node(1, "a"));
c.add( new Node(2, "b"));
c.add( new Node(3, "c"));
c.add( new Node(4, "d"));
Iterator<Node> it = c.iterator();
Node node1 = it.next();
//结构改变
c.remove(node1);
//结构改变
c.add(new Node(5,"e"));
System.out.println(it.next());
System.out.println(c);
//序列改变了需要重新获取迭代器
c.add(new Node(6,"f"));
Iterator<Node> it1 = c.iterator();
while (it1.hasNext()){
System.out.println(it1.next());
}
由上面我们可以知道next()只能从前往后迭代,但是作为iterator的子类listIterator是可以双向移动的,
我们来看看它的源代码
import java.util.*;
public class TestListIterator{
public static void main(String[] args) {
1 ArrayList<String> a = new ArrayList<String>();
2 a.add("aaa");
3 a.add("bbb");
4 a.add("ccc");
5 System.out.println("Before iterate : " + a);
6 ListIterator<String> it = a.listIterator()
7 while (it.hasNext()) {
8 System.out.println(it.next() + ", " + it.previousIndex() + ", " + it.nextIndex());
9 }
10 while (it.hasPrevious()) {
11 System.out.print(it.previous() + " ");
12 }
13 System.out.println();
14 it = a.listIterator(1);//调用listIterator(n)方法创建一个一开始就指向列表索引为n的元素处的ListIterator。
15 while (it.hasNext()) {
16 String t = it.next();
17 System.out.println(t);
18 if ("ccc".equals(t)) {
19 it.set("nnn");
20 } else {
21 it.add("kkk");
22 }
23 }
24 System.out.println("After iterate : " + a);
}
}
迭代器需要注意的⼀些特性:
//双向迭代器使用
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("c");
//这里提前说明一下,lterator实现了Iteratorable接口,然后ListIterator继承于Iterator
ListIterator<String> listIterator = list.listIterator();
//迭代器在第一个对象之前,它之前没有对象找不到就报错了java.util.NoSuchElementException
// System.out.println("hasprevious = " + listIterator.previous());
//是否有下一个对象
System.out.println("hasnext = " + listIterator.hasNext());
//迭代器移动到下一个位置
System.out.println("next = "+listIterator.next());
//迭代器前一个的坐标,如果迭代器在第一个之前,那么显示是-1
System.out.println("previousIndex = " + listIterator.previousIndex());
//迭代器下一个元素的位置
System.out.println("nextindex = " + listIterator.nextIndex());
//此时迭代器在第一个元素之前,所以查找并修改,在迭代器前面的那个元素
listIterator.set("d");
System.out.println(list);//[d, B, c]
//在迭代器所指向位置添加(插入)一个对象
listIterator.add("e");
System.out.println(list);//[d, e, B, c]
System.out.println("previousIndex = " + listIterator.previousIndex());//1
//这里由于list被修改
//如果没有在listIterator.next();之后调用listIterator.remove();
// 既没有调用 next 也没有调用 previous,或者在最后一次调用 next 或 previous 后调用了 remove 或 add
//就会抛出java.lang.IllegalStateException
listIterator.next();
listIterator.remove();
//抛出异常java.lang.IllegalStateException
System.out.println(list);//[d, e, c]
System.out.println("next = " + listIterator.next());
/*
ArrayList运行结果
插入:17ms
for遍历:2ms
while迭代器遍历:2ms
*/
testTime(new ArrayList(),10000);
/*
LinkedList运行结果
插入:4ms
for遍历:180ms
while迭代器遍历:1ms
*/
testTime(new LinkedList(),10000);
/*
结论说明迭代器遍历用时比较短比较好
*/
}
public static void testTime(List list,int times){
for(int i = 0;i < times ; i++){
list.add("A");
}
//索引为10的位置插入10000个B,并计算耗时
long start = System.currentTimeMillis();
for(int i = 0 ; i < times ; i++){
list.add(10,"B");
}
System.out.println("耗时ms="+( System.currentTimeMillis()-start));
//遍历for
start = System.currentTimeMillis();
for(int i=0 ; i<list.size(); i++){
list.get(i);
}
System.out.println("for耗时=" + (System.currentTimeMillis()-start));
//迭代器
start = System.currentTimeMillis();
ListIterator listIterator = list.listIterator();
while (listIterator.hasNext()){
listIterator.next();
}
System.out.println("Iterator耗时=" + (System.currentTimeMillis()-start) );
可以总结出:在不知道list类型的情况下如果需要遍历,优先使用迭代器,当然我们也可以使用instanceof RandomAccess来判断是否支持快速随机访问
这里我附上API帮助文档的说明:
将操作随机访问列表的最佳算法(如 ArrayList)应用到连续访问列表(如 LinkedList)时,可产生二次项的行为。如果将某个算法应用到连续访问列表,那么在应用可能提供较差性能的算法前,鼓励使用一般的列表算法检查给定列表是否为此接口的一个 instanceof,如果需要保证可接受的性能,还可以更改其行为。
现在已经认识到,随机和连续访问之间的区别通常是模糊的。例如,如果列表很大时,某些 List 实现提供渐进的线性访问时间,但实际上是固定的访问时间。这样的 List 实现通常应该实现此接口。实际经验证明,如果是下列情况,则 List 实现应该实现此接口,即对于典型的类实例而言,此循环:
for (int i=0, n=list.size(); i < n; i++)
list.get(i);
的运行速度要快于以下循环:
for (Iterator i=list.iterator(); i.hasNext(); )
i.next();
//RandomAccess
if(list instanceof RandomAccess){
for(int i=0 ; i < times ; i++){
list.get(i);
}
}else {
Iterator iterator = list.listIterator();
while (iterator.hasNext()){
iterator.next();
}
}
总结:
Iterator和ListIterator主要区别在以下方面:
(1)ListIterator有add()方法,可以向List中添加对象,而Iterator不能
(2)ListIterator和Iterator都有hasNext()和next()方法,可以实现顺序向后遍历,但是ListIterator有hasPrevious()和previous()方法,可以实现逆向(顺序向前)遍历。Iterator就不可以。
(3)ListIterator可以定位当前的索引位置,nextIndex()和previousIndex()可以实现。Iterator没有此功能。
(4)都可实现删除对象,但是ListIterator可以实现对象的修改,set()方法可以实现。Iierator仅能遍历,不能修改。
数组列表 ArrayList 内部使⽤数组实现,⽽数组使⽤下标进⾏访问时速度很快;⽽在中间位置插⼊和删除元素时,因为要移动插⼊/删除位置以后的元素,因此速度⽐较慢。
如果能提前对数据大小进行评估,可以提前初始化容量避免扩容
比如:
list list = new ArrayList<>(10000【这里填容量不填默认是0】);
创建 ArrayList 时可以指定初始容量⼤⼩,如果未指定则创建⼀个空数组,⻓度为0
如果存储⻓度⼤于容量⼤⼩时,⾃动进⾏扩容,每次扩容到原来的 1.5 倍。相关代码如下:
int newCapacity = oldCapacity + (oldCapacity >> 1);
elementData = Arrays.copyOf(elementData, newCapacity);
ArrayList 中的⽅法不是同步的,如果有线程安全需要可以使⽤ Vector。Vector 每次扩容到原来的 2 倍。
链表 LinkedList 内部使⽤双向链表实现(Java 语⾔中,所有链表都是双向链接(doubly linked)),从链表中间删除或插⼊⼀个元素代价很低,但随机访问速度慢:删除元素只需要修改位于删除元素前后的元素的 next 和 previous 引⽤指向即可;插⼊元素只需要修改新插⼊元素和插⼊位置前后的元素的 next 和 revious 引⽤指向。
内部实现和源码;
双向链表,内部没有声明数组,而是定义了Node类型的first和last, 用于记录首末元素。同时,定义内部类Node,作为LinkedList中保存数据的基 本结构。Node除了保存数据,还定义了两个变量:
(1)prev变量记录前一个元素的位置
(2)next变量记录下一个元素的位置
源码:
private static class Node {
E item;
Node next;
Node prev;
Node(Node prev, E element, Node next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
链表虽然也实现了随机访问,但是不⽀持快速随机访问,上述代码每次查找元素都会从列表的头部开始重新开 始搜索(查看第 n 个元素时,需要从头开始越过 n - 1 个元素)。LinkedList 对象根本不做任何缓存;仅到索引⼤于 size() / 2 就从列表尾端开始搜索元素。
我们可以通过是否实现了 RandomAccess 接⼝来判断对象是否⽀持快速随机访问。使⽤链表唯⼀的理由是尽可能地减少在列表中间插⼊或删除元素所付出的代价
ArrayList ⻓于随机快速访问,⽽ LinkedList ⻓于在序列中间插⼊/删除元素
(1)Set接口 不保存重复的元素,它最常⽤的就是测试归属,因此查找是 Set 最重要的操作。
(2)Set 基于对象的值来确定归属性,判断两个对象是否相同不是使用 == 运算符,而是根据 equals() 方法,Set 具有和 Collection 完全⼀样的接⼝,set接口没有提供额外的方法
HashSet 内部使⽤ HashMap 来实现,HashMap 的键值存储的即是 HashSet 的值,⽽键值对应的值存放了⼀个静态对象 PRESENT。
(1)HashSet 按 Hash 算法来存储集合中的元素,因此具有很好的存取、查找、删除性能。
(2)特点:
主要代码如下:
public class HashSet extends AbstractSet implements Set {
// ⽤于存放数据,键值即为 Set 集的元素
private transient HashMap map;
private static final Object PRESENT = new Object();
public HashSet() {
map = new HashMap<>();
}
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
public Iterator iterator() { return map.keySet().iterator();
}
}
我们都知道一个对象只要实现了Serilizable接口,这个对象就可以被序列化,java的这种序列化模式为开发者提供了很多便利,我们可以不必关系具体序列化的过程,只要这个类实现了Serilizable接口,这个类的所有属性和方法都会自动序列化。
然而在实际开发过程中,我们常常会遇到这样的问题,这个类的有些属性需要序列化,有些属性不需要被序列化,打个栗 子,如果一个用户有一些敏感信息(如密码,银行卡号等),为了安全起见,不希望在网络操作(主要涉及到序列化操作,本地序列化缓存也适用)中被传输,这些信息对应的变量就可以加上transient关键字。换句话说,这个字段的生命周期仅存于调用者的内存中而不会写到磁盘里持久化
实列代码:
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
/**
* @description 使用transient关键字不序列化某个变量
* 注意读取的时候,读取数据的顺序一定要和存放数据的顺序保持一致
*
* @author Alexia
* @date 2013-10-15
*/
public class TransientTest {
public static void main(String[] args) {
User user = new User();
user.setUsername("Alexia");
user.setPasswd("123456");
System.out.println("read before Serializable: ");
System.out.println("username: " + user.getUsername());
System.err.println("password: " + user.getPasswd());
try {
ObjectOutputStream os = new ObjectOutputStream(
new FileOutputStream("C:/user.txt"));
os.writeObject(user); // 将User对象写进文件
os.flush();
os.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
try {
ObjectInputStream is = new ObjectInputStream(new FileInputStream(
"C:/user.txt"));
user = (User) is.readObject(); // 从流中读取User的数据
is.close();
System.out.println("\nread after Serializable: ");
System.out.println("username: " + user.getUsername());
System.err.println("password: " + user.getPasswd());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
class User implements Serializable {
private static final long serialVersionUID = 8294180014912103005L;
private String username;
private transient String passwd;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPasswd() {
return passwd;
}
public void setPasswd(String passwd) {
this.passwd = passwd;
}
}
输出:
read before Serializable:
username: Alexia
password: 123456
read after Serializable:
username: Alexia
password: null
总结:
java 的transient关键字为我们提供了便利,你只需要实现Serilizable接口,将不需要序列化的属性前添加关键字transient,序列化对象的时候,这个属性就不会序列化到指定的目的地中
1)一旦变量被transient修饰,变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问。
2)transient关键字只能修饰变量,而不能修饰方法和类。注意,本地变量是不能被transient关键字修饰的。变量如果是用户自定义类变量,则该类需要实现Serializable接口。
3)被transient关键字修饰的变量不再能被序列化,一个静态变量不管是否被transient修饰,均不能被序列化。
注意上面代码:
第三点可能有些人觉得有问题,因为User类中的username变量前加上static关键字后,程序运行结果依然不变,即static类型的username也读出来为“Alexia”了,(一个静态变量不管是否被transient修饰,均不能被序列化),反序列化后类中static型变量username的值为当前JVM中对应static变量的值,这个值是JVM中的不是反序列化得出的
弄明白后我们继续:
注:
扩展:
重写hashcode()的原则:
(1)在程序运行时,同一个对象多次调用 hashCode() 方法应该返回相同的值。
(2)当两个对象的 equals() 方法比较返回 true 时,这两个对象的 hashCode()方法的返回值也应相等。
(3)对象中用作 equals() 方法比较的 Field,都应该用来计算 hashCode 值。
重写 equals() 方法的基本原则:
复写equals方法的时候一般都需要同时复写hashCode方法。通
常参与计算hashCode的对象的属性也应该参与到equals()中进行计算
Eclipse/IDEA工具里hashCode()的重写:
以Eclipse/IDEA为例,在自定义类中可以调用工具自动重写equals和hashCode问题:为什么用Eclipse/IDEA复写hashCode方法,有31这个数字?
(1)树集 TreeSet 是⼀个有序集合(sorted collection)。可以以任意顺序将元素插⼊到集合中,在遍历时,每个元素⾃动按照排序后的顺序呈现,确保集合元素处于排序状态。
将⼀个元素添加到 TreeSet 中⽐添加到 HashSet 中慢,但是要⽐将元素添加到数组或链表的正确位置上要快很多。TreeSet 可以⾃动的对元素进⾏排序
public class TreeSet extends AbstractSet implements NavigableSet
{
private transient NavigableMap m;
private static final Object PRESENT = new Object(); TreeSet(NavigableMap m) {
this.m = m;
}
public TreeSet() {
this(new TreeMap());
}
//这里TreeMap采用红黑树的结构
public TreeSet(Comparator super E> comparator) { this(new TreeMap<>(comparator));
}
public boolean add(E e) {
return m.put(e, PRESENT)==null;
}
public Iterator iterator() {
return m.navigableKeySet().iterator();
}
(2)TreeSet底层使用红黑树结构存储数据
具体讲解:
红黑树优秀博客
(3)方法:
新增的方法如下: (了解)
Comparator comparator()
Object first()
Object last()
Object lower(Object e)
Object higher(Object e)
SortedSet subSet(fromElement, toElement)
SortedSet headSet(toElement)
SortedSet tailSet(fromElement)
默认情况TreeSet 自然排序
(1)通过 Comparable 接⼝的 compareTo ⽅法来进⾏排序,根据第一个参数小于、等于或大于第二个参数分别返回负整数、零或正整数
(2)因此如果要添加一个⾃定义对象到 TreeSet,就必须实现 Comparable 接⼝⾃定义排列顺序,因为在 Object 中没有compareTo 接⼝的默认实现,但向 TreeSet 中添加元素时,只有第一个元素无须比较compareTo()方法,后面添加的所有元素都会调用compareTo()方法进行比较,因为只有相同类的两个实例才会比较大小,所以向 TreeSet 中添加的应该是同一个类的对象。
扩展:Comparable 的典型实现:
这种情况下,我们可以通过创建 TreeSet 时将 Comparator 对象传给它的构造器告诉它排序的⽐较⽅法,对于设置了 Comparator 对象的 TreeSet,向⾥添加的对象不需要实现 Comparable 接⼝
interface Comparator { int compare(T a, T b);
}
扩展:定制排序
场景:如果TreeSet的自然排序要求元素所属的类实现Comparable接口,如果元素所属的类没有实现Comparable接口,或不希望按照升序(默认情况)的方式排列元素或希望按照其它属性大小进行排序,则考虑使用定制排序。定制排序,通过Comparator接口来实现。需要重写compare(T o1,T o2)方法。
显然能发现实现的接口不一样了
练习:
//假设Person的hashcode和equals重写过
HashSet set = new HashSet();
Person p1 = new Person(1001,"AA");
Person p2 = new Person(1002,"BB");
set.add(p1);
set.add(p2);
p1.name = "CC"; set.remove(p1);
System.out.println(set);
set.add(new Person(1001,"CC"));
System.out.println(set);
set.add(new Person(1001,"AA"));
System.out.println(set);
(1)链接散列集 LinkedHashSet 继承⾃ HashSet,在使⽤哈希桶的⽅式存放元素的同时,使⽤链表的⽅式来记录插⼊元素的顺序
(2)LinkedHashSet插入性能略低于 HashSet,但在迭代访问 Set 里的全部元素时有很好的性能。
(3)LinkedHashSet 不允许集合元素重复。
自然而然底层就不同于hashSet了
列:
枚举集 EnumSet 是⼀个枚举类型元素集的⾼效实现。由于枚举类型只有有限个实例,所以 EnumSet 内部使⽤位序列实现,如果对应的值在集中,则相应的位被置为 1。
Queue
队列是⼀种“先进先出”的数据结构,可以在队列尾部添加元素,在队列的头部删除元素。Java 6 中引⼊了
Deque 接⼝,并由 ArrayDeque 和 LinkedList 类实现,这两个类都提供了双端队列。队列主要有以下两种:
队列,可以在尾部添加⼀个元素,在头部删除⼀个元素。
双端队列,有两个端头的队列,可以在头部和尾部同时添加或删除元素
注: 队列和双端队列都不⽀持在队列中间添加元素。
java.util.Queue 接⼝主要⽅法:
boolean add(E e)
boolean offer(E e)
如果队列没满,将给定元素添加到队列尾部并返回 true。如果队列满了 add ⽅法抛 IllegalStateException
异常,offer ⽅法返回 false
E remove()
E poll()
如果队列不空,删除并返回这个队列头部的元素。如果队列是空 remove ⽅法抛
NoSuchElementException 异常,poll ⽅法返回 null
E element()
E peek()
如果队列不空,返回队列头部的元素,但不删除。如果队列为空 element ⽅法抛
NoSuchElementException 异常,peek ⽅法返回 null
java.util.Deque 接⼝主要⽅法:
void addFirst(E e)
void addLast(E e)
boolean offerFirst(E e)
boolean offerLast(E e)
将给定的对象添加到双端队列的头部或尾部。如果队列满了,addFirst/addLast ⽅法抛
IllegalStateException 异常,offerFirst/offerLast ⽅法返回 false
E removeFirst()
E removeLast()
E pollFirst()
E pollLast()
如果队列不空,删除并返回队列头部的元素。如果队列为空,removeFirst/removeLast ⽅法抛
NoSuchElementException 异常,pollFirst/pollLast ⽅法返回 null
E getFirst()
E getLast()
E peekFirst()
E peekLast()
如果队列⾮空,返回队列头部的元素,但不删除。如果队列为空,getFirst/getLast ⽅法抛
NoSuchElementException 异常,peekFirst/peekLast ⽅法返回 null
ArrayDeque
ArrayDeque 是使⽤循环数组实现的双端队列,可以在队列两边插⼊和删除元素。循环数组是有界集合,容量有限,当达到容量限制时进⾏⾃动扩容,每次扩容原来的 1 倍。
LinkedList
LinkedList 也实现了 Deque 接⼝,是双端队列的链表实现⽅式。它的性能要⽐ ArrayDeque 低。
PriorityQueue
优先级队列(priority queue)中的元素可以按任意的顺序插⼊,却总是按照排序的顺序进⾏检索。
remove ⽅法总是会获得当前优先级队列中最⼩的元素当使⽤迭代的⽅式处理时,则未进⾏排序
优先级队列使⽤堆(heap)这种数据结构,它是⼀个可以⾃我调整的⼆叉树,对树执⾏添加(add)和删除
(remove)操作,可以让最⼩的元素移动到根,⽽不必对元素进⾏排序。
和 TreeSet ⼀样,优先级队列可以保存实现了 Comparable 接⼝的类对象,也可以保存在构造器中提供⽐较器的对象。
使⽤优先级队列的典型示例是任务调度。
java.util.Map 接⼝主要⽅法:
映射表(map)⽤来存放键/值对,根据键就能查找到值。Java 类有映射表的两个通⽤实现:HashMap 和TreeMap,他们都实现了 Map 接⼝。
散列映射表(HashMap)对键进⾏散列,散列⽐较函数只能作⽤于键,与键关联的值不能进⾏散列⽐较。树映射表(TreeMap)⽤键的整体顺序对元素进⾏排序,并将其组织成搜索树。
键必须是唯⼀的,不能对同⼀个键存放两个值。如果对同⼀个键两次调⽤ put ⽅法,第⼆个的值会取代第⼀个
。
映射表本身并不是⼀个集合,但是我们可以获得映射表的视图,这是⼀组实现了 Collection 接⼝对象,或者它的⼦接⼝的视图。有 3 个视图:
1.键 集 Set keySet()
2.值集合(不是集) Collection values()
3.键/值对集 Set
注意:keySet 既不是 HashSet,也不是 TreeSet,⽽是⼀个实现了 Set 接⼝的其他类的对象。因此可以像使⽤集⼀样使⽤ keySet。
列如:
值是2
jdk8中底层是数组+链表+红黑树
hashcode怎么算的:
int hash = key.hashcode
int i = hash % table.length;
得到数组下标
容量默认
链表和数组都是有序的元素序列,如果我们不在意元素的顺序,只关⼼查找元素的速度,有⼀种数据结构可以实现,这就是散列表。
散列表(hash table)为每⼀个对象计算⼀个整数值,这个整数值称为散列码(hash code)。散列码是由对象的实例域产⽣的⼀个整数。
Object 默认的计算对象散列码的⽅法返回的是对象的地址。因此⾃定义类时,需要实现这个类的 hashCode
⽅法,并且 hashCode ⽅法应该与 equals ⽅法兼容,当 a.equals(b) == true 时,a 与 b 的散列码必须相同(a.hashCode() == b.hashCode())。
在 Java 语⾔中,散列表⽤链表数组的⽅式实现,每个列表被称为桶(bucket),列表⻓度称为桶数,⽽桶由数组组成。要查找或存放对象时,先计算对象的散列码,然后与桶的总数取余,所得的结果就是查找或存放对象 在桶中的索引。如果是查找对象,则将查找对象与桶中的所有对象进⾏⽐较(使⽤ equals ⽅法),如果存在则返回,否则返回 null;如果是保存对象,桶中没有其他元素时直接插⼊到桶中,否则⽤新对象与桶中的所有对象进⾏⽐较,查看对象是否已经存在。如果桶已经被占满了,这种现象称为散列冲突(hash collision)。
桶数和散列表的运⾏性能有关,如果知道最终要插⼊散列表的元素个数,就可以设置桶数,通常设置为预计元素个数的75% ~ 150%。
当散列表太满,就需要再散列(rehashed)。再散列是指创建⼀个桶数更多的表,并将所有元素插⼊到新表中,然后丢弃原来的表。
我们根据装填因⼦(load factor)来判断散列表是否太满,也即是何时进⾏再散列。例如默认装填因⼦为0.75, 当表中超过75%的位置已填⼊元素,这个表就会⽤双倍的桶数⾃动的进⾏再散列。