java -- 容器

注意:向上转型时需注意,在LinkedList中具有List接口中未包含的方法,TreeMap中也具有Map接口中不包含的方法。因此要使用这些方法时,就不能将它们向上转型为更通用的接口。

1、容器添加一组元素

public class AddingGroups {
    public static void main(String[] args) {
        // Arrays.asList()方法接受一个数组或者一个用逗号隔开的元素列表,并将其转化为一个list对象
        Collection<Integer> col = new ArrayList<Integer>(Arrays.asList(1, 2, 3, 4, 5));
        Integer[] more = {5, 6, 7, 8, 9};
        col.addAll(Arrays.asList(more));
        // Collections.addAll()方法接受一个Collection对象,以及一个数组或是一个逗号隔开的元素列表,将元素添加到Collection中
        Collections.addAll(col, 10, 11, 12, 13);
        Collections.addAll(col, more);
        // Arrays.asList()底层表示的是数组,因此不能调整尺寸
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
        list.set(1, 100);
        list.add(21);
        // 可以在Arrays.asList()中间插入一条“线索”,以告诉编译器对于由Arrays.asList()产生的list的类型,实际的目标类型应该是什么。
        // 这是显示类型参数说明
        List<Integer> list1 = Arrays.<Integer>asList(1,3,4);
    }
}

2、详解

在这里插入图片描述

2.1 迭代器

迭代器是一个对象,它的工作是遍历并选择序列中的对象,而客户端程序员不需要知道或关心该序列底层的结构。迭代器统一了对容器的访问方式。
Iterator只支持单向移动:
1)iterator()方法要求容器返回一个Iterator。Iterator将准备返回序列的第一个元素。
2)next()方法获得序列中的下一个元素。
3)hasNext()方法检查序列中是否还有元素。
4)remove()方法将迭代器新近返回的元素删除。
ListIterator是更强大的Iterator的子类,只能用于List类的访问,支持双向移动:
1)listIterator()方法返回一个指向List开始处的ListIterator。并且可以使用listIterator(n)返回一个指向列表索引为n的元素处的ListIterator。
2)set()方法支持替换它访问过的最后一个元素。
3)hasNext(),hasPrevious(),nextIndex(),previousIndex(),next(),previous()等方法。

2.2 List

ArrayList优点是随机访问元素速度快,但是在中间插入和移除元素时较慢。
LinkedLIst通过较低代价在中间插入和移除元素,在随机访问方面相对较慢。
LinkedList除了拥有像ArrayList一样的基本方法之外,还拥有某些使其用作栈、队列或双端队列的方法:
1)getFirst()、element()和peek()方法都是返回列表的第一个元素,前两者在列表为空时,抛出NoSuchElementException,后者返回null。
2)remove()、removeFirst()和pop()方法均移除列表的头并返回,前两者在列表为空时,抛出NoSuchElementException,后者返回null。
3)addFirst()插入元素至列表头部,add()、addLast()、offer()插入元素至列表尾部。
4)removeLast()移除并返回列表的最后一个元素。

2.3 Set–不保存重复的元素

HashSet使用的是散列函数;
TreeSet将元素存储在红-黑树数据结构中。


不同的set实现不仅有不同的行为,而且在不同的set中放至的元素类型也有不同的要求:
Set(interface)因为不保存重复元素,所以元素必须定义equals()方法确保对象的一致性;
HashSet为快速查找而设计的set,存入的元素必须定义hashcode();
TreeSet保存次序的set,存入的元素必须实现Comparable接口;
LinkedHashSet具有HashSet的查询速度,使用链表维护元素的顺序,存入的元素必须定义hashcode()方法。

2.4 Map

HashMap:基于散列表的实现,插入和查询的开销是固定的,可通过构造器设置容量和负载因子来调整容器的性能;
LinkedHashMap:类似于HashMap,但是比HashMap慢一点,且迭代遍历时,获取键值对的顺序是插入的顺序,或者是最近最少使用的次序(在构造器中设定)。但在迭代时反而更快,因为使用链表维护内部次序;
TreeMap:基于红黑树的实现,会自动排序。TreeMap是唯一的带有subMap()方法的Map,可以返回一个子树;
WeakHashMap:弱键映射,允许释放映射所指对象的对象;
ConcurrentHashMap:一种线程安全的Map,不涉及同步加锁;
IdentityHashMap:使用==代替equals()对键进行比较的散列映射。
Map中的所有键都必须实现equals()方法,被用于散列Map的键要实现hashcode()方法,被用于TreeMap的键必须实现Comparable。

散列与散列码

