zoukankan      html  css  js  c++  java
  • Java集合

    集合框架图



    java.lang.Iterable<T> : 实现这个接口允许对象成为 "foreach" 语句的目标
         |     |方法
         |     |------- Iterator<T> iterator() 返回一个在一组 T 类型的元素上进行迭代的迭代器
         |
         |子类
         |--- java.util.Collection<E> Collection 层次结构 中的根接口。
         |                  |             Collection 表示一组对象,这些对象也称为 collection 的元素
         |                  |
         |                  |
         |                  |子类
         |                  |-------- java.util.List<E>
         |                  |                       |
         |                  |                       |------- java.util.ArrayList<E>实现类
         |                  |                       |------- java.util.LinkedList<E>实现类
         |                  |                       |------- java.util.Vector<E>实现类
         |                  |                       |            |子类
         |                  |                       |            |------- java.util.Stack<E>
         |                  |子类
         |                  |-------- java.util.Queue<E>
         |                  |                       |子类
         |                  |                       |------ java.util.Deque<E>
         |                  |                       |        |实现类
         |                  |                       |         |---- java.util.LinkedList<E>
         |                  |
         |                  | 子类
         |                  |-------- java.util.Set<E>
         |                  |          |------- java.util.HashSet<E>实现类
         |                  |          |------- java.util.TreeSet<E>
         |                  |          |实现NavigableSet,NavigableSet继承SortedSet,SortedSet继承Set
         |                  |          |------- java.util.LinkedHashSet<E>实现类并继承HashSet
         
         
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    java.util.Map<K,V>
         |
         |------ java.util.HashMap<K,V>实现类
         |
         |------ java.util.Hashtable<K,V>实现类
         |                       |子类
         |                       |------ java.util.Properties
         |
         |
         |--- java.util.SortedMap<K,V>子类
         |                  |子类
         |                  |-------- java.util.NavigableMap<K,V>
         |                  |                       |实现类
         |                  |                       |------- java.util.TreeMap<K,V>
         |
         |
         |--- java.util.concurrent.ConcurrentMap<K,V>子类
         |                  |实现类
         |                  |-------- java.util.concurrent.ConcurrentHashMap<K,V>
        

    Iterator:迭代器,它是Java集合的顶层接口(不包括 map 系列的集合,Map接口 是 map 系列集合的顶层接口)。

    Collection接口是集合类的根接口,Java中没有提供这个接口的直接的实现类。但是却让其被继承。Collection继承的是类 Iterable,Iterable里面封装了 Iterator 接口。所以只要实现了只要实现了Iterable接口的类,就可以使用Iterator迭代器。Collections中containsAll,contains,removeAll,remove是根据equals方法定义的。

    List、Set和Queue继承Collection。

    Map是Java.util包中的另一个接口,它和Collection接口没有关系,是相互独立的,但是都属于集合类的一部分。Map包含了key-value对。Map不能包含重复的key,但是可以包含相同的value。

    HashMap和Hashtable实现接口Map,SortedMap和ConcurrentMap继承接口Map。

     List接口特点

    1、有序的 collection。
    2、可以对列表中每个元素的插入位置进行精确地控制。
    3、可以根据元素的索引访问元素,并搜索列表中的元素。
    4、列表通常允许重复的元素。
    5、允许存在 null 元素。
    6、实现List接口的常用类有LinkedList、ArrayList、Vector和Stack。

     List排序

    1、按照自然顺序排序 : sort( List<T> list )
    自然顺序: 实现Comparable接口才能自然排序。 java.lang.Comparable<T>Comparable 接口强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序,Comparable类的 compareTo 方法被称为它的自然比较方法。
     public int compareTo( T o ) 如果该对象小于、等于或大于指定对象,则分别返回负整数、零或正整数。

    package ecut.collection;
    
    // 声明 java.lang.Comparable 接口时,<T> 表示类型参数 ( "形参")
    // 在 实现接口 时,<Panda> 也是类型参数 ( "实参" )
    public class Panda implements Comparable<Panda>{
    
        private Integer id;
        private String name;
    
        public Panda(Integer id, String name) {
            super();
            this.id = id;
            this.name = name;
        }
        //确定比较规则,Integer id比较时不能用==,应该用equals方法
        @Override
        public int compareTo( Panda another ) {
            if( this.id != null && another.id != null ) {
                return  this.id - another.id ; // this.id  和 another.id 不能是负数
                /*
                if( this.id < another.id ){
                    return -1 ;
                } else if( this.id.equals(  another.id ) ) {
                    return 0 ;
                } else {
                    return 1 ;
                }
                */
            }
            return 0;
        }
    
        @Override
        public String toString() {
            return "(id=" + id + ", name=" + name + ")";
        }
    
        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;
        }
    
    }
    package ecut.collection;
    
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.List;
    
    public class SortListTest1 {
    
        public static void main(String[] args) {
            
            List<Panda> list = new ArrayList<>();
            
            list.add( new Panda( 100 , "桂花" ) );
            list.add( new Panda( 88 , "花菜" ) );
            list.add( new Panda( 200 , "团团" ) );
            list.add( new Panda( 99 , "圆圆" ) );
            
            System.out.println( list );
            
            Collections.sort( list );//列表中的所有元素都必须实现 Comparable 接口。
            
            System.out.println( list );//重写了toString方法,因此可以直接输出。
            
            
        }
    
    }

    运行结果如下:

    [(id=100, name=桂花), (id=88, name=花菜), (id=200, name=团团), (id=99, name=圆圆)]
    [(id=88, name=花菜), (id=99, name=圆圆), (id=100, name=桂花), (id=200, name=团团)]

    2、按照比较器进行排序: sort(List<T> list, Comparator<? super T> c)
    比较器:  java.util.Comparator<T> 强行对某个对象 collection 进行整体排序 的比较函数
    int    compare ( T o1 , T o2 )  比较用来排序的两个参数:根据第一个参数小于、等于或大于第二个参数分别返回负整数、零或正整数。

    package ecut.collection;
    
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.Comparator;
    import java.util.List;
    
    public class SortListTest2 {
    
        public static void main(String[] args) {
            
            List<Panda> list = new ArrayList<>(); // "菱形"语法
            
            list.add( new Panda( 100 , "桂花" ) );
            list.add( new Panda( 88 , "花菜" ) );
            list.add( new Panda( 200 , "团团" ) );
            list.add( new Panda( 99 , "圆圆" ) );
            
            System.out.println( list );
            
            // 创建一个用来比较 Panda 类型对象的 比较器 ( 裁判 )
            Comparator<Panda> c = new Comparator<Panda>(){
                @Override
                public int compare(Panda o1, Panda o2) {
                    if( o1 != null && o2 != null ) {
                        String name1 = o1.getName() ;
                        String name2 = o2.getName();
                        if( name1 != null ){
                            // String 类实现了 Comparable 接口的 compareTo 方法
                            return name1.compareTo( name2 );
                        }
                    }
                    return 0;
                }
            };//Comparator是接口因此要用匿名内部类实现抽象的方法
    Collections.sort( list , c ); // 根据给定的比较器来排序 System.out.println( list ); } }

    运行结果如下:

    [(id=100, name=桂花), (id=88, name=花菜), (id=200, name=团团), (id=99, name=圆圆)]
    [(id=200, name=团团), (id=99, name=圆圆), (id=100, name=桂花), (id=88, name=花菜)]

    List接口主要实现类

    1、java.util.ArrayList<E> : List 接口的大小可变数组的实现类

    • ArrayList 内部基于 数组 存储 各个元素。
    • 所谓大小可变数组,是指当 数组容量不足以存放新的元素时,创建新数组,并将原数组中的内容复制过来。

    ArrayList类的add方法测试案例:

    package ecut.collection;
    
    import java.util.ArrayList;
    import java.util.Arrays;
    
    public class ArrayListTest1 {
    
        public static void main(String[] args) {
            
            int[] x = { 1 , 2 };
            System.out.println( Arrays.toString( x ) );
            
            // x.length = 10 ; // The final field x.length cannot be assigned
            
            // x[ 2 ] = 3 ; // ArrayIndexOutOfBoundsException
            
            ArrayList<String> list = new ArrayList<>(0); // JDK 1.7 开始支持 "菱形" 语法
            
            System.out.println( list ); // list.toString(),list重写了toString方法,因此可以直接输出。
            list.add( "hello" );
            System.out.println( list );
            
        }
    
    }

    源码:

    /**
         * 向列表的尾部添加指定的元素(可选操作)。 
         *
         * @param e 要添加到列表的元素 
         * @return <tt>true</tt> (根据 Collection.add(E) 的规定) 
         */
        public boolean add(E e) {
            ensureCapacityInternal(size + 1);  // Increments modCount!!
            elementData[size++] = e;
            return true;
        }

    运行结果如下:

    [1, 2]
    []
    [hello]

     add()方法开销为分摊的常数,添加n个元素需要O(n)的时间。其他的方法运行时间为线性。每个ArrayList实例都有一个容量(Capactity),即用于存储元素的数组的大小。这个容量可随着不断添加新元素而自动增加,但是增长算法并没有定义。当需要插入大量元素时,在插入之前可以调用ensureCapacity()方法来增加ArrayList容量已提高插入效率确保容量可用后在末尾添加指定元素。

    ArrayList类的remove方法测试案例:

    package ecut.collection;
    
    import java.util.ArrayList;
    
    public class ArrayListTest2 {
    
        public static void main(String[] args) {
            
            ArrayList<Integer> list = new ArrayList<>(); 
            
            list.add( 2 ); // auto-boxing : 2 ---> Integer.valueOf( 2 )
            list.add( 200 ) ;
            list.add( 2 ) ;
            list.add( 20 );
            
            System.out.println( list );
            
            // T  remove( int index ) 是 List 接口定义的方法
            Integer removed = list.remove( 2 ); // 根据索引删除,而不是根据 Integer 对象删除
            System.out.println( "被删除的元素:" + removed );
            
            System.out.println( list );
            
            Object o = 2 ; // auto-boxing
            // boolean remove( Object o ) 是 Collection 接口定义的方法
            boolean result = list.remove( o ) ; // 根据元素来删除
            System.out.println( result );
            
            System.out.println( list );
            
        }
    
    }

    运行结果如下:

    [2, 200, 2, 20]
    被删除的元素:2
    [2, 200, 20]
    true
    [200, 20]

    自动装箱将基本数据类型包装成Integer对象放入list里。T remove(int index):删除的是下标位置的对象并返回(List接口中定义的方法),boolean remove(Object o):删除对象(Colletion接口中都有的)。

    2、java.util.LinkedList<E> :List 接口的实现类和Queue接口子类Deque的实现类

    • 内部基于链表实现,增删快、迭代快,随机访问能力较差。
    • 链表中的每个节点都是 LinkedList.Node 类型的对象。
    • LinkedList提供额外的get、remove、insert方法在LinkedList的首部或尾部。
    • 这些操作使LinkedList可被用作堆栈(stack),队列(queue)或双向队列(deque)。
    • LinkedList没有同步方法。如果多个线程想访问同一个List,则必须自己实现访问同步。

    LinkedList类的add方法测试案例:

    package ecut.collection;
    
    import java.util.LinkedList;
    
    public class LinkedListTest {
    
        public static void main(String[] args) {
            
            LinkedList<String> list = new LinkedList<>();
            
            list.add( "猴哥" );
            
            list.add( "二师兄" );
            
            list.add( "老沙" );
            
            System.out.println( list );
            
            System.out.println( list.get( 2 ) );
            
            list.add( 1 , "白龙马" );
            
            System.out.println(  list );
    
        }
    
    }

    源码:

        private static class Node<E> {
            E item; // 表示当前节点存放的数据
            Node<E> next; // 指向下一个节点的指针
            Node<E> previous; // 指向前一个节点的指针
    
            Node(Node<E> previous, E element, Node<E> next) {
                this.item = element;
                this.next = next;
                this.previous = previous;
            }
        }
    
        /**
         * 将指定元素添加到此列表的结尾。
         *
         * <p>
         * 此方法等效于{@link #addLast}.
         *
         * @param 要添加到此列表的元素
         * @return {@code true} (根据 Collection.add(E) 的规定)
         */
        public boolean add(E e) {
            linkLast(e);
            return true;
        }
    
        void linkLast(E e) {
            final Node<E> l = last;
            final Node<E> newNode = new Node<>(l, e, null);
            last = newNode;
            if (l == null)
                first = newNode;
            else
                l.next = newNode;
            size++;
            modCount++;
        }

    运行结果如下:

    [猴哥, 二师兄, 老沙]
    老沙
    [猴哥, 白龙马, 二师兄, 老沙]

    add方法调用linkLast 方法去实现,找到最后的节点,创建新的节点以最后的节点为前节点,新节点作为最后的节点。 list.get( 2 )链表没有索引的说法,不是直接访问2的位置而是实际遍历整个链表去查找,因此随机访问能力较差。插入和删除的效率比较高。

    3、java.util.Vector<E> :List 接口的内部用数组存放元素的实现类

    • 内部也是用数组存放元素。
    • 与 ArrayList 不同的是 扩容方式不同,Vector 每次增长的容量是固定的,大部分方法都被 synchronized 关键字修饰。
    • Vector 是线程安全的。

    Vector类的测试案例:

    package ecut.collection;
    
    import java.util.Enumeration;
    import java.util.Vector;
    
    public class VectorTest {
    
        public static void main(String[] args) {
            
            // 创建一个 Vector 实例,其初始容量为 10 ,容量的增量为 5
            Vector<String> v = new Vector<>( 10 , 5 );
            
            v.add( "hello" );
            
            v.addElement( "world" );
            
            System.out.println( v );
            
            v.add( 1 , "你好" );
            
            System.out.println(  v );
            
            v.insertElementAt( "世界" , 2 );
            
            System.out.println( v );
            
            Enumeration<String> e =  v.elements();//类似迭代器
            
            while( e.hasMoreElements() ) {
                String s = e.nextElement();
                System.out.println( s );
            }
    
        }
    
    }

    源码:

       /**
         * 使用指定的初始容量和容量增量构造一个空的向量。
         *
         * @param   initialCapacity     向量的初始容量 
         * @param   capacityIncrement   当向量溢出时容量增加的量 
         * @throws IllegalArgumentException 如果指定的初始容量为负数 
         */
        public Vector(int initialCapacity, int capacityIncrement) {
            super();
            if (initialCapacity < 0)
                throw new IllegalArgumentException("Illegal Capacity: "+
                                                   initialCapacity);
            this.elementData = new Object[initialCapacity];
            this.capacityIncrement = capacityIncrement;
        }

    运行结果如下:

    [hello, world]
    [hello, 你好, world]
    [hello, 你好, 世界, world]
    hello
    你好
    世界
    world

    4、java.util.Stack<E>:List 接口实现类Vector的子类

    • 表示堆栈。
    • 栈的特点: 后进先出。

    Stack类的测试案例:

    package ecut.collection;
    
    import java.util.Stack;
    
    public class StackTest {
    
        public static void main(String[] args) {
            
            Stack<String> stack = new Stack<>();
            
            System.out.println( stack.empty() );
            
            stack.push( "first" );
            stack.push( "second" );
            stack.push( "third" );
            
            System.out.println( stack );
            
            String top = stack.peek();
            System.out.println( "top : " + top );
            System.out.println( stack );
            
            top = stack.pop();
            System.out.println( "top : " + top );
            System.out.println( stack );
            
            System.out.println( stack.empty() );
            
            int index = stack.search( "first" ); // 返回对象在堆栈中的位置,以 1 为基数(只有jdbc和stack.search以 1 为基数)
            System.out.println( index );
            
        }
    
    }

    运行结果如下:

    true
    [first, second, third]
    top : third
    [first, second, third]
    top : third
    [first, second]
    false
    2

    Queue接口特点

    1、先进先出 ( FIFO , First In , First Out )。
    2、Queue接口子类Deque的实现类LinkedList。
    3、除了基本的 Collection 操作外,队列还提供其他的插入、提取和检查操作。每个方法都存在两种形式:一种抛出异常(操作失败时),另一种返回一个特殊值(nullfalse,具体取决于操作)。

     Queue类可能会抛出异常的测试案例:

    package ecut.collection;
    
    import java.util.LinkedList;
    import java.util.Queue;
    
    public class QueueTest1 {
    
        public static void main(String[] args) {
            
            Queue<String> queue= new LinkedList<>();
            
            queue.add( "林冲" );
            
            queue.add( "晁盖" );
            
            queue.add( "武松" );
            
            System.out.println( queue );
            //queue.clear();
            //当无法获取到元素时,element 方法抛出 NoSuchElementException
            String head = queue.element() ; // 获取队列头部的元素,但不删除
            System.out.println( head );
            
            System.out.println( queue );
             //queue.clear();
            //当无法获取到元素时,remove 方法抛出 NoSuchElementException
            head = queue.remove(); // 获取并移除队列头部元素
            System.out.println( head );
            
            System.out.println( queue );
    
        }
    
    }

    运行结果如下:

    [林冲, 晁盖, 武松]
    林冲
    [林冲, 晁盖, 武松]
    林冲
    [晁盖, 武松]

     add(e)插入,remove()移除,element()检查当无法获取到元素时,方法抛出 NoSuchElementException异常。

    Queue类返回一个特殊值的测试案例:

    package ecut.collection;
    
    import java.util.LinkedList;
    import java.util.Queue;
    
    public class QueueTest2 {
    
        public static void main(String[] args) {
            
            Queue<String> queue= new LinkedList<>();
            
            queue.offer( "林冲" );
            
            queue.offer( "晁盖" );
            
            queue.offer( "武松" );
            
            System.out.println( queue );
            
            queue.clear();
            
            // 当无法获取元素时,返回 null,不会抛出异常
            String head = queue.peek() ; // 获取队列头部的元素,但不删除
            System.out.println( head );
            
            System.out.println( queue );
            
            // 当无法获取元素时,返回 null,不会抛出异常
            head = queue.poll(); // 获取并移除队列头部元素
            System.out.println( head );
            
            System.out.println( queue );
    
        }
    
    }

    运行结果如下:

    [林冲, 晁盖, 武松]
    null
    []
    null
    []

    offer(e)插入,poll()移除,peek()检查当无法获取元素时,返回 null,不会抛出异常。

    Queue接口主要实现类

    1、java.util.Deque<E> : Queue接口的的实现类,表示双端队列

    • 一个线性 collection,支持在两端插入和移除元素。
    • 此接口定义在双端队列两端访问元素的方法。提供插入、移除和检查元素的方法。每种方法都存在两种形式:一种形式在操作失败时抛出异常,另一种形式返回一个特殊值(nullfalse,具体取决于操作),共12个方法。
    • 在将双端队列用作队列时,将得到 FIFO(先进先出)行为。
    • 继承Queue的6个方法以及和栈的3个方法,共21个方法需了解。
    • 方法:

            由于继承了Queue,所以Deque拥有Queue的方法,因此以下方法等价:
                add(e)    <-->  addLast(e)
                offer(e)  <-->  offerLast(e)
                remove()  <-->  removeFirst()
                poll()    <-->  pollFirst()
                element() <-->  getFirst()
                peek()    <-->  peekFirst()
                
            也可以用于Stack,因此以下方法等价:
                push(e) <-->  addFirst(e)
                pop()   <-->  removeFirst()
                peek()  <-->  peekFirst()

    Deque测试案例:

    package ecut.collection;
    
    import java.util.Deque;
    import java.util.LinkedList;
    
    public class DequeTest1 {
    
        public static void main(String[] args) {
            
            // 以 输出 deque 的字符串形式 的 "左侧" 为头
            Deque<String> deque = new LinkedList<>();
            //将指定的元素插入此双端队列的末尾
            deque.offerLast( "曹操" ) ;  
            
            deque.offerLast( "曹丕" );
            
            deque.offerLast( "曹爽" );
            
            deque.offerLast( "司马炎" );
            
            System.out.println( deque );
            //获取并移除此双端队列的第一个元素;如果此双端队列为空,则返回 null。 
            String head = deque.pollFirst();
            System.out.println( "移除队列头: " + head );
            System.out.println( deque );
            
            head = deque.pollFirst();
            System.out.println( "移除队列头: " + head );
            System.out.println( deque );
            
            head = deque.pollFirst();
            System.out.println( "移除队列头: " + head );
            System.out.println( deque );
            
            
        }
    
    }

    运行结果如下:

    [曹操, 曹丕, 曹爽, 司马炎]
    移除队列头: 曹操
    [曹丕, 曹爽, 司马炎]
    移除队列头: 曹丕
    [曹爽, 司马炎]
    移除队列头: 曹爽
    [司马炎]

    offerLast插入的元素作为最后一个
            运行过程:
            曹操
            曹操, 曹丕
            曹操, 曹丕, 曹爽
            曹操, 曹丕, 曹爽, 司马炎
            以 输出 deque 的字符串形式 的 "左侧" 为头(第一个进入的元素)

    Deque测试案例:

    package ecut.collection;
    
    import java.util.Deque;
    import java.util.LinkedList;
    
    public class DequeTest2 {
    
        public static void main(String[] args) {
            
            // 以 输出 deque 的字符串形式 的 "右侧" 为头
            Deque<String> deque = new LinkedList<>();
            //将指定的元素插入此双端队列的开头
            deque.offerFirst( "曹操" ) ;  
            
            deque.offerFirst( "曹丕" );
            
            deque.offerFirst( "曹爽" );
            
            deque.offerFirst( "司马炎" );
            
            System.out.println( deque );
            //获取并移除此双端队列的最后一个元素;如果此双端队列为空,则返回 null。 
            String head = deque.pollLast();
            System.out.println( "移除队列头: " + head );
            System.out.println( deque );
            
            head = deque.pollLast();
            System.out.println( "移除队列头: " + head );
            System.out.println( deque );
            
            head = deque.pollLast();
            System.out.println( "移除队列头: " + head );
            System.out.println( deque );
            
            
        }
    
    }

    运行结果如下:

    [司马炎, 曹爽, 曹丕, 曹操]
    移除队列头: 曹操
    [司马炎, 曹爽, 曹丕]
    移除队列头: 曹丕
    [司马炎, 曹爽]
    移除队列头: 曹爽
    [司马炎]

    offerFirst插入的元素作为第一个
            运行过程:
            曹操
            曹丕, 曹操
            曹爽, 曹丕, 曹操
            司马炎, 曹爽, 曹丕, 曹操
            以 输出 deque 的字符串形式 的 "右侧" 为头(第一个进入的元素)

    2、java.util.LinkedList<E> :Queue接口的子类Deque的实现类

    • 内部基于链表实现,增删快、迭代快,随机访问能力较差。
    • 链表中的每个节点都是 LinkedList.Node 类型的对象。
    • LinkedList提供额外的get、remove、insert方法在LinkedList的首部或尾部。
    • 这些操作使LinkedList可被用作堆栈(stack),队列(queue)或双向队列(deque)。双端队列也可用作 LIFO(后进先出)堆栈。
    • LinkedList没有同步方法。如果多个线程想访问同一个List,则必须自己实现访问同步。

     LinkedList 当作 "栈" 来使用测试案例:

    package ecut.collection;
    
    import java.util.LinkedList;
    
    /**
     * 将 LinkedList 当作 "栈" 来使用
     * 栈:  后进先出 ( Last In , First Out , LIFO )
     */
    public class LinkedListStackTest {
    
        public static void main(String[] args) {
            
            LinkedList<String> s = new LinkedList<>();
            
            s.push( "Java" );
            
            s.push( "C++" );
            
            s.push( "Go" );
            
            System.out.println( s );
            
            String top = s.peek() ; // 检查栈顶元素 ( 只获取不删除 )
            System.out.println( top );
            
            System.out.println( s );
            
            top = s.pop(); // 弹出栈顶元素 ( 获取并移除 )
            System.out.println( top );
            System.out.println( s );
            
            top = s.pop(); // 弹出栈顶元素 ( 获取并移除 )
            System.out.println( top );
            System.out.println( s );
            
            top = s.pop(); // 弹出栈顶元素 ( 获取并移除 )
            System.out.println( top );
            System.out.println( s );
    
        }
    
    }

    运行结果如下:

    [Go, C++, Java]
    Go
    [Go, C++, Java]
    Go
    [C++, Java]
    C++
    [Java]
    Java
    []

    Set接口特点

    1、最接近数学中的 "集" 的概念。
    2、元素不重复 ( 必须通过元素的 equals 方法来判断是否存在重复元素 )。
    3、并且最多包含一个 null 元素(可能有限制)。

    Set接口主要实现类

    1、java.util.HashSet<E> : Set接口的的实现类

    •  由 哈希表(实际上是一个 HashMap 实例)支持。
    • 元素可以是 null。
    • 元素存放的位置跟添加顺序无关 ( 元素存放的位置 跟 element.hashCode() 有关 )。
    • 不支持排序(根据hashcode来确定位置的,所以位置不能改变,因此不支持排序)。

    HashSet测试案例:

    package ecut.collection;
    
    import java.util.HashSet;
    
    public class HashSetTest {
    
        public static void main(String[] args) {
            
            HashSet<String> set = new HashSet<>();
            
            set.add( "张三丰" );
            set.add( "张翠山" );
            set.add( "殷素素" );
            set.add( "张无忌" );
            set.add( "谢逊" );
            set.add( "赵敏" );
            
            set.add( "张三丰" );
            
            set.add( null );
            
            System.out.println( set );
    
        }
    
    }

    源码:

    private static final Object PRESENT = new Object();
    public boolean add(E e) {
         return map.put(e, PRESENT)==null;
    }

    HashMap实现的因为key 不能重复因此Set元素不重复,value为固定的PRESENT。

    运行结果如下:

    [null, 殷素素, 张翠山, 张三丰, 谢逊, 赵敏, 张无忌]

    2、Java.util.TreeSet<E>:  基于 TreeMap 来实现 Set 接口 的实现类

    • NavigableSet<E>的实现类,NavigableSet<E>继承SortedSet<E>,SortedSet<E>继承Set<E>。
    • TreeSet基于 TreeMap 来实现TreeMap 内部是基于 红黑树( Red-Black tree)。
    • SortedSet是有顺序因此TreeSet有顺序 ( 根据元素的自然顺序,元素得实现Comparable接口或比较器排序后存放元素)。
    • 元素不可以是 null(每添加一个元素都得进行一次排序调用compareTo方法,一次不能为null)。
    • 元素存放的位置 跟 添加顺序无关。

    TreeSet测试案例:

    package ecut.collection;
    
    import java.util.TreeSet;
    
    public class TreeSetTest1 {
    
        public static void main(String[] args) {
            
            // 如果构造方法没有指定比较器,则根据元素的 自然顺序 排序
            // java.lang.String 支持 自然比较
            TreeSet<String> ts = new TreeSet<>();
            
            ts.add( "张三丰" );
            ts.add( "张翠山" );
            ts.add( "殷素素" );
            ts.add( "张无忌" );
            ts.add( "谢逊" );
            ts.add( "赵敏" );
            ts.add( "张三丰" );
            
            System.out.println( ts );
    
        }
    
    }

    源码:

    public TreeSet() {
            this(new TreeMap<E,Object>());
        }

    实际上是创建一个TreeMap,因此TreeSet的元素和treeMap的key特点一样。

    运行结果如下:

    [张三丰, 张无忌, 张翠山, 殷素素, 谢逊, 赵敏]

    3、java.util.LinkedHashSet<E>: Set接口的的实现类

    • 继承HashSet和LinkedList相似。
    • 内部是基于链表。
    • 不能重复。

    Map接口特点

    1、将键映射到值的对象 ( Map 集合中存放的是 key-value 对 ( Map.Entry ) )。
    2、一个映射不能包含重复的键 ( Map 集合中的 key 不能重复 )。
    3、每个键最多只能映射到一个值 ( Map 集合中的 key 只能对应一个 值 )。
    4、几乎所有的map都具有三个视图所有的 key 组成的 Set 集合,所有的 value 组成的 Collection 集合和所有的 entry 对应的 Set 集合

    Map接口主要方法

    V    put( K key, V value ) : 将指定的值(value)与此映射中的指定键(key)关联。

    V    get( Object key )  : 返回指定键所映射的值;如果此映射不包含该键的映射关系,则返回 null。

    boolean    containsKey(Object key): 如果此映射包含指定键的映射关系,则返回 true

    boolean    containsValue(Object value):如果此映射将一个或多个键映射到指定值,则返回 true。。

    void    clear(): 从此映射中移除所有映射关系(可选操作)。

    boolean    isEmpty(): 如果此映射未包含键-值映射关系,则返回 true

    int    size() :返回此映射中的键-值映射关系数。

    V    remove(Object key):如果存在一个键的映射关系,则将其从此映射中移除(可选操作)。

    void    putAll( Map<? extends K,? extends V> m ) :从指定映射中将所有映射关系复制到此映射中(可选操作)。

    Set<K>    keySet()  :返回所有的 key 组成的 Set 集合

    Collection<V>    values(): 返回所有的 value 组成的 Collection 集合

    Set<Map.Entry<K,V>>    entrySet(): 返回所有的 entry 对应的 Set 集合

    Map接口主要方法的测试案例:

    package ecut.collection;
    
    import java.util.HashMap;
    import java.util.Map;
    
    public class MapTest1 {
    
        public static void main(String[] args) {
            
            Map<String,Integer> map = new HashMap<>();
            //以前与 key 关联的值,如果没有针对 key 的映射关系,则返回 null。
            Integer value = map.put( "Java从入门到精通" , 98 );
            System.out.println( "value : " + value );
            
            // 将指定的值(value)与此映射中的指定键(key)关联
            value = map.put( "Java从入门到精通" , 108 );
            System.out.println( "value : " + value );
            
            // 返回指定键所映射的值;如果此映射不包含该键的映射关系,则返回 null
            System.out.println( map.get( "Java从入门到精通" ) );
            
            System.out.println( map.containsKey( "Java从入门到精通" ) );
            System.out.println( map.containsKey( "Oracle从入门到精通" ) );
            
            System.out.println( map.containsValue( 108 ) );
            
            map.put( "C++大学教程" , 108 );
            
            map.put( "A语言大学教程" , 108 );
            
            System.out.println( map );
            
            
        }
    
    }

    运行结果如下:

    value : null
    value : 98
    108
    true
    false
    true
    {Java从入门到精通=108, A语言大学教程=108, C++大学教程=108}

     Map接口主要方法的测试案例:

    package ecut.collection;
    
    import java.util.HashMap;
    import java.util.Map;
    
    public class MapTest2 {
    
        public static void main(String[] args) {
            
            // map 变量的值 不是 null
            // map 集合中没有放入 任何键值对时,isEmpty 返回 true
            Map<String,Integer> map = new HashMap<>();
            
            System.out.println( "size : " +  map.size() + " , isEmpty : " + map.isEmpty() );
            
            map.put( "软件工程" , 500 );
            map.put( "通信工程" , 200 );
            map.put( "土木工程" , 100 );
            
            System.out.println( "size : " +  map.size() + " , isEmpty : " + map.isEmpty() );
            
            System.out.println( map );
            
            // 删除指定的 key 对应的 key-value 对,并返回 该 key 对应的 value
            Integer value = map.remove( "土木工程" );
            System.out.println( value );
            
            System.out.println( map );
            
            map.clear();
            
            System.out.println( "size : " +  map.size() + " , isEmpty : " + map.isEmpty() );
            
            map.put( "信息科学技术" , 100 );
            
            Map<String,Integer> m = new HashMap<>();
            m.put( "幼儿教育" , 5000 );
            m.put( "信息科学技术" , 150 );
            System.out.println( m );
            
            map.putAll( m );
            
            System.out.println( map );
            
        }
    
    }

    运行结果如下:

    size : 0 , isEmpty : true
    size : 3 , isEmpty : false
    {通信工程=200, 土木工程=100, 软件工程=500}
    100
    {通信工程=200, 软件工程=500}
    size : 0 , isEmpty : true
    {信息科学技术=150, 幼儿教育=5000}
    {信息科学技术=150, 幼儿教育=5000}

    Map接口主要方法的测试案例:

    package ecut.collection;
    
    import java.util.Collection;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Set;
    
    /**
     * keySet()
     * values()
     * entrySet()
     */
    public class MapTest3 {
    
        public static void main(String[] args) {
            
            Map<String,Integer> map = new HashMap<>();
            
            map.put( "软件工程" , 500 );
            map.put( "通信工程" , 200 );
            map.put( "土木工程" , 100 );
            map.put( "幼儿教育" , 300 );
            map.put( "护士护理" , 600 );
            
            // 返回此映射中包含的键的 Set 视图
            Set<String> keys = map.keySet(); // 所有的 key 组成的 Set 集合
            for( String key  : keys ) {
                Integer value = map.get( key );
                System.out.println( key + " : " + value );
            }
            
            System.out.println( "~~~~~~~~~~~~~~~~~~~~~~~~" );
            
            // 返回此映射中包含的值的 Collection 视图
            Collection<Integer> values =  map.values();
            
            for( Integer v  : values ) {
                System.out.println( v );
            }
            
            System.out.println( "~~~~~~~~~~~~~~~~~~~~~~~~" );
            
            // 返回此映射中包含的映射关系的 Set 视图
            Set< Map.Entry<String, Integer> > entries =  map.entrySet();
            
            for(  Map.Entry<String, Integer> entry : entries ){
                System.out.println( entry.getKey() + " : " + entry.getValue() );
            }
            
        }
    
    }

    运行结果如下:

    通信工程 : 200
    土木工程 : 100
    护士护理 : 600
    软件工程 : 500
    幼儿教育 : 300
    ~~~~~~~~~~~~~~~~~~~~~~~~
    200
    100
    600
    500
    300
    ~~~~~~~~~~~~~~~~~~~~~~~~
    通信工程 : 200
    土木工程 : 100
    护士护理 : 600
    软件工程 : 500
    幼儿教育 : 300

    HashCode测试案例:

    package ecut.collection;
    
    /**
     * 对于 Object # hashCode()  
     * 
     * 1、由 Object 类定义的 hashCode 方法确实会针对不同的对象返回不同的整数。
     *    (这一般是通过将该对象的内部地址转换成一个整数来实现的,但是 JavaTM 编程语言不需要这种实现技巧。)
     *    
     * 2、这个整数 与 System.identityHashCode 的返回值相同
     * 
     * 3、这个整数的意义:  Identity Hash Code ( 相当于 每个 对象的 身份证编号 )
     *      
     * 4、Object 提供 hashCode 方法是为了提高哈希表(例如 java.util.Hashtable 提供的哈希表)的性能
     * 
     * 5、这个整数 在第一次 访问时 才触发产生
     *
     */
    public class IdentityHashCodeTest {
    
        public static void main(String[] args) {
            
            // 数组也是引用类型变量
            int[] a = new int[ 10 ];
            int[] b = new int[10] ;
            //返回该对象的哈希码值。
            System.out.println( "a : " + a.hashCode() );
            System.out.println( "b : " + b.hashCode() );
            //这个整数 与 System.identityHashCode 的返回值相同
            System.out.println( "b : " + System.identityHashCode( b ) );
            System.out.println( "a : " + System.identityHashCode( a ) );
            
            
            
        }
    
    }

    运行结果如下:

    a : 366712642
    b : 1829164700
    b : 1829164700
    a : 366712642

    Map接口主要实现类

    1、java.util.HashMap<K,V> : 基于 哈希表 的 Map 接口的实现类

    • key 允许为 null , value 也允许为 null
    • 不保证映射的顺序,特别是它不保证该顺序恒久不变

    HashMap测试案例:

    package ecut.collection;
    
    import java.lang.reflect.Array;
    import java.lang.reflect.Field;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.util.HashMap;
    
    /***
     * hashCode 相同的字符串:
     *  重地 - 通话
     *  方面 - 树人
     *  玉班 - 王环 - 牛顿
     *  王八 - 玌兌
     *  东理 - 两猎 - 二晶 - 伍囗 - 住仙
     *  东华 - 世合
     *  德鹏 - 恢覚
     *  农丰 - 儿女
     *  掌门 - 文境 - 方創
     */
    public class MapHelper {
        
        public static void main(String[] args) {
            
            HashMap<String,String> map = new HashMap<String,String>( 1 );
            //根据传入的 HashMap 实例获取其内部的 哈希表 的容量
            int c = capacity( map );
            System.out.println( "容量: " + c );
            
            map.put( "掌门" , "张三丰" );
            map.put( "方創" , "大众创业万众等死" );
            
            map.put( "农丰" , "三轮车" );
            map.put( "儿女" ,  "张无忌" );
            
            map.put( "重地" , "仓库重地" );
            map.put( "通话" , "通话记录");
            
            map.put( null , null );
            //显示给定 HashMap 实例内部的 哈希表中存储的数据 (只处理到链表层次)
            show( map );
            
            c = capacity( map );
            System.out.println( "容量: " + c );
            
            String key = "儿女" ;
            //计算指定的 key 在给定的 map 集合中的位置(在哈希表中的索引)
            int p = position(map, key );
            System.out.println( key + "在哈希表中的位置: " + p );
            
            key = null ;
            p = position(map, key);
            System.out.println( key + "在哈希表中的位置: " + p );
            
        }
        
        private static Class<?> hashMapClass ;
        private static Field tableField;
        private static Method hashMethod ;
        
        static {
            
            hashMapClass = HashMap.class;
            
            try {
                // 通过反射来获得 HashMap 内部的 table 属性
                tableField = hashMapClass.getDeclaredField( "table" );
                // 让本来因为被封装而不能访问的 table 能够被访问
                tableField.setAccessible( true );
                
                // 通过反射获得 HashMap 内部的 hash 方法
                hashMethod = hashMapClass.getDeclaredMethod( "hash" , Object.class );
                // 让本来因为封装而不能访问的 hash 方法能够被访问
                hashMethod.setAccessible( true );
                
            } catch (NoSuchFieldException e) {
                System.out.println( "在 " + hashMapClass.getName() + " 中未找到 table 属性 : " + e.getMessage() );
            } catch (SecurityException e) {
                System.out.println( "无法访问 " + hashMapClass.getName() + " 的 table 属性 : " + e.getMessage() );
            } catch (NoSuchMethodException e) {
                System.out.println( "在 " + hashMapClass.getName() + " 累中未找到 hash 方法 : " + e.getMessage() );
            }
            
        }
        
        /**
         * 计算指定的 key 在给定的 map 集合中的位置(在哈希表中的索引)
         * @param map
         * @param key
         * @return
         */
        public static int position( HashMap<?,?> map , Object key ) {
            int position = -1 ;
            //获取 哈希表的容量
            int capacity = capacity( map );
            // 根据 键 计算哈希值
            int hash = hash( map , key );
            // 根据 HashMap 中的实现思路,计算 键 的存储位置
            position = ( capacity - 1 ) & hash ;
            
            return position ;
        }
        
        /**
         * 计算指定的 key 的 hash 值
         * @param map
         * @param key
         */
        public static int hash( HashMap<?,?> map , Object key ) {
            int hash = -1 ;
            try {
                // 通过反射调用 HashMap 中的 hash 方法
                Object h = hashMethod.invoke( map , key );
                if( h != null ) {
                    // 判断 h 是否是 int 类型 或 Integer 类型
                    if( h.getClass() == Integer.class || h.getClass() == int.class ) {
                        hash = Integer.class.cast( h )  ; // 通过反射方法,实现强制类型转换
                    }
                }
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
            
            return hash ;
        }
        
        /**
         * 根据传入的 HashMap 实例获取其内部的 哈希表 的容量
         * @param map
         */
        public static int capacity( HashMap<?,?> map ) {
            int capacity = 0 ;
            try {
                // 获得给定的 map 实例 中的 table 属性的值 (获取到哈希表)
                Object value = tableField.get( map );
                
                if( value != null ){
                    Class<?> valueClass = value.getClass(); // 获得 table 属性的 值 的类型
                    if( valueClass.isArray() ){ // 如果是个数组
                        capacity = Array.getLength( value ); // 获取数组长度 (获取哈希表的长度)
                    }
                }
                
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            
            return capacity ;
            
        }
        
        /**
         * 显示给定 HashMap 实例内部的 哈希表中存储的数据 (只处理到链表层次)
         * @param map
         */
        public static void show( HashMap<?,?> map ) {
            
            try {
                // 从参数传入的 map 实例中获得 该实例中的 table 属性的值
                Object value = tableField.get( map );
                
                if( value != null ){
                    Class<?> valueClass = value.getClass(); // 获得 table 属性的 值 的类型
                    if( valueClass.isArray() ){ // 如果是个数组
                        
                        System.out.println( "HashMap 实例中的哈希表:" );
                        
                        StringBuffer buffer = new StringBuffer();
                        
                        // 遍历数组
                        for( int i = 0 , n = Array.getLength( value ) ; i < n ; i++ ){
                            buffer.setLength( 0 );
                            Object e = Array.get( value ,  i ) ; // 从数组中获取 下标是 i 的元素
                            if( e != null ){
                                buffer.append( i ) ;
                                buffer.append( " : " );
                                // 获得 当前循环取得的 元素的类型
                                Class<?> eClass = e.getClass();
                                Field hashField = eClass.getDeclaredField( "hash" ); // 获得 eClass 中 名称是 hash 的属性
                                hashField.setAccessible(true);
                                buffer.append( "[ " );
                                Field keyField = eClass.getDeclaredField( "key" ); // 获得 eClass 中 名称是 key 的属性
                                keyField.setAccessible(true);
                                Field valueField = eClass.getDeclaredField( "value" ); // 获得 eClass 中 名称是 key 的属性
                                valueField.setAccessible(true);
                                Field nextField = eClass.getDeclaredField( "next" ); // 获得 eClass 中 名称是 key 的属性
                                nextField.setAccessible(true);
                                
                                Object hash = hashField.get( e );
                                buffer.append( "<");
                                buffer.append( hash );
                                buffer.append( ">" );
                                
                                Object k = keyField.get( e );
                                buffer.append( k );
                                buffer.append( "=" );
                                Object v = valueField.get( e );
                                buffer.append( v );
                                
                                Object next = nextField.get( e );
                                while( next != null ) {
                                    buffer.append( " , " );
                                    hash = hashField.get( e );
                                    k = keyField.get( next );
                                    v = valueField.get( next );
                                    buffer.append( "<");
                                    buffer.append( hash );
                                    buffer.append( ">" );
                                    buffer.append( k );
                                    buffer.append( "=" );
                                    buffer.append( v );
                                    next = nextField.get( next ) ; // 继续获得下一个节点
                                }
                                
                                buffer.append( " ]" );
                            } else {
                                buffer.append( i );
                                buffer.append( " : [ empty ]" );
                            }
                            
                            System.out.println( buffer.toString() );
                            
                        }// end of for loop
                    }
                } else {
                    System.out.println( "null");
                }
                
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (SecurityException e) {
                e.printStackTrace();
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            
        }
    
    }
    package ecut.collection;
    
    import java.util.HashMap;
    
    public class HashMapTest {
    
        public static void main(String[] args) {
            
            HashMap<String,Integer> map = new HashMap<>( 20 , 0.8F );
            /** 
            this.loadFactor = loadFactor;
            this.threshold = tableSizeFor( initialCapacity );
            **/
            
            // 因为 HashMap 内部用 哈希表来存储 键值对
            // 并且 HashMap 采用 key 的hashCode 来确定 键值对 在哈希表中的位置
            // 因此,添加键值对的顺序,跟它们在哈希表中的存放位置可能不同
            map.put( "软件工程" , 500 );
            map.put( "通信工程" , 200 );
            map.put( "土木工程" , 100 );
            map.put( "幼儿教育" , 300 );
            map.put( "护士护理" , 600 );
            
            map.put( "东理" , 600 );
            map.put( "两猎" , 600 );
            map.put( "二晶" , 600 );
            map.put( "伍囗" , 600 );
            map.put( "住仙" , 600 );
            
            System.out.println( map );
            
            int capacity = MapHelper.capacity( map );
            System.out.println( "HashMap实例内部的哈希表的长度: " + capacity );
            
            int index = MapHelper.position( map ,  "护士护理" );
            System.out.println( "元素在哈希表中的存放位置: " + index  );
            
            MapHelper.show( map );
            
            
        }
    
    }

    运行结果如下:

    {东理=600, 两猎=600, 二晶=600, 伍囗=600, 住仙=600, 土木工程=100, 软件工程=500, 幼儿教育=300, 通信工程=200, 护士护理=600}
    HashMap实例内部的哈希表的长度: 32
    元素在哈希表中的存放位置: 24
    HashMap 实例中的哈希表:
    0 : [ empty ]
    1 : [ empty ]
    2 : [ empty ]
    3 : [ <649571>东理=600 , <649571>两猎=600 , <649571>二晶=600 , <649571>伍囗=600 , <649571>住仙=600 ]
    4 : [ empty ]
    5 : [ empty ]
    6 : [ <690577222>土木工程=100 ]
    7 : [ empty ]
    8 : [ empty ]
    9 : [ empty ]
    10 : [ <1114081802>软件工程=500 ]
    11 : [ empty ]
    12 : [ empty ]
    13 : [ <741421005>幼儿教育=300 ]
    14 : [ empty ]
    15 : [ empty ]
    16 : [ empty ]
    17 : [ empty ]
    18 : [ empty ]
    19 : [ empty ]
    20 : [ empty ]
    21 : [ <1119401141>通信工程=200 ]
    22 : [ empty ]
    23 : [ empty ]
    24 : [ <774976728>护士护理=600 ]
    25 : [ empty ]
    26 : [ empty ]
    27 : [ empty ]
    28 : [ empty ]
    29 : [ empty ]
    30 : [ empty ]
    31 : [ empty ]

    2、java.util.Hashtable<K,V>: Map接口的的实现类

    • Properties类继承了Hashtable。
    • 实现一个哈希表,该哈希表将键映射到相应的值
    • 几乎所有的方法都是线程安全的
    • Hashtable 的 key 和 value 都不能为 null

    Hashtable测试案例:

    package ecut.collection;
    
    import java.util.Enumeration;
    import java.util.Hashtable;
    
    /**
     * 一个 Map 接口的实现类的自我修养:
     *     1、将键映射到值的对象 ( Map 集合中存放的是 key-value 对 ( Map.Entry ) )
           2、一个映射不能包含重复的键 ( Map 集合中的 key 不能重复 )
           3、每个键最多只能映射到一个值 ( Map 集合中的 key 只能对应一个 值 )
           
         Hashtable 的特点:
            1、key 和 value 都不能为 null  
            2、几乎所有的方法都是线程安全的( 几乎所有的方法都被 synchronized 修饰 )
            
     */
    public class HashtableTest {
        
        public static void main(String[] args) {
            
            Hashtable<String,Integer> ht = new Hashtable<>();
            
            //ht.put(null, null);//抛出NullPointerException,key 和 value 都不能为 null  
            
            ht.put( "罗玉凤" , 250 );
            
            ht.put( "罗玉龙" , 500 );
            
            System.out.println( ht ); // ht.toString()
            
            System.out.println( ht.get( "罗玉龙" ) );
            
            Enumeration<String> keys =  ht.keys();
            
            while( keys.hasMoreElements() ){
                String key = keys.nextElement();
                Integer value = ht.get( key );
                System.out.println( key + " : " + value );
            }
            
        }
    
    }

    Enumeration接口的功能与 Iterator 接口的功能是重复的。此外,Iterator 接口添加了一个可选的移除操作,并使用较短的方法名。新的实现应该优先考虑使用 Iterator 接口而不是 Enumeration 接口。

    运行结果如下:

    {罗玉龙=500, 罗玉凤=250}
    500
    罗玉龙 : 500
    罗玉凤 : 250

    3、java.util.Properties:Hashtable的子类,表示属性集

    • Properties 可保存在流中或从流中加载。
    • 属性列表中每个键及其对应值都是一个字符串,应该用 setProperty 和 getProperty 方法来 操作属性 ,而不是使用 put 和 get。

    Properties测试案例:

    package ecut.collection;
    
    import java.util.Properties;
    
    public class PropertiesTest1 {
    
        public static void main(String[] args) {
            
            Properties props = new Properties();
            
             // props.put(key, value) ;
            props.setProperty( "driver", "com.mysql.jdbc.Driver" );
            props.setProperty( "url" , "jdbc:msyql://127.0.0.1:3306/ecut" );
            
            // props.get(key);
            String d = props.getProperty( "driver" );
            System.out.println( d );
            
            String user = props.getProperty( "user" );
            System.out.println( user );
            
            // 当指定的属性名在集合中不存在时,使用 第二个参数 当作默认值返回
            user = props.getProperty( "user" , "root" );
            System.out.println( user );
            
        }
    
    }

    运行结果如下:

    com.mysql.jdbc.Driver
    null
    root

    Properties测试案例:

    jdbc.url = jdbc:oracle:thin:@127.0.0.1:1521:ecut
    jdbc.driver = oracle.jdbc.driver.OracleDriver
    jdbc.user = ecut
    jdbc.password = ecut2017
    package ecut.collection;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.util.Properties;
    
    /**
     * 1、从类路径下获得任意资源对应的输入流
     * 2、用 Properties 加载 属性文件 ( 以 .properties 为后缀,其中的内容形式是 key = value )
     */
    public class PropertiesTest2 {
    
        public static void main(String[] args) {
            
            Properties props = new Properties();
            System.out.println( "属性个数: " + props.size() );
            //任何一个类型都可以通过.class来获得自己的类型对应Class 对象
            Class<?> c = PropertiesTest2.class ;// 通过 java.lang.Class 类的 getResourceAsStream 方法获得指定资源名称对应的输入流
            // 如果仅仅指定的是 文件名,则该文件必须跟 当前类在同一个包
            // 如果在 文件名之前使用了 / 则表示 从 当前的 类路径 的根路径开始寻找
            InputStream inStream = c.getResourceAsStream( "/jdbc.properties" ) ;
            
            try {
                props.load( inStream ); // 从指定的流中读取 属性 ,并加入到 属性集合 中
            } catch (IOException e) {
                System.err.println( "加载失败: " + e.getMessage() );
            } catch( NullPointerException e ) {
                System.err.println( "未找到文件: " + e.getMessage() );
            }
            
            if( props.size() > 0 ) {
                System.out.println( props.getProperty( "jdbc.url" ) );
                System.out.println( props.getProperty( "jdbc.driver" ) );
                System.out.println( props.getProperty( "jdbc.user" ) );
                System.out.println( props.getProperty( "jdbc.password" ) );
            }
            
        }
    
    }

    运行结果如下:

    属性个数: 0
    jdbc:oracle:thin:@127.0.0.1:1521:ecut
    oracle.jdbc.driver.OracleDriver
    ecut
    ecut2017

    HashMap 和 Hashtable 的相同点
    1、内部都是基于 哈希表 存储数据(内部都有数组table,HashTable是Entry<?,?>[] table,HashMap是Node<K,V>[] table)。
    2、HashMap 和 Hashtable 的迭代器 都是 快速失败 ( fail-fast ) 的
    HashMap 和 Hashtable 的区别
    1、用来确定元素在哈希表中的位置的方式不同:
         HashMap 根据 key.hashCode() 重新计算一个值:

     public V put(K key, V value) {
            return putVal(hash(key), key, value, false, true);
        }
    static final int hash( Object key ) {
               int h;
                return (key == null) ? 0 : ( h = key.hashCode() ) ^ ( h >>> 16 );
    }

         然后再根据这个值来确定元素在哈希表中的位置:

    部分源码

      final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                       boolean evict) {
            Node<K,V>[] tab; Node<K,V> p; int n, i;
            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);
    ..................................
    源码if ((p = tab[i = (n - 1) & hash]) == null)转换:
                tab[i] = newNode(hash, key, value, null);
     int n = table.length ;
     int hash = hash( key ) ;
     table[ ( n - 1) & hash ] = newNode( hash, key, value, null ) ; // 不是源码

         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
         Hashtable 中的实现:

    部分源码:

       public synchronized V put(K key, V value) {
            //确保value 不能为空
            if (value == null) {
                throw new NullPointerException();
            }
    
            // 确保key不在哈希表
            Entry<?,?> tab[] = table;
            int hash = key.hashCode();
            int index = (hash & 0x7FFFFFFF) % tab.length;
            @SuppressWarnings("unchecked")
    ......................................
      int hash = key.hashCode(); // key 必须不能为 null,不然抛出空指针异常
      int index = (hash & 0x7FFFFFFF) % tab.length;

    2、HashMap 不支持线程安全的 ( 所有的方法都没有 synchronized 修饰 )。
         Hashtable 支持线程安全的 ( 几乎所有的方法都被 synchronized 修饰 )。
    3、HashMap 的迭代器是 快速失败 ( fail-fast ) 的 , Hashtable 的迭代器是 也是 快速失败 ( fail-fast ) 的,
          但是 由 Hashtable 的 键 ( keys() ) 和 元素 ( elements() ) 方法返回的 Enumeration 不 是快速失败的,是安全失败。

    注意,迭代器的快速失败行为不能得到保证,一般来说,存在非同步的并发修改时,不可能作出任何坚决的保证。快速失败迭代器尽最大努力抛出 ConcurrentModificationException(此异常不一定抛出)

    快速失败测试案例:

    package ecut.collection;
    
    import java.util.HashMap;
    import java.util.Iterator;
    import java.util.Set;
    
    public class FailFastTest {
    
        public static void main(String[] args) {
            
            HashMap<String,Integer> map = new HashMap<>();
            
            map.put( "炒粉" , 3 );
            map.put( "炒面" , 5 );
            map.put( "包子" , 1 );
            map.put( "皮蛋瘦肉粥" , 2 );
            
            Set<String> keySet = map.keySet();
            
            Iterator<String> itor = keySet.iterator();
            
            while( itor.hasNext() ){
                String key = itor.next() ;
                Integer value = map.get( key );
                System.out.println( key + " : " + value );
                // map.put( "罗玉凤" , 250 ); // 尽最大可能抛出 ConcurrentModificationException
                // map.remove( "包子" ); // 尽最大可能抛出 ConcurrentModificationException
                if( key.equals( "包子" )){
                    itor.remove(); // 不抛出 ConcurrentModificationException
                }
            }
            
            System.out.println( "~~~~~~~~~~~~~~~" );
            
            itor = keySet.iterator();
    
            while (itor.hasNext()) {
                String key = itor.next();
                Integer value = map.get(key);
                System.out.println(key + " : " + value);
            }
            
        }
    
    }

    运行结果如下:

    包子 : 1
    炒粉 : 3
    炒面 : 5
    皮蛋瘦肉粥 : 2
    ~~~~~~~~~~~~~~~
    炒粉 : 3
    炒面 : 5
    皮蛋瘦肉粥 : 2

    添加和移除元素触发了因快速失败抛出的异常ConcurrentModificationException,是因为我们在使用迭代器对map里的元素进行迭代的时候,对map的结构进行了修改,map发生更改,迭代器是不允许的。

    快速失败:我们正在使用迭代器对可以获得迭代器的集合(map不能获得迭代器但是对应的keyset视图可以获得迭代器),keyset对他进行的迭代的是,如果同时对他进行修改,map的结构发生改变,触发异常ConcurrentModificationException,称之为快速失败。要避免则只能使用迭代器本身的 remove 方法或 add 方法。

    安全失败测试案例:

    package ecut.collection;
    
    import java.util.Enumeration;
    import java.util.Hashtable;
    
    public class FailSafeTest {
    
        public static void main(String[] args) {
            
            Hashtable<String,Integer> table = new Hashtable<>(); //Alt+Shift+R快速修改某个变量的名称
            
            table.put( "炒粉" , 3 );
            table.put( "炒面" , 5 );
            table.put( "包子" , 1 );
            table.put( "皮蛋瘦肉粥" , 2 );
            
            Enumeration<String> keys = table.keys();
            
            while( keys.hasMoreElements() ){
                String key = keys.nextElement();
                Integer value = table.get( key );
                System.out.println( key + " : " + value  );
                table.put( "拌面" , 3 );
            }
    
            
            System.out.println( "~~~~~~~~~~~~~~~" );
            
            keys = table.keys();
            
            while( keys.hasMoreElements() ){
                String key = keys.nextElement();
                Integer value = table.get( key );
                System.out.println( key + " : " + value  );
            }
            
        }
    
    }

    运行结果如下:

    包子 : 1
    炒粉 : 3
    拌面 : 3
    炒面 : 5
    皮蛋瘦肉粥 : 2
    ~~~~~~~~~~~~~~~
    包子 : 1
    炒粉 : 3
    拌面 : 3
    炒面 : 5
    皮蛋瘦肉粥 : 2

    次要的区别:
    4、父类不同: Hashtable 的 父类 Dictionary ,而 HashMap 的父类是 AbstractMap。
    5、对 键集、值集、键值对集 的处理方式不同:
         HashMap 和 Hashtable 都具有:
                Set<K>    keySet()  返回所有的 key 组成的 Set 集合
               Collection<V>    values() 返回所有的 value 组成的 Collection 集合
               Set<Map.Entry<K,V>>    entrySet() 返回所有的 entry 对应的 Set 集合
        Hashtable 还具有:
                Enumeration<K>    keys()  返回此哈希表中的键的枚举
                Enumeration<V>    elements()   返回此哈希表中的值的枚举
    6、因为 Hashtable 是线程安全的,因此 在 单线程环 境下比 HashMap 要慢。

    7、HashMap允许存在一个为null的key,多个为null的value 。Hashtable的key和value都不允许为null。

    SortedMap接口特点

    1、进一步提供关于键的总体排序 的 Map。该映射是根据其键的自然顺序进行排序的,或者根据通常在创建有序映射时提供的 Comparator 进行排序。
    2、对有序映射的 collection 视图(由 entrySetkeySetvalues 方法返回)进行迭代时,此顺序就会反映出来。
    3、要采用此排序方式,还需要提供一些其他操作(此接口是 SortedSet 的对应映射)。

    SortedMap接口主要实现类

    1、java.util.TreeMap<K,V>: SortedMap接口的的实现类

    • NavigableMap类继承了SortedMap,TreeMap实现了NavigableMap。
    •  基于红黑树( Red-Black tree )来实现 Map 集合 ( 同时实现了  NavigableMap 、SortedMap )。
    • 该映射根据其键 ( key ,得实现Comparable接口,支持自然排序) 的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法。
    • TreeMap 的 key 不可以为 null ,而 value 可以为 null。
    • TreeMap 中的键值对根据 键来排序 ( 根据自然顺序 或 比较器排序)。

    TreeMap测试案例:

    package ecut.collection;
    
    public class Fox implements Comparable<Fox> {
    
        private Integer id ; // 对象标识符 ( Object Identifier )
        private String name ;
        
        public Fox(Integer id, String name) {
            super();
            this.id = id;
            this.name = name;
        }
    
        @Override
        public int compareTo(Fox o) {
            if( this.id != null && o.id != null ) {
                if( this.id > o.id ){
                    return 1 ;
                } else if( this.id == o.id ){
                    return 0 ;
                } else {
                    return -1 ;
                }
            }
            return 0;
        }
        
        @Override
        public String toString() {
            return "[id=" + id + ", name=" + name + "]";
        }
    
        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;
        }
    
    }
    package ecut.collection;
    
    import java.util.TreeMap;
    
    public class TreeMapTest1 {
    
        public static void main(String[] args) {
            
            // 创建 TreeMap 对象时,如果没有指定比较器,则默认按照 key 的 自然顺序排序
            TreeMap<Fox,Double> tm = new TreeMap<>();
            
            Fox fox1 = new Fox( 100 , "妲己" );
            tm.put( fox1 ,  1.0 );
            
            Fox fox2 = new Fox( 200 , "褒姒" );
            tm.put( fox2 ,  0.8 );
            
            System.out.println( tm );
            
            Fox fox3 = new Fox( 150 , "金角大王他干娘" );
            tm.put( fox3 ,  100.0 );
            
            System.out.println( tm );
            
        }
    
    }

    运行结果如下:

    {[id=100, name=妲己]=1.0, [id=200, name=褒姒]=0.8}
    {[id=100, name=妲己]=1.0, [id=150, name=金角大王他干娘]=100.0, [id=200, name=褒姒]=0.8}

    TreeMap测试案例:

    package ecut.collection;
    
    import java.util.Comparator;
    import java.util.TreeMap;
    
    public class TreeMapTest2 {
    
        public static void main(String[] args) {
            
            Comparator<Fox> c = new Comparator<Fox>(){
                @Override
                public int compare( Fox o1, Fox o2 ) {
                    if( o1 != null && o2 != null && o1.getName() != null && o2.getName() != null ){
                        String name1 = o1.getName() ;
                        String name2 = o2.getName() ;
                        return  name1.compareTo( name2 ) ;
                    }
                    return 0;
                }
            };
            
            // 创建 TreeMap 对象时,指定比较器,则按照 比较器 排序
            TreeMap<Fox,Double> tm = new TreeMap<>( c );
            
            Fox fox3 = new Fox( 150 , "金角大王他干娘" );
            tm.put( fox3 ,  100.0 );
            System.out.println( tm );
            
            Fox fox2 = new Fox( 200 , "褒姒" );
            tm.put( fox2 ,  0.8 );
            System.out.println( tm );
            
            Fox fox1 = new Fox( 100 , "妲己" );
            tm.put( fox1 ,  1.0 );
            System.out.println( tm );
            
        }
    
    }

    运行结果如下:

    {[id=150, name=金角大王他干娘]=100.0}
    {[id=200, name=褒姒]=0.8, [id=150, name=金角大王他干娘]=100.0}
    {[id=100, name=妲己]=1.0, [id=200, name=褒姒]=0.8, [id=150, name=金角大王他干娘]=100.0}

    ConcurrentMap接口特点

    1、,它可以处理并发访问。
    2、ConcurrentMap除了继承自java.util.Map的方法,还有一些自己的原子方法。

    ConcurrentMap接口主要实现类

    1、java.util.concurrent.ConcurrentHashMap<K,V>: ConcurrentMap接口的的实现类

    • ConcurrentMap接口的的实现类。
    • ConcurrentHashMap 是 安全失败 ( fail-safe )。
    •  ConcurrentHashMap 为了提高并发性能而出现的map集合,支持完全的并发操作,默认支持16个线程。
    • 不会 抛出 ConcurrentModificationException,因此不保证数据的一致性。

    ConcurrentHashMap测试案例:

    package ecut.collection;
    
    import java.util.Enumeration;
    import java.util.Iterator;
    import java.util.Map;
    import java.util.Set;
    import java.util.concurrent.ConcurrentHashMap;
    
    /**
     * ConcurrentHashMap 是 安全失败 ( fail-safe )
     * ConcurrentHashMap 支持完全的并发操作
     */
    public class ConcurrentHashMapTest {
    
        public static void main(String[] args) {
            
            ConcurrentHashMap<String,Integer> map = new ConcurrentHashMap<>();
            
            map.put( "炒粉" , 3 );
            map.put( "炒面" , 5 );
            map.put( "包子" , 1 );
            map.put( "皮蛋瘦肉粥" , 2 );
            
            Set< Map.Entry<String,Integer>  > entrySet = map.entrySet();
            
            Iterator< Map.Entry<String,Integer> > itor = entrySet.iterator();
            
            while( itor.hasNext() ){
                Map.Entry<String,Integer> entry = itor.next();
                System.out.println( entry.getKey() + " : " + entry.getValue() );
                map.put( "拌面" , 3 );
            }
            
            System.out.println( "~~~~~~~~~~~~~~~" );
            
            Enumeration<String> keys = map.keys();
            
            while( keys.hasMoreElements() ){
                String key = keys.nextElement();
                Integer value = map.get( key );
                System.out.println( key + " : " + value  );
                map.remove( "拌面" );
            }
    
        }
    
    }

    运行结果如下:

    包子 : 1
    炒粉 : 3
    拌面 : 3
    炒面 : 5
    皮蛋瘦肉粥 : 2
    ~~~~~~~~~~~~~~~
    包子 : 1
    炒粉 : 3
    炒面 : 5
    皮蛋瘦肉粥 : 2

    转载请于明显处标明出处

    http://www.cnblogs.com/AmyZheng/p/8463722.html

  • 相关阅读:
    selenium手机百度搜索
    selenium模拟手机浏览器
    selenium模拟登录赶集网,手动解决验证码问题
    selenium模拟登录京东,手动解决验证码问题,抓取购物车价格
    selenium模拟登录QQ空间,手动解决验证码问题
    两数之和&N数之和(求教!)
    Linux内核设计与实现——从内核出发
    Linux内核简介
    编程规范
    GIT基础使用
  • 原文地址:https://www.cnblogs.com/AmyZheng/p/8463722.html
Copyright © 2011-2022 走看看