JAVA类集(JDK1.2 ,java.util)
JAVA类集本质:动态对象数组
复习Collection接口及其子接口List与Set,熟悉常见子类比如ArrayList与HashSet用法。ArrayList与Vector的区别等
1.Collection 接口 :
针对单个对象的处理,是单个集合保存的最大父接口。定义如下:
public interface Collection<E> extends Iterable<E>
public interface Iterable<T>:实现了此接口,可以使用for-each循环
Iterator<T> interator() [JDK1.5之前直接在Collection接口中定义];
Iterator :集合遍历类。迭代器模式 Scanner 类是 Iterator的集合类
Collection 有两个子接口 list 和 Set
List extends Collection: 允许数据重复
Set extends Collection:不允许数据重复
2.List接口(80%):
E get(int index):根据索引取得保存数据
E set(int index,E element): 修改指定索引元素的内容,返回修改前内容
List 子接口与Collection接口相比最大的特点在于其有一个get()方法,可以根据索引取得内容,由于List本身还是接口,要想取得接口的实例对象,就必须有子类,在List接口下有三个常用的子类:ArrayList Vector LinkedList
2.1 ArrayList :底层为数组
List允许重复数据
List 基本处理:
public class Test {
public static void main(String[] args) {
List<String> list=new ArrayList<>();
list.add("hello");
list.add("hello");
System.out.println(list);
}
}
//结果: [hello,hello]
//List的其他操作
public class Test {
public static void main(String[] args) {
List<String> list=new ArrayList<>();
System.out.println(list.size()+"、"+list.isEmpty());
list.add("hello");
list.add("hello");
System.out.println(list.size()+"、"+list.isEmpty());
System.out.println(list);
System.out.println(list.remove("hello"));
System.out.println(list.contains("ABC"));
System.out.println(list.contains("hello"));
System.out.println(list);
}
}
List本身有一个好的支持:存在get()方法,可以利用get()方法结合索引取得数据。
//List的get()方法
public class Test {
public static void main(String[] args) {
List<String> list=new ArrayList<>();
list.add("hello");
list.add("hello");
for(int i=0;i<list.size();i++) {
System.out.println(list.get(i));
}
}
}
get()方法是List子接口提供的,如果现在操作的是Collection接口,那么对于此时数据的取出只能将集合变为对象数组操作
//Collection 接口,对象的取出只能变成对象数组的方法
public class Test {
public static void main(String[] args) {
Collection<String> list=new ArrayList<>();
list.add("hello");
list.add("hello");
Object[] result=list.toArray();
System.out.println(Arrays.toString(result));
}
}
2.2 Vector: 底层为数组
旧的子类Vector使用的较少,Vector是从JDK1.0提出的,而ArrayList是从JDK1.2提出的
//使用Vector
public class Test {
public static void main(String[] args) {
List<String> list=new Vector<>();
list.add("lisifan");
list.add("lisifan");
System.out.println(list);
list.remove("lisifan");
System.out.println(list);
}
}
面试题:
ArrayList 于 Vector 区别:
1.Vector JDK1.0 ,ArrayList JDK1.2
2.Vector 采用同步处理,线程安全,效率低
ArrayList采用异步处理,线程不安全,效率高
3. ArrayList支持 Iteratro,ListIterator,foreach 输出
Vactor 支持 Iterator, ListIterator, foreach,Enumeration输出
2.3 LinkedList:底层为双链表
//使用LinkedList,这个子类向父接口转型的话,使用形式与之前没有任何区别
public class Test {
public static void main(String[] args) {
List<String> list=new LinkedList<>();
list.add("lisifan");
list.add("lisifan");
System.out.println(list);
list.remove("lisifan");
System.out.println(list);
}
}
面试题:
ArrayList 与 LinkedList 区别:
1.ArrayList采用数组实现,LinkedList采用链表实现
2.ArrayList适用于频繁查找元素的场景,LinkedList适用于频繁修改元素的场景
ArrayList 封装的是数组;LinkedList封装的是链表
ArrayList时间复杂度为O(1),LinkedList时间复杂度为O(N)
2.4 List与简单JAVA类
remove()、contains()等方法需要equals()方法的支持(需要覆写equals方法比较属性值)
3.Set接口:
(本质为没有Value值得Map,先有的Map集合,才有得Set接口)
Set集合接口与List接口再大的不同在于:Set接口中的内容是不允许重复的。
Set接口并没有对Collection接口进行扩充,在Set接口中没有get()方法。
Set集合有两个常用子类:HashSet(无序存储)、TreeSet(有序存储)
3.1 HashSet(无序存储,实际上就是HashMap):底层为数组
1.允许为空,且不能重复,元素乱序存储
2.判断重复的依据 hashCode()+equals()
3.两个对象(第三方的类)相等判断:覆写 hashCode() equals() 方法消除重复
必须hashCode()与equals()均返回true才认为相等
public class Test {
public static void main(String[] args) {
Set<String> set=new HashSet<>();
set.add("ly");
set.add("ying1");
set.add("ly");
set.add(null);
System.out.println(set);
}
}
// 结果为:[null, ying1, ly]
3.2 TreeSet(有序存储):底层为红黑树(平衡二叉树的一种)
1.TreeSet不允许为null,有序存储,并且使用 升序 队列
2.TreeSet判断重复元素由CompareTo()来实现
3.自定义类要是想使用TreeSet,必须覆写Comparable接口,无需再覆写equals、hashCode
4.public int compareTo(T o);
a.返回 >0,当前对象大于目标对象
b.返回 <0,当前对象小于目标对象
c.返回 =0,当前对象等于目标对象
面试题:
如果两个对象的hashCode()相同,equals()不同结果是什么? 答:不能消除重复
如果两个对象的hashCode()不同,equals()相同结果是什么? 答:不能消除重复
个人建议:
1.保存自定义对象的时候使用 list 接口
2.保存系统信息的时候使用 Set 接口(避免重复)
4. 集合输出
四种:Iterator(推荐使用)、ListIterator、Enumeration、for-each
4.1 迭代器输出 Iterator (List接口 、Set接口 都有这个方法,且只能从前往后输出)
a. hasNext(): 判断是否有下一个元素
b.next() : 取得当前元素
c. remove(): 删除当前元素
4.2 双向迭代器 ListIterator (List接口独有)
除了 Iterator 提供的方法外:
hasPrevious(): 判断是否有上个元素
previous(); 取得上个元素
要想从后向前输出,必须先从前向后走一遍,否则无法进行从后向前
4.3 Enumeration 枚举输出(只有Vector类才有)
4.4 for-each 输出
5.Map集合:
一次性保存两个对象,且两个对象关系为 key = value (键值对对象)
Map特点:可以通过 key 找到对象 value public interface Map<K,V>
常用方法:
put(K key,V value): 向Map中追加数据
get(K key): 根据key取得对应value,没找到返回 null
keySet():返回所有key信息,保存在Set集合中(key值不能重复)
values():取得所有value信息,保存在Collection中 (value 值可以重复)
entrySet(): Set<Map,Entry<K,V>> 将Map集合变成Set集合
interface Entry<K,V> (方便Map 集合输出以及元素保存):Map集合内部接口,具体保存键值对对象
Map集合内部实际上是一个一个的Map.Entry对象
getKey():取得key值
getValue():取得Value值
ConcurrentHashMap:线程安全的HashMap
5.1 HashMap子类(): HashMap允许 key,value 为null,value 允许多个null
public class Test {
public static void main(String[] args) {
Map<Integer, String> map= new HashMap<>();
map.put(1, "hello");
map.put(2, "world");
map.put(1, null);
map.put(null, null);
Set<Integer> set=map.keySet();
Iterator<Integer> iterator=set.iterator();
while(iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
// 结果为:null 1 2
5.2 HashMap 源码解析:
5.2.1 HashMap 内部实现 :数组+链表
数组被分为一个个桶(bucket),通过hash决定键值对在数组中的寻址;hash相同的键值对,以链表形式存储。
当链表大小超过阈值(TREEIFY_THRESHOLD=8),将其树化成(红黑树)。
构造方法:
//初始化默认负载因子 0.75
this.loadFactor = DEFAULT_LOAD_FACTOR;
HashMap采用lazy_load,在初次使用时对table初始化
put方法实际调用 putVal(int hash, K key,V value,bollean onlulfAbsent,boolean evict)
putval()参数解析:
hash:表示键值对hash码
onlyIfAbsent:表示当key值重复时是否允许修改,默认为false
evict:当前HashMap状态,false表示正在初始化阶段
putVal():
a.如果表格为null,resize方法会初始化它(只在使用时初始化 lazy_load)
b.resize方法负责创建初始化存储表格或者在容量不满足需求时扩容
c.键值对在哈希表中的位置由(并非key的hash码),i=(n-1)&hash ,避免哈希碰撞
resize(): 初始化表格
static final int DEFAULT_INITIAL_CAPACITY=1<<4;
初始化时桶大小为16;
a.门限值=负载因子*容量 (什么时候需要调整容量)
b.门限值通常以倍数调整(newThr = oldThr <<1),当元素个数超过门限值大小时,调整Map大小
c.扩容后将老数组拷贝到新数组
容量、负载因子:
容量:
public HashMap(int initialCapacity):设置容量
预设容量的大小需要满足 > "预估元素容量/负载因子" 且同时是2的幂次方
public HashMap(int initialCapacity,float loadFactor)
负载因子:
a.不推荐改变,默认负载因子满足通用场景需求
b.不推荐设置负载因子超过0.75的值,会显著增加Hash冲突,降低HashMap性能;
同时也不能设置太小的负载因子值,会导致更加频繁的扩容,影响访问性能
树化:
JDK1.8为什么引进树化?
答:安全问题。当对象键值对哈希冲突时,会放在同一个桶中以链表形式存储,如果链表过长,会严重影响
存取的性能。
当一个桶中元素个数 > 8 个会调用treeifyBin() 尝试树化
如果容量小于 MIN_TREEIFY_CAPACITY,只会再次调用resize() 扩容
如果容量大于 MIN_TREEIFY_CAPACITY,才会树化改造(只把容量超过8的链表变成红黑树)
当链表变为红黑树后,元素删除n次后,如果红黑树的节点个数 < UNTREEIFY_THRESHOLD(默认为6)
在下一次调用resize()后,又会将红黑树变为链表
5.2.2 Hashtable子类(JDK1.0,第一个存储二元偶对象的值)
Hashtable 中,key,value 均不为空
线程安全:在增删改查等方法上加锁(synchronized,同Vector)
面试题:
HashMap 与 Hashtable
Hashtable 是早期 Java类提供的一个哈希表实现,本身是同步的,不支持 null 键和 null 值,
由于同步导致的性能开销,所以很少被推荐使用
HashMap JDK1.2 主要区别在于 HashMap 不是同步的,支持 null 键和 null 值。通常情况下。
HashMap进行 put 或 get 操作,可以达到常数时间的性能,所以它是绝大部分利用
键值对存储场景的首选。eg:实现一个用户ID和用户信息对应的运行时存储结构。
NO 区别· HashMap HashTable
1 性能 异步处理,效率高 同步处理,效率低
2 线程安全 线程不安全 线程安全
3 null操作 允许键和值为null(只允许一对) 不允许键和值为空
4 推出版本 JDK1.2 JDK1.0
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
所有与并发操作相关的集合:都在Java.util.concurrent 包
//将ArrayList包装为线程安全的List List<Integer> list=collection.synchronizedList(new ArrayList<>());
5.3 ConcurrentHashMap子类
5.3.1 为什么会有ConcurrentHashMap?
因为Collection集合提供的线程安全包装方法本质上与HashTable、Vector实现线程安全的核心思想一致,线程安全包装、
方法在各种修改方法(add,remove,set等方法)使用内建锁实现同步,没有真正意义上的改进,效率较低。而ConcurrentHashMap
的特点 = HashTable的安全性+HashMap的高性能,它既可以保证多个线程更新数据的同步,又可以保证很高的查询速度,并且
ConcurrentHashMap不允许数据为null.
2. ConcurrentHashMap JDK1.7的实现
答:a.分离锁。将内部结构分段(分成16个:segment),内部存放的HashEntry数组,hash相同的条目也按照链表存储,并发操作的时候只锁条目
对应的Segment段,有效避免类似Hashtable整体同步的问题,大大提高性能。
b.HashEntry内部使用volatile的value字段来保证可见性。
构造时,Segment数量由concurrentcylevel决定,默认为16,必须为2的幂数值。
####################################################################################################################
快速失败策略有关问题?
什么是并发修改?
答:当一个或多个线程正在遍历一个集合Collection,此时另一个线程修改了这个集合的内容(增、删、改),这就是并发修改
产生ConcurrentModeficationException的类集?
答: ArrayList、HashSet()、HashMap()
如何产生ConcurrentModification Exception?
//(modeCount)描述当前集合被修改的次数
protected transient int modeCount=0;
expectedModCount 在调用iterator时取得迭代器等于当前的modCount
//遍历时就赋值:
int expectedModeCount=modCount;
checkForComodification();
ConcurrentModifcationException产生于迭代器内部
expectedModCount!=modCount
如何解决ConcurrentModificationException的类集?
答:a.并发遍历集合时,不要修改集合的内容!
b.使用fail-safe类集(CopyOnWriteArrayList、ConcurrentHashMap)
什么是fail-fast 机制?(为了数据安全性)
fail-fast机制在遍历一个集合时,当集合结构被修改,会抛出ConcurrentModification Exception
fail-safe机制集合均为线程安全集合
CopyOnWriteArrayList使用Lock锁实现线程安全(JDK1.7之前的ConcurrentHashMap也采用Lock锁)
faile-safe任何对集合结构的修改都会在一个复制的集合上进行修改,因此不会抛出ConcurrentModificationException,因为原来集合根本就没变!
fail-safe机制有两个问题
(1)需要复制集合,产生大量的无效对象,开销大
(2)无法保证读取的数据是目前原始数据结构中的数据