java集合
Collection和Map都实现了Iterator接口
1:HashMap和Hashtable 、ConcurrentHashMap
HashMap、Hashtable
a. HashMap不是线程安全的;Hashtable线程安全,但效率低,由于是Hashtable是使用synchronized的,全部线程竞争同一把锁; html
b. HashMap的键和值均可觉得null,HashTable不能够。java
c. 多线程环境下,一般也不是用HashTable,由于效率低。算法
ConcurrentHashMap
经过分析Hashtable就知道,synchronized是针对整张Hash表的,即每次锁住整张表让线程独占,ConcurrentHashMap容许多个修改操做并发进行,其关键在于使用了锁分离技术。它使用了多个锁来控制对hash表的不一样部分进行的修改。 数组
ConcurrentHashMap内部使用segment数组来分别表示这些不一样的部分,每个segment都包含了一个Entry数组的hashtable,每一个段其实就是一个小的hashtable,它们有本身的锁。只要多个修改操做发生在不一样的段上,它们就能够并发进行。安全
有些方法须要跨段,好比size()和containsValue(),它们可能须要锁定整个表而而不只仅是某个段,这须要按顺序锁定全部段,操做完毕后,又按顺序释放全部段的锁。这里“按顺序”是很重要的,不然极有可能出现死锁数据结构
Hash表的一个很重要方面就是如何解决hash冲突,ConcurrentHashMap 和HashMap使用相同的方式,都是将hash值相同的节点放在一个hash链中。 多线程
问:咱们可使用CocurrentHashMap来代替HashTable吗?
能够并发
问:如何线程安全使用hashmap
HashMap配合Collections工具类使用实现线程安全Collections.synchronizedMap()。app
同时还有ConcurrentHashMap能够选择,该类的线程安全是经过Lock的方式实现的,因此效率高于Hashtable。jvm
问:为何HashMap是线程不安全的?
一:若是多个线程同时使用put方法添加元素,并且假设正好存在两个put的key(调用hashcode())发生了碰撞(hash值同样),那么根据HashMap的实现,这两个key会添加到数组的同一个位置,这样最终就会发生其中一个线程的put的数据被覆盖。
二:多个线程同时检测到元素个数超过数组大小*loadFactor(负载因子),这样就会发生多个线程同时对数组进行扩容,都在从新计算元素位置以及复制数据,可是最终只有一个线程扩容后的数组会赋给table,也就是说其余线程的都会丢失
HashMap原理:
哈希表 : 数组连续内存空间,查找速度快,增删慢;链表充分利用了内存,存储空间是不连续的,首尾存储上下一个节点的信息,因此寻址麻烦,查找速度慢,可是增删快;哈希表呢,综合了它们两个的有点,一个哈希表,由数组和链表组成。
HashMap的内部存储结构实际上是数组和链表的结合。当实例化一个HashMap时,系统会建立一个长度为Capacity的Entry数组,这个长度在哈希表中被称为容量(Capacity),在这个数组中能够存放元素的位置咱们称之为“桶”(bucket),每一个bucket都有本身的索引,系统能够根据索引快速的查找bucket中的元素。 每一个bucket中存储一个元素,即一个Entry对象,但每个Entry对象能够带一个引用变量,用于指向下一个元素,所以,在一个桶中,就有可能生成一个Entry链。
HashMap有四种构造方法:
HashMap():初始容量16,默认加载因子0.75
HashMap(int initialCapacity):自定义初始容量
HashMap(int initialCapacity,float loadFactor)
HashMap(Map<? extends K,? extends V> m)
这四个构造方法其实都受两个参数的影响:容量和加载因子。容量是哈希表中桶的数量,初始容量为16。加载因子是对哈希表的容量在自动增长resize()以前所达到尺度的描述。当哈希表中的条目数超过threshold(=Capacity*loadFactor) 的值时,要对哈希表进行rehash操做。
默认加载因子 (.75) 在时间和空间成本上寻求一种折衷。加载因子太高虽然减小了空间开销,但同时也增长了查询成本(在大多数HashMap 类的操做中,包括 get 和 put 操做,都反映了这一点)。在设置初始容量时应该考虑到映射中所需的条目数及其加载因子,以便最大限度地减小 rehash 操做次数。
HashMap遍历的两种方式
第一种:迭代器
Map map = new HashMap();
Iterator iter = map.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry) iter.next();
Object key = entry.getKey();
Object val = entry.getValue();
}(效率高)
Map map = new HashMap();
Iterator iter = map.keySet().iterator();
while (iter.hasNext()) {
Object key = iter.next();
Object val = map.get(key);
}(效率低)
第二种:for each遍历
for(Entry<Integer ,String>entry :hs.entrySet()){
System.out.println("key:"+entry.getKey()+" value:"+entry.getValue()); }
HashMap插入值
put(key, value)存储对象到HashMap中,使用get(key)从HashMap中获取对象。当咱们给put()方法传递键和值时,咱们先对键调用hashCode()方法,返回的hashCode用于找到bucket位置来储存Entry对象。
hashcode相同,因此它们的bucket位置相同,‘碰撞’会发生,存入对应链表
“若是HashMap的大小超过了负载因子(load factor)定义的容量,怎么办?”除非你真正知道HashMap的工做原理,不然你将回答不出这道题。默认的负载因子大小为0.75,也就是说,当一个map填满了75%的bucket时候,和其它集合类(如ArrayList等)同样,将会建立原来HashMap大小的两倍的bucket数组,来从新调整map的大小,并将原来的对象放入新的bucket数组中。这个过程叫做rehashing,由于它调用hash方法找到新的bucket位置。
解决哈希表的冲突-开放地址法和链地址法
1 开放地址法
这个方法的基本思想是:当发生地址冲突时,按照某种方法继续探测哈希表中的其余存储单元,直到找到空位置为止。这个过程可用下式描述:
H i ( key ) = ( H ( key )+ d i ) mod m ( i = 1,2,…… , k ( k ≤ m – 1))
增量 d 能够有不一样的取法,并根据其取法有不一样的称呼:
( 1 ) d i = 1 , 2 , 3 , …… 线性探测再散列;
( 2 ) d i = 1^2 ,- 1^2 , 2^2 ,- 2^2 , k^2, -k^2…… 二次探测再散列;
( 3 ) d i = 伪随机序列 伪随机再散列;
二、链地址法
将全部哈希地址相同的记录都连接在同一链表中图形相似于图2。也就是说,当HashMap中的每个bucket里只有一个Entry,不发生冲突时,Hashmap是一个数组,根据索引能够迅速找到Entry,可是,当发生冲突时,单个的bucket里存储的是一个Entry链,系统必须按顺序遍历每一个Entry,直到找到为止。
为了减小数据的遍历,冲突的元素都是直接插入到第一个Entry后面的,因此,最先放入bucket中的Entry,位于Entry链中的最末端。这从put(K key,V value)中也能够看出,在同一个bucket存储Entry链的状况下,新放入的Entry老是位于bucket中。
2 : ArrayList , LinkedList , Vector, Stack
ArrayList 是一个可改变大小的数组.当更多的元素加入到ArrayList中时,其大小将会动态地增加.内部的元素能够直接经过get与set方法进行访问,由于ArrayList本质上就是一个数组.
LinkedList 是一个双链表,在添加和删除元素时具备比ArrayList更好的性能.但在get与set方面弱于ArrayList. 固然,这些对比都是指数据量很大或者操做很频繁的状况下的对比,若是数据和运算量很小,那么对比将失去意义.
Vector 和ArrayList相似,但他是线程安全的。若是你的程序自己是线程安全的(thread-safe,没有在多个线程之间共享同一个集合/对象),那么使用ArrayList是更好的选择。
Vector和ArrayList在更多元素添加进来时会请求更大的空间。Vector每次请求其大小的双倍空间,而ArrayList每次对size增加50%(初始容量10).
Stack是继承自Vector也是线程安全的, 因为Vector是经过数组实现的,这就意味着,Stack也是经过数组实现的,而非链表。固然,咱们也能够将LinkedList看成栈来使用!
1. public push (item ) 把项 压入栈顶。其做用与 addElement (item ) 相同。
参数 item 压入栈顶的项 。 返回: item 参数 ;add返回boolean
2. public pop () 移除栈顶对象,并做为函数的值 返回该对象。
返回:栈顶对象(Vector 对象的中的最后一项)。
抛出异常 : EmptyStackException 若是堆栈式空的 。。。
3. public peek() 查看栈顶对象而不移除它。。
返回:栈顶对象(Vector 对象的中的最后一项)。
抛出异常 : EmptyStackException 若是堆栈式空的 。。。
4. public boolean empty (测试堆栈是否为空。)
问:为何String, Interger这样的wrapper类适合做为键?
String, Interger这样的wrapper类做为HashMap的键是再适合不过了,并且String最为经常使用。由于String是不可变的,也是final的,并且已经重写了equals()和hashCode()方法了。(包装类都是不可变的)
问:咱们可使用自定义的对象做为键吗?
可能使用任何对象做为键,只要它遵照了equals()和hashCode()方法的定义规则,而且当对象插入到Map中以后将不会再改变了。
Iterator迭代器:
next() hasNext()
remove()将会删除上次调用next方法时返回的元素,若是想要删除指定位置上的元素,须要越过这个元素,若是调用remove以前没有调用next将是不合法的,若是这样作,将会抛出一个IllegalStateException异常。删除集合中Iterator指向位置后面的元素
ListIterator迭代器
add(E e): 将指定的元素插入列表,插入位置为迭代器当前位置以前
hasPrevious()
previous()
TreeSet和TreeMap的关系
其中 TreeMap 是 Map 接口的经常使用实现类,而 TreeSet 是 Set 接口的经常使用实现类。虽然 TreeMap 和 TreeSet 实现的接口规范不一样,但TreeSet 底层是经过 TreeMap 来实现的(如同HashSet底层是是经过HashMap来实现的同样),所以两者的实现方式彻底同样。而 TreeMap的实现就是红黑树算法。底层数据结构都是红黑树
相同点:
1. TreeMap和TreeSet都是有序的集合,也就是说他们存储的值都是排好序的。
TreeMap和TreeSet都是非同步集合,所以他们不能在多线程之间共享,不过可使用方法Collections.synchroinzedMap()来实现同步
运行速度都要比Hash集合慢,他们内部对元素的操做时间复杂度为O(logN)
不一样点:
1. 最主要的区别就是TreeSet和TreeMap非别实现Set和Map接口
2. TreeSet只存储一个对象,而TreeMap存储两个对象Key和Value(仅仅key对象有序)
3. TreeSet中不能有重复对象,而TreeMap中能够存在
HashMap和TreeMap的区别?
HashMap-- 底层是哈希表数据结构,能够存入null键null值,线程不一样步的
TreeMap -- 底层是二叉树数据结构(红黑树),线程不一样步,能够给map集合中的键进行排序
HashMap经过hashcode对其内容进行快速查找,
而TreeMap中全部的元素都保持着某种固定的顺序,若是你须要获得一个有序的结果你就应该使用TreeMap
红黑树有如下限制:
1. 节点必须是红色或者是黑色
2. 根节点是黑色的
3. 全部的叶子节点是黑色的。
4. 每一个红色节点的两个子节点是黑色的,也就是不能存在父子两个节点全是红色
5. 从任意每一个节点到其每一个叶子节点的全部简单路径上黑色节点的数量是相同的。
问:咱们能本身写一个容器类,而后使用 for-each 循环码?
能够,你能够写一个本身的容器类。若是你想使用 Java 中加强的循环来遍历,你只须要实现 Iterable 接口。若是你实现 Collection 接口,默认就具备该属性。
问:ArrayList 和 HashMap 的默认大小是多少?
在 Java 7 中,ArrayList 的默认大小是 10 个元素(扩充百分之五十),HashMap 的默认大小是16个元素(两倍)。Vector(两倍)
问:为何在重写 equals 方法的时候须要重写 hashCode 方法?
若是你重载了equals,好比说是基于对象的内容实现的,而保留hashCode的实现不变,那么极可能某两个对象明明是“相等”,而hashCode却不同。
附:synchronized(Java语言的关键字)
1、当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程获得执行。另外一个线程必须等待当前线程执行完这个代码块之后才能执行该代码块。
2、然而,当一个线程访问object的一个synchronized(this)同步代码块时,另外一个线程仍然能够访问该object中的非synchronized(this)同步代码块。
3、尤为关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其余线程对object中全部其它synchronized(this)同步代码块的访问将被阻塞。
4、第三个例子一样适用其它同步代码块。也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,它就得到了这个object的对象锁。结果,其它线程对该object对象全部同步代码部分的访问都被暂时阻塞。
synchronized和lock、volatile
lock:
加锁和解锁处须要经过lock()和unlock()显示指出。会在finally块中写unlock()以防死锁。
是一个类
线程须要的锁被占用,就是能够尝试得到锁,线程能够不用一直等待
大量同步的状况使用明显提升性能
Synchronized:
synchronized能够加在方法上,也能够加在特定代码块中
Java的关键字,在jvm层面上
线程须要的锁被占用,会一直等待
少许同步的状况使用
volatile
用volatile修饰的变量,线程在每次使用变量的时候,都会读取变量最新值。volatile很容易被误用,用来进行原子性操做。