Java梳理之理解集合(二)

Collection集合

查看上面的结构图可以发现,Collection接口继承了Iterable接口,在Iterable接口中就拥有iterator()方法,可以和上面的Iterator接口联系起来;往下看又存在Queue子接口、Set子接口、List子接口,同时还有集合抽象基础类AbstractCollection,其余的抽象基础类AbstractListAbstractSetAbstractQueue都继承集合抽象基础类,同时又实现自己内部的接口ListSetQueue。故而理解这些抽象基础类之后可以更容易的分析之后的具体类。

List
SubList类

java.util包下,可以找到几个List类,如ArrayListLinkedListSubList。而这些类都是继承于AbstractList类,其中有个方法subList()很有趣,如下所示:

/**
**AbstractList类中subList方法源码
**/
public List subList(int i, int j) {
        return ((List) ((this instanceof RandomAccess) ? new RandomAccessSubList(this, i, j)
                : new SubList(this, i, j)));
}

其中RandomAccess仅仅只是个标记接口,内部不存在任何的方法。在上面的方法运行后会返回一个SubList对象,很重要的一点:在new这个对象的时候,传入的是当前的this,点开SubList类的源码可知,其拥有字段如下:

/**
**SubList类源码  字段
**/
private AbstractList l; 
private int offset;
private int size;
private int expectedModCount;

其中拥有的构造器如下:

/**
**SubList类源码 构造器
**/
SubList(AbstractList abstractlist, int i, int j) {
        if (i < 0)
            throw new IndexOutOfBoundsException((new StringBuilder()).append("fromIndex = ").append(i).toString());
        if (j > abstractlist.size())
            throw new IndexOutOfBoundsException((new StringBuilder()).append("toIndex = ").append(j).toString());
        if (i > j) {
            throw new IllegalArgumentException((new StringBuilder()).append("fromIndex(").append(i)
                    .append(") > toIndex(").append(j).append(")").toString());
        } else {
        **  l = abstractlist;**
        **  offset = i;**
        **  size = j - i;**
        **  expectedModCount = l.modCount;**
            return;
        }
    }

如上所示,这里是直接把当前类this传给了SubList,而不是重新创建一个对象。故而AbstractList类的subList方法返回的仅仅是一个视图,对它的返回对象做的任何操作都会反映到原来的List,其中size=j-i,表明它获取的长度并不包括原数组下标j的数据。当然并不是说不好,它还是有个很好的用法的,如下所示:

/**
**subList的应用
**/
public static void main(String[] arg0){
        ArrayList list = new ArrayList();
        list.add("test1");
        list.add("test2");
        list.add("test3");
        list.add("test4");
        list.add("test5");
        list.add("test6");
        list.subList(1, 4).clear();
        for(String s:list){
            System.out.println(s);
        }
    }
输出:
test1
test5
test6

可以看到,这样可以很方便的对list中间的部分数据进行处理,SubList还提供了一些常用的方法,用来操作这个视图的数据,相应的可以自行去了解。

ArrayList类

这个类继承AbstractList类,同时实现ListRandomAccessCloneableSerializable等四个接口。打开源码可以看到,这个类有如下几个字段:

/**
**ArrayList源码 字段
**/
private transient Object elementData[];  //容器数组
private int size; //List长度
protected transient int modCount; //父类继承而来,修改的次数

如上所示,这三个字段中,功能字段只有前两个elementData[]size。从这里也可以看出,ArrayList的底层是使用数组elementData来实现的,这个数组存的对象为Object。同时,它提供了三个构造器,如下所示:

/**
*ArrayList源码 构造器
**/
public ArrayList(int i) {
        if (i < 0) {
            throw new IllegalArgumentException((new StringBuilder()).append("Illegal Capacity: ").append(i).toString());
        } else {
            elementData = new Object[i];
            return;
        }
    }
public ArrayList() {
        this(10);
    }