正确的equals()方法必须满足下列5个条件:
1)自反性:对任意x,x.equals(x)一定返回true;
2)对称性:对任意的x,y,如果x.equals(y)返回true,则y.equals(x)也返回true;
3)传递性:对任意的x,y,z,如果x.equals(y)和y.equals(z)均返回true,那么x.equals(z)一定返回true;
4)一致性:对于任意的x,y,无论调用多少次x.equals(y),只要用于等价比较的信心没有改变,返回的结果必须保持一致;
5)对于任何不是null的x,x.equals(null)一定返回false。
hashcode()并不需要总是能够返回唯一的标识码,但是equals()方法必须严格判断两个对象是否相等。
散列的价值在于速度,存储一组元素最快的数据结构是数组,所以使用它来表示键的信息,数组并不保存键本身,而是通过键对象生成一个数字,将其作为数组的下标,这个数字就是散列码。为了解决数组容量固定的问题,不同的键可以生成相同的下标,也就是会发生冲突,冲突通常由外部链表处理:数组并不直接保存值,而是保存值的list。为使散列均匀分布,桶的数量通常使用质数。结构示意图如下所示。
在这里插入图片描述

HashMap源码
// 存储数据的节点
static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;    // 用来做索引
        final K key;     //map对应的key
        V value;       // map对应的value
        Node<K,V> next;    //map的下一个节点

        Node(int hash, K key, V value, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }

        public final K getKey()        { return key; }
        public final V getValue()      { return value; }
        public final String toString() { return key + "=" + value; }

        public final int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }

        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }

        public final boolean equals(Object o) {
            if (o == this)
                return true;
            if (o instanceof Map.Entry) {
                Map.Entry<?,?> e = (Map.Entry<?,?>)o;
                if (Objects.equals(key, e.getKey()) &&
                    Objects.equals(value, e.getValue()))
                    return true;
            }
            return false;
        }
    }

1、构造函数

// 第一种:指定初始容量和负载因子的构造函数
public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        this.loadFactor = loadFactor;
        this.threshold = tableSizeFor(initialCapacity);
 }
// 找到比输入大的,并且与输入相邻的2的次方数
 static final int tableSizeFor(int cap) {
        int n = cap - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

// 第二种:指定初始容量,使用默认负载因子的构造函数
public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
}

// 第三种:使用默认初始容量和负载因子的构造函数
public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }

其中默认的负载因子是0.75f,默认初始容量是16。
2、get操作

    public V get(Object key) {
       Node<K,V> e;
       return (e = getNode(hash(key), key)) == null ? null : e.value;
   }
   // 获取hash值
   static final int hash(Object key) {
       int h;
       return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
   }

   final Node<K,V> getNode(int hash, Object key) {
       Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
       // 如果数组不是null,且数组大小大于0,且计算得出下标位置的桶不为空
       // hash & length-1 定位数组下标
       if ((tab = table) != null && (n = tab.length) > 0 &&
           (first = tab[(n - 1) & hash]) != null) {
           // 总是先检查第一个节点
           if (first.hash == hash &&  ((k = first.key) == key || (key != null && key.equals(k))))
               return first;
           // 如果不是第一个节点继续检查剩余的节点
           if ((e = first.next) != null) {
         		// 第一个节点是TreeNode,则采用位桶+红黑树结构,调用TreeNode.getTreeNode(hash,key),遍历红黑树,得到节点的value
               if (first instanceof TreeNode)
                   return ((TreeNode<K,V>)first).getTreeNode(hash, key);
               // 否则就循环链表,直至找到对应的value
               do {
                   if (e.hash == hash &&
                       ((k = e.key) == key || (key != null && key.equals(k))))
                       return e;
               } while ((e = e.next) != null);
           }
       }
       return null;
   }

3、contains操作

	public boolean containsKey(Object key) {
        return getNode(hash(key), key) != null;
    }

4、put操作

   public V put(K key, V value) {
       return putVal(hash(key), key, value, false, true);
   }

   final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
       Node<K,V>[] tab; Node<K,V> p; int n, i;
       // 如果数组长度为0,则resize
       if ((tab = table) == null || (n = tab.length) == 0)
           n = (tab = resize()).length;
   	// 如果计算出对应下标的桶为空,则直接新建一个节点
       if ((p = tab[i = (n - 1) & hash]) == null)
           tab[i] = newNode(hash, key, value, null);
       else {
           Node<K,V> e; K k;
   		// 如果第一个节点的key值和需要加入元素的key值相等,即为此节点
           if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
               e = p;
   		// 如果第一个节点是TreeNode,红黑树结构
           else if (p instanceof TreeNode)
               e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
           else {
   			// 链表结构
               for (int binCount = 0; ; ++binCount) {
   				// 到达链表的尾端也没有找到key值相同的节点,则生成一个新的Node
                   if ((e = p.next) == null) {
                       p.next = newNode(hash, key, value, null);
   					// 判断链表的节点个数是不是到达转换成红黑树的上界,若达到,则转换成红黑树( TREEIFY_THRESHOLD = 8)
                       if (binCount >= TREEIFY_THRESHOLD - 1) 
                           treeifyBin(tab, hash);
                       break;
                   }
                   if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
                       break;
                   p = e;
               }
           }
           if (e != null) { 
               V oldValue = e.value;
               if (!onlyIfAbsent || oldValue == null)
                   e.value = value;
               afterNodeAccess(e);
   			//返回旧的value值
               return oldValue;
           }
       }
       ++modCount;
       if (++size > threshold)
           resize();
       afterNodeInsertion(evict);
       return null;
   }
