持有对象

持有对象

  • 其实这章就是告诉我们如何使用容器。除了数组外,Java还提供了一套相当完整的容器类来解决这个问题。其中最基本的类型是ListSetQueueMap。这些对象类型也称为集合类,但由于Java的类库中使用了Collection这个名字来指代该类库的一个特殊子集,所以也被称为容器
  • 下面是关于容器的关系图:
    这里写图片描述

泛型和类型安全的容器

  • 这一小节主要讲了在使用List和Map这些容器的时候,加上泛型能预防一些类型转换的错误。并且get的时候也不必加上强转的代码。总之好处多多,因为我平常就是这么做的,我也就不记录了。

基本概念

  • Java容器类类库的用途是保存对象,并将其化为两个不同的概念:
    1. Collection一个独立元素的序列,这些元素都服从一条或多条规则List必须按照插入的顺序保存元素,而Set不能有重复元素。Queue按照排队规则来确定对象产生的顺序。
    2. Map。一组成对的键值对对象,允许你使用键来查找值。映射表允许我们使用另一个对象来查找某个对象,它也被称为关联数组,或被称为字典,是一个强大的编程工具。

添加一组元素

  • java.util包中的ArraysCollections类中有很多实用方法。下面会举例说明Arrays.asList()、Collections.addAll()Collection.addAll()的用法。
import java.util.*;

public class Test {
    public static void main(String args[]) {
        Integer[] i = new Integer[] {1, 2, 3, 4, 5};
        Collection c = new ArrayList<>();//向上转型
        Collection c1 = new ArrayList<>();
        c.addAll(c1);//这种是直接将另外一个Collection加入到旧Collection中。
        c.addAll(Arrays.asList(i));//这种是直接将另外一个Collection加入到旧Collection中。
        c.addAll(Arrays.asList(1, 2, 3, 4, 5));//这种是直接将另外一个Collection加入到旧Collection中。

        Collections.addAll(c, i);
        //Collections.addAll(c, Arrays.asList(i));//这种方式是添加一个数组进去,不要混淆
        Collections.addAll(c, 1, 2, 3, 4, 5);

        //要注意通过Arrays.asList(i)转换而来的List由于底层是个固定大小的数组是不能修改大小的。
        //看过源码可得知,此处的返回类型其实是Arrays中的一个嵌套类,它的数组使用了final修饰词
        List<Integer> ints = Arrays.asList(i);
        ints.set(0, 50);//可以修改 [50, 2, 3, 4, 5]
        System.out.println(ints);//由于toString已经被重写了,可以直接打印。
        //ints.add(60);//不能更改大小,add或remove
        //java.lang.UnsupportedOperationException

        //此外还可以通过构造方法创建一个浅拷贝List
        ArrayList c2 = new ArrayList<StringBuffer>();
        c2.add(new StringBuffer("hello"));
        List<StringBuffer> c3 = new ArrayList<>(c2);//浅拷贝
        System.out.println(c3);
        c3.get(0).append(" world");
        System.out.println(c2);//hello world
    }
}
  • 下面是关于asList()产生的类型的一些要注意的地方:
import java.util.*;

class O {}
class A extends O {}
class B extends O {}
class A1 extends A {}
class A2 extends A {}
class B1 extends B {}
class B2 extends B {}

public class Test {
    public static void main(String args[]) {
        List<O> ol0 = Arrays.asList(new A(), new B());
        List<O> ol1 = Arrays.asList(new A1(), new B1());
        List<A> ol2 = Arrays.asList(new A1(), new A2());
        //Type mismatch: cannot convert from List<A> to List<O>
        //List<O> ol3 = Arrays.asList(new A1(), new A2());
        List<O> ol4 = new ArrayList<O>();
        //ol4.addAll(new A1(), new A2());//观察源码可知此处是extends T
        Collections.addAll(ol4, new A1(), new A2());//观察源码可知此处是super T
        List<O> ol5 = Arrays.<O>asList(new A1(), new A2());//显式类型参数说明
    }
}


