Nivelle 开拓视野冲破艰险看见世界 身临其境贴近彼此感受生活

java基础(十五)之容器

2017-06-10
nivelle

容器

Full Container Taxonomy

image

填充容器

class StringAddress {
    private String s;

    public StringAddress(String s) {
        this.s = s;
    }

    public String toString() {
        return super.toString() + " " + s;
    }
}

public class FillingLists {
    public static void main(String args[]) {
        List<StringAddress> list = new ArrayList<StringAddress>(Collections.nCopies(4, new StringAddress("hello")));
        System.out.println(list);
        Collections.fill(list, new StringAddress("world!"));
        System.out.println(list);
    }
}


一种Generator解决方案

事实上,所有的Collection子类型都有一个接收另一个Collection对象的构造器,用所接收的Collection对象中的元素来填充新的容器.

public class CollectionData<T> extends ArrayList<T> {
    public CollectionData(Generator<T> gen, int quantity) {
        for (int i = 0; i < quantity; i++) {
            add(gen.next());
        }
    }

    public static <T> CollectionData<T> list(Generator<T> gen, int quantity) {
        return new CollectionData<T>(gen, quantity);
    }
}

这个类使用Generator在容器中放置所需数量的对象,然后所产生的容器可以传递给任何Collection的构造器,这个构造器会把其中的的数据复制到自身中.

Map生成器

我们可以对Map使用相同的方式,但是这需要有一个Pair类,因为为了组装Map,每次调用Generator的next()方法都必须产生一个对象对:

public class Parir<K,V>{
  public final K key;
  public final V value;

  public Pair(K k,V v){
     key = k;
     value =v;
  }
}


Collection的功能方法

下表列出了可以通过Collection执行的所有操作(不包括从Obeject继承而来的方法).因此,它们也是可通过set或list执行的所有操作.Map不是继承自Collection的.

方法 说明
boolean add(T) 确保容器持有具有泛型类型T的参数.如果没有将此参数添加进容器,则返回false.
boolean addAll(Collection<? extends T>) 添加参数中的所有元素.添加了任意元素就返回true.
void clear() 移除容器中的所有元素.
boolean contains(T) 如果容器中已经持有具有泛型类型T此参数,则返回true
boolean containsAll(Collection<?>) 如果此容器持有此参数中的所有元素,则返回true.
boolean isEmpty() 容器中没有元素时返回true.
Iterator<> iterator() 返回一个Iterator<> ,可以用来遍历容器中的元素
boolean remove(Object) 如果参数在容器中,则移除此元素的一个实例.如果做了移除动作,则返回true
boolean removeAll(Collection<?>) 移除参数中的所有元素.只要有移除动作发生就返回true.
boolean retainAll(Collection<?>) 只保存参数中的元素(应用集合论的交集概念).只要Collection发生了改变就返回true
int size() 返回容器中元素的数目
Object[] toArray() 返回一个数组,该数组包含容器中的所有元素
<> T[] toArray(T[]a) 返回一个数组,该数组包含容器中的所有元素.返回结果的运行时类型与参数数组a的类型相同,而不是单纯的Obejct/

其中不包括随机访问所选择元素的get()方法.因为Collection包括Set,而Set是自己维护内部顺序的.因此,如果想检查Collection中的元素,那就必须使用迭代器.

Set和存储顺序

方法 说明
Set(interface) 存入Set的每个元素都必须是唯一的,因为Set不保存重复元素.加入Set的元素必须定义equals()方法以确保对象的唯一性.Set与Collection有完全一样的接口.Set接口不保证维护元素的次序.
HashSet* 为快速查找设计的Set.存入HashSet的元素必须定义hashCode()
TreeSet 保持次序的Set,底层为树结构.使用它可以从Set中提取有序的序列.元素必须实现Comparable接口.
LinkedHashSet 具有HashSet的查询速度,且内部使用链表维护元素的顺序.于是在使用迭代器遍历Set时,结果会按元素插入的次序显示.元素也必须定义hashCode()方法.

