java集合

来源:互联网 发布:视图可以对数据 编辑:程序博客网 时间:2024/06/06 18:41


集合包括两大接口:collection接口和Map接口。

同步(Synchronous)和异步(Asynchronous)的区别:

同步(阻塞)指发送一个请求,需要等待返回,才能发送下一个请求。

异步(非阻塞)指发送一个请求,不需要等待返回,即可继续发送下一个请求。

同步是线程安全的,异步不是线程安全的。同步是线程加锁的,不论哪一个线程运行到这个方法时,都要检查有没有其他的线程正在用这个方法,有的话要等该方法运行完在运行此方法。


collection接口

collection是最基本的集合接口,java SDK不提供直接继承collection,而java SDK提供的类继承自collection接口,如:List接口和Set接口。

collection是最基本的集合接口,java SDK不提供直接继承collection,而java SDK提供的类继承自collection接口,如:List接口和Set接口。

1、             List接口

List接口是有序的collection,List是有序的,可以使用索引(类似于数组的下标)来访问List中的元素,与Set不同的是,List允许有重复的数据。

collection和List都是接口,不是类,所以只能用ArrayList等实现类去实例化,而:

List l = newList();

Collection c = new Collection();

是编译错误的,若是:

List l = new ArrayList();

Collection c = new ArrayList();

则是可以的。

           Collection常用的方法:

boolean  add(Objecto)    向集合中添加对象

boolean addAll(Collection c)   将集合c中所有的元素添加到集合中

void  clear()   删除集合中所有元素

boolean contains(Object o) 判断元素o是否在集合中,若存在返回true,否则false

boolean containsAll(Collection c)  判断集合中是否包含集合c中所有元素

boolean isEmpty()  判断集合是否为空

Iterator iterator()  返回该集合的迭代器

Collection c = newArrayList();

      c.add("abc");

      c.add("abc");

c.add("abc");

Iteratori = c.iterator();

     while(i.hasNext()){

        System.out.println(i.next());  }

结果:

abc

abc

abc

        boolean remove(Object o)  如果存在元素o就删除,返回true,否则返回false,若有多个重复的,例如有3个abc,一个remove只会删掉一个abc,还剩下2个。

     注意:

     如果list内有5个字符串“123”,

for (int i = 0; i <list.size(); i++) {

  if (((String) list.get(i)).startsWith("1")) {

  list.remove(i);

  }

运行后list的size不是0,而是2,这是因为List每remove掉一个元素以后,后面的元素都会向前移动,此时如果执行i=i+1,则刚刚移过来的元素没有被读取。

解决方法:

1.倒过来遍历list

for (int i = list.size()-1; i >=0; i--) {

if (((String)list.get(i)).startsWith("1")) {

list.remove(i);

}}

2.每移除一个元素以后再把i移回来

for (int i = 0; i < list.size();i++) {

if (((String)list.get(i)).startsWith("1")) {

list.remove(i);

i=i-1;

}}

3.使用iterator.remove()方法删除

for (Iterator it = list.iterator();it.hasNext();) {

String str = (String)it.next();

if (str.equals("123")){

it.remove();

}}

        booleanremoveAll(Collection c)  将该集合中,只要是c中存在的,全部删除。

                  boolean retainAll(Collectionc)  与removeAll相反,将该集合中,只要c不存在的,全部删除,只留下c中存在的元素。

                  int size()   返回集合的大小,即集合中元素的个数

                  Object[] toArray()  将集合中的元素以数组的形式返回,返回类型是Object

                  Object[] toArray(Object[]a)  返回集合中所有元素的数组,返回数组的运行时类型与指定数组的运行时类型一致。

                  List常用方法:

list继承了Collection所有方法,并且有一些新增的方法:

Object get(intindex)  返回索引值为index的元素

List subList(intfromIndex,int toIndex)  返回新的List,元素为原来List的第fromIndex个,到第toIndex个,但不包括最后的第toIndex个元素。

int indexOf(Objecto)    在集合中查找元素o,若存在,返回找到的第一个元素所在的索引值,若不存在,返回-1。

intlastIndexOf(Object o)  与indexOf类似,只不过返回的是匹配到的最后一个元素的索引值。

Object set(intindex,Object o)  将第index个元素,替换为o,返回被替换的元素(原来的)。

Object remove(intindex)  移除第index个元素,返回移除的元素。

 

实现List接口的常用类有:LinkedList,ArrayList,Vector,Stack

a)       LinkedList类

LinkedList实现了List接口,允许null元素。LinkedList是异步(Asynchronous)的。

构造器:

LinkedList()  空的集合

LinkedList(Collection c) 包含c集合所有元素的集合,c不可以为null,否则抛出异常。

方法:

element()   返回集合的头(第一个元素)

get(int index)  返回index索引的元素

getFirst()  返回第一个元素

getLast()  返回最后一个元素

indexOf(Object o)  同List

