java集合

 

Set接口

HashSet:线程不安全,存取速度快

     底层实现是一个HashMap(保存数据),实现Set接口

     默认初始容量为16(为何是16,见下方对HashMap的描述)

     加载因子为0.75:即当 元素个数 超过 容量长度的0.75倍 时,进行扩容

     扩容增量:原容量的 1

     如 HashSet的容量为16,一次扩容后是容量为32

          1 . Set中是不能出现重复数据的。

          2 . Set中可以出现空数据。

          3 . Set中的数据是无序的。

LinkedHashSet:这个相对于HashSet来说有一个很大的不一样是LinkedHashSet是有序的。

TreeSet:

         1.写入的数据是有序的。

         2.不写入重复数据

问:TreeSetTreeMap的关系

TreeSet 底层是通过 TreeMap 来实现的(如同HashSet底层是是通过HashMap来实现的一样),底层数据结构都是红黑树

相同点:

TreeMapTreeSet都是有序的集合,也就是说他们存储的值都是排好序的。

TreeMapTreeSet都是非同步集合,因此他们不能在多线程之间共享,不过可以使用方法Collections.synchroinzedMap()来实现同步

运行速度都要比Hash集合慢,他们内部对元素的操作时间复杂度为O(logN)

不同点:

最主要的区别就是TreeSetTreeMap非别实现SetMap接口

TreeSet只存储一个对象,而TreeMap存储两个对象KeyValue(仅仅key对象有序)

TreeSet中不能有重复对象,而TreeMap中可以存在

List接口

ArrayListVector、stack默认初始容量为10

Vector:线程安全,但速度慢

    底层数据结构是数组结构

    加载因子为1:即当 元素个数 超过 容量长度 时,进行扩容

    扩容增量:原容量的 2

ArrayList:线程不安全,查询速度快

    底层数据结构是数组结构

    扩容增量:扩容50%

linkedList :底层是双向链表,新增和删除比arrayList更好。但是查找和修改不如arrayList

Stackstack继承自vector,那么底层也是数组实现的。他的方法如下

1. public push  item   把项压入栈顶。  返回: item 参数  

2. public pop () 移除栈顶对象,并作为函数的值 返回该对象

返回:栈顶对象  

3. public peek() 查看栈顶对象而不移除它

返回:栈顶对象 

4.public boolean empty (测试堆栈是否为空。)

问:ArrayListVectorstacklinkedList区别

问:iterator listiterator

在使用java集合的时候,都需要使用Iterator。但是java集合中还有一个迭代器ListIterator,在使用ListArrayListLinkedListVector的时候可以使用。

Iterator迭代器:

next()     hasNext()

remove()将会删除上次调用next方法时返回的元素,如果想要删除指定位置上的元素,需要越过这个元素如果调用remove之前没有调用next将是不合法的,如果这样做,将会抛出一个IllegalStateException异常。删除集合中Iterator指向位置后面的元素

ListIterator迭代器

add(E e): 在指定位置插入一个元素。

hasPrevious()

previous()

Queue

PriorityQueue:保存队列元素的顺序不是按照元素添加的顺序来保存的,而是在添加元素的时候对元素的大小排序后再保存的。因此在PriorityQueue中使用peek()pool()取出队列中头部的元素,取出的不是最先添加的元素,而是最小的元素。

Map

HashMap:

1.  HashMap不是线程安全的 

2. HashMap的键和值都可以为null 

问:为什么HashMap是线程不安全的?

一:如果多个线程同时使用put方法添加元素,而且假设正好存在两个putkey(调用hashcode())发生了碰撞(hash值一样),那么根据HashMap的实现,这两个key会添加到数组的同一个位置,这样最终就会发生其中一个线程的put的数据被覆盖。

二:多个线程同时检测到元素个数超过数组大小*loadFactor(负载因子),这样就会发生多个线程同时对数组进行扩容,都在重新计算元素位置以及复制数据,但是最终只有一个线程扩容后的数组会赋给table,也就是说其他线程的都会丢失