List

  • List接口在Collection的基础上添加了大量的方法,使得可以在List的中间插入和移除元素。有三种常用类型的List:
    1. ArrayList,底层是用数组存放元素,所以随机访问元素比较快,但是由于插入和移除元素时需要考虑数组大小和扩容的问题,所以在插入和移除元素时速度较慢。初始大小为10,扩容方式为扩展到1.5倍,还不够就直接扩容到最大下标值。
    2. LinkedList,底层是用一个内部类(Node)的对象包含元素实体前后元素引用的形式实现的。所以相对于ArrayList,它的插入和移除元素速度较快,但是随机访问元素速度较慢(只能通过第一个元素循环获得下一个元素的位置)。
    3. Vector,底层也是用数组存放元素,和ArrayList大同小异( Vector的方法都是同步的(Synchronized),是线程安全的(thread-safe)),接下来不会进行过多分析。大家可以看看这篇文章。初始大小为10,默认扩容为自身2倍,也有传入每次扩容大小的构造器。不过现在已经过时了,不再推荐使用这个类。
  • 下面我总结了一下List中的方法,记录是为了过一遍API,真的要使用可以去查看API(E指泛型):
    1. boolean add(E e);void add(int index, E e);前者调用了后者在末尾插入元素,后者是在指定位置插入元素,使该元素的下标为index。如果该元素后面还有元素,则要依次下标+1(其实是通过System.arraycopy()生成新数组实现)。这里如果是ArrayList也可以看出效率不高。
    2. boolean addAll(Collection< ? extends E> c);boolean addAll(int index, Collection< ? extends E> c); 和上面类似,c的顺序和迭代器顺序相同。比如插入的是List那就相当于保持原样。
    3. void clear(),移除所有元素。
    4. boolean contains(Object o);boolean containsAll(Collection< ?> c) ArrayListLinkedList都是通过indexOf()方法判断是否包含对象,而indexOf()方法则是通过对象的equals()方法判断两个对象是否相等。这也意味着一般情况下两个属性完全相同的对象也不会返回false(除非你重写equals()),但是两个实体相同的String对象则会返回true。
    5. boolean equals(Object o) 这个方法是继承自AbstractList,如果o不是List的实现类型返回false,否则对每个元素按下标进行equals()比较,如果是两个null也被认为是符合的。
    6. E get(int index)这个方法在ArrayListLinkedList中的实现有所不同。在LinkedList中会先检查下标是否越界,然后判断下标的位置。如果在前半部分,则通过firstNode往后查询,否则通过lastNode往前查询。而在ArrayList就没有那么麻烦了,除了同样需要检查是否越界以外,ArrayList只需通过下标返回数组中相应的元素就行了。
    7. int hashCode() 这个方法是继承自AbstractList的,简单来说就是对每个元素的hashCode()进行公式累加(有兴趣可以看看源码),因为其为int类型,所以可能出现越界返回负值。
    8. int indexOf(Object o);int lastIndexOf(Object o); 这个方法比较的方式是调用对象的equals()方法。所以如果是普通的对象则是用==去比较。但是类似String类型,就可能重写这个方法。另外还可以传入null,将会返回从左往右第一个为null的下标。后者则是从右到左
    9. boolean isEmpty() 如果List为空返回true。
    10. Iterator< E> iterator() 返回一个迭代器,下一小节细讲。
    11. ListIterator< E> listIterator(); ListIterator< E> listIterator(int index);返回一个列表迭代器,下一小节细讲。
    12. E remove(int index);boolean remove(Object o);这个不多说了,前者返回类型是E,后者如果没有该元素返回false。这两个方法在ArrayListLinkedList中的实现有所不同。前者需要重新生成一个数组,后者只需要修改一下引用就可以了。
    13. boolean removeAll(Collection< ?> c) ;boolean retainAll(Collection< ?> c) 前者是移除c中的元素,后者是保留c中的元素。有意思的是在源码中,ArrayList对这两个方法其实使用了同一个私有方法实现,只是传入的boolean值有所不同。算法大致是这样的。拿remove举例,先对原来的Collection进行遍历,如果c不包含该元素,则将该元素设置为原Collection的第0个下标,依次类推。如果中间出现异常的话(我也不知道会出现什么异常),则认为还未遍历的元素都符合并通过System.arraycopy加入原Collection。最后再将后面都置null(见下面代码1)。而LinkedList则是则是直接继承AbstractCollection中的方法,直接利用Iterator的remove()实现。
    14. E set(int index, E element) ;用指定元素替换列表中指定位置的元素,具体实现的话ArrayList就是直接换,LinkedList需要先定位,然后将内部类的item置换成element就行了。
    15. int size() 返回列表中的元素数。
    16. List< E> subList(int fromIndex, int toIndex) 返回列表中指定的 fromIndex(包括 )和 toIndex(不包括)之间的部分视图。
    17. Object[] toArray(); < T> T[] toArray(T[] a) 前者没有什么好说的,后者的话用法见代码2
