JAVA常用数据结构及原理分析

来源:互联网 发布:windows系统正版验证 编辑:程序博客网 时间:2024/06/05 04:38

http://www.2cto.com/kf/201506/412305.html

前不久面试官让我说一下怎么理解java数据结构框架,之前也看过部分源码,balabala讲了一堆,现在总结一下。

java.util包中三个重要的接口及特点:List(列表)、Set(保证集合中元素唯一)、Map(维护多个key-value键值对,保证key唯一)。其不同子类的实现各有差异,如是否同步(线程安全)、是否有序。
常用类继承树:
这里写图片描述

以下结合源码讲解常用类实现原理及相互之间的差异。

Collection (所有集合类的接口)
List、Set都继承自Collection接口,查看JDK API,操作集合常用的方法大部分在该接口中定义了。
这里写图片描述

Collections (操作集合的工具类)
对于集合类的操作不得不提到工具类Collections,它提供了许多方便的方法,如求两个集合的差集、并集、拷贝、排序等等。
由于大部分的集合接口实现类都是不同步的,可以使用Collections.synchronized*方法创建同步的集合类对象。
如创建一个同步的List:
List synList = Collections.synchronizedList(new ArrayList());
其实现原理就是重新封装new出来的对象,操作对象时用关键字synchronized同步。看源码很容易理解。
Collections部分源码:

//Collections.synchronizedList返回的是静态类SynchronizedCollection的实例,最终将new出来的ArrayList对象赋值给了Collection<e> c。
staticclass SynchronizedCollection<e> implementsCollection<e>, Serializable {
 
        finalCollection<e> c;  // Backing Collection
        finalObject mutex;     // Object on which to synchronize
 
        SynchronizedCollection(Collection<e> c) {
            if(c==null)
                thrownew NullPointerException();
            this.c = c;
            mutex = this;
        }
        //...
        publicboolean add(E e) {
            //操作集合时简单调用原本的ArrayList对象,只是做了同步
            synchronized(mutex) {returnc.add(e);}
        }
        //...

List (列表)

ArrayList、Vector是线性表,使用Object数组作为容器去存储数据的,添加了很多方法维护这个数组,使其容量可以动态增长,极大地提升了开发效率。它们明显的区别是ArrayList是非同步的,Vector是同步的。不用考虑多线程时应使用ArrayList来提升效率。
ArrayList、Vector 部分源码:

//ArrayList.add
publicbooleanadd(E e) {
    ensureCapacityInternal(size + 1); // Increments modCount!!
    //可以看出添加的对象放到elementData数组中去了
    elementData[size++] = e;
    returntrue;
}
//ArrayList.remove
publicE remove(intindex) {
    rangeCheck(index);
 
    modCount++;
    E oldValue = elementData(index);
 
    intnumMoved = size - index - 1;
    if(numMoved > 0)
        //移除元素时数组产生的空位由System.arraycopy方法将其后的所有元素往前移一位,System.arraycopy调用虚拟机提供的本地方法来提升效率
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--size] = null;// Let gc do its work
 
    returnoldValue;
}
 
//Vector add方法上多了synchronized关键字
publicsynchronizedboolean add(E e) {
    modCount++;
    ensureCapacityHelper(elementCount + 1);
    elementData[elementCount++] = e;
    returntrue;
}

LinkedList是链表,略懂数据结构就知道其实现原理了。链表随机位置插入、删除数据时比线性表快,遍历比线性表慢。
双向链表原理图:
这里写图片描述
LinkedList部分源码:

?

//源码很清晰地表达了原理图
publicclassLinkedList<e>
extendsAbstractSequentialList<e>
implementsList<e>, Deque<e>, Cloneable, java.io.Serializable
{
    //头尾节点
    transientNode<e> first;
    transientNode<e> last;
}
//节点类
 privatestaticclass Node<e> {
    //节点存储的数据
    E item;
    Node<e> next;
    Node<e> prev;
    Node(Node<e> prev, E element, Node<e> next) {
        this.item = element;
        this.next = next;
        this.prev = prev;
    }
}

由此可根据实际情况来选择使用ArrayList(非同步、非频繁删除时选择)、Vector(需同步时选择)、LinkedList(频繁在任意位置插入、删除时选择)。

Map(存储键值对,key唯一)

HashMap结构的实现原理是将put进来的key-value封装成一个Entry对象存储到一个Entry数组中,位置(数组下标)由key的哈希值与数组长度计算而来。如果数组当前下标已有值,则将数组当前下标的值指向新添加的Entry对象。
有点晕,看图吧:
这里写图片描述
看完图再看源码,非常清晰,都不需要注释。

