集合类

java集合类图

java集合类框架图

集合类_第1张图片

 上述类图中,实线边框的是实现类,比如ArrayList,LinkedList,HashMap等,折线边框的是抽象类,比如AbstractCollection,AbstractList,AbstractMap等,而点线边框的是接口,比如Collection,Iterator,List等。

  发现一个特点,上述所有的集合类,都实现了Iterator接口,这是一个用于遍历集合中元素的接口,主要包含hashNext(),next(),remove()三种方法。它的一个子接口LinkedIterator在它的基础上又添加了三种方法,分别是add(),previous(),hasPrevious()。也就是说如果是先Iterator接口,那么在遍历集合中元素的时候,只能往后遍历,被遍历后的元素不会在遍历到,通常无序集合实现的都是这个接口,比如HashSet,HashMap;而那些元素有序的集合,实现的一般都是LinkedIterator接口,实现这个接口的集合可以双向遍历,既可以通过next()访问下一个元素,又可以通过previous()访问前一个元素,比如ArrayList。

  还有一个特点就是抽象类的使用。如果要自己实现一个集合类,去实现那些抽象的接口会非常麻烦,工作量很大。这个时候就可以使用抽象类,这些抽象类中给我们提供了许多现成的实现,我们只需要根据自己的需求重写一些方法或者添加一些方法就可以实现自己需要的集合类,工作流昂大大降低。

集合类_第2张图片

2.1HashSet

HashSet是Set接口的一个子类,主要的特点是:里面不能存放重复元素,而且采用散列的存储方法,所以没有顺序。这里所说的没有顺序是指:元素插入的顺序与输出的顺序不一致。

代码实例:HashSetDemo


package edu.sjtu.erplab.collection;import java.util.HashSet;import java.util.Iterator;import java.util.Set;public class HashSetDemo {    public static void main(String[] args) {
        Set<String> set=new HashSet<String>();
        
        set.add("a");
        set.add("b");
        set.add("c");
        set.add("c");
        set.add("d");        
        //使用Iterator输出集合
        Iterator<String> iter=set.iterator();        while(iter.hasNext())
        {
            System.out.print(iter.next()+" ");
        }
        System.out.println();        //使用For Each输出结合
        for(String e:set)
        {
            System.out.print(e+" ");
        }
        System.out.println();        
        //使用toString输出集合        System.out.println(set);
    }
}

代码实例:SetTest

package edu.sjtu.erplab.collection;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.InputStream;import java.util.HashSet;import java.util.Iterator;import java.util.Scanner;import java.util.Set;public class SetTest {    public static void main(String[] args) throws FileNotFoundException {
        Set<String> words=new HashSet<String>();        //通过输入流代开文献        //方法1:这个方法不需要抛出异常
        InputStream inStream=SetTest.class.getResourceAsStream("Alice.txt");        
        //方法2:这个方法需要抛出异常        //InputStream inStream = new FileInputStream("D:\\Documents\\workspace\\JAVAStudy\\src\\edu\\sjtu\\erplab\\collection\\Alice.txt");
        Scanner in=new Scanner(inStream);        while(in.hasNext())
        {
            words.add(in.next());
        }
        
        Iterator<String> iter=words.iterator();        
        for(int i=0;i<5;i++)
        {            if(iter.hasNext())
            System.out.println(iter.next());
        }
        
        System.out.println(words.size());

    }
}

2.2ArrayList

ArrayList是List的子类,它和HashSet想法,允许存放重复元素,因此有序。集合中元素被访问的顺序取决于集合的类型。如果对ArrayList进行访问,迭代器将从索引0开始,每迭代一次,索引值加1。然而,如果访问HashSet中的元素,每个元素将会按照某种随机的次序出现。虽然可以确定在迭代过程中能够遍历到集合中的所有元素,但却无法预知元素被访问的次序。

代码实例:ArrayListDemo

package edu.sjtu.erplab.collection;import java.util.ArrayList;import java.util.Iterator;import java.util.List;public class ArrayListDemo {    public static void main(String[] args) {
        List<String> arrList=new ArrayList<String>();
        
        arrList.add("a");
        arrList.add("b");
        arrList.add("c");
        arrList.add("c");
        arrList.add("d");        //使用Iterator输出集合
        Iterator<String> iter=arrList.iterator();        while(iter.hasNext())
        {
            System.out.print(iter.next()+" ");
        }
        System.out.println();        //使用For Each输出结合
        for(String e:arrList)
        {
            System.out.print(e+" ");
        }
        System.out.println();        
        //使用toString输出集合        System.out.println(arrList);
    }
}