lastIndexOf(Object o) 同List

peek()  返回第一个元素,同element()

poll()  找到并删除头(第一个元素)

set(int index , E element) 同List

b)       ArrayList类

ArrayList类允许所有元素,包括null。ArrayList是异步(Asynchronous)的。

三个构造方法:

ArrayList()   构造空的ArrayList对象,初始容量10.

ArrayList(int initialCapacity)  构造有初始容量的ArrayList集合

ArrayList(Collection c)  包含集合c中所有元素的ArrayList集合。

方法:

boolean add(Object o) 将元素o加到集合末尾

add(int index , Object o)  将o插入到index位置,并且该位置及后面的元素全部后移。

boolean addAll(Collection c)  将c集合所有元素,加到集合末尾

boolean addAll(int index,Collection c)  将集合c中所有元素插到index位置,被插队的后移。

void clear()  删除集合中所有元素

Object clone() 克隆集合,返回Object对象可以转换为集合。(深克隆,浅克隆???)

boolean contains(Object o) 集合是否包含元素o,包含返回true,否则返回false

void ensureCapacity(int minCapacity)  增加ArrayList对象容量。

get(int index)  返回指定索引的元素

int indexOf(Object o) 返回o第一次出现的位置,没有返回-1

int lastIndexOf(Object o) 返回o最后一次出现的位置,没有返回-1

boolean isEmpty()  判断集合是否为空,和size()==0一样

Object remove(int index) 删除指定位置的元素

boolean remove(Object o) 删除指定元素o,若存在多个重复的,只会删除一个,不会全部删除。

void removeRange(int fromIndex,int toIndex)  删除索引在from到to之间的所有元素,包括from,不包括to。

Object set(int index,Object o)  将index位置元素,替换为o,返回被替换的元素(原来的元素)。

int size()  集合大小(元素个数)

Object[] toArray()  返回包含所有元素的数组

void trimToSize()   将该ArrayList的容量调整为当前大小

c)       Vector类

Vector类非常类似ArrayList,但是Vector是同步(Synchronous)的,由Vector创建的iterator,虽然和ArrayList创建的iterator是同一接口,但是因为Vector是同步的,当一个iterator被创建而且被使用时,另一个线程改变了这个Vector的状态,例如添加或删除了一些元素,这时调用iterator的方法时会抛出ConcurrentModificationException异常,需要捕获异常。

Vector类常用方法:

四个构造器:

Vector()  构造一个空的Vector对象,初始容量为10。(注:size()属性指的是元素个数,不是容量大小)

Vector(int initialCapacity) 构造初始容量的Vector对象。

Vector(int initialCapacity , int capacityIncrement)  构造初始容量和增量的Vector对象。增量是指当容量满了时,容量的增量。

Vector(Collection c) 构造以c集合中所有元素为初始数据的Vector对象。

方法:

addElement(Object o) 将元素o加到集合末尾。

void insertElementAt(Object o,int index)  将o元素插入到index位置,该位置和后面所有元素后移。

d)       Stack类

Stack类继承自Vector类,实现一个后进先出的堆栈。Stack也是线程安全的。

2、             Set接口

Set接口也是实现的Collection接口,但是Set是无序的,并且是没有重复元素的,若将重复的或已存在的元素(即两个元素equals相等)放入到Set中,没有任何操作(不会覆盖)。

证明:

Set s = new HashSet();

      s.add("a");

      String b = new String("a");

      s.add(b);

      Iterator i = s.iterator();

System.out.println(i.next()=="a");

        输出:

true

新增加的newString("a")和"a"是equals相等的,但是将b加入后,输出结果仍为true,说明b没有将a覆盖,而是没有任何操作。

Set接口可以存入null。

Set接口常用方法:

(所有的Collection接口的方法都被继承)

boolean  add(Objecto)  将元素o加到集合中,若已存在,则无变化

boolean  addAll(Collection c)  将c中所有元素加到集合中

void clear()  删除集合中所有元素

boolean contains(Object o) 判断集合中是否存在元素o,存在返回true,否则返回false

boolean containsAll(Collection c)  判断集合c中元素,是否在集合中全部存在,存在返回true,否则返回false。

boolean  equals(Objecto)  判断对象o是否与集合相等,相等返回true,否则返回false,相等的条件是:对象也是一个列表,两个列表有相同的大小,两个列表相同位置的元素都相等(3个条件需要都满足)。

boolean  isEmpty() 判断集合是否为空

Iterator iterator()  返回集合的迭代器,用于对集合进行遍历。

boolean remove(Object o)  移除指定的元素,若不存在,集合无变化。

boolean removeAll(Collection c)  删除集合中,在参数集合c存在的全部元素。

boolean retainAll(Collection c)  与removeAll相反,除了c中存在的,其他全部删除。

int  size()  返回集合元素个数

Object[] toArray()   将集合中元素以数组形式返回。