//代码1 remove complement为false retainAll为true
private boolean batchRemove(Collection<?> c, boolean complement) {
        final Object[] elementData = this.elementData;
        int r = 0, w = 0;
        boolean modified = false;
        try {
            for (; r < size; r++)
                if (c.contains(elementData[r]) == complement)
                    elementData[w++] = elementData[r];
        } finally {
            // Preserve behavioral compatibility with AbstractCollection,
            // even if c.contains() throws.
            if (r != size) {
                System.arraycopy(elementData, r,
                                 elementData, w,
                                 size - r);
                w += size - r;
            }
            if (w != size) {
                // clear to let GC do its work
                for (int i = w; i < size; i++)
                    elementData[i] = null;
                modCount += size - w;//这个值有其他用处 此处可以不用关注
                size = w;
                modified = true;
            }
        }
        return modified;
    }
 
//代码2 ArrayList中 toArray(T[] t)的实现 LinkedList大同小异省略
public <T> T[] toArray(T[] paramArrayOfT)
  {
    if (paramArrayOfT.length < this.size) {
      return (Object[])Arrays.copyOf(this.elementData, this.size, paramArrayOfT.getClass());
    }
    System.arraycopy(this.elementData, 0, paramArrayOfT, 0, this.size);
    if (paramArrayOfT.length > this.size) {
      paramArrayOfT[this.size] = null;
    }
    return paramArrayOfT;
  }
-----------------------------以下是测试代码:
import java.util.*;

public class Test {
    public static void main(String args[]) {
        String s1 = new String("123");
        ArrayList<String> l = new ArrayList<String>();
        l.add(s1);
        l.add(s1);
        l.add(s1);
        String[] i = new String[0];
        String[] copyi = l.toArray(i);
        String[] copyi1 = l.toArray(new String[0]);
        //String[] copyi2 = (String[]) l.toArray();//ClassCastException
        Object[] copyi3 = l.toArray();
        System.out.println(Arrays.toString(copyi));
        System.out.println(Arrays.toString(copyi1));
        System.out.println(Arrays.toString(copyi3));
        System.out.println(Arrays.toString(i));
    }
}
--------------------运行结果
[123, 123, 123]
[123, 123, 123]
[123, 123, 123]
[]//这里也能反映Java调用方法是传值而不是传递引用。
 
 
  • 1

List接口中的方法已经简单说明了。当然ArrayListLinkedList中还有很多额外的方法,此处就不展开了,平常开发中使用List的方法差不多就够了。不过还是需要知道他们实现的区别。有兴趣可以去看源码。

迭代器

  • 迭代器是一种设计模式。迭代器是一个对象,它的工作是遍历并选择序列中的对象,而客户端程序员不必知道或关心该序列底层的结构。此外,迭代器通常被称为轻量级对象:创建它的代价小。所以限制也比较多(因为还是借用其底层结构实现了相应的接口方法),例如Java的Iterator只能单向移动。CollectionMap接口都是继承自Iterable接口。这个接口有一个 Iterator< T> iterator();方法。所以我们可以对CollectionMap的实例通过iterator()方法获得一个迭代器对象。
  • Iterator接口一共有三个方法:
    1. boolean hasNext();
    2. E next();
    3. void remove();
  • 不同类型的实现方式有所不同,这里主要讲一下ArrayList的实现方式,来看一下源代码:
public Iterator<E> iterator() {
    return new Itr();
}

/**
 * An optimized version of AbstractList.Itr
 */
private class Itr implements Iterator<E> {
    int cursor;       // index of next element to return
    int lastRet = -1; // index of last element returned; -1 if no such
    int expectedModCount = modCount;