2.3LinkedList

LinkedList是一种可以在任何位置进行高效地插入和删除操作的有序序列。

代码实例:LinkedListTest

package edu.sjtu.erplab.collection;import java.util.ArrayList;import java.util.Iterator;import java.util.List;import java.util.ListIterator;public class LinkedListTest {    public static void main(String[] args) {

        List<String> a=new ArrayList<String>();
        a.add("a");
        a.add("b");
        a.add("c");
        System.out.println(a);
        
        List<String> b=new ArrayList<String>();
        b.add("d");
        b.add("e");
        b.add("f");
        b.add("g");
        System.out.println(b);        
        //ListIterator在Iterator基础上添加了add(),previous()和hasPrevious()方法
        ListIterator<String> aIter=a.listIterator();        //普通的Iterator只有三个方法,hasNext(),next()和remove()
        Iterator<String> bIter=b.iterator();        
        //b归并入a当中,间隔交叉得插入b中的元素
        while(bIter.hasNext())
        {            if(aIter.hasNext())
                aIter.next();
            aIter.add(bIter.next());
        }
        System.out.println(a);        
        //在b中每隔两个元素删除一个
        bIter=b.iterator();        
        while(bIter.hasNext())
        {
            bIter.next();            if(bIter.hasNext())
            {
                bIter.next();//remove跟next是成对出现的,remove总是删除前序                bIter.remove();
            }
        }
        System.out.println(b);        
        //删除a中所有的b中的元素        a.removeAll(b);
        System.out.println(a);
    }
}

 2.4HashMap

HashMap的数据结构

  数组的特点是:寻址容易,插入和删除困难;而链表的特点是:寻址困难,插入和删除容易。那么我们能不能综合两者的特性,做出一种寻址容易,插入删除也容易的数据结构?答案是肯定的,这就是我们要提起的哈希表,哈希表有多种不同的实现方法,我接下来解释的是最常用的一种方法—— 拉链法,我们可以理解为链表的数组 ,如图:

集合类_第3张图片

 

从上图我们可以发现哈希表是由数组+链表组成的,一个长度为16的数组中,每个元素存储的是一个链表的头结点。那么这些元素是按照什么样的规则存储到数组中呢。一般情况是通过hash(key)%len获得,也就是元素的key的哈希值对数组长度取模得到。比如上述哈希表中,12%16=12,28%16=12,108%16=12,140%16=12。所以12、28、108以及140都存储在数组下标为12的位置。

  HashMap其实也是一个线性的数组实现的,所以可以理解为其存储数据的容器就是一个线性数组。这可能让我们很不解,一个线性的数组怎么实现按键值对来存取数据呢?这里HashMap有做一些处理。

  1.首先HashMap里面实现一个静态内部类Entry,其重要的属性有 key , value, next,从属性key,value我们就能很明显的看出来Entry就是HashMap键值对实现的一个基础bean,我们上面说到HashMap的基础就是一个线性数组,这个数组就是Entry[],Map里面的内容都保存在Entry[]里面。

2.HashMap的存取实现

     既然是线性数组,为什么能随机存取?这里HashMap用了一个小算法,大致是这样实现:

//存储时:int hash = key.hashCode();// 这个hashCode方法这里不详述,只要理解每个key的hash是一个固定的int值int index = hash % Entry[].length;
Entry[index] = value;//取值时:int hash = key.hashCode();int index = hash % Entry[].length;return Entry[index];

到这里我们轻松的理解了HashMap通过键值对实现存取的基本原理

    3.疑问:如果两个key通过hash%Entry[].length得到的index相同,会不会有覆盖的危险?

  这里HashMap里面用到链式数据结构的一个概念。上面我们提到过Entry类里面有一个next属性,作用是指向下一个Entry。打个比方, 第一个键值对A进来,通过计算其key的hash得到的index=0,记做:Entry[0] = A。一会后又进来一个键值对B,通过计算其index也等于0,现在怎么办?HashMap会这样做:B.next = A,Entry[0] = B,如果又进来C,index也等于0,那么C.next = B,Entry[0] = C;这样我们发现index=0的地方其实存取了A,B,C三个键值对,他们通过next这个属性链接在一起。所以疑问不用担心。也就是说数组中存储的是最后插入的元素。到这里为止,HashMap的大致实现,我们应该已经清楚了。

  当然HashMap里面也包含一些优化方面的实现,这里也说一下。比如:Entry[]的长度一定后,随着map里面数据的越来越长,这样同一个index的链就会很长,会不会影响性能?HashMap里面设置一个因素(也称为因子),随着map的size越来越大,Entry[]会以一定的规则加长长度。

