java中集合类两大分支
- Collection
- Map
HashMap、Hashtable、ConcurrentHashMap特点
共同点:都实现了Map接口
HashMap特点
- 非线程synchronized,线程不安全
- 可存放null键、null值
- 数组+链表数据结构
- 初始化大小为16,加载因子0.75
Hashtable特点
- 线程synchronized,线程安全
- 不可存放null键、null值
LinkedHashMap特点
- LinkedHashMap是HashMap的子类
- 非线程synchronized,线程不安全
- 可存放null键、null值
- 数组+链表数据结构
ConcurrentHashMap特点
- 线程synchronized,线程安全
- 不可存放null键、null值
HashMap示例
HashMap<String, String> map = new HashMap<>();
map.put("name", "xiaoming");
map.put("", "");
map.put(null, null);
System.out.println("=" + map.get(null));
map.put(null, "nihao");
System.out.println("="+map.get(null));//当是值是null是返回null
System.out.println("="+map.get(""));
System.out.println("="+map.get("age"));//当没有键时返回值为null
打印结果
=null
=nihao
=
=null
总结
HashMap中不能由get()方法来判断HashMap中是否存在某个键,而应该用containsKey()方法来判断
Hashtable示例
Hashtable<String, String> table = new Hashtable<>();
table.put("", "");
System.out.println(table.get("")+"==="+table.get("ni"));
总结
Hashtable中采用的锁机制是一次锁住整个hash表,从而同一时刻只能由一个线程对其进行操作。由于Hashtable是线程安全的也是synchronized, 所以在单线程环境下它比HashMap要慢。如果你不需要同步,只需要单一线程,那么使用HashMap性能要快于Hashtable
LinkedHashMap示例
LinkedHashMap<String, String> linkedHashMap = new LinkedHashMap<>();
linkedHashMap.put(null, null);
System.out.println("linked="+linkedHashMap.get(null));
总结
LinkedHashMap是HashMap的子类,与HashMap有着同样的存储结构,但它加入了一个双向链表的头结点,将所有put到LinkedHashmap的节点按照插入的先后顺序依次加入到以head为头结点的双向循环链表的尾部。
实际上就是HashMap和LinkedList两个集合类的存储结构的结合。在LinkedHashMapMap中,所有put进来的Entry都HashMap的存储结构进行存储,但它又额外定义了一个以head为头结点的空的双向循环链表,每次put进来Entry,除了将其保存到哈希表中对应的位置上外,还要将其插入到双向循环链表的尾部。
在 LinkedHashMap 内部accessOrder标志位特点
- 当accessOrder为false(默认),表示双向链表中的元素按照Entry插入LinkedHashMap中的先后顺序排序,每次put到LinkedHashMap中的Entry都放在双向链表的尾部
- 当accessOrder为true时,表示双向链表中的元素按照访问的先后顺序排列,从而实现LRU算法
ConcurrentHashMap示例
ConcurrentHashMap<String, String> concurrentHashMap = new ConcurrentHashMap<>();
concurrentHashMap.put("", "");//不能存放null键 null值
System.out.println("con="+concurrentHashMap.get(""));
总结
ConcurrentHashMap中在JDK6和JDK中采用的是分段锁,只有在同一个分段内才存在竞态关系,不同的分段锁之间没有锁竞争。相比于对整个Map加锁的Hashtable设计,分段锁大大的提高了高并发环境下的处理能力。在JDK8中采用CAS(Compare-and-swap),即在不使用锁的情况下,在没有线程阻塞的情况下实现同步。这样可以避免竞态、死锁等问题。
说明:CAS是一个原子操作,用于多线程环境下的同步。它比较内存中的内容和给定的值,只有当两者相同时(说明其未被修改),才会修改内存中的内容。
SynchronizedSortedMap示例
Map<String, String> synchronizedMap = Collections.synchronizedMap(map);
if (synchronizedMap.containsKey("name")) {
synchronizedMap.remove("name");
}
总结
线程A执行了containsKey方法返回true,准备执行remove操作;
这时另一个线程B开始执行,同样执行了containsKey方法返回true,并接着执行了remove操作;然后线程A接着执行remove操作时发现此时已经没有这个元素了。显然没有按照我们的要求去执行
要保证这段代码按我们的意愿工作,一个办法就是对这段代码进行同步控制但是这么做付出的代价太大。
ArrayList、Vector、LinkedList特点
ArrayList特点
- 基于动态数组的数据结构
- 查询速度快(不需要移动指针)
- 线程不安全
- 初始化大小为10,扩容算法=(旧容量*3)/2+1
Vector特点
- Verctor类和ArrayList的功能近乎相同
- 线程安全
LinkedList特点
- 基于双向循环链表数据结构
- 添加和删除速度快(不需要移动数据)
- 线程不安全
sleep、wait特点
sleep特点
- sleep来自Thread类
- sleep方法没有释放锁
- sleep(milliseconds)可以用时间指定使它自动唤醒过来,如果时间不到只能调用interrupt()强行打断
wait特点
- wait来自Object类
- wait方法释放了锁
- 要等待其他线程调用notify/notifyAll唤醒等待池中的所有线程
注意
- sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常
- sleep是Thread的静态类方法,谁调用的谁去睡觉,即使在a线程里调用b的sleep方法,实际上还是a去睡觉,要让b线程睡觉要在b的代码中调用sleep
- wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用
synchronized(x){
x.notify()
//或者wait()
}
接口和抽象类有什么区别
相同点
- 抽象类和接口都不能直接实例化
- 抽象类里的抽象方法必须全部被子类实现,否则子类也只能是抽象类,
- 实现接口的类,如果不能全部实现接口方法,那该类也只能是抽象类
不同点
- 接口只能声明方法,抽象类可声明方法,也可实现方法(抽象类里可以没有抽象方法)
- 接口可继承接口,并可多继承接口,类只能单继承
- 抽象方法可用public、protected和default修饰符
- 接口方法默认修饰符是public。也不可使用其它修饰符
- 如果一个类有抽象方法,那这个类只能是抽象类
- 抽象方法需要被实现,所以不能用static修饰,也不能用private修饰
递归优缺点
优点
- 简洁
- 在树的前序,中序,后序遍历算法中,递归的实现明显要比循环简单得多
缺点
- 递归由于是函数调用自身,而函数调用是有时间和空间的消耗的:每一次函数调用,都需要在内存栈中分配空间以保存参数、返回地址以及临时变量,而往栈中压入数据和弹出数据都需要时间
- 递归中很多计算都是重复的,由于其本质是把一个问题分解成两个或者多个小问题,多个小问题存在相互重叠的部分,则存在重复计算
- 调用栈可能会溢出,其实每一次函数调用会在内存栈中分配空间,而每个进程的栈的容量是有限的,当调用的层次太多时,就会超出栈的容量,从而导致栈溢出