publicclassHashMap<k,v>
extendsAbstractMap<k,v>
implementsMap<k,v>, Cloneable, Serializable
{
    transientEntry<k,v>[] table;
    publicV put(K key, V value) {
        if(key == null)
            returnputForNullKey(value);
        inthash = hash(key);
        inti = indexFor(hash, table.length);
        //遍历当前下标的Entry对象链,如果key已存在则替换
        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);
            returnoldValue;
        }
    }
       addEntry(hash, key, value, i);
        returnnull;
    }
}
staticclassEntry<k,v> implementsMap.Entry<k,v> {
    finalK key;
    V value;
    Entry<k,v> next;
    inthash;
}



TreeMap是由Entry对象为节点组成的一颗红黑树,put到TreeMap的数据默认按key的自然顺序排序,new TreeMap时传入Comparator自定义排序。红黑树网上很多资料,我讲不清,这里就不介绍了。

Set(保证容器内元素唯一性)
之所以先讲Map是因为Set结构其实就是维护一个Map来存储数据的,利用Map结构key值唯一性
HashSet部分源码:


publicclassHashSet<e>
extendsAbstractSet<e>
implementsSet<e>, Cloneable, java.io.Serializable
{   
 
    //无意义对象来作为Map的value
    privatestaticfinal Object PRESENT = newObject();
    publicbooleanadd(E e) {
        returnmap.put(e, PRESENT)==null;
    }
}

HashSet、TreeSet分别默认维护一个HashMap、TreeMap。



1. ArrayList

 ArrayList<String> c = new ArrayList<String>(); c.add("hello"); c.add("world"); String a = new String("aya"); c.add(a); for( Object obj : c  ) { System.out.println( (String)obj ); }  c.set(2, "111");  Iterator<String> it = c.iterator(); while(it.hasNext()) { System.out.println( (String)it.next() );  }

ArrayList 的迭代器还有 ListIterator 可以有正反两种查询,先hasNext知道最后 hasPrevious才能有值

 ListIterator<String> it2 = c.listIterator();  while(it2.hasNext()) { System.out.println( "hasNext " + (String)it2.next() ); }  while(it2.hasPrevious()) { System.out.println( "hasPrevious " + (String)it2.previous() ); }


2. HashSet 集合是无序的。

HashSet集合判断两个元素相等的标准是两个对象通过equals 方法比较相等,并且两个对象的hashCode()方法返回值也相等。

向HashSet中添加可变对象时,必须小心,如果修改HashSet集合中的对象有可能导致该对象与集合中其他对象相等,从而导致Hashset无法准确访问该对象。

 

3. TreeSet 集合有序

由于TreeSet有序, 所有有访问第一个,前一个,后一个,最后一个并可以截取子集。

排序的顺序并不是根据插入的顺序,而是根据元素实际值来排序。

 TreeSet<Integer> nums = new TreeSet<Integer>(); nums.add(4); nums.add(3); nums.add(2); nums.add(1);  System.out.println(nums);  //整个集合 System.out.println(nums.first());  System.out.println(nums.last()); System.out.println(nums.headSet(3)); //小于3,但不包括3 System.out.println(nums.tailSet(3)); //大于等于3 System.out.println(nums.subSet(1, 3));  大于等于1,小于3

结果为

[1, 2, 3, 4]
1
4
[1, 2]
[3, 4]
[1, 2]

 TreeSet自然排序:根据元素大小,升序排列。 对于TreeSet判断两个元素相等的标准是,两个对象通过equals方法比较返回true,并且通过compareTo(Object obj)比较 返回0。

和HashSet一样集合中为可变对象时,修改对象值后可能容易出错,为了健壮性,HashSet和TreeSet推荐放入不可变对象。

 TreeSet定序排列: 使用comparator接口帮助。

 

4. EnumSet 是一个专为枚举类设计的集合类,其中所有的值都是指定枚举类型的枚举值。没有暴露构造器,必须用静态方法创建。