你必须为散列存储和树型存储都创建一个equals()方法,但是hashCode()只有在这个类将会被置于HashSet或者LinkedHashSet中时才是必需的.但是,对于良好的编程风格而言,你应该在覆盖equals()方法时,总是同时覆盖hashCode()方法.

public class SortedSetDemo {
    public static void main(String args[]) {
        SortedSet<DataCom> sortedSet = new TreeSet<DataCom>(new DataCompare());
        for(int i=0;i<=10;i++){
            sortedSet.add(new DataCom(i));
        }
        System.out.println(sortedSet);
        DataCom low = sortedSet.first();
        DataCom high = sortedSet.last();
        System.out.println(low);
        System.out.println(high);

        Comparator comparator1 = sortedSet.comparator();
        System.out.println(comparator1);
        Iterator<DataCom> it = sortedSet.iterator();
        for (int i = 0; i <= 6; i++) {
            if (i == 3) low = it.next();
            if (i == 6) high = it.next();
            else it.next();
        }
        System.out.println(low);
        System.out.println(high);

        System.out.println(sortedSet.subSet(low, high));
        System.out.println(sortedSet.headSet(low));
        System.out.println(sortedSet.tailSet(low));
    }
}


public class DataCom  {
    public int i;

    public DataCom(int i) {
        this.i = i;
    }

    public String toString(){
        return  "i="+i;
    }

}


结论:

[i=0, i=1, i=2, i=3, i=4, i=5, i=6, i=7, i=8, i=9, i=10]

i=0

i=10

自定义比较器

i=3

i=7

[i=3, i=4, i=5, i=6]

[i=0, i=1, i=2]

[i=3, i=4, i=5, i=6, i=7, i=8, i=9, i=10]

队列

JavaSE5仅有的两个 实现是LinkedList和PriorityQueue.它们的差异在于排序行为而不是性能.


public class QueueBehavior {
    private static int count = 10;

    static <T> void test(Queue<T> queue, Generator<T> gen) {
        for (int i = 0; i < count; i++) {
            queue.offer(gen.next());
        }
        while (queue.peek() != null) {
            System.out.print(queue.remove() + ",");
        }
        System.out.println();
    }

    static class Gen implements Generator<String> {
        String[] s = ("one two three four five six seven eight nine ten").split(" ");
        int i;

        public String next() {
            return s[i++];
        }
    }

    public static void main(String args[]) {
        test(new LinkedList<String>(), new Gen());
        test(new PriorityQueue<String>(), new Gen());
        test(new ArrayBlockingQueue<String>(count), new Gen());
        test(new ConcurrentLinkedDeque<String>(), new Gen());
        test(new LinkedBlockingDeque<String>(), new Gen());
        test(new PriorityBlockingQueue<String>(), new Gen());
    }
}


结论:

one,two,three,four,five,six,seven,eight,nine,ten

eight,five,four,nine,one,seven,six,ten,three,two

one,two,three,four,five,six,seven,eight,nine,ten

one,two,three,four,five,six,seven,eight,nine,ten

one,two,three,four,five,six,seven,eight,nine,ten

eight,five,four,nine,one,seven,six,ten,three,two


理解Map

标准java类库中包含了Map的几种基本实现,包括HashMap,TreeMap,LinkedHashMap,WeakHashMap,ConcurrentHashMap,IdentityHashMap.它们都有同样的基本接口Map.

性能是映射表中的一个重要问题,当在get()中使用线性搜索时,执行速度会相当慢,而这正是HashMap提高速度的地方.HashMap使用了特殊的值,称作散列码,来取代对键的缓慢搜索.

散列码是”相对唯一”的,用以代表对象的int值,它是通过将该对象的某些信息进行转换而生成的.HashCode()是跟类Object中的方法,因此所有Java对象都能产生散列码.HashMap就是使用hashCode()进行快速查询的,此方法能显著提高性能.

