HashMap与ConcurrentHashMap还有HashTable
见链接
HashMap
http://zhangshixi.iteye.com/blog/672697
HashMap判断key是否相等用到了什么?
e.hash == hash && ((k = e.key) == key || key.equals(k))
==与equal的区别:
“==”比较的是值【变量(栈)内存中存放的对象的(堆)内存地址】
equal用于比较两个对象的值是否相同【不是比地址】
很多都重写了equal
什么是FailFast?
modCount跟expectedModCount不等,则有其他线程修改了 HashMap
抛出ConcurrentModificationException
由所有HashMap类的“collection 视图方法”所返回的迭代器都是快速失败的:在迭代器创建之后,如果从结构上对映射进行修改,除非通过迭代器本身的 remove 方法,其他任何时间任何方式的修改,迭代器都将抛出 ConcurrentModificationException。
JDK1.8做了什么优化?
在Java8中为了优化该最坏情况下的性能,采用了平衡树来存放这些hash冲突的键值对,性能由此可以提升至O(logn)。
put函数中,如果链表长度大于8,链表转化为红黑树
ConcurrentHashMap
http://www.importnew.com/22007.html
https://my.oschina.net/hosee/blog/639352
HashTable与ConcurrentHashMap
HashTable是一个线程安全的类,它使用synchronized来锁住整张Hash表来实现线程安全,即每次锁住整张表让线程独占。
ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术。
volatile变量在这里的作用?
volatile让变量每次在使用的时候,都从堆内存中取。而不是从各个线程的“工作内存”。
volatile具有synchronized关键字的“可见性”,但是没有synchronized关键字的“并发正确性”,也就是说不保证线程执行的有序性。
也就是说,volatile变量对于每次使用,线程都能得到当前volatile变量的最新值。但是volatile变量并不保证并发的正确性。
为什么ConcurrentHashMap同时进行删除和遍历不会互相影响?
而且每次删除一个节点时,会将删除节点之前的所有节点 拷贝一份组成一个新的链,而将当前节点的上一个节点的next指向当前节点的下一个节点,从而在删除以后 有两条链存在,因而可以保证即使在同一条链中,有一个线程在删除,而另一个线程在遍历,它们都能工作良好,因为遍历的线程能继续使用原有的链。
什么是happens-before?
一种更加细粒度的happens-before关系,即如果遍历线程在删除线程结束后开始,则它能看到删除后的变化,如果它发生在删除线程正在执行中间,则它会使用原有的链,而不会等到删除线程结束后再执行,即看不到删除线程的影响。
而HashMap中的Entry只有key是final的
不变 模式(immutable)是多线程安全里最简单的一种保障方式。
可以看到除了value不是final的,其它值都是final的,这意味着不能从hash链的中间或尾部添加或删除节点,因为这需要修改next 引用值,所有的节点的修改只能从头部开始。对于put操作,可以一律添加到Hash链的头部。但是对于remove操作,可能需要从中间删除一个节点,这就需要将要删除节点的前面所有节点整个复制一遍,最后一个节点指向要删除结点的下一个结点。这在讲解删除操作时还会详述。为了确保读操作能够看到最新的值,将value设置成volatile
Concurrent 与HashTable的比较
https://my.oschina.net/hosee/blog/675423
HashTable与HashMap的不同
继承不同
HashTable继承自Dictionary
HashMap继承AbastractMap
初始大小不同
HashTable初始大小为11。因子0.75
HashMap初始大小16 因子 0.75
扩大方法不同方法不同
HashTable采用rehash方法 扩大为原来的2倍+1
HashMap采用resize方法扩大为原来的2倍
线程与其他
HashMap是HashTable的轻量级实现,用containsvalue、containsKey取代了contains方法
HashTable是线程安全的
HashTable使用Enumeration,HashMap用Iterator
Java基本数据类型以及包装类
http://blog.youkuaiyun.com/u010293702/article/details/44621675
基本类型(原始数据类型) byte short int long float double char boolean
这种类型是通过诸如 int a=7; 的形式来定义的,称为自动变量。这里自动变量是字面值。不是类的实例,即不是类的引用,这里并没有类的存在。a 是指向一个 int 类型的引用,指向 7 这个字面值。由于其大小确定生存期可知(这些定义在某个程序块中,程序块退出后,字段值就消失),因此存在栈中.
由于栈的数据可以共享,因此 int a=3; int b=3; 这段代码,编译器首先处理 int a =3; ,先会在栈中创建一个变量为 a 的引用,然后查找有没有字面值为 3的地址,没有找到,就开辟一个存放 3 这个字面值的地址,然后将a 指向 3 的地址。接下来处理int b =3; 在创建完 b 这个引用变量后,由于栈中已经有 3 这个字面值,便将 b 指向 3 的地址。【定义变量,给变量赋值】
JDK1.5 为Integer 增加了一个全新的方法:public static Integer valueOf(int i) 在自动装箱过程时,编译器调用的是static Integer valueOf(int i)这个方法 于是Integer a=3; ==> Integer a=Integer.valueOf(3);。
如果不需要新的 Integer 实例,则通常应优先使用该方法,而不是构造方法 Integer(int),因为该方法有可能通过缓存经常请求的值而显著提高空间和时间性能 .
public static Integer valueOf(int i) {
assert IntegerCache.high >= 127;
//static final int low = -128;
//当-128=<i<=127的时候,就直接在缓存中取出 i de Integer 类型对象
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
//否则就在堆内存中创建
return new Integer(i);
}
private static class IntegerCache {
static final int low = -128; //最小值是固定的
static final int high;
static final Integer cache[];//cache 缓存是一个存放Integer类型的数组
static { //初始化,最大值可以配置
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
}
high = h;
cache = new Integer[(high - low) + 1]; //初始化数组
int j = low;
//缓存区间数据
for(int k = 0; k < cache.length; k++)
//将-128~127包装成256个对象存入缓存
cache[k] = new Integer(j++);
}
private IntegerCache() {}
其他的包装器:
Boolean: (全部缓存)
Byte: (全部缓存)
Character ( <=127 缓存)
Short (-128~127 缓存)
Long (-128~127 缓存)
Float (没有缓存)
Doulbe (没有缓存)
ArrayList
采用数组来存储数据,默认大小为10
如果空间不足?如何处理
调用 ensureCapacityInternal方法
首先判断是否是空方法?
如果是空,则调整为最小的数组,否则,调用
ensureExplicitCapacity方法,modcount++
如果最小minCapacity大于length则
调用grow方法
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
采用右移一位加上原来的空间大小
扩大为原来的1.5倍
Vector与ArrayList的不同
初始化方法:
可以增加一个capacityIncrement参数,设置每次增加的大小,否则每次增加一倍,既原来的两倍
ArrayList 每次增加为原来的1.5倍
返回大小
Vector capacity方法,synchronized
ArrayList size方法
其他
大部分方法使用synchronized
Object有哪些方法
- getClass () native方法
- hashCode() native
- equals 用==实现 被很多类重写
- clone native
- toString 被很多类重写
- notify final native
- notifyAll final native
- wait final native
- finalize
collection 接口实现的 集合 比较列表
类型 | 初始大小 | 实现方法 | 扩大的倍数 | 插入数据的方法 | 是否线程安全 | 是否允许空值 | 备注 |
---|---|---|---|---|---|---|---|
HashMap | 16最大2^30,默认扩大因子0.75,最大为2^30 | Entry[] +链地址法 | 原来的2倍 | put函数,对key做hash(移位操作),并对数组大小做与操作得到数组的index | 非线程安全 | 允许空Key和Value,空Key插入到table[0] | JDK1.8中使用红黑树来代替链表,当(链表大于8时) |
ConcurrentHashMap | 16最大2^30 | segment[](继承了重入锁)+链地址法 | 一个segment相当于一个HashTable | key通过一次hash运算得到一个hash值,将得到hash值向右按位移动segmentShift位,然后再与segmentMask做&运算得到segment的索引j。 | 线程安全(分段锁) | 接使用Key的hashCode,所以不允许空Key(hashseed^k.hashCode()),也不允许空Value | JDK1.8改用了无锁算法(CAS)沿用了与它同时期的HashMap版本的思想,底层依然由“数组”+链表+红黑树的方式思想,又增加了很多辅助的类,例如TreeBin,Traverser |
HashTable | 11默认扩大因子0.75 | Entry[] +链地址法 | 2倍+1 | put,直接使用Key的hashCode,所以不允许空Key(hashseed^k.hashCode()),也不允许空Value,index = (hash & 0x7FFFFFFF) % tab.length | 线程安全 synchronized | 不允许空Value,与空Key | 一个旧的实现,集成的不是abstractMap而是Direction,且效率比concurrentHashMap低,而且没有迭代器使用的是Enumeration |
TreeMap | 采用红黑树,无默认大小,可以定义comparator | 红黑树,Entry() | 红黑树,无须扩大 | put,调用了compare方法对Key作比较,所以Key不能为空 | 非线程安全 | 不允许空Key允许空Value | 常用语排序,可以根据getFirstEntry或者getLastEntry获取最大或者最小的值 |
ArrayList | 默认大小为10,可以自定义大小 | 数组实现 Object[] elementData | 原来的1.5倍 | add方法,如果大小不够,则扩大为原来的1.5倍 | 非线程安全 | 允许null,但是容易出错 | 常用的列表工具,功能强大 |
Vector | 默认大小为10,可自定义大小,同时可自定义扩大的大小(capacityIncrement) | 与ArrayList一样,数组实现 | 原来的2倍,如果设置了capacityIncrement则增加capactityIncrement | add方法,与ArrayList相一致 | 线程安全,synchronized | 允许null与ArrayList一致 | 同步的列表 |
LinkedList | 链表实现,无初始大小 | 双向循环链表 Node | 链表实现,无须扩大 | add方法,添加到链表末尾,可以addFirst或者addLast | 非线程安全 | 不允许控制会报错 | 链表实现,可当做链表使用,getLast getFirst |
阻塞队列的实现 ArrayBlockingQueue
通过数组来保存队列,先入先出,在初始化就确定好大小,不能更改
通过contion来控制,有notEmpty、notFull两个contion
put函数,如果已满,则notFull.await();
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length)
notFull.await();
insert(e);
} finally {
lock.unlock();
}
}
take函数,如果为空则notEmpty.await();
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return extract();
} finally {
lock.unlock();
}
}
在执行期间会加锁,在finally会解锁
用wait和notify实现阻塞队列
设置两个boolean值,isEmpty和isFull
等待方:
1 ) 获取对象的锁,使用锁同步(synchronized\lock等)
2)如果条件不满足,调用对象的wait方法。被唤醒后仍然需要检查条件
3)条件满足,执行逻辑
通知方:
1)获得对象锁
2)改变条件
3)对象.notify()(或notifyAll())
用java统计一个文本文件中出现的频率最高的20个单词
用TreeMap
指定排序器 Comparator 自己实现compareto方法
firstKey
http://huangqiqing123.iteye.com/blog/1461163
Value排序 将
List<Entry<String, String>> list = new ArrayList<Entry<String, String>>(map.entrySet());
Collections.sort(list,new Comparator<Map.Entry<String,String>>() {
//升序排序
public int compare(Entry<String, String> o1, Entry<String, String> o2) {
return o1.getValue().compareTo(o2.getValue());
}
});
http://blog.youkuaiyun.com/liuxiao723846/article/details/50454622
LinkedList
是基于链表的
java多态的实现原理
运行时绑定
父类的引用指向子类对象
接口指向实现类
锁的等级:方法锁、对象锁、类锁
http://m.blog.youkuaiyun.com/article/details?id=52200865
在修饰代码块的时候需要一个reference对象作为锁的对象.
在修饰方法的时候默认是当前对象作为锁的对象.
在修饰类时候默认是当前类的Class对象作为锁的对象.
对象锁(synchronized修饰方法或代码块)
类锁(synchronized 修饰静态的方法或代码块)
AIO/NIO/BIO
同步阻塞IO(JAVA BIO):
同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
同步非阻塞IO(Java NIO) : 同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。用户进程也需要时不时的询问IO操作是否就绪,这就要求用户进程不停的去询问。
异步阻塞IO(Java NIO):
此种方式下是指应用发起一个IO操作以后,不等待内核IO操作的完成,等内核完成IO操作以后会通知应用程序,这其实就是同步和异步最关键的区别,同步必须等待或者主动的去询问IO是否完成,那么为什么说是阻塞的呢?因为此时是通过select系统调用来完成的,而select函数本身的实现方式是阻塞的,而采用select函数有个好处就是它可以同时监听多个文件句柄(如果从UNP的角度看,select属于同步操作。因为select之后,进程还需要读写数据),从而提高系统的并发性!
(Java AIO(NIO.2))异步非阻塞IO:
在此种模式下,用户进程只需要发起一个IO操作然后立即返回,等IO操作真正的完成以后,应用程序会得到IO操作完成的通知,此时用户进程只需要对数据进行处理就好了,不需要进行实际的IO读写操作,因为真正的IO读取或者写入操作已经由内核完成了。
xml的解析
DOM、SAX、PULL
1. DOM生成和解析XML文档
为 XML 文档的已解析版本定义了一组接口。解析器读入整个文档,然后构建一个驻留内存的树结构,然后代码就可以使用 DOM 接口来操作这个树结构。优点:整个文档树在内存中,便于操作;支持删除、修改、重新排列等多种功能;缺点:将整个文档调入内存(包括无用的节点),浪费时间和空间;使用场合:一旦解析了文档还需多次访问这些数据;硬件资源充足(内存、CPU)。
2.SAX生成和解析XML文档
为解决DOM的问题,出现了SAX。SAX ,事件驱动。当解析器发现元素开始、元素结束、文本、文档的开始或结束等时,发送事件,程序员编写响应这些事件的代码,保存数据。优点:不用事先调入整个文档,占用资源少;SAX解析器代码比DOM解析器代码小,适于Applet,下载。缺点:不是持久的;事件过后,若没保存数据,那么数据就丢了;无状态性;从事件中只能得到文本,但不知该文本属于哪个元素;使用场合:Applet;只需XML文档的少量内容,很少回头访问;机器内存少;
DOM4J生成和解析XML文档
DOM4J 是一个非常非常优秀的Java XML API,具有性能优异、功能强大和极端易用使用的特点,同时它也是一个开放源代码的软件。如今你可以看到越来越多的 Java 软件都在使用 DOM4J 来读写 XML,特别值得一提的是连 Sun 的 JAXM 也在用 DOM4J。JDOM生成和解析XML
为减少DOM、SAX的编码量,出现了JDOM;优点:20-80原则,极大减少了代码量。使用场合:要实现的功能简单,如解析、创建等,但在底层,JDOM还是使用SAX(最常用)、DOM、Xanan文档。Apache Commons Configuration
•·当xml结构大变化的时候不用过多的修改解析xml的代码
•用户只需要修改自己的解析语法树即可。
•客户只需要修改语法树框架去解析,思考的起点是不是跟设计模式中的解释器模式类似。构建抽象语法树并解释执行。
•用户只需要关心和修改自己的解析语法树即可。
•用户不用关系如何解析只需要配置对应的解析语法规则即可。
•简化程序xml配置结构变化后大幅度的修改代码。
JNI
JNI是Java Native Interface的缩写,它提供了若干的API实现了Java和其他语言的通信(主要是C&C++)。
主要用于调用其他语言
静态分派与动态分派
这里所谓的分派指的是在Java中对方法的调用。
分派是多态性的体现,Java虚拟机底层提供了我们开发中“重写”和“重载”的底层实现。其中重载属于静态分派,而重写则是动态分派的过程。
静态分派
静态分派只会涉及重载,而重载是在编译期间确定的,那么静态分派自然是一个静态的过程(因为还没有涉及到Java虚拟机)。静态分派的最直接的解释是在重载的时候是通过参数的静态类型而不是实际类型作为判断依据的。比如创建一个类O,在O中创建了静态类内部类A,O中又有两个静态类内部类B、C继承了这个静态内部类A
public class O{
static class A{}
static class B extends A{}
static class C extends A{}
public void a(A a){
System.out.println("A method");
}
public void a(B b){
System.out.println("B method");
}
public void a(C c){
System.out.println("C method");
}
public static void main(String[] args){
O o = new O();
A b = new B();
A c = new C();
o.a(b);
o.a(c);
}
}
运行的结果是打印出连个“A method”。原因在于静态类型的变化仅仅在使用时发生,变量本省的类型不会发生变化。比如我们这里中A b = new B();
虽然在创建的时候是B的对象,但是当调用o.a(b)的时候才发现是A的对象,所以会输出“A method”。也就是说在发生重载的时候,Java虚拟机是通过参数的静态类型而不是实际参数类型作为判断依据的。
因此,在编译阶段,Javac编译器选择了a(A a)这个重载方法。
虽然编译器能够在编译阶段确定方法的版本,但是很多情况下重载的版本不是唯一的,在这种模糊的情况下,编译器会选择一个更合适的版本。
动态分派
动态分派的一个最直接的例子是重写。对于重写,我们已经很熟悉了,那么Java虚拟机是如何在程序运行期间确定方法的执行版本的呢?
解释这个现象,就不得不涉及Java虚拟机的invokevirtual指令了,这个指令的解析过程有助于我们更深刻理解重写的本质。该指令的具体解析过程如下:
找到操作数栈栈顶的第一个元素所指向的对象的实际类型,记为C
如果在类型C中找到与常量中描述符和简单名称都相符的方法,则进行访问权限的校验,如果通过则返回这个方法的直接引用,查找结束;如果不通过,则返回非法访问异常
如果在类型C中没有找到,则按照继承关系从下到上依次对C的各个父类进行第2步的搜索和验证过程
如果始终没有找到合适的方法,则抛出抽象方法错误的异常
从这个过程可以发现,在第一步的时候就在运行期确定接收对象(执行方法的所有者程称为接受者)的实际类型,所以当调用invokevirtual指令就会把运行时常量池中符号引用解析为不同的直接引用,这就是方法重写的本质。
Java实现一个cache
采用 LFU最近最少使用
LRU最近最少使用
FIFO算法
使用Map来保存,并设置一个过期时间 expire
CacheObject(K2 key, V2 value, long ttl) {
this.key = key;
this.cachedObject = value;
this.ttl = ttl;
this.lastAccess = System.currentTimeMillis();
}
final K2 key;
final V2 cachedObject;
long lastAccess; // 最后访问时间
long accessCount; // 访问次数
一个简单的缓存实现
分为新的缓存空间(ConcurrentHashMap)和旧的缓存空间(WeakhashMap),利用了WeakhashMap的特性,如果近期被使用了就放在新空间,新空间满了就全放入待回收空间,等待回收,如果没有被调用,那么等没有应用后就会被回收
http://www.oschina.net/code/snippet_55577_3887
- 以弱键 实现的基于哈希表的 Map。在 WeakHashMap 中,当某个键不再正常使用时,将自动移除其条目。更精确地说,对于一个给定的键,其映射的存在并不阻止垃圾回收器对该键的丢弃,这就使该键成为可终止的,被终止,然后被回收。丢弃某个键时,其条目从映射中有效地移除
-
WeakHashMap,此种Map的特点是,当除了自身有对key的引用外,此key没有其他引用那么此map会自动丢弃此值
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public final class SimpleCache<K,V> {
private final Lock lock = new ReentrantLock();
private final int maxCapacity;
private final Map<K,V> eden;
private final Map<K,V> longterm;
public SimpleCache(int maxCapacity) {
this.maxCapacity = maxCapacity;
this.eden = new ConcurrentHashMap<K,V>(maxCapacity);
this.longterm = new WeakHashMap<K,V>(maxCapacity);
}
public V get(K k) {
V v = this.eden.get(k);
if (v == null) {
lock.lock();
try{
v = this.longterm.get(k);
}finally{
lock.unlock();
}
if (v != null) {
this.eden.put(k, v);
}
}
return v;
}
public void put(K k, V v) {
if (this.eden.size() >= maxCapacity) {
lock.lock();
try{
this.longterm.putAll(this.eden);
}finally{
lock.unlock();
}
this.eden.clear();
}
this.eden.put(k, v);
}
}