3.解决hash冲突的办法

  1. 开放定址法(线性探测再散列,二次探测再散列,伪随机探测再散列)

  2. 再哈希法

  3. 链地址法

  4. 建立一个公共溢出区

Java中hashmap的解决办法就是采用的链地址法。

4.实现自己的HashMap

Entry.java

package edu.sjtu.erplab.hash;public class Entry<K,V>{    final K key;
    V value;
    Entry<K,V> next;//下一个结点 
    //构造函数
    public Entry(K k, V v, Entry<K,V> n) {
        key = k;
        value = v;
        next = n;
    }    public final K getKey() {        return key;
    }    public final V getValue() {        return value;
    }    public final V setValue(V newValue) {
    V oldValue = value;
        value = newValue;        return oldValue;
    }    public final boolean equals(Object o) {        if (!(o instanceof Entry))            return false;
        Entry e = (Entry)o;
        Object k1 = getKey();
        Object k2 = e.getKey();        if (k1 == k2 || (k1 != null && k1.equals(k2))) {
            Object v1 = getValue();
            Object v2 = e.getValue();            if (v1 == v2 || (v1 != null && v1.equals(v2)))                return true;
        }        return false;
    }    public final int hashCode() {        return (key==null   ? 0 : key.hashCode()) ^ (value==null ? 0 : value.hashCode());
    }    public final String toString() {        return getKey() + "=" + getValue();
    }

}

MyHashMap.java

