集合类概述

来源:互联网 发布:ugm和mac队关系 编辑:程序博客网 时间:2024/06/11 22:21

一、容器/集合类

1.集合类概述

Java容器类有几个特点:首先,这种容器是高性能的,对基本数据集合(动态数组、链接表、树和散列表)的实现是高效率的。第二,容器类允许不同类型的类集合以相同的方式和高度互操作方式工作。第三,容器类是容易扩展或修改的

容器类的常用的基本类型有List、Set和Map,这些对象类型也称为集合类,但是在Java中使用了Collection(集合)这个名字来指代该类库的一个特殊子集,所以业界使用了范围更广泛的“容器”(就是所谓的容器类或集合类)来称呼。

容器(集合)框架如下:
Collection接口
├List 接口
│├LinkedList 链表
│├ArrayList 顺序结构动态数组类
│└Vector 向量
│ └Stack 栈
└Set接口
Map接口
├Hashtable
├HashMap

Java API中所用的集合类,都是实现了Collection接口,继承结构如下:

                 Collection<--List<--Vector
                          Collection<--List<--ArrayList
                          Collection<--Set<--HashSet
                          Collection<--Set<--HashSet<--LinkedHashSet
                          Collection<--Set<--SortedSet(也是接口)<--TreeSet
    集合类存放于java.util包中。集合类存放的都是对象的引用而非对象本身
    集合类型主要有3种:set(集)、list(列表)和map(映射)。

2.Collection接口

Collection:是一个接口,继承自Iterable接口,说明是可以用Iterator迭代器来访问该集合中的元素的。又有List、Set和QueueJava 1.5 添加新的数据结构接口)接口继承Collection接口。

2.1 List接口

List:可包括重复元素的列表。同时拥有Collection内的方法外,还添加了大量的方法,使得可以在List的中间插入和删除元素。列表在数据结构中分别表现为:数组向量、链表、堆栈队列

Vector : 基于Array的List,其实就是封装了Array所不具备的一些功能方便我们使用,它不可能走出Array的限制。性能也就不可能超越Array。所以,在可能的情况下,我们要多运用Array。另外很重要的一点就是Vector“sychronized”的,这个也是VectorArrayList的唯一的区别。

ArrayList:同Vector一样是一个基于Array上的链表,但是不同的是ArrayList不是同步的。所以在性能上要比Vector优越一些,但是当运行到多线程环境中时,可需要自己在管理线程的同步问题。

LinkedList:不是基于Array的,在实现中采用链表数据结构。擅长于对元素的插入和删除操作,但对于随机访问元素比较慢。该类的实现是以双向链表的数据结构为基础的,所以是比较消耗内存的。

所有的List中可以有相同的元素,所有的List中可以有null元素

在各种Lists中,最好的做法是ArrayList作为缺省选择。当插入、删除频繁时,使用LinkedList;Vector总是比ArrayList慢,所以要尽量避免使用。

2.2 Set接口

Set是最简单的一种集合。集合中的对象不按特定的方式排序,并且存放的是对象的引用,没有重复对象

Set接口主要实现了两个实现类:

HashSet : HashSet类按照哈希算法来存取集合中的对象,存取速度比较快。

HashSet的存储方式是HashMap中的Key作为Set的对应存储项。HashMap的key是不能有重复的,这个也是为什么在Set中不能像在List中一样有重复的项的根本原因。[LinkedHashSet:HashSet的一个子类,一个链表。]

TreeSet: TreeSet类实现了SortedSet接口,能够对集合中的对象进行排序。它不同于HashSet的根本就是TreeSet是有序的。它是通过SortedMap来实现的。

Set总结:

1.Set实现的基础是Map(HashMap),但不是显示地存储键-值对,这点要注意;

2.  Set中的元素是不能重复的;

HashSet通常优于TreeSet(插入、查找)。只有当需要产生一个经过排序的序列,才用TreeSet。TreeSet存在的唯一理由:能够维护其内元素的排序状态。

3.Map接口

Map 是一种把键对象和值对象映射的集合,它的每一个元素都包含一对键对象和值对象。Map没有继承于Collection接口

Collection和Map接口之间的主要区别在于:

[Collection中存储了一组对象的引用]这句话什么意思????????

Collection中存储了一组对象的引用,而Map存储关键字/值对