<T> T[] toArray(T[] a)  将集合中所有元素,以数组形式返回,运行时类型与参数运行时类型相同。

 

a)      HashSet类

虽然Set和List都实现了Collection接口,但是他们的实现方式是不一样的,List基本上是以Array为基础,但是Set则是在HashMap的基础上实现的。HashSet的存储方式是将HashMap中的Key作为Set中的对应存储项,看到HashSet的add方法便一目了然:

publicbooleanadd(E e) {

   return map.put(e, PRESENT)==null;}

这也是Set中为什么不能有重复项的原因,因为HashMap中key是不能重复的。

HashSet中可以添加null,但是由于不能重复,所以最多有一个null。HashSet不保证元素的顺序,即不但不会排序,连插入集合的顺序都不能保证,比如依次放入1,2.3,输出后可能为2,3,1。

HashSet构造方法:

HashSet()  构造空的HashSet对象,初始容量为16.

HashSet(intinitialCapacity)  构造指定容量的对象

HashSet(Collection  c)  以集合c中所有元素为基础数据的HashSet对象。

 

 

b)      LinkedHashSet类

LinkedHashSet是HashSet的一个子类,是一个链表。

 

c)      SortedSet类

SortedSet继承自Set接口,不但具有Set的全部功能,而且是一个Sorted类型的Set。不管插入的顺序是什么,集合都会有顺序的进行升序排列。

SortedSet常用方法:

Object first()  返回SortedSet中第一个元素。

Object  last()  返回最后一个元素。

SortedSet  headSet(Object  toElement) 返回所有小于指定元素,但不包括指定元素的SortedSet。

SortedSet  tailSet(Object  fromElement) 返回所有大于指定元素,并且包括指定元素的SortedSet。

SortedSet  subset(ObjectformElement , Object toElement)  返回从起始元素到指定元素中所有元素,但不包括结束元素的SortedSet集合。

d)     TreeSet类

TreeSet类是SortedSet的子类,是有顺序的。TreeSet是SortedSet的唯一实现形式。

TreeSet构造方法:

TreeSet()  返回空的TreeSet对象。

TreeSet(Collection c)  以集合c中所有元素为初始内容的集合。

TreeSet(Comparator c) c为指定的比较器,返回具有指定比较器的空的TreeSet对象。

TreeSet(SortedSet  s)  返回以s集合中所有的元素为基础数据的TreeSet集合。

例如,创建TreeSet对象并遍历:

SortedSet ss = new TreeSet();

      ss.add("1");

      ss.add("12");

      ss.add("50");

      ss.add("31");

      Iterator i = ss.iterator();

      while(i.hasNext()){

         System.out.println(i.next());

    }

     输出结果是升序排列的:

                                   1

12

31

50

3、             队列Queue

Queue接口与List、Set同一级别,都是继承了Collection接口。LinkedList实现了Queue接 口。Queue接口窄化了对LinkedList的方法的访问权限(即在方法中的参数类型如果是Queue时,就完全只能访问Queue接口所定义的方法 了,而不能直接访问 LinkedList的非Queue的方法),以使得只有恰当的方法才可以使用。BlockingQueue 继承了Queue接口。

Java提供的线程安全的Queue可以分为阻塞队列非阻塞队列,其中阻塞队列的典型例子是BlockingQueue,非阻塞队列的典型例子是ConcurrentLinkedQueue,在实际应用中要根据实际需要选用阻塞队列或者非阻塞队列。

阻塞队列和非阻塞队列对比:

BlockingQueue,顾名思义,“阻塞队列”:可以提供阻塞功能的队列。

首先,BlockingQueue提供的常用方法:

可能报异常       返回布尔值 可能阻塞     设定等待时间

入队     add(e)    offer(e)  put(e)     offer(e,timeout, unit)

出队     remove()       poll()      take()     poll(timeout,unit)

查看     element()      peek()    无   无

add(e) remove()element() 方法不会阻塞线程。当不满足约束条件时,会抛出IllegalStateException 异常。例如:当队列被元素填满后,再调用add(e),则会抛出异常。

offer(e) poll() peek() 方法即不会阻塞线程,也不会抛出异常。例如:当队列被元素填满后,再调用offer(e),则不会插入元素,函数返回false。

要想要实现阻塞功能,需要调用put(e) take() 方法。当不满足约束条件时,会阻塞线程。

ConcurrentLinkedQueue,它是一个无锁的并发线程安全的队列。

对比锁机制的实现,使用无锁机制的难点在于要充分考虑线程间的协调。简单的说就是多个线程对内部数据结构进行访问时,如果其中一个线程执行的中途因为一些原因出现故障,其他的线程能够检测并帮助完成剩下的操作。这就需要把对数据结构的操作过程精细的划分成多个状态或阶段,考虑每个阶段或状态多线程访问会出现的情况。