public ArrayList(Collection collection)
    {
        elementData = collection.toArray();
        size = elementData.length;
        if(((Object) (elementData)).getClass() != [Ljava/lang/Object;)
            elementData = Arrays.copyOf(elementData, size, [Ljava/lang/Object;);
    }

在构造器这部分可以看到,它会初始化这个数组elementData,这里支持的三种构造器前两种会给数组初始化长度i或者默认长度10,最后一种则是使用了Collection接口中的方法toArray()直接转成数组。
在这字段和构造器看完之后,根据数组结构可知,数组本身是有下标的存在,在ArrayList中也保留了数组下标的作用,故而使得操作会变的简单很多,比如添加add(int i,Object obj)、设置set(int i,Object obj)、删除remove(int i)等。除此之外,还有两个方法如下:

/**
**ArrayList 
**/
public void trimToSize() {
        modCount++;
        int i = elementData.length;
        if (size < i)
            elementData = Arrays.copyOf(elementData, size);
    }

public void ensureCapacity(int i) {
        modCount++;
        int j = elementData.length;
        if (i > j) {
            Object aobj[] = elementData;
            int k = (j * 3) / 2 + 1;
            if (k < i)
                k = i;
            elementData = Arrays.copyOf(elementData, k);
        }
    }

其中trimToSize()方法缩小数组长度,类似于String中的trim()方法,而ensureCapactity()方法用来扩容数组。至此,ArrayList内部实现大体上梳理完全,从实现方面思考自然就可以解决一些面试问题。

LinkedList类

LinkedList不同于ArrayList,它的底层并不是使用数组来写的;查看源码可知它继承了AbstractSequentialList,同时实现了ListDequeCloneableSerializable这四个接口,其中AbstractSequentialList类继承了AbstractList类并使用ListIterator来实现了List中一些有需要下标的操作,如add(i, E);而比较特别的是Deque接口,在这个接口中定义了一系列双端队列的操作,即两头都可以操作,还增加了poll(),peek(),push(),pop()操作,所以LinkedList类的操作方法看起来相比ArrayList多了一些。
下面从字段开始,查看源码可知,它有两个字段如下:

/**
**LinkedList类 字段
**/
private transient Entry header;
private transient int size;

这里可以看到,字段header的类型是Entry,这是个LinkedList的内部类,源码如下:

/**
** LinkedList内部类 Entry 源码
**/
private static class Entry {

        Object element;
        Entry next;
        Entry previous;

        Entry(Object obj, Entry entry1, Entry entry2) {
            element = obj;
            next = entry1;
            previous = entry2;
        }
    }

在静态内部类Entry中存在三个字段,这里就可以看出为什么叫LinkedList了,类LinkedList内部实现并不是用数组而是保存下一个元素的地址,形成像铁链一样的结构。这里也是说明为什么说LinkedList便于插入或者删除操作。类LinkedList的构造器如下所示:

/**
**LinkedList类的构造器
**/
public LinkedList() {
        header = new Entry(null, null, null);
        size = 0;
        header.next = header.previous = header;
    }

public LinkedList(Collection collection) {
        this();
        addAll(collection);
    }

可以看到,LinkedListsize默认初始值是0,当然,这里也提供了入参是Collection的构造器,说明这个接口下的所有类都可以转化为LinkedList。在这之后,查看它的方法就知道,它的操作脱离不了Entry实例和长度size,其中Entry类的next字段和previous字段是这个类的核心操作点,根据这点,源码中方法就很清楚的展示出来了,通过常见的方法就可以完成对LinkedList的操作,如下所示:

/**
**LinedList类的 entry()方法
**/
private Entry entry(int i) {
        if (i < 0 || i >= size)
            throw new IndexOutOfBoundsException(
                    (new StringBuilder()).append("Index: ").append(i).append(", Size: ").append(size).toString());
        Entry entry1 = header;
        if (i < size >> 1) {
            for (int j = 0; j <= i; j++)
                entry1 = entry1.next;

        } else {
            for (int k = size; k > i; k--)
                entry1 = entry1.previous;

        }
        return entry1;
    }

在这个方法中通过下标i来获取LinkedList的数据,也就是普通的ifor操作。其他的方法,或调用这个方法,或进行类似的操作,具体的其他方法就不在这里放了。

当然,并不是说,List下就只有这三个类,其实这三个类只是三个并不是线程安全的类,如果要扯上线程安全的问题,那么还有类VecterStack栈,只是这两个类在使用中并不常见,而且在新版本中已经对线程安全的List有了替代类,详情查看java.util.concurrent包下的各个同步集合类。

Set

在结构图中可以发现,Set下面有个子类AbstractSet,一应的Set类都继承这个抽象类。在Java.util包中可以找到的Set集合类有EnumSetHashSetLinkedHashSetTreeSet。在这里就从HashSet开始:

HashSet

在上篇文章中梳理过Map之后,那么Set就很简单了。在HashSet的源码中可以看到其中有两个字段,如下所示:

/**
**HashSet源码 字段
**/
private transient HashMap map;
private static final Object PRESENT = new Object();

在这里就可以看到,HashSet的底层是使用HashMap实现的,看过HashMap源码之后,这个HashSet就非常简单了,只是HashMap的一层包装,查看Set的方法add(obj)如下:

/**
**HashSet的add方法
**/
public boolean add(Object obj) {
        return map.put(obj, PRESENT) == null;
}

put方法内部使用map.put()方法来实现,将静态对象PRESENT置入,而我们的Set存放的对象则作为Key来存放,所以说SetMap的一层包装。

TreeSet

类似于HashSetTreeSet也是使用对于的Map来实现的。查看TreeMap中的源码,其字段如下所示:

/**
**TreeSet源码 字段
**/
private transient NavigableMap map;
private static final Object PRESENT = new Object();

可以看到,其中的字段NavigableMap类型的map,查看构造器如下:

/**
**TreeMap源码 构造器
**/
TreeSet(NavigableMap navigablemap) {
        m = navigablemap;
    }
public TreeSet() {
        this(((NavigableMap) (new TreeMap())));
    }

public TreeSet(Comparator comparator1) {
        this(((NavigableMap) (new TreeMap(comparator1))));
    }

public TreeSet(Collection collection) {
        this();
        addAll(collection);
    }

public TreeSet(SortedSet sortedset) {
        this(sortedset.comparator());
        addAll(sortedset);
    }

对于每个构造器而言,最后都会new一个TreeMap,并将这个TreeMap转化为NavigableMap传给被保护的构造器。这样就知道TreeSet内使用的就是TreeMap来存储TreeSet
TreeSet中的存储和HashSet一样,如下所示:

/**
**TreeSet的add方法
**/
public boolean add(Object obj) {
        return m.put(obj, PRESENT) == null;
}

可以看到,这里使用的也是mapput方法,并将PRESENT字段存放在val中,整个的TreeSet相当于是TreeMap包装了一层。

到此为止,Set大体上就是这样的,如果清楚了Map的内部结构,那么Set则没有什么难点。

Queue

Java中存在队列这么一个结构,就像我们平常所知道的队列一样,它奉行的是先进先出的原则,可以看到在源码中其定义如下:

/**
**Queue接口
**/
public interface Queue extends Collection {
    public abstract boolean add(Object obj); //插入指定元素到容器中,成功返回true,失败报异常。
    public abstract boolean offer(Object obj);  //插入到容器,失败返回false。
    public abstract Object remove();  //获取元素并从容器中移除
    public abstract Object poll();  //获取元素并从容器中移除,为空则返回null
    public abstract Object element();  //获取头元素,但是不移除
    public abstract Object peek();  //获取头元素,但是不移除,为空则返回null
}

Queue接口下,存在子类AbstractQueue,其中对几个方法拥有简单的实现,并没有什么需要特别注意的点。在AbstractQueue下还则存在一个类PriorityQueue,可以看它的实现:

PriorityQueue

在看了之前的那些之后,类PriorityQueue也能简单的分析出来,首先可以查看它的结构拥有一个内部类Itr实现了Iterator接口,就像之前看到的类似的内部类一样,它必定是用在Collection接口的Iterator()方法中。除此之外,可以查看这个类的字段:

/**
** PriorityQueue源码 字段
**/
private static final int DEFAULT_INITIAL_CAPACITY = 11; //初始化长度
private transient Object queue[]; //内部容器
private int size;  //大小
private final Comparator comparator;  //比较器
private transient int modCount;  //修改的次数

到这里的时候,这几个字段相对来说已经很熟悉了,相较于ArrayList类来说,字段方面很类似,PriorityQueue也是使用的数组来实现的。那么查看它的构造器,如下:

/**
**PriorityQueue构造器
**/
public PriorityQueue() {
        this(11, null);
    }
public PriorityQueue(int i){...}
public PriorityQueue(int i, Comparator comparator1) {
        size = 0;
        modCount = 0;
        if (i < 1) {
            throw new IllegalArgumentException();
        } else {
            queue = new Object[i];
            comparator = comparator1;
            return;
        }
}
public PriorityQueue(Collection collection){...}
public PriorityQueue(PriorityQueue priorityqueue){...}
public PriorityQueue(SortedSet sortedset){...}
//构造器内部方法
private void initFromCollection(Collection collection)
    {
        Object aobj[] = collection.toArray();
        if(((Object) (aobj)).getClass() != [Ljava/lang/Object;)
            aobj = Arrays.copyOf(aobj, aobj.length, [Ljava/lang/Object;);
        queue = aobj;
        size = aobj.length;
    }

如上所示,在第一个构造器中可知PriorityQueue内部的数组默认长度为11,它也能接受各种参数Collection参数,对于上面两种类型的参数PriorityQueueSortedSet而言,其中的差别在于使用的比较器不同,两个都是有顺序的Collection使用的也是它自身的Comparator
对于类PriorityQueue内部的实现而言,其实是比较简单的,如下所示offer()方法:

/**
**PriorityQueue源码 offer方法
**/
public boolean offer(Object obj) {
        if (obj == null)
            throw new NullPointerException();
        modCount++;
        int i = size;
        if (i >= queue.length)
            grow(i + 1);
        size = i + 1;
        if (i == 0)
            queue[0] = obj;
        else
            siftUp(i, obj);
        return true;
    }
private void siftUp(int i, Object obj) {
        if (comparator != null)
            siftUpUsingComparator(i, obj);
        else
            siftUpComparable(i, obj);
    }
private void siftUpUsingComparator(int i, Object obj) {
        do {
            if (i <= 0)
                break;
            int j = i - 1 >>> 1;
            Object obj1 = queue[j];
            if (comparator.compare(obj, obj1) >= 0)
                break;
            queue[i] = obj1;
            i = j;
        } while (true);
        queue[i] = obj;
    }

从如上方法就可以看出,它的实现,只是简单的比较,然后赋值,并没有复杂的逻辑。而另一个添加方法add则是调用的offer()方法。

至此,关于Collection这部分就简单的梳理完成,虽然并没有细致的分析到每一个方法或者类,但是做到这些集合类的实现方式都清楚;如果需要重新设计集合类,也可以根据现有的集合类或者类似的实现方式完成逻辑需要。当然,也有人说:不要局限于过去的所知有限的数据结构,设计出优秀好用的集合类就是好的。

Collections和Arrays

在工具类中,Collections负责的是集合操作,包括没有继承Collection接口的Map集合,其中主要有如下几个类型的操作:

  1. 排序
    list列表进行排序操作,如sort(list)sort(list,comparator)
    倒序 reverse(list)
    对List中的元素随机排列 shuffle(list)shuffle(list,random)
    对比较器相反操作使得使用的集合倒序 reverseOrder()reverseOrder(comparator)
  2. 查找
    二分查找 如binarySearch(list,obj)binarySearch(list,obj,comparator)
    集合中的最大最小值min(collection)min(collection,comparator)max(collection)max(collection,comparator)
    返回指定源列表中第一次出现指定目标列表的起始位置,如果没有出现这样的列表,则返回 -1 indexOfSubList(list1, list2)
    返回指定源列表中最后一次出现指定目标列表的起始位置,如果没有出现这样的列表,则返回-1 lastIndexOfSubList()
    返回指定collectionobj的个数 frequency(collection,obj)
    3.移位
    交换列表中指定两个元素的位置 swap(list,i,j)
    循环移动 rotate(list1,i),例:list包含[a,b,c,d,e]。在调用Collection.rotate(list, 1)或者Collection.rotate(list, -4)后, list将为[e, a, b, c, d]
    4.替换
    使用指定元素替换列表中的所有元素 fill(list, obj)
    使用另一个值替换列表中出现的所有某一指定值replaceAll(list1,obj,obj1)
  3. 拷贝
    拷贝列表list2 copy(list,list2)
    拷贝i个对象obj成为一个数组,不可变 nCopies(i,obj)
    6.比较
    判断相等 eq(obj1,obj2)
    两个集合是否有重复元素 disjoint(collection1,collection2)
    7.转换
    Collection集合转Enumeration枚举 enumeration(collection)
    枚举EnumerationList list(enumeration)
    MapSet newSetFromMap(map)
    DeqQueue ``asLifoQueue(deq)
    8.添加
    addAll(Collection,obj[])
    9.只读集合:这些集合一旦初始化以后就不能修改,任何修改这些集合的方法都会抛出UnsupportedOperationException异常
    unmodifiableCollection(collection)
    unmodifiableSet(set)
    unmodifiableSortedSet(sortedset)
    unmodifiableList(list1)
    unmodifiableMap(map)
    unmodifiableSortedMap(sortedmap)
    10.同步集合:为集合加锁,保证数据安全性
    synchronizedCollection(collection)
    synchronizedSet(set)
    synchronizedSortedSet(sortedset)
    synchronizedList(list1)
    synchronizedMap(map)
    synchronizedSortedMap(sortedmap)
    11.检查集合:在插入的同时检查是否是这个类型:
    checkedCollection(collection,class)
    checkedSet(set,class1)
    checkedSortedSet(sortedset,class1)
    checkedList(list1,class1)
    checkedMap(map,class1,class2)
    checkedSortedMap(sortedMap,class1,class2)
    12.无元素的空集合
    emptySet()
    emptyList()
    emptyMap()
    13.单一元素并且只读
    singleton(obj)
    singletonList(obj)
    singletonMap(obj)

相较于Collections来说,Arrays的方法会少很多,主要包括数组的排序sort1()、交换swap()、查找binarySearch()、比较equals()deepEquals()、填充fill()、拷贝copyOf()copyOfRange()hashhashcode()、深度hash算法deepHashCode()toString()方法和deepToString()。其中包括基本数据类型和泛型方法,具体的使用还需要根据实际情况而定。

到此为止,集合已经大体上梳理过了,其中还有些很细节的东西需要注意,比如asList()方法返回的list并不能被操作、保持compareToequals同步等,其中的缘由在源码中都可以找到。 基础系列到这基本上也都写了,如果其中有疑问或不解的地方请留言,我会认真查看并修改解答,准备下一个计划吧~

参考:
java.utl.*

你可能感兴趣的:(Java梳理之理解集合(二))