[豪の学习笔记] JavaReStudy#12
跟学视频:韩顺平Java课程
集合
1 - 集合的理解与好处
数组
长度开始时必须指定,而且一旦指定就不能更改
保存的必须为同一类型的元素
使用数组进行增加/删除元素比较麻烦
集合
可以动态保存任意多个对象
提供了一系列方便的操作对象的方法
使用集合添加/删除新元素更加简洁
2 - 集合的框架体系
Java的集合类很多,主要分为Collection和Map两大类
集合主要是单列集合和双列集合
Collection接口有两个重要的子接口List、Set,它们的实现子类都是单列集合
Map接口的实现子类是双列集合,存放的是key-value
3 - Collection方法
public interface Collection
Collection实现子类可以存放多个元素,每个元素可以是Object
有些Collection的实现类,可以存放重复的元素,有些不可以
有些Collection的实现类是有序的(List),有些不是有序(Set)
Collect接口没有直接的实现子类,是通过它的子接口Set和List来实现的
常用方法
add() //添加单个元素
remove() //删除指定元素
contains() //查找元素是否存在
size() //获取元素个数
isEmpty() //判断是否为空
clear() //清空
addAll() //添加多个元素
containsAll() //查看多个元素是否都存在
removeAll() //删除多个元素
遍历方式一 使用Iterator
Iterator对象称为迭代器,主要用于遍历Collection集合中的元素
所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator接口的对象,即可以返回一个选代器
Iterator仅用于遍历集合,其本身并不存放对象
Iterator的执行原理
Iterator iterator = coll.iterator(); //得到一个集合的迭代器
//hasNext(): 判断是否还有下一个元素
while(iterator.hasNext()){
System.out.println(iterator.next()); //next()作用:1.下移2.将下移以后集合位置上的元素返回
}
在调用iterator.next()方法之前必须要调用iterator.hasNext()进行检测
若不调用,且下一条记录无效,直接调用iterator.next()会抛出NoSuchElementException异常
遍历方式二 增强for循环
增强for循环是简化版的iterator,可以代替iterator迭代器,本质一样,只能用于遍历集合或数组
for(元素类型 元素名: 集合名或数组名){
访问元素
}
4 - List接口和常用方法
List集合类中元素有序(即添加顺序和取出顺序一致)、且可重复
List集合中的每个元素都有其对应的顺序索引,即支持索引
List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素
常用方法
void add(int index, Object ele) //在index位置插入ele元素
boolean addAll(int index, Collection eles) //从index位置开始将eles中的所有元素添加进来
Object get(int index) //获取指定index位置的元素
int indexOf(Object obj) //返回obj在集合中首次出现的位置
int lastlndexOf(Object obj) //返回obj在当前集合中末次出现的位置
Object remove(int index) //移除指定index位置的元素,并返回此元素
Object set(int index, Object ele) //设置指定index位置的元素为ele,相当于是替换
List subList(int fromlndex, int tolndex) //返回从fromlndex到tolndex位置的子集合
5 - ArrayList
注意事项
permits all elements,including null,ArrayList可以加入null,并且可以加入多个null
ArrayList是由数组来实现数据存储的
ArrayList基本等同于Vector
ArrayList线程不安全,但执行效率高
底层源码
ArrayList中维护了一个Object类型的数组elementData
transient Object[] elementData; //transient 瞬间、短暂的,表示该属性不会被序列化
当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData容量为0,第一次添加,则扩容elementData为10,如需要再次扩容,则扩容elementData为1.5倍
如果使用的是指定大小的构造器,则初始elementData容量为指定大小,如果需要扩容,则直接扩容elementData为1.5倍
6 - Vector
public class Vector<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, Serializable
Vector底层也是一个对象数组,protected Object[] elementData;
Vector是线程同步的,即线程安全,Vector类的操作方法带有synchronized
在开发中,需要线程同步安全时考虑使用Vector
如果是无参构造方法,则默认10空间,如需再次扩容则按两倍扩容
如果是指定大小,则每次直接按两倍扩容
7 - LinkedList
LinkedList底层实现了双向链表和双端队列特点
可以添加任意元素,元素可重复,包括null
线程不安全,未实现同步
底层操作机制
LinkedList底层维护了一个双向链表
LinkedList中维护了两个属性first和last,分别指向首节点和尾节点
每个节点(Node对象),里面又维护了prev、next、item三个属性,其中通过prev指向前一个,通过next指向后一个节点,最终实现双向链表
LinkedList的元素的添加和删除不是通过数组完成,故相对来说效率较高
@SuppressWarnings({"all"})
public class LinkedListCRUD {
public static void main(String[] args) {
LinkedList linkedList = new LinkedList();
linkedList.add(1);
linkedList.add(2);
linkedList.add(3);
System.out.println("linkedList=" + linkedList);
//演示一个删除结点的
linkedList.remove(); // 这里默认删除的是第一个结点
//linkedList.remove(2);
System.out.println("linkedList=" + linkedList);
//修改某个结点对象
linkedList.set(1, 999);
System.out.println("linkedList=" + linkedList);
//得到某个结点对象
//get(1) 是得到双向链表的第二个对象
Object o = linkedList.get(1);
System.out.println(o);//999
//因为 LinkedList 是 实现了 List 接口, 遍历方式
System.out.println("===LinkeList 遍历迭代器====");
Iterator iterator = linkedList.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
System.out.println("next=" + next);
}
System.out.println("===LinkeList 遍历增强 for====");
for (Object o1 : linkedList) {
System.out.println("o1=" + o1);
}
System.out.println("===LinkeList 遍历普通 for====");
for (int i = 0; i < linkedList.size(); i++) {
System.out.println(linkedList.get(i));
}
}
}
/*
1. LinkedList linkedList = new LinkedList();
public LinkedList() {}
2. 这时 linkeList 的属性 first = null last = null
3. 执行 添加
public boolean add(E e) {
linkLast(e);
return true;
}
4.将新的结点,加入到双向链表的最后
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
*/
/*
linkedList.remove(); // 这里默认删除的是第一个结点
1. 执行 removeFirst
public E remove() {
return removeFirst();
}
2. 执行
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
3. 执行 unlinkFirst, 将 f 指向的双向链表的第一个结点拿掉
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
final E element = f.item;
final Node<E> next = f.next;
f.item = null;
f.next = null; // help GC
first = next;
if (next == null)
last = null;
else
next.prev = null;
size--;
modCount++;
return element;
}
*/
ArrayList和LinkedList的比较
底层结构 | 增删效率 | 改查效率 | |
---|---|---|---|
ArrayList | 可变数组 | 较低,通过数组扩容 | 较高 |
LinkedList | 双向链表 | 较高,通过链表追加 | 较低 |
如果改查操作多,则选择ArrayList
如果增删操作多,则选择LinkedList
8 - Set接口和常用方法
Set接口基本介绍
无序,添加和取出的顺序不一致
没有索引,故Set接口对象不能通过索引来获取
不允许重复元素,故最多包含一个null
Set接口的常用方法
和List接口一样,Set接口也是Collection的子接口,故常用方法和Collection接口一样
Set接口的遍历方式
同Collection的遍历方式一样,可以使用迭代器或增强for,不能使用索引的方式来获取
9 - HashSet
基本介绍
HashSet实现了Set接口
HashSet实际上是HashMap
public HashSet(){
map = new HashMap<>();
}
可以存放null值,但是只能有一个null
HashSet不保证元素是有序的,取决于hash后,再确定索引的结果(即不保证存放元素的顺序与取出顺序一致)
不能有重复的元素/对象
@SuppressWarnings({"all"})
public class HashSet01 {
public static void main(String[] args) {
HashSet set = new HashSet();
//说明
//1. 在执行 add 方法后,会返回一个 boolean 值
//2. 如果添加成功,返回 true, 否则返回 false
//3. 可以通过 remove 指定删除哪个对象
System.out.println(set.add("john"));//T
System.out.println(set.add("lucy"));//T
System.out.println(set.add("john"));//F
System.out.println(set.add("jack"));//T
System.out.println(set.add("Rose"));//T
set.remove("john");
System.out.println("set=" + set);//3个
set = new HashSet();
System.out.println("set=" + set);//0
//4. Hashset 不能添加相同的元素/数据?
set.add("lucy");//添加成功
set.add("lucy");//加入不了
set.add(new Dog("tom"));//OK
set.add(new Dog("tom"));//Ok
System.out.println("set=" + set);
//再加深一下. 非常经典的面试题
//看源码,分析底层机制,看add方法到底如何实现的,机制很复杂
set.add(new String("magic"));//ok
set.add(new String("magic"));//加入不了
System.out.println("set=" + set);
}
}
class Dog {
private String name;
public Dog(String name) {
this.name = name;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' + '}';
}
}
底层机制说明
HashSet底层是HashMap,HashMap底层是数组+链表+红黑树
添加一个元素时,先得到hash值,此值会转成索引值
找到存储数据表table,看这个索引位置是否已经存放了元素,如果没有则直接加入;如果有则调用equals比较,如果相同就放弃添加,如果不同就添加到最后
在Java8中,如果一条链表的元素个数超过TREEIFY_THRESHOLD(默认是8),并且table的大小>=MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树)
@SuppressWarnings({"all"})
public class HashSetSource {
public static void main(String[] args) {
HashSet hashSet = new HashSet();
hashSet.add("java");//到此位置,第1次 add 分析完毕
hashSet.add("php");//到此位置,第2次 add 分析完毕
hashSet.add("java");
System.out.println("set=" + hashSet);
//韩老师的HashSet源码解读
//1.执行HashSet()
public HashSet() {
map = new HashMap<>();
}
//2.执行add()
public boolean add(E e) {//e = "java"
return map.put(e, PRESENT)==null;//(static) PRESENT = new Object();
}
//3.执行put(), 该方法会执行hash(key)得到key对应的hash值 算法 h = key.hashCode()) ^ (h >>> 16)
public V put(K key, V value) {//key = "java" value = PRESENT 共享
return putVal(hash(key), key, value, false, true);
}
//4.执行 putVal
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i; //定义了辅助变量
//table 就是 HashMap 的一个数组,类型是 Node[]
//if 语句表示如果当前 table 是 null, 或者 大小=0,就是第一次扩容,到 16 个空间.
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//(1)根据 key,得到 hash 去计算该 key 应该存放到 table 表的哪个索引位置,并把这个位置的对象,赋给 p
//(2)判断 p 是否为 null
//(2.1) 如果 p 为 null, 表示还没有存放元素, 就创建一个 Node (key="java",value=PRESENT)
//(2.2) 就放在该位置 tab[i] = newNode(hash, key, value, null)
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
//如果当前索引位置对应的链表的第一个元素和准备添加的 key 的 hash 值一样并且满足下面两个条件之一:(1)准备加入的key和p指向的Node结点的key是同一个对象(2)p指向的Node 结点的key的equals()和准备加入的key比较后相同,就不能加入
if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//再判断p是不是一颗红黑树, 如果是就调用putTreeVal,来进行添加
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
//如果 table 对应索引位置,已经是一个链表, 就使用 for 循环依次和该链表的每一个元素比较,若都不相同则加入到该链表的最后
//注意在把元素添加到链表后,立即判断该链表是否已经达到8个结点, 若达到则调用 treeifyBin()对当前这个链表进行树化(转成红黑树)
//注意,在转成红黑树时,要进行判断:
// if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY(64))
// resize();
//如果上面条件成立,先 table 扩容;有上面条件不成立时,才进行转成红黑树
//在该链表的每一个元素比较过程中,如果有相同情况,就直接break
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD(8) - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash && (k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
//size就是我们每加入一个结点Node(k,v,h,next), size++
if (++size > threshold)
resize();//扩容
afterNodeInsertion(evict);
return null;
}
}
}
HashSet练习 - 重写hashcode方法
定义一个Employee类,该类包含:private成员属性 name,age,当name和age的值相同时,认为是相同员工,不能添加到HashSet集合中
public class HashSetExercise{
public static void main(String[] args){
HashSet hashSet = new HashSet();
hashSet.add(new Employee("mon3ter", 18));//ok
hashSet.add(new Employee("doctor", 66));//ok
hashSet.add(new Employee("mon3ter", 18));//加入不成功
}
}
class Employee{
private String name;
private int age;
public Employee(String name, int age){
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(){
this.age = age;
}
@Override
public String toString(){
return "Employee{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o){
if(this == o){
return true;
}
if(o == null || getClass() != o.getClass()){
return false;
}
Employee employee = (Employee) o;
return age == employee.age && Object.equals(name, employee.name);
}
@Override
public int hashCode(){
return Object.hash(name, age);
}
}
10 - LinkedHashSet
基本介绍
LinkedHashSet是HashSet的子类
LinkedHashSet底层是一个LinkedHashMap,底层维护了一个数组+双向链表,区别于HashSet底层的数组+单向链表
LinkedHashSet根据元素的hashCode值来决定元素的存储位置,同时使用链表维护元素的次序,这使得元素看起来是以插入顺序保存的
LinkedHashSet不允许添加重复元素
底层机制
在LinkedHashSet中维护了一个hash表和双向链表(LinkedHashSet有head和tail),底层维护的是一个LinkedHashMap(是HashMap的子类)
每一个节点有before和after属性,这样可以形成双向链表
在添加一个元素时,先求hash值再求索引,确定该元素在table的位置,然后将添加的元素加入到双向链表(如果已经存在,不添加[原则与HashSet一致])
第一次添加时,直接将数组table扩容到16,存放的结点类型是LinkedHashMap$Entry
数组是HashMap$Node[]存放的元素是LinkedHashMap$Entry类型,继承关系是在内部类完成
static class Entry<K,V> extends HashMap.Node<K,V>{
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
11 - Map接口和常用方法
Map接口实现类的特点(JDK8)
Map与Collection并列存在,用于保存具有映射关系的数据:Key-Value
Map中的key和value可以是任何引用类型的数据,会封装到HashMap%Node对象中
Map中的key不允许重复,当有重复时会覆盖原先对应value的数据
Map中的value可以重复
Map的key可以为null,value也可以为null,但key为null的情况只能有一个
常用String类作为Map的key
key和value之间存在单向一对一关系,即通过指定的key总能找到对应的value,一对k-v是放在一个HashMap$Node中的
// k-v最后是HashMap$Node node = newNode(hash, key, value, null)
// k-v为了方便程序员的遍历,还会创建EntrySet集合,该集合存放的元素的类型是Entry,而一个Entry对象就有key和value
// EntrySet<Entry<k,v>>即transient Set<Map.Entry<k,v>> entrySet;
// entrySet中,定义的类型是Map.Entry,但是实际上存放的还是HashMap$Node,这是因为HashMap$Node implements Map.Entry
// 当把HashMap$Node对象存放到entrySet就方便我们的遍历,因为Map.Entry提供了重要的方法:K getKey(); V getValue();
Map接口常用方法
put(key, value); //添加
remove(key); //根据键删除映射关系
get(key); //根据键获得值
size(); //获取当前元素个数
isEmpty(); //判断是否为空
clear(); //清楚所有K-V
containsKey(key); //查找该键是否存在
Map接口遍历方式
1) containsKey 查找键是否存在
2) keySet 获取所有的键
3) entrySet 获取所有关系k-v
4) values 获取所有的值
@SuppressWarnings({"all"})
public class MapFor{
public static void main(String[] args){
Map map = new HashMap();
map.put("key1", "value1");
map.put("key2", "value2");
map.put("key3", "value3");
map.put("key4", "value4");
map.put("key5", "value5");
map.put("key6", "value6");
//第一种:先取出所有的key,再通过key取出对应的value
Set keySet = map.keySet();
System.out.println("-----增强for-----");
for(Object key : keySet){
System.out.println(key + "-" + map.get(key));
}
System.out.println("-----迭代器-----");
Iterator iterator = keySet.iterator();
while(iterator.hasNext()){
Object key = iterator.next();
System.out.println(key + "-" + map.get(key));
}
//第二种:把所有的value取出来
Collection values = map.values();
//这里可以使用所有的Collections使用的遍历方法
// (1)增强for (2)迭代器
//第三种:通过EntrySet来获取K-V
Set entrySet = map.entrySet();
System.out.println("-----使用EntrySet的增强for-----");
for(Object entry : entrySet){
//将entry转成Map.Entry
Map.Entry m = (Map.Entry)entry;
System.out.println(m.getKey() + "-" + m.getValue());
}
System.out.println("-----使用EntrySet的迭代器-----");
Iterator iterator1 = entrySet.iterator();
while(iterator1.hasNext()){
Object entry = iterator1.next();
Map.Entry m = (Map.Entry)entry;
System.out.println(m.getKey() + "-" + m.getValue());
}
}
}
12 - HashMap
HashMap小结
Map接口的常用实现类:HashMap、Hashtable、Properties
HashMap是Map接口使用频率最高的实现类,它以key-value键值对的方式来存储数据(HashMap$Node类型)
key不能重复,但是值可以重复,允许使用null键和null值
如果添加相同的key,则会覆盖原来的key-value,key不变,value被覆盖
与HashSet一样,不保证映射的顺序,因为底层是以hash表的方式来存储的(JDK8的HashMap底层 数组+链表+红黑树)
HashMap没有实现同步,方法没有做同步互斥的操作,即没有synchronized,因此是线程不安全的,
HashMap底层机制及源码剖析
(key,value)是一个Node,实现了Map.Entry<K,V>
JDK7.0的HashMap底层实现是[数组+链表],而在JDK8.0则是[数组+链表+红黑树]
---扩容机制---
HashMap底层维护了Node类型的数组table,默认为null
当创建对象时,将加载因子(loadfactor)初始化为0.75
当添加key-value时,通过key的哈希值得到在table的索引,然后判断该索引处是否有元素。如果没有元素则直接添加;如果该索引处有元素,则继续判断该元素的key和准备加入的key是否相等。如果相等,则直接替换val;如果不相等,则需要判断是树结构还是链表结构,做出相应处理。如果添加时发现容量不够,则需要进行扩容
第一次添加,则需要扩容table容量为16,临界值(threshold)为12(16*0.75),之后再扩容则需要扩容table容量为原来的2倍(32),临界值为原来的2倍(24),以此类推
在Java8中,如果一条链表的元素个数超过TREEIFY_THRESHOLD(默认是8),并且table的大小>=MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树)
@SuppressWarnings({"all"})
public class HashMapSource {
public static void main(String[] args){
HashMap map = new HashMap();
map.put("key1", "value1"); //ok
map.put("key2", "value2"); //ok
map.put("key1", "value3"); //替换value
System.out.println("map=" + map);
/*
1.执行构造器 new HashMap(),
初始化加载因子 loadfactor = 0.75
HashMap$Node[] table = null
2.执行put 调用hash方法, 计算key的hash值(h = key.hashCode()) ^ (h >>> 16)
public V put(K key, V value){
return putVal(hash(key), key, value, false, true);
}
3.执行putVal
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;//辅助变量
//如果底层的table数组为null, 或者length = 0 , 就扩容到16
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//取出hash值对应的table的索引位置的Node, 如果为null, 就直接把加入的k-v创建成一个Node, 加入该位置即可
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;//辅助变量
//如果table的索引位置的key的hash和新的key的hash值相同,且满足(table现有的结点的key和准备添加的key是同一个对象 || equals 返回真),就认为不能加入新的 k-v
if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)//如果当前的table的已有的Node是红黑树,就按照红黑树的方式处理
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个,到8个后就调用treeifyBin方法进行红黑树的转换
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
//如果在循环比较过程中发现有相同,就break,就只是替换value
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value; //替换key对应value
afterNodeAccess(e);
return oldValue;
}
}
++modCount;//每增加一个 Node ,就 size++
if (++size > threshold[12-24-48]) //如size > 临界值,就扩容
resize();
afterNodeInsertion(evict);
return null;
}
4.关于树化(转成红黑树)
//如果table为null或者大小还没有到64,就暂时不树化,而是进行扩容,否则才会真正的树化 -> 剪枝
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
}
*/
}
}
13 - HashTable
基本介绍
存放的元素是键值对,即K-V
HashTable的键和值都不能为null
HashTable的使用方法基本上和HashMap一样
HashTable是线程安全的(synchronized),HashMap是线程不安全的
底层简单剖析
底层有数组HashTable$Entry[],初始化大小为11
扩容临界值threshold 8 = 11 * 0.75
---扩容机制---
执行方法 addEntry(hash,,key,value,index); 添加K-V封装到Entry
当 if(count >= threshold) 满足时,就进行扩容
按照 int newCapacity = (oldCapacity << 1) + 1; 的大小扩容
14 - Properties
基本介绍
Properties类继承自HashTable类,并且实现了Map接口,也是使用一种键值对的形式来保存数据
它的使用特点与HashTable类似
Properties还可用于从 xxx.properties 文件中加载数据到Properties类对象,并进行读取和修改(xxx.properties文件通常作为配置文件)
15 - 集合选型规则
在开发中选择什么集合实现类,主要取决于业务操作特点,然后根据集合实现类特性进行选择
1)先判断存储的类型(一组对象或一组键值对)
2)一组对象:Collection接口
允许重复:List
增删多:LinkedList 底层维护了一个双向链表
改查多:ArrayList 底层维护Object类型的可变数组
不允许重复:Set
无序:HashSet 底层是HashMap,维护了一个哈希表,即数组+链表+红黑树
排序:TreeSet
插入和取出顺序一致:LinkedHashSet 维护数组+双向链表
3)一组键值对:Map
键无序:HashMap 底层是哈希表
键排序:TreeMap
键插入和取出顺序一致:LinkedHashMap
读取文件:Properties
16 - TreeSet
当我们使用无参构造器创建TreeSet时,仍然是无序的
使用 TreeSet 提供的一个构造器,可以传入一个比较器(匿名内部类)并指定排序规则
// TreeSet treeSet = new TreeSet();
TreeSet treeSet = new TreeSet(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
//下面调用 String 的 compareTo 方法进行字符串大小比较
return ((String) o1).length() - ((String) o2).length();
}
});
---源码解读---
// 1.构造器把传入的比较器对象,赋给了TreeSet的底层的TreeMap的属性 this.comparator
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
// 2.在调用 treeSet.add("tom"), 在底层会执行到
if (cpr != null) {//cpr 就是我们的匿名内部类(对象)
do {
parent = t;
//动态绑定到我们的匿名内部类(对象)compare
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else //如果相等,即返回 0,这个 Key 就没有加入
return t.setValue(value);
} while (t != null);
}
17 - TreeMap
使用默认的构造器创建TreeMap,是无序的(也没有排序)
使用 TreeMap 提供的一个构造器,可以传入一个比较器(匿名内部类)并指定排序规则
// TreeMap treeMap = new TreeMap();
TreeMap treeMap = new TreeMap(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
//按照传入的 k(String) 的大小进行排序
//按照 K(String) 的长度大小排序
//return ((String) o2).compareTo((String) o1);
return ((String) o2).length() - ((String) o1).length();
}
});
---源码解读---
// 1.构造器 把传入的实现了Comparator接口的匿名内部类(对象),传给 TreeMap的comparator
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
// 2.调用put方法
// 2.1.第一次添加, 把k-v封装到Entry对象, 放入root
Entry<K,V> t = root;
if (t == null) {
compare(key, key); // type (and possibly null) check
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
// 2.2.以后添加
Comparator<? super K> cpr = comparator;
if (cpr != null) {
do { //遍历所有的 key , 给当前 key 找到适当位置
parent = t;
cmp = cpr.compare(key, t.key);//动态绑定到我们的匿名内部类的compare
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else //如果遍历过程中,发现准备添加 Key 和当前已有的 Key 相等,就不添加
return t.setValue(value);
} while (t != null);
}
18 - Collections方法
Collections工具类介绍
Collections是一个操作Set、List和Map等集合的工具类
Collections中提供了一系列静态的方法对集合元素进行排序、查询、修改等操作
排序操作(均为static方法)
reverse(List); //反转List中元素的顺序
shuffle(List); //对List集合元素进行随机排序
sort(List); //根据元素的自然顺序对指定List集合元素按升序排序
sort(List, Comparator); //根据指定的Comparator产生的顺序对List集合元素进行排序
swap(List, int, int); //将指定List集合中的i处元素和j处元素进行交换
查找、替换
Object max(Collection); //根据元素的自然顺序,返回给定集合中的最大元素
Object max(Collection, Comparator); //根据Comparator指定的顺序,返回给定集合中的最大元素
Object min(Collection); //根据元素的自然顺序,返回给定集合中的最小元素
Object min(Collection, Comparator); //根据Comparator指定的顺序,返回给定集合中的最小元素
int frequency(Collection, Object); //返回指定集合中指定元素的出现次数
void copy(List dest, List src); //将src中的内容复制到dest中
boolean replaceAll(List list, Object oldVal, Object newVal); //使用新值替换List对象的所有旧值