ConcurrentLinkedQueue有两个volatile的线程共享变量:head,tail。要保证这个队列的线程安全就是保证对这两个Node的引用的访问(更新,查看)的原子性和可见性,由于volatile本身能够保证可见性,所以就是对其修改的原子性要被保证。

队列详细内容:

队列是一种数据结构.它有两个基本操作:在队列尾部加人一个元素,和从队列头部移除一个元素就是说,队列以一种先进先出的方式管理数据。

常用方法:

add 增加一个元索如果队列已满,则抛出一个IIIegaISlabEepeplian异常

remove 移除并返回队列头部的元素,如果队列为空,则抛出一个NoSuchElementException异常

element 返回队列头部的元素,如果队列为空,则抛出一个NoSuchElementException异常

offer 添加一个元素并返回true,如果队列已满,则返回false

poll 移除并返问队列头部的元素,如果队列为空,则返回null

peek 返回队列头部的元素,如果队列为空,则返回null

put 添加一个元素,如果队列满,则阻塞

take 移除并返回队列头部的元素,如果队列为空,则阻塞

remove、element、offer 、poll、peek 其实是属于Queue接口。

java.ulil.concurrent包提供了阻塞队列的4个变种:

LinkedBlockingQueue:

默认情况下是没有上限的,但是也可以选择指定其最大容量,它是基于链表的队列。此队列按 FIFO(先进先出)排序元素。

ArrayBlockingQueue:

ArrayBlockingQueue在构造时需要指定容量,并可以选择是否需要公平性,如果公平参数被设置true,等待时间最长的线程会优先得到处理(其实就是通过将ReentrantLock设置为true来 达到这种公平性的:即等待时间最长的线程会先操作)。通常,公平性会使你在性能上付出代价,只有在的确非常需要的时候再使用它。它是基于数组的阻塞循环队列,此队列按先进先出原则对元素进行排序。

PriorityBlockingQueue:

PriorityBlockingQueue是一个带优先级的队列,而不是先进先出队列。元素按优先级顺序被移除,该队列也没有上限。但是如果队列为空,那么取元素的操作take就会阻塞,所以它的检索操作take是受阻的。另外,往入该队列中的元 素要具有比较能力。

DelayQueue:

最后,DelayQueue(基于PriorityQueue来实现的)是一个存放Delayed 元素的无界阻塞队列,只有在延迟期满时才能从中提取元素。该队列的头部是延迟期满后保存时间最长的 Delayed 元素。如果延迟都还没有期满,则队列没有头部,并且poll将返回null。当一个元素的getDelay(TimeUnit.NANOSECONDS) 方法返回一个小于或等于零的值时,则出现期满,poll就以移除这个元素了。此队列不允许使用 null 元素。


Map接口:

Map没有继承Collection接口。Map提供key到value的映射,一个Map中不能包含相同的key,(value可以相同),一个key只能映射一个value。

Map接口常用方法:

void clear()  删除所有键值对(清空Map)。

boolean  containsKey(Object  key)  判断是否存在相应的key,存在返回true,否则返回false。

boolean  containsValue(Object  value) 判断是否存在相应的value,存在返回true,否则返回false。

Object  get(Object key)  返回相应key对应的值(value)。

boolean  isEmpty() 判断是否为空

Object  put(Object key , Object  value) 将指定的key-value加入到HashMap中,若有相同的key存在,则将其value替换(覆盖),并返回原来的value,若不存在,则直接插入,返回null。

void  putAll(Map t) 将参数集合t中所有的键值对都加入到集合中。

Object  remove(Object key)  删除相应key的键值对,并返回相应的value,若不存在,返回null。

int  size() 返回键值对的个数。

迭代方法:

迭代时首先获取所有的key的集合(s获取的所有key的集合,即hm.keySet()是一个set集合),然后在对key的集合进行迭代:

HashMap hm = new HashMap();

      hm.put("1", "100");

      hm.put("3", "300");

      hm.put("2", "200");

      Iterator i =hm.keySet().iterator();

      while(i.hasNext()){

         Object o = i.next();

         System.out.println(o);

         System.out.println(hm.get(o));

     }

 

1、HashTable类

Hashtable继承Map接口,实现了key-value映射的哈希表,Hashtable是同步(Synchronous)的。Hashtable的key或者value都不许为空,否则会抛出空指针异常。

使用Hashtable、HashSet等hash算法的集合时,必须重写equals()和hashCode()方法(不要只写其中的一个),这两个方法都是继承自根类Object,因为作为key的对象,通过计算其散列函数来确定与之对应的value的位置。根据散列函数的定义,如果两个对象相同,即满足a.equals(b)=true,那么他们的hashCode必须相同,否则会产生冲突。

hashCode的作用是,在Hash表中存放数据,集合会通过对象hashcode的值,将集合分为多个区域,查找对象时仅在相应的区域寻找,这样可以提高查询的速度。如果在想集合中加入对象后,又修改了hashcode计算涉及到的值,导致hashcode计算出现偏差,在移除对象时会导致无法移除该对象,造成内存泄露。