    public boolean hasNext() {
        return cursor != size;
    }

    @SuppressWarnings("unchecked")
    public E next() {
        checkForComodification();
        int i = cursor;
        if (i >= size)
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        cursor = i + 1;
        return (E) elementData[lastRet = i];
    }

    public void remove() {
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();

        try {
            ArrayList.this.remove(lastRet);
            cursor = lastRet;
            lastRet = -1;
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }

    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}
 
 


  • ArrayList中是以一个私有内部类的形式,返回一个实现了Iterator接口的对象。构造方式非常简单,几乎不需要什么代价。在这个内部类中一共有三个属性和三个方法。前面两个看注释也就懂了,expectedModCount 这个属性主要是用来判断在迭代过程中,原集合是否大小进行了修改。这个我没有仔细去解读过,modCountAbstractList的一个属性:protected transient int modCount = 0;用法大致就是在add和remove的时候自增。三个方法的作用我觉得一目了然,就不再多说了。
  • ListIteratorIterator的一个子类型。前面我们已经提到List接口中有两个方法是返回这个类型的。相对于Iterator,由于它是针对List对象,所以它的方法也更加丰富。由于其允许双向移动,所以ListIterator< E> listIterator(int index) 允许我们传入一个下标创建一个ListIterator对象。我们来看看ArrayList中的源代码:
public ListIterator<E> listIterator() {
   return new ListItr(0);
}
public ListIterator<E> listIterator(int index) {
    if (index < 0 || index > size)
        throw new IndexOutOfBoundsException("Index: "+index);
    return new ListItr(index);
}
/**
 * An optimized version of AbstractList.ListItr
 */
private class ListItr extends Itr implements ListIterator<E> {
    ListItr(int index) {
        super();
        cursor = index;
    }

    public boolean hasPrevious() {
        return cursor != 0;
    }

    public int nextIndex() {
        return cursor;
    }

    public int previousIndex() {
        return cursor - 1;
    }

    @SuppressWarnings("unchecked")
    public E previous() {
        checkForComodification();
        int i = cursor - 1;
        if (i < 0)
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        cursor = i;
        return (E) elementData[lastRet = i];
    }