在Map对象中,一个键最多能绑定一个值。Map集合中的键对象不允许重复,也就说,任意两个键对象通过equals()方法比较的结果都是falsenull可以作为键,这样的键只有一个可以有一个或多个键所对应的值为null。当get()方法返回null值时,可以表示Map中没有该键,也可以表示该键所对应的值为null。因此Map中不能由get()方法来判断Map中是否存在某个键,而应该用containsKey()方法来判断。

继承Map接口的类有:HashMap,HashTable
    HashMap:Map的实现类缺省情况下是非同步的;   

HashTable:Dictionary的子类缺省是线程同步的(指对其成员的操作不会因为多线程的原因发生错误不允许关键字或值为null

当元素的顺序很重要时选用TreeMap;当元素不必以特定的顺序进行存储时,使用HashMap。Hashtable的使用不被推荐,因为HashMap提供了所有类似的功能,并且速度更快。当你需要在多线程环境下使用时,HashMap也可以转换为同步的。

* 在各种MapsHashMap用于快速查找。

* 当元素个数固定,用Array,因为Array效率是最高的。

内部哈希: 哈希映射技术

哈希映射结构由一个存储元素的内部数组组成[vector,arraylist,setmap最终都是基于数组存储的,而linkedlist是基于链表的]。 由于内部采用数组存储,因此必然存在一个用于确定任意键访问数组的索引机制。该机制需要提供一个小于数组大小的整数索引值,该机制称作哈希函数。在 Java 基于哈希的 Map 中,哈希函数将对象转换为一个适合内部数组的整数。每个对象都包含一个返回整数值的 hashCode() 方法。 要将该值映射到数组,只需将其转换为一个正值,然后在将该值除以数组大小后取余数即可。 以下是一个简单的、适用于任何对象的 Java 哈希函数 int hashvalue =Maths.abs(key.hashCode()) % table.length;(% 二进制运算符(称作模)将左侧的值除以右侧的值,然后返回整数形式的余数。)

注意:1、Collection没有get()方法来取得某个元素。只能通过iterator()遍历元素。2、一般使用ArrayList。用LinkedList构造堆栈stack、队列queue3、Map put(k,v)/ get(k),还可以使用containsKey()/containsValue()来检查其中是否含有某个key/value

结论:最常用的是ArrayList,HashSet,HashMap,Array。

 

 

________________________________________________________________________

7.Set中equals()和hashCode()?

equals()是判读两个Set是否相等[前提是equals()在类中被覆盖]。==决定引用值是否指向同一对象。

1、当向集合set中增加对象时,首先计算要增加对象的hashCode码,根据该值来得到一个位置来存放当前的对象,当在该位置没有一个对象存在的话,那么集合set认为该对象在集合中不存在,直接增加进去。如果在该位置有一个对象的话,接着将准备增加到集合中的对象与该位置上的对象进行equals方法比较,如果该equals方法返回false,那么集合认为集合中不存在该对象,再进行一次散列,将该对象放到散列后计算出的新地址里,如果equals方法返回true,那么集合认为集合中已经存在该对象了,不会再将该对象增加到集合中了。

2、当重写equals方法时,必须要重写hashCode方法

在java的集合中,判断两个对象是否相等的规则是

 

      1),判断两个对象的hashCode是否相等

        如果不相等,认为两个对象也不相等,完毕 ;如果相等,转入2

      2),判断两个对象用equals运算是否相等

        如果不相等,认为两个对象也不相等

        如果相等,认为两个对象相等(equals()是判断两个对象是否相等的关键)

 

    可见hashcode()相等时,equals()方法也可能不等

 

 

 

 