2、HashMap类

HashMap和Hashtable类似,但是HashMap是异步(Asynchronous)的。HashMap允许key或者value为null,甚至key和value都为null也可以。

HashMap常用构造器:

HashMap()  构造空的HashMap对象,初始容量为16

HashMap(Map  m)  构造以m为初始数据的HashMap对象。

HashMap(int initialCapacity):构建一个初始容量为 initialCapacity,负载因子为 0.75 的 HashMap。初始容量是大于initialCapacity的最小的2的n次幂。如initialCapacity=6,那么初始容量为8

HashMap(int initialCapacity, float loadFactor):以指定初始容量、指定的负载因子创建一个 HashMap。

HashMap多线程并发问题:

HashMap是非线程安全的,多线程应该用ConcurrentHashMap。


import java.util.HashMap; public class Test {int a = 1000000;     private HashMap map = new HashMap();     public Test() {        Thread t1 = new Thread() {            public void run() {                for (int i = 0; i < a; i++) {                    map.put(new Integer(i), i);                }                System.out.println("t1 over");            }        };         Thread t2 = new Thread() {            public void run() {                for (int i = 0; i < a; i++) {                    map.put(new Integer(i), i);                }                 System.out.println("t2 over");            }        };         Thread t3 = new Thread() {            public void run() {                for (int i = 0; i < a; i++) {                    map.put(new Integer(i), i);                }                 System.out.println("t3 over");            }        };         Thread t4 = new Thread() {            public void run() {                for (int i = 0; i < a; i++) {                    map.put(new Integer(i), i);                }                 System.out.println("t4 over");            }        };         Thread t5 = new Thread() {            public void run() {                for (int i = 0; i < a; i++) {                    map.put(new Integer(i), i);                }                 System.out.println("t5 over");            }        };         Thread t6 = new Thread() {            public void run() {                for (int i = 0; i < a; i++) {                    map.get(new Integer(i));                }                 System.out.println("t6 over");            }        };         Thread t7 = new Thread() {            public void run() {                for (int i = 0; i < a; i++) {                    map.get(new Integer(i));                }                 System.out.println("t7 over");            }        };         Thread t8 = new Thread() {            public void run() {                for (int i = 0; i < a; i++) {                    map.get(new Integer(i));                }                 System.out.println("t8 over");            }        };         Thread t9 = new Thread() {            public void run() {                for (int i = 0; i < a; i++) {                    map.get(new Integer(i));                }                 System.out.println("t9 over");            }        };         Thread t10 = new Thread() {            public void run() {                for (int i = 0; i < a; i++) {                    map.get(new Integer(i));                }                 System.out.println("t10 over");            }        };         t1.start();        t2.start();        t3.start();        t4.start();        t5.start();         t6.start();        t7.start();        t8.start();        t9.start();        t10.start();    }     public static void main(String[] args) {        new Test();}}

就是启了10个线程,不断的往一个非线程安全的HashMap中put内容/get内容,但反复运行这个程序,会出现线程t1、t2被hang住的情况,多数情况下是一个线程被hang住另一个成功结束,偶尔会10个线程都被hang住。

产生这个死循环的根源在于对一个未保护的共享变量 — 一个”HashMap”数据结构的操作。当在所有操作的方法上加了”synchronized”后,一切恢复了正常,建议在这样的场景下应采用”ConcurrentHashMap”,

CPU利用率过高一般是因为出现了出现了死循环,导致部分线程一直运行,占用cpu时间。问题原因就是HashMap是非线程安全的,多个线程put的时候造成了某个key值Entry key List的死循环,问题就这么产生了。

当另外一个线程get 这个EntryList 死循环的key的时候,这个get也会一直执行。最后结果是越来越多的线程死循环,最后导致服务器dang掉。我们一般认为HashMap重复插入某个值的时候,会覆盖之前的值,这个没错。但是对于多线程访问的时候,由于其内部实现机制(在多线程环境且未作同步的情况下,对同一个HashMap做put操作可能导致两个或以上线程同时做rehash动作,就可能导致循环键表出现,一旦出现线程将无法终止,持续占用CPU,导致CPU使用率居高不下),就可能出现安全问题了。

注意:不合理使用HashMap导致出现的是死循环而不是死锁。

HashMap的数据结构:

HashMap底层就是一个数组结构,数组中的每一项又是一个链表。当新建一个HashMap的时候,就会初始化一个数组。

static class Entry<K,V>implements Map.Entry<K,V> { 

    final K key; 

    V value; 

   Entry<K,V> next; 

    final int hash; 

    ……  } 

可以看出,Entry就是数组中的元素,每个 Map.Entry 其实就是一个key-value对,它持有一个指向下一个元素的引用,这就构成了链表。

HashMap的存取实现:

put:


public V put(K key, V value) {      // HashMap允许存放null键和null值。      // 当key为null时,调用putForNullKey方法,将value放置在数组第一个位置。      if (key == null)          return putForNullKey(value);      // 根据key的keyCode重新计算hash值。      int hash = hash(key.hashCode());      // 搜索指定hash值在对应table中的索引。      int i = indexFor(hash, table.length);      // 如果 i 索引处的 Entry 不为 null,通过循环不断遍历 e 元素的下一个元素。      for (Entry<K,V> e = table[i]; e != null; e = e.next) {          Object k;          if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {              V oldValue = e.value;              e.value = value;              e.recordAccess(this);              return oldValue;          }      }      // 如果i索引处的Entry为null,表明此处还没有Entry。      modCount++;      // 将key、value添加到i索引处。      addEntry(hash, key, value, i);      return null;  } 

从上面的源代码中可以看出:当我们往HashMap中put元素的时候,先根据key的hashCode重新计算hash值,根据hash值得到这个元素在数组中的位置(即下标),如果数组该位置上已经存放有其他元素了,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。如果数组该位置上没有元素,就直接将该元素放到此数组中的该位置上。

get:


public V get(Object key) {      if (key == null)          return getForNullKey();      int hash = hash(key.hashCode());      for (Entry<K,V> e = table[indexFor(hash, table.length)];          e != null;          e = e.next) {          Object k;          if (e.hash == hash && ((k = e.key) == key || key.equals(k)))              return e.value;      }      return null;  }

从HashMap中get元素时,首先计算key的hashCode,找到数组中对应位置的某一元素,然后通过key的equals方法在对应位置的链表中找到需要的元素。

resize(rehash):

当HashMap中的元素越来越多的时候,hash冲突的几率也就越来越高,因为数组的长度是固定的。所以为了提高查询的效率,就要对HashMap的数组进行扩容,数组扩容这个操作也会出现在ArrayList中,这是一个常用的操作,而在HashMap数组扩容之后,最消耗性能的点就出现了:原数组中的数据必须重新计算其在新数组中的位置,并放进去,这就是resize。那么HashMap什么时候进行扩容呢?当HashMap中的元素个数超过数组大小*loadFactor时,就会进行数组扩容,loadFactor的默认值为0.75,这是一个折中的取值。也就是说,默认情况下,数组大小为16,那么当HashMap中元素个数超过16*0.75=12的时候,就把数组的大小扩展为 2*16=32,即扩大一倍,然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作,所以如果我们已经预知HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能。

Fail-Fast机制:

我们知道java.util.HashMap不是线程安全的,因此如果在使用迭代器的过程中有其他线程修改了map,那么将抛出ConcurrentModificationException,这就是所谓fail-fast策略。

这一策略在源码中的实现是通过modCount域,modCount顾名思义就是修改次数,对HashMap内容的修改都将增加这个值,那么在迭代器初始化过程中会将这个值赋给迭代器的expectedModCount。在迭代过程中,判断modCount跟expectedModCount是否相等,如果不相等就表示已经有其他线程修改了Map。

HashMap多线程并发死循环的产生:

在单线程下,只有一个线程对HashMap的数据结构进行操作,是不可能产生闭合的链路的。那就只有在多线程并发的情况下才会出现这种情况,那就是在put操作的时候,如果size>initialCapacity*loadFactor,那么这时候HashMap就会进行rehash操作。

void addEntry(int hash, K key, V value,int bucketIndex) {

        if ((size >=threshold)&& (null!=table[bucketIndex])){

            resize(2 * table.length);

            hash = (null != key) ? hash(key) :0;

            bucketIndex = indexFor(hash,table.length);

        }

 

        createEntry(hash, key,value, bucketIndex);

    }

如果size超过了threshold,那么就要进行resize操作。

void resize(int newCapacity) {

        Entry[] oldTable = table;

        int oldCapacity = oldTable.length;

        if (oldCapacity ==MAXIMUM_CAPACITY) {

            threshold = Integer.MAX_VALUE;

            return;

        }

 

        Entry[] newTable = new Entry[newCapacity];

        transfer(newTable,initHashSeedAsNeeded(newCapacity));

        table = newTable;

        threshold = (int)Math.min(newCapacity *loadFactor, MAXIMUM_CAPACITY+ 1);

    }

transfer()将旧的Entry数组的数据转移到新的Entry数组上,闭合的链路就是在这里产生的。

void transfer(Entry[]newTable,booleanrehash) {

        int newCapacity = newTable.length;

        for (Entry<K,V> e :table) {

            while(null != e) {

                Entry<K,V>next = e.next;

                if (rehash) {

                    e.hash = null== e.key? 0 : hash(e.key);

                }

                int i =indexFor(e.hash, newCapacity);

                e.next = newTable[i];

                newTable[i] = e;

                e = next;

            }

        }}

例如旧链表 1,2,3-》null  新链表 O O O -》null

1,2,3-》null     O O O -》null

第一次遍历:

e=1,next=e.next=2,e.next=null,newTable[0]=1,e=2    1 O O ->null

第二次遍历:

e=2,next=e.next=3,e.next=null,newTable[1]=2,e=3    1 2 O ->null

第三次遍历:

e=3,next=e.next=null,e.next=null,newTable[2]=3,e=null  1 2 3 ->null

e=null,遍历结束

当线程在进行最后一次遍历时,e.next的值应该为null,这决定了这次遍历为最后一次,但其他线程将e.next置为其他线程的先一个元素,即不为空的元素,则会形成闭合链表,put动作将会循环下去,形成死循环;而get动作同样会循环下去,形成死循环。

这里注意,newTable的索引i,是根据e通过hash算法得出,所以是在1 2 3 上不停的循环,而不是将newTable创建的无限大的,这里需要注意。

1、TreeMap类

HashMap是通过hashCode对内容进行快速查找的,顺序是不固定的,而TreeMap中所有元素都保持着固定的顺序,如果需要一个有序的结果,就使用TreeMap。

TreeMap类继承于AbstractMap,同时实现了SortedMap接口,是SortedMap接口的基于红黑树的实现。

TreeMap常用构造器:

TreeMap()  构造空的TreeMap对象。

TreeMap(SortedMap  s)  以参数SortedMap为基础数据的TreeMap对象。

TreeMap(Comparator  comparator) 以comparator为指定比较器的对象,可以指定键的排序规则。

总结:

1、               ArrayList和LinkedList区别:

如果需要快速的随机访问元素,应该优先使用ArrayList,ArrayList实现了基于动态数组的数据结构,是有顺序的,访问较快;如果需要快速的插入、删除元素,应该优先使用LinkedList。在集合的末尾增加或移除元素时,ArrayList和Vector的时间复杂度是一样的,都是O(1),但是如果不是末尾,而是其他位置,则复杂度则是:O(n-i),其中n是集合的元素个数,i是增加或移除元素的索引位置,这是因为增加或移除操作之后,第i个元素后面所有的元素都要执行位移操作,这时就优先使用LinkedList,LinkedList使用双向链表实现存储,增加、删除元素的时间复杂度都是O(1),但是LinkedList查找时效率较慢,需要从第一个索引开始查找,时间复杂度为O(i)。

2、               ArrayList和Vector区别:

Vector是线程同步的,是线程安全的;而ArrayList不是同步的,如果考虑线程安全,只能使用Vector,如果不考虑线程安全,则优先使用ArrayList,因为ArrayList不用考虑线程安全,效率较高。

当集合内元素的数量大于目前集合的大小时,Vector会将长度增长100%,而ArrayList的增长率为50%,所有当集合的数据量较大时,使用Vector有一定的优势,长度增长的频繁度较低。

3、               HashMap和Hashtable区别:

HashMap是Hashtable的轻量级实现,Hashtable是线程安全的,而HashMap是非同步的,也就是非线程安全的,所以HashMap的效率较高;

他们都完成了Map接口,HashMap允许空的key或value,Hashtable key或value都不可以为空;

Hashtable继承自Dictionary类,而HashMap是java1.2引进的Map interface的一个实现;

4、               HashMap和TreeMap区别:

HashMap和HashTable的顺序都是不固定的,而TreeMap是有顺序的。例如:

HashMap<String, String> map = new HashMap<String, String>();

        map.put("a", "aaa");

        map.put("b", "bbb");

        map.put("c", "ccc");

        map.put("d", "ddd");

        Iterator<String> iterator =map.keySet().iterator();

        while (iterator.hasNext()) {

        Object key = iterator.next();

        System.out.println("map.get(key) is :" + map.get(key));

        }

       

        Hashtable<String, String> tab = new Hashtable<String,String>();

        tab.put("a", "aaa");

        tab.put("b", "bbb");

        tab.put("c", "ccc");

        tab.put("d", "ddd");

        Iterator<String> iterator_1 =tab.keySet().iterator();

        while (iterator_1.hasNext()) {

        Object key = iterator_1.next();

        System.out.println("tab.get(key) is :" + tab.get(key));

        }

 

        TreeMap<String, String> tmp = new TreeMap<String,String>();

        tmp.put("a", "aaa");

        tmp.put("b", "bbb");

        tmp.put("c", "ccc");

        tmp.put("d", "cdc");

        Iterator<String> iterator_2 =tmp.keySet().iterator();

        while (iterator_2.hasNext()) {

        Object key = iterator_2.next();

        System.out.println("tmp.get(key) is :" +tmp.get(key));}

输出的结果为:

map.get(key) is :ddd

map.get(key) is :bbb

map.get(key) is :ccc

map.get(key) is :aaa

tab.get(key) is :bbb

tab.get(key) is :aaa

tab.get(key) is :ddd

tab.get(key) is :ccc

tmp.get(key) is :aaa

tmp.get(key) is :bbb

tmp.get(key) is :ccc

tmp.get(key) is :cdc

         可见,HashMap和Hashtable都是顺序不固定的,而TreeMap则可以保留原来的顺序。

5、               如果在单线程环境中,使用非同步的类,效率较高;如果多线程同时操作一个类,则应该使用同步的类,例如:Vector、Hashtable等。

使用Hashtable时,作为key的对象,必须重写equals()和hashCode()方法。


collections类:

类Collections是一个包装类。它包含有各种有关集合操作的静态多态方法。此类不能实例化,就像一个工具类,服务于Java的Collection框架。

Collections中常用的方法:

(1)sort()排序方法

        函数定义:public static <T extendsComparable<? super T>> void sort(List<T> list) 根据元素的

        自然顺序对指定列表按升序进行排序。

        参数:要排序的列表。

        函数定义: public static <T> voidsort(List<T> list,Comparator<? super T> c),根据指定比较器产生的顺序对指定列表进行排序。此列表内的所有元素都必须可使用指定比较器相互比较。

        参数:list-要排序的列表;c-确定列表顺序的比较器。

(2)binarySearch()二分查找方法

        函数定义:public static <T> intbinarySearch(List<? extends Comparable<? super T>> list,T key)

        使用二分搜索法搜索指定列表,以获得指定对象,在进行此方法调用前比较要将列表元素按照升序排序,否则结果不确定,此方法会执行O(n)次链接遍历和O(log n)次元素比较。

        参数: list-要搜索的链表,key-要搜索的键。

        函数定义: public static <T> intbinarySearch(List<? extends T> list, T key, Comparator<? super T>c) 根据指定的比较器对列表进行升序排序。

        参数:list-要搜索的列表,key-要搜索的键,c-排序列表的比较器。

(3)reverse()反转方法

         函数定义:public static voidreverse(List<?> list),反转指定列表中元素的顺序,此方法以线性时间运行。

        参数:list-元素要被反转的列表

(4)shuffle()改组方法

       函数定义:public static voidshuffle(List<?> list),使用默认随机源对指定列表进行置换,所有置换发生的可能性都是大致相等的。

        参数:list-要改组的列表

        函数定义:public static voidshuffle(List<?> list,Random rnd),使用指定的随机源对指定列表进行置换。

    参数:list-要改组的列表,rnd-用来改组列表的随机源。

(5)swap()交换方法

        函数定义:public static voidswap(List<?> list,int i,int j),在指定列表的指定位置处交换元素。

        参数:list-进行元素交换的列表,i-要交换的一个元素的索引,j-要交换的另一个元素的索引。

(6)fill()替换方法

        函数定义:public static <T> voidfill(List<? super T> list,T obj),使用指定元素替换指定列表中的所有元素,线性时间运行。

        参数:list-使用指定元素填充的列表,obj-用来填充指定列表的元素。

(7)copy()复制方法

        函数定义:public static <T> voidcopy(List<? super T> dest,List<? extends T> src),将所有元素从一个列表复制到另一个列表。执行此操作后,目标列表中每个已复制元素的索引将等同于源列表中该元素的索引,目标列表的长度至少必须等于源列表。

        参数:dest-目标列表,src-源列表。

(8)min()最小值法

        函数定义:public static <T extendsObject & Comparable<? super T>> T min(Collection<? extendsT> coll),根据元素的自然顺序返回给定Collection的最小元素,Collection中的所有元素必须实现Comparable接口,此外,collection中的所有元素都必须是可相互比较的。

        参数:coll-将确定其最小元素的collection。

        函数定义:public static <T> Tmin(Collection<? extends T> coll,Comparator<? super T> comp),根据指定比较器产生的顺序,返回给定collection的最小元素。

        参数:coll-将确定其最小元素的collection,comp-用来确定最小元素的比较器。

(9)max()最大值方法

        函数定义:public static <T extendsObject & Comparable<? super T>> T max(Collection<? extendsT> coll),根据元素的自然顺序,返回给定collection的最大元素。

        参数:coll-将确定其最大元素的collection。

        函数定义:public static <T> Tmax(Collection<?extends T> coll,Comparator<? super T> comp),根据指定比较器产生的顺序,返回给定collection的最大元素。

        参数:coll-将确定其最大元素的collection,comp-用来确定最大元素的比较器

(10)rotate()轮换方法

        函数定义:public static voidrotate(List<?> list,int distance),根据指定的距离轮转指定列表中的元素。

        参数:list-要轮换的列表,distance-列表轮换的距离,可以使0、负数或者大于list.size()的数。

(11)replaceAll()替换所有函数

        函数定义:public static <T> booleanreplaceAll(List<T> list,T oldVal,T newVal),使用另一个值替换列表总出现的所有的某一指定值。

        参数:list-在其中进行替换的列表;oldVal-将被替换的原值;newVal-替换oldVald的新值。
原创粉丝点击