问:hashMap底层实现

数组连续内存空间,查找速度快,增删慢;链表充分利用了内存,存储空间是不连续的,首尾存储上下一个节点的信息,所以寻址麻烦,查找速度慢,但是增删快;哈希表呢,综合了它们两个的优点,一个哈希表,由数组和链表组成。

HashMap的内部存储结构其实是数组和链表的结合。当实例化一个HashMap时,系统会创建一个长度为CapacityEntry数组,这个长度在哈希表中被称为容量(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插入值与解决冲突方法

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、链地址法 

     将所有哈希地址相同的记录都链接在同一链表中图形类似于图2。也就是说,当HashMap中的每一个bucket里只有一个Entry,不发生冲突时,Hashmap是一个数组,根据索引可以迅速找到Entry,但是,当发生冲突时,单个的bucket里存储的是一个Entry链,系统必须按顺序遍历每个Entry,直到找到为止。

为了减少数据的遍历,冲突的元素都是直接插入到第一个Entry后面的,所以,最早放入bucket中的Entry,位于Entry链中的最末端。这从put(K keyV value)中也可以看出,在同一个bucket存储Entry链的情况下,新放入的Entry总是位于bucket中。 

问:如何线程安全使用hashmap

HashMap配合Collections工具类使用实现线程安全Collections.synchronizedMap()。

同时还有ConcurrentHashMap可以选择,该类的线程安全是通过Lock的方式实现的,所以效率高于Hashtable

LinkedHashMap:

保存了元素插入的顺序

问: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()); }

HashTable:

1.  Hashtable线程安全,但效率低,因为是Hashtable是使用synchronized的,所有线程竞争同一把锁

2.HashTable键值不能为空

TreeMap:

1.  不是线程安全的 

2. 底层是红黑树

3.元素键值按某种顺序排列

问:HashMapHashtable ConcurrentHashMap 区别

HashMapHashtable 

a.  HashMap不是线程安全的;Hashtable线程安全,但效率低,因为是Hashtable是使用synchronized的,所有线程竞争同一把锁;

b. HashMap的键和值都可以为nullHashTable不可以。 

ConcurrentHashMap 

通过分析Hashtable就知道,synchronized是针对整张Hash表的,即每次锁住整张表让线程独占,ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术。它使用了多个锁来控制对hash表的不同部分进行的修改。  

ConcurrentHashMap内部使用segment数组分别表示这些不同的部分,每一个segment都包含了一个Entry数组的hashtable,每个段其实就是一个小的hashtable,它们有自己的锁。只要多个修改操作发生在不同的段上,它们就可以并发进行。

有些方法需要跨段,比如size()containsValue(),它们可能需要锁定整个表而不仅仅是某个段,这需要按顺序锁定所有段,操作完毕后,又按顺序释放所有段的锁。这里按顺序是很重要的,否则极有可能出现死锁

问:包装类是否适合作为hashmap键值

包装类都是不可变的final,而且已经重写了equals()hashCode()方法了。 

我们重写了equals()hashCode()方法的类也可以作为键。

问:synchronized

1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象。其他线程无法访问此对象的所有同步代码块,但可以访问非同步代码块。 
2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象; 
3. 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象; 

synchronizedlock、volatile

lock:

加锁和解锁处需要通过lock()和unlock()显示指出。会在finally块中写unlock()以防死锁。

是一个类

线程需要的锁被占用,就是可以尝试获得锁,线程可以不用一直等待

大量同步的情况使用明显提高性能

Synchronized:

synchronized可以加在方法上,也可以加在特定代码块中

Java的关键字,在jvm层面上

线程需要的锁被占用,会一直等待

少量同步的情况使用

volatile

用volatile修饰的变量,线程在每次使用变量的时候,都会读取变量最新值。volatile很容易被误用,用来进行原子性操作。

posted on 2017-04-24 15:55  bullets  阅读(464)  评论(0编辑  收藏  举报

导航