public static voidmain(String args[]){

String s1=newString("zhaoxudong"); //此语句创建了两个对象,一个是字符串对象“zhaoxudong”(存放于栈中的字面量),另一个是new后在堆中产生的对象。详细见下面的四.4

String s2=new String("zhaoxudong");

//上述两条语句一共是产生了三个对象,因为栈中只有产生了一个对象。

System.out.println(s1==s2);//false

System.out.println(s1.equals(s2));//true

System.out.println(s1.hashCode());//s1.hashcode()等于s2.hashcode() ,指向同一内存的引用

System.out.println(s2.hashCode());//equalshashCode方法只用于两个对象的比较和容器中,与对象的创建没有关系

Set hashset=new HashSet();

hashset.add(s1);

hashset.add(s2); /*在添加s1,s2时, hashset认为s1s2是相等的,所以让s2覆盖了s1;*/

Iteratorit=hashset.iterator();

            while(it.hasNext()){

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

            } //最后在while循环的时候只打印出了一个”zhaoxudong”。

这是因为String类已经重写了equals()方法和hashcode()方法

但是看下面的程序:

public classHashSetTest {

   public static void main(String[] args)   {

                HashSet hs=new HashSet();

                 hs.add(newStudent(1,"zhangsan"));

                 hs.add(new Student(2,"lisi"));

                 hs.add(newStudent(3,"wangwu"));

                 hs.add(newStudent(1,"zhangsan"));

                Iterator it=hs.iterator();

                 while(it.hasNext()){

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

                 } } }

class Student {

     int num;

     Stringname;

     Student(intnum,String name) {

                this.num=num;

                 this.name=name; }

     public String toString() { returnnum+":"+name; }

           }     

输出结果为:

                   1:zhangsan

                   1:zhangsan

                   3:wangwu

                   2:lisi

问题出现了,为什么hashset添加了相等的元素呢,这是不是和hashset的原则违背了呢?回答是:没有因为在根据hashcode()对两次建立的newStudent(1,"zhangsan")对象进行比较时,生成的是不同的哈希码值,所以hashset把他当作不同的对象对待了,当然此时的equals()方法返回的值也不等。那么为什么会生成不同的哈希码值呢?原因就在于我们自己写的Student类并没有重新自己的hashcode()equals()方法,所以在比较时,是继承的object类中的hashcode()方法,它是一个本地方法比较的是对象的地址(引用地址),使用new方法创建对象,两次生成的当然是不同的对象了,造成的结果就是两个对象的hashcode()返回的值不一样。那么怎么解决这个问题呢??

答案是:在Student类中重新hashcode()和equals()方法。

例如:

class Student{

int num;

String name;

Student(int num,String name){

            this.num=num;

            this.name=name; }

public inthashCode(){ //重写hashCode的方法

            return num*name.hashCode();}

public booleanequals(Object o) {

           Student s=(Student)o;

            return num==s.num&& name.equals(s.name);

}

public StringtoString(){return num+":"+name; }

}

根据重写的方法,即便两次调用了newStudent(1,"zhangsan"),我们在获得对象的哈希码时,根据重写的方法hashcode(),获得的哈希码肯定是一样的。所以运行修改后的程序时,我们会看到重复元素的问题已经消除。

 

9.Map线程安全几种实现方法    [未完成]

如果需要使 Map 线程安全,大致有这么四种方法:
1、使用synchronized 关键字,代码如下

synchronized(anObject) {   

    value = map.get(key);}  

2、使用JDK1.5提供的锁(java.util.concurrent.locks.Lock)。代码如下

lock.lock();   

value = map.get(key);   

lock.unlock();  

3、使用 JDK1.5 提供的读写锁(java.util.concurrent.locks.ReadWriteLock)。代码如下

rwlock.readLock().lock();   

value = map.get(key);   

rwlock.readLock().unlock();  

这样两个读操作可以同时进行,理论上效率会比方法 2 高。
4、使用 JDK1.5 提供的 java.util.concurrent.ConcurrentHashMap。该类 Map的存储空间分为若干块,每块拥有自己的锁,大大减少了多个线程争夺同一个锁的情况。代码如下

value = map.get(key); //同步机制内置在 get 方法中 

比较: 

1、不同步确实最快,与预期一致。
2、四种同步方式中,ConcurrentHashMap是最快的,接近不同步的情况。

3、synchronized 关键字非常慢,比使用锁慢了两个数量级。如果需自己实现同步,则使用JDK1.5 提供的锁机制,避免使用 synchronized关键字。

1.  public class MapTest{  

2.      public static final int THREAD_COUNT = 1;   

3.      public static final int MAP_SIZE = 1000;   

4.      public static final int EXECUTION_MILLES = 1000;   

5.      public static final int[] KEYS = new int[100];         

6.      public static void main(String[] args) throws Exception{  

7.          //初始化   

8.          Random rand = new Random();  

9.          for (int i = 0; i < KEYS.length; ++i)  KEYS[i] = rand.nextInt();  

10.         //创建线程   

11.          long start = System.currentTimeMillis();  

12.          Thread[] threads = new Thread[THREAD_COUNT];  

13.          for (int i = 0; i < THREAD_COUNT; ++i) {  

14.              threads[i] = new SynchronizedThread();  

15.              //threads[i] = new LockThread();  

16.               threads[i].start();  

17.          }  

18.         //等待其它线程执行若干时间   

19.          Thread.sleep(EXECUTION_MILLES);

20.          //统计 get 操作的次数  

21.          long sum = 0;         

22.          for (int i = 0; i < THREAD_COUNT; ++i){  

23.          sum += threads[i].getClass().getDeclaredField("count").getLong(threads[i]);          }   

24.          long millisCost = System.currentTimeMillis() - start;  

25.          System.out.println(sum + "(" + (millisCost) + "ms)");  

26.          System.exit(0);   

27.      }   

28.         

29.      public static void fillMap(Map<Integer, Integer> map){  

30.          Random rand = new Random();

31.          for (int i = 0; i < MAP_SIZE; ++i){  

32.              map.put(rand.nextInt(), rand.nextInt());  

33.          }  

34.      }   

35.  }   

36.  class SynchronizedThread extends Thread{  

37.      private static Map<Integer, Integer> map = new HashMap<Integer, Integer>();   

38.      public long count = 0;

39.      static {   

40.          MapTest.fillMap(map);  

41.      }   

42.      public void run()  {  

43.          for (;;) {  

44.              int index = (int)(count % MapTest.KEYS.length);  

45.              synchronized(SynchronizedThread.class){   

46.                  map.get(MapTest.KEYS[index]);  

47.              }  

48.              ++count;  

49.          }  

50.      }   

51.  }   

52.    

53.  class LockThread extends Thread{  

54.      private static Map<Integer, Integer> map = new HashMap<Integer, Integer>();   

55.      private static Lock lock = new ReentrantLock();  

56.      public long count = 0;         

57.      static {   

58.          MapTest.fillMap(map);  

59.      }         

60.      public void run() {  

61.          for (;;) {  

62.              int index = (int)(count % MapTest.KEYS.length);  

63.              lock.lock();   

64.              map.get(MapTest.KEYS[index]);  

65.              lock.unlock();   

66.              ++count;  

67.          }  

68.      }   

69.  }    

11.Comparable接口和Comparator接口

  在“集合框架”中有两种比较接口:Comparable接口和Comparator接口。像String和Integer等Java内建类实现Comparable接口以提供一定排序方式,但这样只能实现该接口一次。对于那些没有实现Comparable接口的类、或者自定义的类,您可以通过 Comparator接口来定义您自己的比较方式。

  1.Comparable接口

  在java.lang包中,Comparable接口适用于一个类有自然顺序的时候。假定对象集合是同一类型,该接口允许您把集合排序成自然顺序。

     int compareTo(Object o): 比较当前实例对象与对象o,如果位于对象o之前,返回负值,如果两个对象在排序中位置相同,则返回0,如果位于对象o后面,则返回正值

  Byte, Double,Float,Integer,Long,Short  按数字大小排序

Character  按 Unicode 值的数字大小排序

String  按字符串中字符 Unicode 值排序

   利用Comparable接口创建您自己的类的排序顺序,只是实现compareTo()方法的问题。通常就是依赖几个数据成员的自然排序。同时类也应该覆盖equals()和hashCode()以确保两个相等的对象返回同一个哈希码。

  2.Comparator接口

  若一个类不能用于实现java.lang.Comparable,或者您不喜欢缺省的Comparable行为并想提供自己的排序顺序(可能多种排序方式),你可以实现Comparator接口,从而定义一个比较器。

    (1)int compare(Object o1,Object o2): 对两个对象o1和o2进行比较,如果o1位于o2的前面,则返回负值,如果在排序顺序中认为o1和o2是相同的,返回0,如果o1位于o2的后面,则返回正值

  “与Comparable相似,0返回值不表示元素相等。一个0返回值只是表示两个对象排在同一位置。由Comparator用户决定如何处理。如果两个不相等的元素比较的结果为零,您首先应该确信那就是您要的结果,然后记录行为。”

  (2)boolean equals(Object obj): 指示对象obj是否和比较器相等。

“该方法覆写Object的equals()方法,检查的是Comparator实现的等同性,不是处于比较状态下的对象。”

12.集合类和接口中的方法?

1.Collection 接口:用于表示任何对象或元素组。

    (1) 单元素添加、删除操作:

   boolean add(Object o):将对象添加给集合

   boolean remove(Object o): 如果集合中有与o相匹配的对象,则删除对象o

  (2) 查询操作:

   int size() :返回当前集合中元素的数量

   boolean isEmpty() :判断集合中是否有任何元素

   booleancontains(Object o) :查找集合中是否含有对象o

Iterator iterator() :返回一个迭代器,用来访问集合中的各个元素

  (3) 组操作:作用于元素组或整个集合

   boolean containsAll(Collectionc): 查找集合中是否含有集合c 中所有元素

   boolean addAll(Collection c) : 将集合c 中所有元素添加给该集合

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

   void removeAll(Collection c) : 从集合中删除集合c 中的所有元素

   void retainAll(Collection c) : 从集合中删除集合c 中不包含的元素

  (4) Collection转换为Object数组 :

   Object[] toArray() :返回一个内含集合所有元素的array

   Object[] toArray(Object[] a) :返回一个内含集合所有元素的array。运行期返回的array和参数a的型别相同,需要转换为正确型别。

  Collection不提供get()方法。如果要遍历Collectin中的元素,就必须用Iterator

1.2.Iterator 接口

Collection 接口的iterator()方法返回一个 Iterator。

   (1) boolean hasNext(): 判断是否存在另一个可访问的元素

    Object  next(): 返回要访问的下一个元素。如果到达集合结尾,则抛出

NoSuchElementException异常。

(2) void remove(): 删除上次访问返回的对象。

2.List接口

  List 接口继承了 Collection 接口以定义一个允许重复项的有序集合。该接口不但能够对列表的一部分进行处理,还添加了面向位置的操作。

  void add(int index, Objectelement): 在指定位置index上添加元素element

  boolean addAll(int index,Collection c): 将集合c的所有元素添加到指定位置index

  Objectget(int index): 返回List中指定位置的元素

  int indexOf(Object o): 返回第一个出现元素o的位置,否则返回-1

  int lastIndexOf(Object o) :返回最后一个出现元素o的位置,否则返回-1

  Objectremove(int index) :删除指定位置上的元素

  Object set(int index, Objectelement) :用元素element取代位置index上的元素,并且返回旧的元素

  ListIterator listIterator() : 返回一个列表迭代器,用来访问列表中的元素

   ListIterator listIterator(intindex) : 返回一个列表迭代器,用来从指定位置index开始访问列表中的元素

3.Set接口及其实现

  HashSet是使用一个哈希表存储元素的,是非排序的,可以随机访问【?????】,是Set的最优性能实现.TreeSet实现了SortedSet接口,使用一个红黑树来存储元素,提供了元素的有序存储和访问.

  HashSet在底层实现上依赖于HashMap.

   public HashSet() {

 map = newHashMap<E,Object>();

    }

    public boolean add(E o) {

 return map.put(o,PRESENT)==null;

    }

    public booleanremove(Object o) {

 returnmap.remove(o)==PRESENT;

    }

    public booleancontains(Object o) {

       returnmap.containsKey(o);

    }

从代码可见,HashSet在底层借用HashMap,使用一个Object类型的哑元值作为HashSet中元素在底层HashMap存储中的映射值.它抓住了HashMap的键不允许重复的特性.对于add()中调用底层映射的put(),将欲添加的元素和一个PRESENT哑元值放入底层map.如果底层Map返回null,说明原来的集合中并不存在该键.

注意:对于Map接口的put()返回null时有两种解释,一是原来的Map中不包含该键;另一种可能是原来的Map中已经存储了该健,但该键映射到null.而在HashSet中的add()remove()等中的返回null只有一种解释,即原来的集合不包含该元素.这是因为HashSet的底层的映射中存储的都是一个名为PRESENTObject类型的对象,不可能是null.

4. Map接口

  (1) 添加、删除操作:

  Object put(Object key, Objectvalue): 将互相关联的一个关键字与一个值放入该映像。如果该关键字已经存在,那么与此关键字相关的新值将取代旧值。方法返回关键字的旧值,如果关键字原先并不存在,则返回null

  Object remove(Object key): 从映像中删除与key相关的映射

  void clear(): 从映像中删除所有映射

  (2) 查询操作:

  Object get(Object key): 获得与关键字key相关的值,并且返回与关键字key相关的对象,如果没有在该映像中找到该关键字,则返回null

 boolean containsKey(Object key): 判断映像中是否存在关键字key

booleancontainsValue(Object value): 判断映像中是否存在值value

  int size(): 返回当前映像中映射的数量

  boolean isEmpty() :判断映像中是否有任何映射