zoukankan      html  css  js  c++  java
  • java集合

    java集合

    Collection和Map都实现了Iterator接口

    1:HashMap和Hashtable 、ConcurrentHashMap

    HashMap、Hashtable

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

    b. HashMap的键和值都可以为null,HashTable不可以。

    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()。

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

    问:为什么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、链地址法 

         将所有哈希地址相同的记录都链接在同一链表中图形类似于图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语言的关键字)

    一、当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。

         二、然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。

         三、尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。

         四、第三个例子同样适用其它同步代码块。也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。

    synchronized和lock、volatile

    lock:

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

    是一个类

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

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

    Synchronized:

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

    Java的关键字,在jvm层面上

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

    少量同步的情况使用

    volatile

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

  • 相关阅读:
    使用ASP的优势和劣势
    rman的catalog命令
    sql exist和in的区别及查询效率比较
    zblog模板修改字体大小的教程
    js 刷新和关闭页面触发的事件 及操作COOKIE
    Flex走出低谷的关键: 打破Flex固有的定义
    服务网健康日志中 radio传值解决方案
    sql语句查询出表里的第二条、第三条记录(附加多个条件)
    鼠标经过超链接文字变色
    (asp.net)鼠标放上去的时候文本框的提示消失,鼠标离开又重新显示
  • 原文地址:https://www.cnblogs.com/Curry-Coder/p/6810762.html
Copyright © 2011-2022 走看看