enum Season{SPRING,SUMMER,FALL,WINTER;}public class Main {public static void main(String[] args) {EnumSet es1 = EnumSet.allOf(Season.class); //创建一个EnumSet集合,集合元素就是Season枚举类的全部枚举值System.out.println(es1);EnumSet es2 = EnumSet.noneOf(Season.class); //空集合,但指定其中的元素是Season类的枚举值System.out.println(es2);es2.add(Season.SPRING);es2.add(Season.SUMMER);System.out.println(es2);EnumSet es3 = EnumSet.of(Season.SPRING, Season.SUMMER); // 以指定枚举值创建EnumSetSystem.out.println(es3);EnumSet es4 = EnumSet.range(Season.SPRING, Season.FALL); // 范围值System.out.println(es4);EnumSet es5 = EnumSet.complementOf(es4); //Season中 除去es4里剩下的部分 为es5System.out.println(es5);}}

输出结果:

[SPRING, SUMMER, FALL, WINTER]
[]
[SPRING, SUMMER]
[SPRING, SUMMER]
[SPRING, SUMMER, FALL]
[WINTER]

 

5. LinkedList 不仅可以当做双向队列使用,还可以当做“栈”使用,其包含有 pop出栈 和 push入栈 方法

 LinkedList<String> books = new LinkedList<String>(); books.offer("test1");  //加入队列尾部 books.push("test2");   //入栈 books.offerFirst("test3"); //放入头部for(int i=0; i<books.size(); i++){System.out.println( books.get(i) );}System.out.println( books.peekFirst() ); //访问不删除System.out.println( "log1: " + books );System.out.println( books.pollFirst() );  //访问并删除System.out.println( "log2: " + books );

输出结果:

test3
test2
test1
test3
log1: [test3, test2, test1]
test3
log2: [test2, test1]

 

6. Map

HashMap 和 HashTable 判断Value相等的条件只需要equals方法返回true即可,判断Key相等的条件是equals()和hashCode()返回值都相等。

LinkedHashMap以链表来维护内部顺序。性能低于HashMap,消耗低于TreeMap。

TreeMap key的排序也是根据红黑树排列,有两种排序方式,自然排序和定制排序。

WeekHashMap 只保留key对应对象的弱引用,可能被垃圾回收。

enum Season{SPRING,SUMMER,FALL,WINTER;}public class Main {public static void main(String[] args) {HashMap<Integer, String> map1 = new HashMap<Integer, String>();map1.put(1, "hello");map1.put(2, "world");System.out.println(map1);System.out.println(map1.containsKey(3));System.out.println(map1.containsValue("world"));map1.put(2, "aya");System.out.println(map1);for(Map.Entry<Integer, String> m : map1.entrySet()){System.out.println(m);System.out.println(m.getKey());}EnumMap map2 = new EnumMap(Season.class);map2.put(Season.SPRING, "spring");map2.put(Season.SUMMER, "summer");System.out.println(map2);}}


7. Collections 操作list map set 的工具类

ArrayList<Integer> list = new ArrayList<Integer>();list.add(1);list.add(2);list.add(3);System.out.println(list);Collections.reverse(list);  //反转排序 System.out.println("reverse " + list);Collections.shuffle(list);  //乱序 洗牌System.out.println("shuffle " + list);Collections.sort(list);     //自然排序System.out.println("sort " + list);Collections.swap(list, 0, 1); //交换位置 0 - 1交换System.out.println("swap " + list);Collections.rotate(list, 2);  //向右推移2位System.out.println("rotate " + list);System.out.println("max " + Collections.max(list)); //最大值System.out.println("min " + Collections.min(list)); //最小值Collections.replaceAll(list, 3, 1);    //替换,将所有的 3 替换为 1System.out.println("replaceAll " + list); System.out.println("fre " + Collections.frequency(list, 1)); // 1 出现的频率System.out.println("binarySearch " + Collections.binarySearch(list, 2)); //二分法查找, 2的引锁值

输出结果:

[1, 2, 3]
reverse [3, 2, 1]
shuffle [3, 2, 1]
sort [1, 2, 3]
swap [2, 1, 3]
rotate [1, 3, 2]
max 3
min 1
replaceAll [1, 1, 2]
fre 2
binarySearch 2

 

8. 控制线程安全

之上的list map set 都是不能做到线程安全的,

可以如下建立: List<String> list2 = Collections.synchronizedList(new ArrayList<String>()); 

同样 map  set 也可以这样建立,做到线程安全。

 

9. 创建不可修改集合

利用Collections.unmodifiableXXX即可, set  map list 均可, list2将不可修改, list2.add() 将报异常。

    ArrayList<Integer> list = new ArrayList<Integer>();list.add(1);list.add(2);list.add(3);List<Integer> list2 = Collections.unmodifiableList(list);list2.add(1);