2.5 Stack

栈通常指“后进先出”的容器,也称为下压栈。LinkedList具有能够直接实现栈的所有功能的方法。

2.6 Queue

队列是“先进先出”的容器。LinkedList提供了方法以支持队列的行为,并且它实现了Queue接口,因此LinkedList可以用作Queue的一种实现。
优先队列PriorityQueue声明下一个弹出元素是最需要的元素(具有最高的优先级)。Integer、String、Character可以与PriorityQueue一起工作,因为这些类已经内建了自然排序,如果要使用自己的类,就必须包括额外的功能以产生自然排序。
双向队列可以在任何一端添加或移除元素,LInkedList中包含支持双向队列的方法,但在Java标准类库中没有任何显示的用于双向队列的接口。
LinkedList、PriorityQueue、ArrayBlockingQueue、ConcurrentLinkedQueue、LinkedBlockingQueue、PriorityBlockingQueue中除了优先队列均是按照插入顺序排列。

2.7 接口的选择

1)对于HashTable、Vector、Stack,它们都是过去遗留下来的类,目的是支持老的程序,因此在新程序中最好不要使用。
2)ArrayList底层由数组支持,LinkedList底层由双向链表实现,因此,如果需要经常在表中插入或删除元素,LinkedList就比较合适;否则应该使用速度更快的ArrayList。如果使用的是固定数量的元素,那么既可以使用背后有数组支撑的List(如Arrays.asList()),也可以选择真正的数组。
3)HashSet是最常用的,且查询速度最快;LinkedHashSet保持元素插入顺序;TreeSet总是处于排序状态。对于插入操作,LinkedHashSet比HashSet的代价更高,这是由维护链表所带来额外的开销造成的。
4)使用Map时第一选择是HashMap;如果要保持有序,可选用TreeMap;LinkedHashMap在插入时比HashMap慢一点,因为在维护散列结构的同时还要维护链表,但是其迭代速度更快。
HashMap的性能因子:
  容量:表中桶的位数。
  初始容量:表在创建时拥有桶的位数。
  尺寸:表中当前存储的项数。
  负载因子:尺寸/容量。空表的负载因子是0,半满表的负载因子是0.5,负载轻的表产生冲突的可能性小,因此对于插入和查找都是最理想的。当负载情况达到负载因子的水平时,容器将自动增加其容量,使容器大致加倍,并重新将现有的对象分布到新的桶位中,称之再散列。
  HashMap的默认负载因子是0.75。如果知道将要在HashMap中存储多少项,那么创建一个拥有合理初始容量的容器将避免自动再散列的开销。

2.8 比较
public class Student {
    Integer id;
    String name;
    Integer age;

    public Student(Integer id, String name, Integer age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String toString() {
        return "id:" + id + " name:" + name + " age:" + age;
    }

    public static void main(String[] args) {
        List<Student> list = new ArrayList<>();
        list.add(new Student(132, "小强", 24));
        list.add(new Student(4325, "小明", 15));
        list.add(new Student(436, "小花", 46));
        list.add(new Student(277, "小王", 37));
        Collections.sort(list, new Comparator<Student>() {
            public int compare(Student o1, Student o2) {
                // 升序
                // return o1.getAge() - o2.getAge();
                // return o1.getAge().compareTo(o2.getAge());
                // 降序
                // return o2.getAge()-o1.getAge();
                return o2.getAge().compareTo(o1.getAge());
            }
        });
        System.out.println(list);
        // lambda表达式
        Collections.sort(list, (o1, o2) -> (o1.getAge() - o2.getAge()));
        System.out.println(list);
    }
}
public class Student implements Comparable<Student> {
    Integer id;
    String name;
    Integer age;

    public Student(Integer id, String name, Integer age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String toString() {
        return "id:" + id + " name:" + name + " age:" + age;
    }

    @Override
    public int compareTo(Student s) {
        return this.getId().compareTo(s.getId());
    }

    public static void main(String[] args) {
        List<Student> list = new ArrayList<>();
        list.add(new Student(132, "小强", 24));
        list.add(new Student(4325, "小明", 15));
        list.add(new Student(436, "小花", 46));
        list.add(new Student(277, "小王", 37));
        Collections.sort(list);
        System.out.println(list);
    }
}

猜你喜欢

转载自blog.csdn.net/weixin_44331516/article/details/88841081