方法 说明
HashMap Map基于散列表的实现.插入和查询”键值对”的开销是固定的.可以通过构造器设置容量和负载因子,以调整容器的性能
LinkedHashMap 类似于HashMap,但是迭代遍历它时,取得”键值对”的顺序是插入次序,或者是最近最少使用的次序.只比HashMap慢一点,在迭代访问时反而更快,因为它使用链表维护内部次序.
TreeMap 基于红黑树的实现.查看键或者键值对时,它们会被排序(次序由Comparable或Compatator决定).TreeMap的特点在于,所得到的结果是经过排序的.TreeMap是唯一带有subMao()方法的Map,可以返回一个子树.
weakHashMap 弱键映射,允许释放映射所指向的对象,这是为解决某类特殊问题而设计的.如果映射之外没有指向某个键,则此件可以被垃圾收集器回收.
ConcurrentHashMap 一种线程安全的Map,它不涉及同步加锁.
IdentityHashMap 使用==代替equals()对键进行比较的散列映射.

对Map中使用额键的要求与对Set中的元素要求一样,任何键都必须具有一个equals()方法,如果键被用于散列Map,那么必须换具有恰当的HashCode()方法.如果被用于TreeMap,那么它必须实现Comparable.

理解HashCode()

package test;

import java.util.*;
/**
 * Created by nivelle on 2017/8/17.
 */
public class SlowMap<K,V> extends AbstractMap<K,V> {
    private List<K> keys=new ArrayList<K>();
    private List<V> values = new ArrayList<V>();
    public V put(K key,V value){
        V oldValue = get(key);
        if(!keys.contains(key)){
            keys.add(key);
            values.add(value);
        }else{
            values.set(keys.indexOf(key),value);
        }
        return oldValue;
    }
    public V get(Object key){
        if(!keys.contains(key)){
            return  null;
        }
        return  values.get(keys.indexOf(key));
    }
    public Set<Map.Entry<K,V>> entrySet(){
       Set<Map.Entry<K,V>> set = new HashSet<Map.Entry<K,V>>();
        Iterator<K>ki=keys.iterator();
        Iterator<V>vi=values.iterator();
        while(ki.hasNext()){
            set.add(new MapEntry<K,V>(ki.next(),vi.next()));
        }

        return  set;
    }
    public static void main(String args[]){
        SlowMap<String,String> m= new SlowMap<>();
        m.put("1","one");
        m.put("2","two");
        m.put("3","three");
        m.put("4","four");
        System.out.println(m.entrySet());
        System.out.println(m.keySet());
        System.out.println(m.values());
        System.out.println(m);
        Iterator it = m.entrySet().iterator();
        while(it.hasNext()){
            System.out.println(it.next());
        }
        for(Object o :m.entrySet()){
            System.out.println(o);
        }
    }

}


package test;

import java.util.Map;

/**
 * Created by nivelle on 2017/8/18.
 */
public class MapEntry<K,V> implements Map.Entry<K,V> {
    private K key;
    private V value;
    public MapEntry(K key,V value){
        this.key = key;
        this.value=value;
    }
    public K getKey(){return  key;}
    public V getValue(){return  value;}
    public V setValue(V v){
        V result = value;
        value =v;
        return result;
    }
    public int hashCode(){
        return  (key==null?0:key.hashCode())^(value==null?0:value.hashCode());
    }
    public boolean equals(Object o){
        if(!(o instanceof  MapEntry)){return  false;}
        MapEntry me = (MapEntry)o;
        return  (key==null?me.getKey()==null:key.equals(me.getKey()))&& (value==null?me.getValue()==null:value.equals(me.getValue()));
    }
    public String toString(){return key+"="+value;}

}


持有引用

java.lang.ref类库包含了一组类,有三个继承自抽象类Reference的类:SoftReference,WeakReference,PhantomReferenct.当垃圾回收器正在考察的对象只能通过某个reference对象才”可获得”,以上三个不同派生类为垃圾回收器提供了不同级别的间接性指示.

如果想继续持有对某个对象的引用,希望以后还能访问到该对象,但是也希望能够允许垃圾回收器释放它,这时就应该使用Reference对象.这样,就可以继续使用该对象,而在内存消耗殆尽时又允许释放该对象.

以Reference对象作为你和普通引用之间的媒介,另外一定不能有普通的引用指向那个对象,这样就能达到上诉目的.(普通的引用指没有经Refeference对象包装过的引用)如果垃圾回收器发现某个对象通过普通引用时可获得的,该对象就不会被释放.


评论