    public void set(E e) {
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();
        try {
            ArrayList.this.set(lastRet, e);
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }

    public void add(E e) {
        checkForComodification();
        try {
            int i = cursor;
            ArrayList.this.add(i, e);
            cursor = i + 1;
            lastRet = -1;
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }
}


  • 这代码多看几遍就明白了,这里也不想过多解释。新增方法有add(E e),set(E e),previous(),previousIndex(),nextIndex()(感觉是为了对称才出的),hasPrevious()

LinkedList

  • LinkedList除了实现List接口外还实现了Deque接口,这个接口继承了Queue接口,所以LinkedList还实现了可以使其用作栈、队列或双端队列的方法。这些方法中有些彼此之间只是名称有些差异,或者只存在些许差异,以使得这些名字在特定用法的上下文环境中更加适用。例如getFirst()element()完全一样,都是返回列表中首元素,如果列表为空,则抛出NoSuchElementException。而peek()方法也是返回首元素,只不过在列表为空的情况下会返回null
  • removeFirst()remove()也是一样的,都是移除首个元素并返回,不存在抛出NoSuchElementExceptionpoll()稍有差异,在列表为空的时候返回nulladdLast()add()都是在尾部添加元素,而addFirst()是在首部添加元素。removeLast()是移除并返回最后一个元素。

Stack

  • 通常指后进先出的容器。LinkedList具有能够直接实现栈的所有功能的方法,因此LinkedList可以直接作为栈来使用。下面是组合LinkedList创建的一个Stack类。
class MyStack<T> {
    private LinkedList<T> storage = new LinkedList<T>();
    public void push(T e) {storage.addFirst(e);}
    public T peek() {return storage.getFirst();}
    public T pop() {return storage.removeFirst();}
    public boolean empty() {return storage.isEmpty();}
    public String toString() {return storage.toString();}
}
 
 
  • 1


  • Stack没有被设计成接口,可能是因为Java1.0中的设计欠佳的java.util.Stack类占用了这个名字。尽管已经有了java.util.Stack,但是LinkedList可以产生更好的Stack(但是LinkedList不是线程安全的)。java.util.Stack设计欠佳是因为底层使用的是数组而不是链表,而Stack需要频繁进栈出栈,所以使用链表更加合理。
import java.util.*;

public class Test {
    public static void main(String[] args) {
        List<Integer> data1 = new ArrayList<>();
        for (int i=0; i<10; i++) {
            data1.add(i);
        }
        List<Integer> data2 = new ArrayList<>();
        for (int i=0; i>-10; i--) {
            data2.add(i);
        }
        //使用SubStack始终不会报错,因为在用toString()打印容器的时候,该方法是加锁了的(继承自Vector)
        //StackInterface<Integer> s1 = new SubStack<Integer>();
        //使用MyStack则偶尔会报错,因为toString()过程中,可以对stack进行修改,导致modCount变化或者其他错误。
        StackInterface<Integer> s1 = new MyStack<Integer>();
        PushThread<Integer> pt1 = new PushThread(s1, data1);
        PushThread<Integer> pt2 = new PushThread(s1, data2);

        pt1.start();
        pt2.start();
    }
}
//为了代码共用 我创建了一个接口
interface StackInterface<T> {
    public T push(T e);
    public T peek();
    public T pop();
    public boolean empty();
    public String toString();
}
//继承Stack的线程安全的栈
class SubStack<T> extends Stack<T> implements StackInterface<T> {}
//组合LinkedList的线程不安全的栈
class MyStack<T> implements StackInterface<T> {
    private LinkedList<T> storage = new LinkedList<T>();
    public T push(T e) {storage.addFirst(e);return e;}
    public T peek() {return storage.getFirst();}
    public T pop() {return storage.removeFirst();}
    public boolean empty() {return storage.isEmpty();}
    public String toString() {return storage.toString();}
}

class PushThread<T> extends Thread {
    StackInterface<T> si;
    List<T> pushList;
    public PushThread(StackInterface<T> si, List<T> pushList) {
        this.si = si;
        this.pushList = pushList;
    }
    @Override
    public void run() {
        for (int i = 0; i < pushList.size(); i++) {
            T t = si.push(pushList.get(i));
            System.out.println("目前: " + si);
        }
    }
}
class PopThread<T> extends Thread {
    StackInterface<T> si;
    int popSize;
    public PopThread(StackInterface<T> si, int popSize) {
        this.si = si;
        this.popSize = popSize;
    }
    @Override
    public void run() {
        for (int i = 0; i < popSize; i++) {
            T t = si.pop();
            System.out.println("目前: " + si + " pop:" + t);
        }
    }
}
 
 


Set

  • 这里要开始讲另外一个与List接口平起平坐的Set接口。不保存重复的元素是Set的最大特点。Set中最被常使用的是测试归属性,你可以很容易地询问某个对象是否在某个Set中。所以查找就成为了Set中最重要的操作,因此通常会选择HashSet作为实现,它专门对快速查找做了优化。
  • 其实看了源码你会发现,Set接口的定义居然和Collection一模一样,虽然List接口也是继承Collection的,但是List至少对Collection有一些扩展,这个Set同学就很过分了,完全就是照抄,那为什么需要定义一个Set接口呢?其实这里只不过是体现了继承和多态的思想。多定义一个Set接口,以此表名该接口具有的特殊行为。下面先简单介绍一下Set接口。一共有三个类实现了该接口:
    1. HashSet,使用了散列(后面章节介绍),保存顺序与插入顺序无关,查询速度优秀。它借助了HashMap实现了数据的存储。
    2. TreeSet,将元素存储于红-黑树结构中,保存顺序与插入顺序无关,但是它会按照一定规则自动对元素进行排序(构造方法能够传入比较器,关于比较器后期会进行说明)。它借助了TreeMap实现了数据的存储。
    3. LinkedHashSet,也使用了散列,保存的顺序与插入顺序相同,同时查询速度也不赖。它借助了LinkedHashMap实现了数据的存储。
  • 这位Set同学不仅照抄Collection,连下面的实现都是借助了Map,真的是上梁不正下梁歪。等说到Map了再仔细说说HashTree是如何实现的吧。下面是一个简单的应用Set的例子。
public class Test {
    public static void main(String[] args) {
        Set<Integer> s1 = new HashSet<Integer>();
        Set<Integer> s2 = new TreeSet<Integer>();
        Set<Integer> s3 = new LinkedHashSet<Integer>();
        test(s1);
        test(s2);
        test(s3);
    }
    public static void test(Set s) {
        s.add(5);
        s.add(4);
        s.add(1);
        s.add(1);//能够去重
        s.add(2);
        s.add(3);
        for (int i=6; i<30; i++) {
            s.add(i);//元素太少,无法反应HashSet的无序性
        }
        System.out.println(s);
    }
}
-----------------执行结果:
HashSet:后面元素位置不会保持顺序
TreeSet:按照从小到大排序
LinkedHashSet:与插入顺序相同
 
 
  • 1



Map

  • 其他就不废话,接下来主要说说HashMapLinkedHashMapTreeMap是如何实现的。关于Map此处不再展开讨论方法的使用方式,只是简单说一下原理。

HashMap

  • HashMap中有一个default属性Entry< K, V>[] table以此存放元素,初始默认大小为16,阈值为16*0.75=12。当元素大于阈值时会进行扩容,每次扩大2倍。扩容的同时也会对元素进行重排列。这个Entry是一个内部类,4个属性是K key,V value,int hash,Entry next.这个hash值就是通过散列函数计算得来的,而next就是一种链表的应用。
  • 这个table属性的长度就是HashMap的最大长度,接下来说明为什么HashMap的优势是查找速度。当你根据一个key要插入一个value时,它会根据keyhash值(int型)映射到这个table对应的下标。然后生成对应的Entry放入table中,如果多个key对应了同个下标,那么table只会保存最后一个Entry,而之前的Entry则以next属性的形式去维持。这样当我们根据一个key值要去获得value时,就不必遍历整个Map了,我们可以根据hash值得知相应的table下标,再用next去遍历这个链表。

LinkedHashMap

  • 这个类继承于HashMap,它有一个新的属性Entry header;这里的Entry同样也继承了HashMap中的Entry,扩展了Entry beforeEntry afterheader的功能也就是维持一个插入顺序的链表。当每次插入新的元素时,LinkedHashMap就会使该元素成为header元素,然后使之前的header元素成为新headerafter值。所以就是在HashMap的基础上再多维持一个链表而已。而生成迭代器时,迭代器的方法会借助这个属性。

TreeMap

  • TreeMap的实现借助了红黑树,具体它是如何维持整棵树的顺序,以及addremove后树的调整这里不再展开。简单来说,TreeMap中维护了一个root属性,该属性是该树的根节点。当我们要查找一个key时,会根据key的大小不断遍历节点的左子树或右子树(小左右大)。它的查找最多次数即是树的层数,可见其查询速度也是优秀的(TreeSet的查找速度比LinkedList快,比HashSet慢)。它的最大的特点是有序。我们可以通过后序遍历获得一个按照key排序的EntrySet

Queue

  • 队列是一个先进先出的容器。LinkedList实现了Queue接口,提供了支持队列行为的方法。这部分就不重新复述了。接下来讲一下Queue的一个实现类。

PriorityQueue

  • 优先级队列声明下一个弹出元素是最需要的元素(具有最高的优先级)。当你在PriorityQueue上调用offer()方法来插入一个对象时,这个对象会在队列中被排序,由此确保当你调用peek(),poll()或remove()方法时,获得的元素将是队列中优先级最高的元素。
  • 当我读完定义时,我就想到是否可以借助TreeSet的有序性实现PriorityQueue
import java.util.Comparator;
import java.util.Queue;
import java.util.TreeSet;
class TestObj {
    public int pri;
    private String key;
    public TestObj(int pri, String key) {
        this.pri = pri;
        this.key = key;
    }
    public String toString() {
        return key;
    }
}
public class MyPriQueue<E> extends TreeSet<E> implements Queue<E> {
    public static void main(String args[]) {
        Queue<TestObj> queue = new MyPriQueue<TestObj>(
                new Comparator<TestObj>() {
                    @Override
                    public int compare(TestObj o1, TestObj o2) {
                        return o1.pri - o2.pri;
                    }
                }
            );
        //缺点就是不能放两个相同的key
        queue.offer(new TestObj(5, "5"));
        queue.offer(new TestObj(4, "4"));
        queue.offer(new TestObj(3, "3"));
        System.out.println(queue.poll());
        queue.offer(new TestObj(6, "6"));
        System.out.println(queue.poll());
    }
    public MyPriQueue() {
    }
    public MyPriQueue(Comparator<? super E> c) {
        super(c);
    }

    public boolean offer(E e) {
        return add(e);
    }

    public E poll() {
        return pollFirst();
    }

    public E element() {
        return first();
    }

    public E peek() {
        return first();
    }

    public E remove() {
        return remove();
    }

}
 


  • JDK已经实现了PriorityQueue,他存放元素使用的是Object[],并且还有一个 Comparator< ? super E> comparator。因为像IntegerString这些都实现了Comparator接口,比较方式就是其默认的比较方式,PriorityQueue的行为除了在add的时候通过比较器进行比较获得插入的位置以外,其余的和ArrayList类似。不过它获取插入的位置的算法特别有意思,并不是简单的维持整个数组有序,它只保持了局部有序,这种方式优化了出队入队的速度。
//paramInt 为当前size paramE为要插入的元素
private void siftUpUsingComparator(int paramInt, E paramE)
  {
    while (paramInt > 0)
    {
      int i = paramInt - 1 >>> 1;
      Object localObject = this.queue[i];
      if (this.comparator.compare(paramE, localObject) >= 0) {
        break;
      }
      this.queue[paramInt] = localObject;
      paramInt = i;
    }
    this.queue[paramInt] = paramE;
  }
 
 


  • 通过这个方法可以看出,当要插入一个元素时,它会将其与当前数组的(-1 /2)位置进行比较,如此并维护了局部方向上的有序性。而当我们将元素出队列时,会调用下列方法:
//paramInt 为0 paramE为queue[size-1]即最后一个元素
private void siftDownUsingComparator(int paramInt, E paramE)
  {
    int i = this.size >>> 1;
    while (paramInt < i)
    {
      int j = (paramInt << 1) + 1;
      Object localObject = this.queue[j];
      int k = j + 1;
      if ((k < this.size) && (this.comparator.compare(localObject, this.queue[k]) > 0)) {
        localObject = this.queue[(j = k)];
      }
      if (this.comparator.compare(paramE, localObject) <= 0) {
        break;
      }
      this.queue[paramInt] = localObject;
      paramInt = j;
    }
    this.queue[paramInt] = paramE;
  }


  • 将元素从0到(*2+1和 *2+2)中的更小的值调换,首先0位置已经是空的了。 根据之前的插入规则,我们可以得出 0 *2+1和 0 *2+2 将是剩余元素中的最小的2个值。设置好0的位置以后,我们还需要将后面的位置也设置好。这个算法有兴趣可以研究一下。

Foreach和迭代器

  • 我们知道数组和实现Collection的接口类都是直接能够使用foreach语法的,最重要的原因是Collection实现了Iterable接口。以下是我做的一个测试类:
import java.util.*;

public class Test {

    public static void main(String args[]) {
        Test2<Integer> t = new Test2<Integer>(10);
        t.add(1);
        t.add(2);
        t.add(4);
        t.add(8);
        for (Integer i:t) {
            System.out.println(i);
        }
    }
}
class Test2<T> implements Iterable<T> {
    private final Object[] t;
    private final int length;
    private int size;
    public Test2(int size) {
        length = size;
        t = new Object[size];
    }
    public void add(T obj) {
        t[size++] = obj;
    }
    public Iterator<T> iterator() {
        return new Iterator<T>() {
            private int index = 0;
            public boolean hasNext() {
                return index < length && t[index] != null;
            }
            public T next() {
                    return (T) t[index++];
            }
            public void remove() {
                //暂时不实现这个方法
            }
        };
    }
}


小结

  • 其实只有四种容器:MapListSetQueue,他们各有两到三个实现版本。看到此处希望你可以再次回味一下这四个接口的用法。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值