package edu.sjtu.erplab.hash;//保证key与value不为空public class MyHashMap<K, V> {    private Entry[] table;//Entry数组表
    static final int DEFAULT_INITIAL_CAPACITY = 16;//默认数组长度
    private int size;    // 构造函数
    public MyHashMap() {
        table = new Entry[DEFAULT_INITIAL_CAPACITY];
        size = DEFAULT_INITIAL_CAPACITY;
    }    //获取数组长度
    public int getSize() {        return size;
    }    
    // 求index
    static int indexFor(int h, int length) {        return h % (length - 1);
    }    //获取元素
    public V get(Object key) {        if (key == null)            return null;        int hash = key.hashCode();// key的哈希值
        int index = indexFor(hash, table.length);// 求key在数组中的下标
        for (Entry<K, V> e = table[index]; e != null; e = e.next) {
            Object k = e.key;            if (e.key.hashCode() == hash && (k == key || key.equals(k)))                return e.value;
        }        return null;
    }    // 添加元素
    public V put(K key, V value) {        if (key == null)            return null;        int hash = key.hashCode();        int index = indexFor(hash, table.length);        // 如果添加的key已经存在,那么只需要修改value值即可
        for (Entry<K, V> e = table[index]; e != null; e = e.next) {
            Object k = e.key;            if (e.key.hashCode() == hash && (k == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;                return oldValue;// 原来的value值            }
        }        // 如果key值不存在,那么需要添加
        Entry<K, V> e = table[index];// 获取当前数组中的e
        table[index] = new Entry<K, V>(key, value, e);// 新建一个Entry,并将其指向原先的e
        return null;
    }

}

MyHashMapTest.java

package edu.sjtu.erplab.hash;public class MyHashMapTest {    public static void main(String[] args) {

        MyHashMap<Integer, Integer> map = new MyHashMap<Integer, Integer>();
        map.put(1, 90);
        map.put(2, 95);
        map.put(17, 85);
    
        System.out.println(map.get(1));
        System.out.println(map.get(2));
        System.out.println(map.get(17));
        System.out.println(map.get(null));
    }
}

 2.5WeekHashMapDemo

package edu.sjtu.erplab.collection;import java.util.WeakHashMap;public class WeekHashMapDemo {    public static void main(String[] args) {        int size = 100;        if (args.length > 0) {
            size = Integer.parseInt(args[0]);
        }

        Key[] keys = new Key[size];
        WeakHashMap<Key, Value> whm = new WeakHashMap<Key, Value>();        for (int i = 0; i < size; i++) {
            Key k = new Key(Integer.toString(i));
            Value v = new Value(Integer.toString(i));            if (i % 3 == 0) {
                keys[i] = k;//强引用            }
            whm.put(k, v);//所有键值放入WeakHashMap中        }

        System.out.println(whm);
        System.out.println(whm.size());
        System.gc();        
        try {            // 把处理器的时间让给垃圾回收器进行垃圾回收
            Thread.sleep(4000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } 
        
        System.out.println(whm);
        System.out.println(whm.size());
    }

}class Key {
    String id;    public Key(String id) {        this.id = id;
    }    public String toString() {        return id;
    }    public int hashCode() {        return id.hashCode();
    }    public boolean equals(Object r) {        return (r instanceof Key) && id.equals(((Key) r).id);
    }    public void finalize() {
        System.out.println("Finalizing Key " + id);
    }
}class Value {
    String id;    public Value(String id) {        this.id = id;
    }    public String toString() {        return id;
    }    public void finalize() {
        System.out.println("Finalizing Value " + id);
    }

}

输出结果

{50=50, 54=54, 53=53, 52=52, 51=51, 46=46, 47=47, 44=44, 45=45, 48=48, 49=49, 61=61, 60=60, 63=63, 62=62, 65=65, 64=64, 55=55, 56=56, 57=57, 58=58, 59=59, 76=76, 75=75, 74=74, 73=73, 72=72, 71=71, 70=70, 68=68, 69=69, 66=66, 67=67, 85=85, 84=84, 87=87, 86=86, 81=81, 80=80, 83=83, 82=82, 77=77, 78=78, 79=79, 89=89, 88=88, 10=10, 90=90, 91=91, 92=92, 93=93, 94=94, 95=95, 96=96, 97=97, 98=98, 99=99, 20=20, 21=21, 12=12, 11=11, 14=14, 13=13, 16=16, 15=15, 18=18, 17=17, 19=19, 8=8, 9=9, 31=31, 4=4, 32=32, 5=5, 6=6, 30=30, 7=7, 0=0, 1=1, 2=2, 3=3, 29=29, 28=28, 27=27, 26=26, 25=25, 24=24, 23=23, 22=22, 40=40, 41=41, 42=42, 43=43, 38=38, 37=37, 39=39, 34=34, 33=33, 36=36, 35=35}
100
Finalizing Key 98
Finalizing Key 97
Finalizing Key 95
Finalizing Key 94
Finalizing Key 92
Finalizing Key 91
Finalizing Key 89
Finalizing Key 88
Finalizing Key 86
Finalizing Key 85
Finalizing Key 83
Finalizing Key 82
Finalizing Key 80
Finalizing Key 79
Finalizing Key 77
Finalizing Key 76
Finalizing Key 74
Finalizing Key 73
Finalizing Key 71
Finalizing Key 70
Finalizing Key 68
Finalizing Key 67
Finalizing Key 65
Finalizing Key 64
Finalizing Key 62
Finalizing Key 61
Finalizing Key 59
Finalizing Key 58
Finalizing Key 56
Finalizing Key 55
Finalizing Key 53
Finalizing Key 52
Finalizing Key 50
Finalizing Key 49
Finalizing Key 47
Finalizing Key 46
Finalizing Key 44
Finalizing Key 43
Finalizing Key 41
Finalizing Key 40
Finalizing Key 38
Finalizing Key 37
Finalizing Key 35
Finalizing Key 34
Finalizing Key 32
Finalizing Key 31
Finalizing Key 29
Finalizing Key 28
Finalizing Key 26
Finalizing Key 25
Finalizing Key 23
Finalizing Key 22
Finalizing Key 20
Finalizing Key 19
Finalizing Key 17
Finalizing Key 16
Finalizing Key 14
Finalizing Key 13
Finalizing Key 11
Finalizing Key 10
Finalizing Key 8
Finalizing Key 7
Finalizing Key 5
Finalizing Key 4
Finalizing Key 2
Finalizing Key 1
{54=54, 51=51, 45=45, 48=48, 60=60, 63=63, 57=57, 75=75, 72=72, 69=69, 66=66, 84=84, 87=87, 81=81, 78=78, 90=90, 93=93, 96=96, 99=99, 21=21, 12=12, 15=15, 18=18, 9=9, 6=6, 30=30, 0=0, 3=3, 27=27, 24=24, 42=42, 39=39, 33=33, 36=36}
34

3.比较


你可能感兴趣的:(集合类)