java集合详解

Posted jyy599

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java集合详解相关的知识,希望对你有一定的参考价值。

一、数组和集合的比较

数组不是面向对象的,存在明显的缺陷,集合弥补了数组的缺点,比数组更灵活更实用,而且不同的集合框架类可适用不同场合。如下:

  1. 数组能存放基本数据类型和对象,而集合类存放的都是对象,集合类不能存放基本数据类型。数组和集合存放的对象皆为对象的引用地址。
  2. 数组容易固定无法动态改变,集合类容量动态改变。
  3. 数组无法判断其中实际存有多少元素,length只告诉了数组的容量,而集合的size()可以确切知道元素的个数
  4. 集合有多种实现方式和不同适用场合,不像数组仅采用顺序表方式
  5. 集合以类的形式存在,具有封装、继承、多态等类的特性,通过简单的方法和属性即可实现各种复杂操作,大大提高了软件的开发效率

 

二、Java集合

 技术图片

 

 

 

 

技术图片

 

 

 

Collection和Map,是集合框架的根接口。

Collection的子接口:Collection是所有集合的顶级接口,规定了所有集合都必须具备的相关方法。

 

Set: 接口 ---实现类: HashSet、LinkedHashSet
Set  的子接口SortedSet接口---实现类:TreeSet
List: 接口---实现类: LinkedList,Vector,ArrayList

Collection下面有两个常见的集合类型:
* java.util.List:可重复集,并且有序
* java.util.Set:不可重复集
* Set集合不可以存放重复元素,而元素是否重复是依靠元素
* 自身equals比较的结果而定。

        Collection c = new ArrayList();
        //HashSet是最常用的Set集合实现类
        Collection c = new HashSet();
        boolean add(E e)
        向当前集合中添加给定元素,成功添加后则返回true
        c.add("one");
        c.add("two");
        c.add("three");
        c.add("four");
        c.add("five");
        c.add("five");//Set集合相同元素不能放进去2次
        System.out.println(c);
        int size()
        返回当前集合的元素个数
        int size = c.size();
        boolean isEmpty()
        判断当前集合是否为一个空集
        集合不含有任何元素(size=0)
        boolean isEmpty = c.isEmpty();
        清空集合元素
        c.clear();
   

 


           删除集合元素
           boolean remove(E e)
           从集合中删除给定元素,删除的是集合中与给定元素equals
           比较为true的元素。

        Collection c = new ArrayList();
        c.add(new Point(1,2));
        c.add(new Point(3,4));
        c.add(new Point(5,6));
        c.add(new Point(7,8));
        c.add(new Point(1,2));
        System.out.println(c);// [(1,2), (3,4), (5,6), (7,8), (1,2)]
        Point p = new Point(1,2); c.remove(p); 
System.out.println(c);
// [(3,4), (5,6), (7,8), (1,2)]

 

        Collection c1 = new ArrayList();
        c1.add("java");
        c1.add("c++");
        c1.add("c");
        System.out.println(c1);//[java, c++, c]
        Collection c2 = new HashSet();
        c2.add("android");
        c2.add("ios");
        c2.add("java");
        System.out.println(c2);//[java, android, ios]
        /*
         * addAll将给定集合中的所有元素添加到当前
         * 集合中
         */
        c1.addAll(c2);
        System.out.println(c1);//[java, c++, c, java, android, ios]
     
        Collection c3 = new ArrayList();
        c3.add("c++");
        c3.add("android");
      //c3.add("php"); 
        /*
         * boolean contains(E e)
         * 判断当前集合是否包含给定元素,判断也是根据元素
         * equals比较的。
         * 
         * boolean containsAll(Collection c)
         * 判断当前集合是否包含给定集合的所有元素。
         */
        boolean contains = c1.contains("java");
        System.out.println("包含:"+contains);//包含:true
        contains = c1.containsAll(c3);
        System.out.println("全包含:"+contains);//全包含:true
        /*
         * 删除当前集合中与给定集合的共有元素(删交集)
         */
        c1.removeAll(c3);
        System.out.println(c1);//[java, c, java, ios]
 集合只能存放引用类型元素,并且保存的也是元素的引用(地址)

遍历集合元素

Collection提供了统一的遍历集合元素的操作:迭代器模式
 Iterator iterator()
 该方法会返回一个用于遍历该集合的迭代器,使用这个迭代器  便可以遍历当前集合元素
 java.util.Iterator接口
它是所有迭代器的顶级接口,不同的集合都实现了一个用于遍历 自身元素的迭代器实现类。
迭代器遍历集合遵循的原则为:问,取,删 。其中删除元素不是遍历过程中的必要操作。

        Collection c = new ArrayList();
        c.add("one");
        c.add("#");
        c.add("two");
        c.add("#");
        c.add("three");
        c.add("#");
        c.add("four");
        c.add("#");
        c.add("five");
        System.out.println(c);
        //获取迭代器
        Iterator it = c.iterator();
        /*
         * boolean hasNext()
         * 判断集合是否还有下一个元素可以迭代
         */
        while(it.hasNext()) {
            /*
             * E next()
             * 获取集合下一个元素
             */
            String str = (String)it.next();
            System.out.println(str);
            if("#".equals(str)) {
                /*
                 * 迭代器要求在遍历的过程中不得使用集合
                 * 的方法增删元素,否则遍历时会抛出异常
                 */
               //c.remove(str);
                /*
                 * 迭代器也提供了remove方法,删除是通过
                 * next方法取出的元素。
                 */
                it.remove();
            }
        }
        
        System.out.println(c);

 增强for循环:

    JDK5之后推出了一个特性:增强for循环 也称为新循环,for each.
    新循环不是用来取代传统for循环的操作,而仅用来遍历集合 或数组使用。

      编译器会将新循环遍历集合改为迭代器遍历  所以在遍历的过程中不能通过集合的方法增删元素

ArrayList:

底层是Object数组,所以ArrayList具有数组的查询速度快的优点以及增删速度慢的缺点。

而在LinkedList的底层是一种双向循环链表。在此链表上每一个数据节点都由三部分组成:前指针(指向前面的节点的位置),数据,后指针(指向后面的节点的位置)。最后一个节点的后指针指向第一个节点的前指针,形成一个循环。

双向循环链表的查询效率低但是增删效率高。

ArrayList和LinkedList在用法上没有区别,但是在功能上还是有区别的。

ArrayList自动扩充机制:

实现机制:ArrayList.ensureCapacity(int minCapacity)
首先得到当前elementData 属性的长度oldCapacity。
然后通过判断oldCapacity和minCapacity参数谁大来决定是否需要扩容, 如果minCapacity大于 oldCapacity,那么我们就对当前的List对象进行扩容。 扩容的的策略为:取(oldCapacity * 3)/2 + 1和minCapacity之间更大的那个。然后使用数组拷 贝的方法,把以前存放的数据转移到新的数组对象中 如果minCapacity不大于oldCapacity那么就不进行扩容。

LinkedList:

LinkedList是采用双向循环链表实现的。

利用LinkedList实现栈(stack)、队列(queue)、双向队列(double-ended queue )。 它具有方法addFirst()、addLast()、getFirst()、getLast()、removeFirst()、removeLast()等。

经常用在增删操作较多而查询操作很少的情况下:

队列和堆栈。

队列:先进先出的数据结构。

栈:后进先出的数据结构。

注意:使用栈的时候一定不能提供方法让不是最后一个元素的元素获得出栈的机会。

用LinkedList实现队列:

队列(Queue)是限定所有的插入只能在表的一端进行,而所有的删除都在表的另一端进行的线性表。
表中允许插入的一端称为队尾(Rear),允许删除的一端称为队头(Front)。
队列的操作是按先进先出(FIFO)的原则进行的。
队列的物理存储可以用顺序存储结构,也可以用链式存储结构。

用LinkedList实现栈:

栈(Stack)也是一种特殊的线性表,是一种后进先出(LIFO)的结构。
栈是限定仅在表尾进行插入和删除运算的线性表,表尾称为栈顶(top),表头称为栈底(bottom)。
栈的物理存储可以用顺序存储结构,也可以用链式存储结构。

 

List:
   java.util.List  线性表
 * List是Collection 常用的子接口,是一个可以重复的集合 并且特点是有序。提供了一套通过下标操作元素的方法。
 * 
 * 常见实现类:
 * java.util.ArrayList:内部使用数组实现
 * java.util.LinkedList:内部使用链表实现
 * 
 * ArrayList 查询性能更好,增删元素慢
 * LinkedList 增删元素性能好,尤其首尾增删元素性能最好 但是查询元素效率慢  对性能没有极端苛刻情况下通常使用ArrayList; 

void add(int index, Object element) :添加对象element到位置index上
boolean addAll(int index, Collection collection) :在index位置后添加容器collection中所有的元素
Object get(int index) :取出下标为index的位置的元素
int indexOf(Object element) :查找对象element 在List中第一次出现的位置
int lastIndexOf(Object element) :查找对象element 在List中最后出现的位置
Object remove(int index) :删除index位置上的元素 
ListIterator listIterator(int startIndex) :返回一个ListIterator 跌代器,开始位置为startIndex 
List subList(int fromIndex, int toIndex) :返回一个子列表List ,元素存放为从 fromIndex 到toIndex之前的一个元素

 

 

数组转换为List集合:

      数组转换为集合
      数组的工具类:Arrays提供了一个静态方法asList,可以将 给定的数组转换为一个List集合。

String[] array = {"one","two","three","four"};
List<String> list = Arrays.asList(array);
System.out.println(list); //[one, two, three, four]
/*
* 对该集合的操作就是对原数组的操作
*/
list.set(1, "2");
System.out.println(list); //[one, 2, three, four]
System.out.println(Arrays.toString(array)); // [one, 2, three, four]
/*
* 由于数组是定长的,所以集合增删元素的操作是
* 不支持的,会抛出异常。
*/
// list.add("five");
// System.out.println(list);
// System.out.println(Arrays.toString(array));
/*
* 若想对数组转换的集合元素增删,那只能自行创建
* 一个集合并包含该集合元素。
* 所有集合都提供了一个参数为Collection的构造
* 方法,可有在创建当前集合的同时包含给定集合中
* 的所有元素。
*/
List<String> list1 = new ArrayList<>(list);
System.out.println(list1); //[one, 2, three, four]
list1.add("five");
System.out.println(list1); //[one, 2, three, four, five]

 

 

集合转换为数组:

           集合转换为数组
         Collection定义了一个方法:toArray,可以将当前集合 转换为一个数组

        Collection<String> c = new ArrayList<>();
        c.add("one");
        c.add("two");
        c.add("three");
        c.add("four");
        c.add("five");
        System.out.println(c); //[one, two, three, four, five]
        
     // Object[] arr = c.toArray();
        
        String[] arr = c.toArray(new String[c.size()]); 
        System.out.println(arr.length); //5
        System.out.println(Arrays.toString(arr)); //[one, two, three, four, five]

 

 

队列:

   队列
   队列是经典的数据结构之一,队列可以保存一组元素,但是存取元素必须遵循先进先出。

   java.util.Queue 接口
   Queue继承自Collection,队列定义了存取元素的相关方法。
   常用实现类:java.util.LinkedList


Queue<String> queue = new LinkedList<>(); //入队操作,将元素添加到队列末尾 queue.offer("one"); queue.offer("two"); queue.offer("three"); queue.offer("four"); System.out.println(queue); //[one, two, three, four] /* * poll 出队操作 * 获取并删除当前队列的队首元素 */ String str = queue.poll(); System.out.println(str); //one System.out.println(queue); //[two, three, four] /* * peek 引用队首元素 * 获取队首元素,但是不做删除操作 */ str = queue.peek(); System.out.println(str); //two System.out.println(queue); //[two, three, four] /* * 遍历队列 * 使用迭代器遍历后,元素还是在队列当中的。 */ for(String s : queue) { System.out.println(s); } System.out.println(queue); //[two, three, four]

 

 

双端队列:

          java.util.Deque 双端队列
         Deque继承自Queue
         双端队列是两端都可以进出队的队列。常用实现类:LinkedList

        Deque<String> deque = new LinkedList<>();
        deque.offer("one");
        deque.offer("two");
        deque.offer("three");
        System.out.println(deque); //[one, two, three]
        
        deque.offerFirst("four");
        System.out.println(deque); //[four, one, two, three]
        
        deque.offerLast("five");
        System.out.println(deque); //[four, one, two, three, five]
        
        
        String str = deque.poll();
        System.out.println(str); //four
        System.out.println(deque); //[one, two, three, five]
        
        str = deque.pollFirst();
        System.out.println(str); //one
        System.out.println(deque); //[two, three, five]
        
        str = deque.pollLast();
        System.out.println(str); //five
        System.out.println(deque); //[two, three]

 

 

栈:

          栈结构
         栈也是经典的数据结构之一
         栈可以保存一组元素,但是存取必须遵循先进后出原则。
         通常使用栈完成"后退"这样的功能

        Deque<String> stack = new LinkedList<>();
        stack.push("one");
        stack.push("two");
        stack.push("three");
        stack.push("four");
        stack.push("five");
        System.out.println(stack); //[five, four, three, two, one]
        
        String str = stack.pop();
        System.out.println(str); //five
        System.out.println(stack); //[four, three, two, one]
        
        //遍历操作
        for(String s : stack) {
            System.out.println(s);
        }
        System.out.println(stack); //[four, three, two, one]
        
        //用pop方法遍历栈
        while(stack.size()>0) {
            String s = stack.pop();
            System.out.println(s); // four three two one
        }
        System.out.println(stack); //[]

 

 

集合排序:

Comparator接口 
当一个类并未实现Comparable,或者不喜欢缺省的Comaparable行为。可以实现Comparator接口
直接实现Comparator的compare接口完成自定义比较类。
例:Arrays.sort(results, new Comparator<RepDataQueryResultVO>() 数组排序 RepDataQueryExecutor
例:Collections.sort(lst,new Comparator<TaskPrintSchemeVO>()

          集合的工具类:java.util.Collections
         其提供了一个静态方法:sort,可以对List集合进行自然排序
         自然排序(从小到大)

        Random random = new Random();
        List<Integer> list = new ArrayList<>();
        for(int i=0;i<10;i++) {
            list.add(random.nextInt(100));
        }
        System.out.println(list); //[56, 60, 17, 60, 26, 58, 13, 65, 25, 61]
        Collections.sort(list);
        System.out.println(list); //[13, 17, 25, 26, 56, 58, 60, 60, 61, 65]
        //乱序
//        Collections.shuffle(list);    
//        System.out.println(list);
        //使用比较器按照数字从大到小排序
        Collections.sort(list, new Comparator<Integer>() {
            public int compare(Integer o1, Integer o2) {
                return o2-o1;
            }    
        });
        System.out.println(list); //[65, 61, 60, 60, 58, 56, 26, 25, 17, 13]

 

 

排序自定义类型元素:

Comparable和Comparator 
Comparable 接口以提供自然排序顺序。
对于那些没有自然顺序的类、或者当您想要一个不同于自然顺序的顺序时,您可以实现Comparator 接口来定义您自己的排序函数。可以将Comparator传递给Collections.sort或Arrays.sort。

        List<Point> list = new ArrayList<>();
        list.add(new Point(5,6));
        list.add(new Point(1,9));
        list.add(new Point(8,7));
        list.add(new Point(4,10));
        list.add(new Point(3,2));
        list.add(new Point(7,5));
        System.out.println(list); //[(5,6), (1,9), (8,7), (4,10), (3,2), (7,5)]
        /*
         * sort方法要求集合元素必须实现Comparable接口。
         * java提供的常用类如:包装类,String等他们都
         * 实现了这个接口,定义了元素之间大小规则。
         * 
         * 但是对于我们自定义类型的元素,不建议实现这个
         * 接口,因为这样的操作对我们的代码有侵入性,不
         * 利于代码维护。
         * 
         */
//        Collections.sort(list);
        /*
         * 重载的sort方法要求再传入一个参数,是Comparator接口的
         * 实现类。
         * 该接口是用来单独定义一个比较器,为集合元素制定比较大小
         * 的规则。
         * 这样一来,该sort方法会将集合元素利用该比较器的比较规则
         * 两两比较,并按照从小到大的顺序将集合排序。
         */
        Collections.sort(list, new Comparator<Point>() {
            /*
             * compare方法用来定义参数o1,o2的比较大小规则,
             * 返回值是一个整数,该值关注的是取值范围
             * 当返回值>0时表示:o1>o2
             * 当返回值<0时表示:o1<o2
             * 当返回值=0时表示:o1==o2
             */
            public int compare(Point o1, Point o2) {
                int len1 = o1.getX()*o1.getX()+o1.getY()*o1.getY();
                int len2 = o2.getX()*o2.getX()+o2.getY()*o2.getY();
                return len1-len2;
            }
        });
        
        System.out.println(list); //[(3,2), (5,6), (7,5), (1,9), (8,7), (4,10)]

 

 排序字符串:

        字符串String已经实现了Comparable接口定义了比较大小的规则

List<String> list = new ArrayList<>(); list.add("安其拉"); list.add("王者荣耀"); list.add("赵云"); System.out.println(list); //[安其拉, 王者荣耀, 赵云] // Collections.sort(list); /* * 对于已经实现了Comparable接口,但是其自身定义的比较 * 规则不满足我们排序需求时,也可以额外提供一个比较器 * 并按照该规则比较后排序。 */ Collections.sort(list, new Comparator<String>() { public int compare(String o1, String o2) { return o1.length()-o2.length(); } }); System.out.println(list); //[赵云, 安其拉, 王者荣耀]

 

 

Vector:

(与ArrayList相似,区别是Vector是重量级的组件,使用使消耗的资源比较多。)

结论:在考虑并发的情况下用Vector(保证线程的安全)。

在不考虑并发的情况下用ArrayList(不能保证线程的安全)。

Set集合

扩展Collection接口
无序集合,不允许存放重复的元素;允许使用null元素
对 add()、equals() 和 hashCode() 方法添加了限制
HashSet和TreeSet是Set的实现

Set
 
hashSet-linkedHashSet
SortedSet
TreeSet

HashSet 的后台有一个HashMap;初始化后台容量;只不过生成一个HashSet的话,系统只提供key的访问; 如果有两个Key重复,那么会覆盖之前的;

实现类 :

HashSet:equals返回true,hashCode返回相同的整数;哈希表;存储的数据是无序的。
LinkedHashSet:此实现与HashSet的不同之外在于,后者维护着一个运行于所有条目的双重链接列表。存储的数据是有序的。

HashSet类

HashSet类直接实现了Set接口,其底层其实是包装了一个HashMap去实现的。HashSet采用HashCode算法来存取集合中的元素,因此具有比较好的读取和查找性能。

HashSet的特征:

  • 不仅不能保证元素插入的顺序,而且在元素在以后的顺序中也可能变化(这是由HashSet按HashCode存储对象(元素)决定的,对象变化则可能导致HashCode变化)
  • HashSet是线程非安全的
  • HashSet元素值可以为NULL

HashSet常用方法:

  • public boolean contains(Object o) :如果set包含指定元素,返回true
  • public Iterator iterator()返回set中元素的迭代器
  • public Object[] toArray() :返回包含set中所有元素的数组public Object[] toArray(Object[] a) :返回包含set中所有元素的数组,返回数组的运行时类型是指定数组的运行时类型
  • public boolean add(Object o) :如果set中不存在指定元素,则向set加入
  • public boolean remove(Object o) :如果set中存在指定元素,则从set中删除
  • public boolean removeAll(Collection c) :如果set包含指定集合,则从set中删除指定集合的所有元素
  • public boolean containsAll(Collection c) :如果set包含指定集合的所有元素,返回true。如果指定集合也是一个set,只有是当前set的子集时,方法返回true

实现Set接口的HashSet,依靠HashMap来实现的。 我们应该为要存放到散列表的各个对象定义hashCode()和equals()。

HashSet的equals和HashCode:

前面说过,Set集合是不允许重复元素的,否则将会引发各种奇怪的问题。那么HashSet如何判断元素重复呢?
HashSet需要同时通过equals和HashCode来判断两个元素是否相等,具体规则是,如果两个元素通过equals为true,并且两个元素的hashCode相等,则这两个元素相等(即重复)。
所以如果要重写保存在HashSet中的对象的equals方法,也要重写hashCode方法,重写前后hashCode返回的结果相等(即保证保存在同一个位置)。所有参与计算 hashCode() 返回值的关键属性,都应该用于作为 equals() 比较的标准。
试想如果重写了equals方法但不重写hashCode方法,即相同equals结果的两个对象将会被HashSet当作两个元素保存起来,这与我们设计HashSet的初衷不符(元素不重复)。
另外如果两个元素哈市Code相等但equals结果不为true,HashSet会将这两个元素保存在同一个位置,并将超过一个的元素以链表方式保存,这将影响HashSet的效率。
如果重写了equals方法但没有重写hashCode方法,则HashSet可能无法正常工作;

 

如何达到不能存在重复元素的目的?

“键”就是我们要存入的对象,“值”则是一个常量。这样可以确保,我们所需要的存储的信息是“键”。而“键”在Map中是不能重复的,这就保证了我们存入Set中的所有的元素都不重复。
HashSet如何过滤重复元素
调用元素HashCode获得哈希码--》判断哈希码是否相等,不相等则录入 ---》相等则判断equals()后是否相等,不相等在进行 hashcode录入,相等不录入

LinkedHashSet的特征:

LinkedHashSet是HashSet的一个子类,LinkedHashSet也根据HashCode的值来决定元素的存储位置,但同时它还用一个链表来维护元素的插入顺序,插入的时候即要计算hashCode又要维护链表,而遍历的时候只需要按链表来访问元素。

 

LinkedHashSet本质上也是从LinkedHashMap而来,LinkedHashSet的所有方法都继承自HashSet, 而它能维持元素的插入顺序的性质则继承自LinkedHashMap.

下面是一个LinkedHashSet维持元素插入顺序的例子:

        Set set = new LinkedHashSet();
        set.add("abc");
        set.add("efg");
        set.add("hjk");
        System.out.println(set); //[abc, efg, hjk]
        set.remove(new String("abc"));
        set.add("abc");
        System.out.println(set); //[efg, hjk, abc]

 

几种Set的比较:
HashSet外部无序地遍历成员。 
成员可为任意Object子类的对象,但如果覆盖了equals方法,同时注意修改hashCode方法。 
TreeSet外部有序地遍历成员; 
附加实现了SortedSet, 支持子集等要求顺序的操作 
成员要求实现Comparable接口,或者使用Comparator构造TreeSet。成员一般为同一类型。 
LinkedHashSet外部按成员的插入顺序遍历成员 
成员与HashSet成员类似 
HashSet是基于Hash算法实现的,其性能通常都优于TreeSet。我们通常都应该使用HashSet,在我们需要排序的功能时,我们才使用TreeSet。

HashSet的元素存放顺序和我们添加进去时候的顺序没有任何关系,而LinkedHashSet 则保持元素的添加顺序。TreeSet则是对我们的Set中的元素进行排序存放。

一般来说,当您要从集合中以有序的方式抽取元素时,TreeSet实现就会有用处。为了能顺利进行,添加到 TreeSet 的元素必须是可排序的。 而您同样需要对添加到TreeSet中的类对象实现 Comparable 接口的支持。一般说来,先把元素添加到 HashSet,再把集合转换为 TreeSet 来进行有序遍历会更快。

各种Set集合性能分析

    • HashSet和TreeSet是Set集合中用得最多的I集合。HashSet总是比TreeSet集合性能好,因为HashSet不需要额维护元素的顺序。
    • LinkedHashSet需要用额外的链表维护元素的插入顺序,因此在插入时性能比HashSet低,但在迭代访问(遍历)时性能更高。因为插入的时候即要计算hashCode又要维护链表,而遍历的时候只需要按链表来访问元素。
    • EnumSet元素是所有Set元素中性能最好的,但是它只能保存Enum类型的元素

 

Map:

集合框架的第二类接口树。
它提供了一组键值的映射。其中存储的每个对象都有一个相应的关键字(key),关键字决定了对象在Map中的存储位置。
关键字应该是唯一的,每个key 只能映射一个value。

实现类:
HashMap、TreeMap、LinkedHashMap、Hashtable等

  • HashMap:键值对,key不能重复,但是value可以重复;key的实现就是HashSet;value对应着放;允许null的键或值;
  • Hashtable:线程安全的,不允许null的键或值;
  • Properties::key和value都是String类型,用来读配置文件;
  • TreeMap:对key排好序的Map; key 就是TreeSet, value对应每个key; key要实现Comparable接口或TreeMap有自己的构造器;
  • LinkedHashMap: 此实现与HashMap的不同之处在于,后者维护着一个运行于所有条目的双重链接列表。存储的数据是有序的。
HashMap:
  • Map 主要用于存储键(key)值(value)对,根据键得到值,因此键不允许重复,但允许值重复。
  • HashMap 是一个最常用的Map,它根据键的HashCode值存储数据,根据键可以直接获取它的值,具有很快的访问速度。
  • HashMap最多只允许一条记录的键为Null;允许多条记录的值为 Null;
  • HashMap不支持线程的同步,即任一时刻可以有多个线程同时写HashMap;可能会导致数据的不一致。如果需要同步,可以用 Collections的synchronizedMap方法使HashMap具有同步的能力。
HashMap实现原理---散列

Hash哈希算法的意义在于提供了一种快速存取数据的方法,它用一种算法建立键值与真实值之间的对应关系。散列表又称为哈希表。散列表算法的基本思想是:以结点的关键字为自变量,通过一定的函数关系(散列函数)计算出对应的函数值,以这个值作为该结点存储在散列表中地址。
当散列表中的元素存放太满,就必须进行再散列,将产生一个新的散列表,所有元素存放到新的散列表中,原先的散列表将被删除。在Java语言中,通过负载因子(load factor)来决定何时对散列表进行再散列。例如:如果负载因子0.75,当散列表中已经有75%位置已经放满,那么将进行再散列。
负载因子越高(越接近1.0),内存的使用效率越高,元素的寻找时间越长。负载因子越低(越接近0.0),元素的寻找时间越短,内存浪费越多。

何时需重写equals?

当一个类有自己特有的“逻辑相等”概念(不同于对象身份的概念);
Object类仅仅提供了一个对引用的比较,如果两个引用不是同一个那就返回false,这是无法满足大多数对象比较的需要的,所以要覆盖;
使用==操作符检查实参是否为指向对象的引用”
使用instanceof操作符检查实参是否为正确的类型
把实参转换到正确的类型;
对于该类中每一个“关键”域,检查实参中的域与当前对象中对应的域值是否匹配。对于既不是float也不是double类型的基本类型的域,可以使用==操作符进行比较;对于对象引用类型的域,可以递归地调用所引用的对象的equals方法,对于float和double类型的域,先转换成int或long类型的值,然后使用==操作符比较;
当你编写完成了equals方法之后,应该问自己三个问题:它是否是对称的、传 递的、一致的? 如果答案是否定的,那么请找到 这些特性未能满足的原因,再修改equals方法的代码

equals()和hashCode()同时覆写

尤其强调当一个对象被当作键值(或索引)来使用的时候要重写这两个方法;
覆写equals后,两个不同实例可能在逻辑上相等,但是根据Object.hashCode方法却产生不同的散列码,违反“相等的对象必须具有相等的散列码”。
导致,当你用其中的一个作为键保存到hashMap、hasoTable或hashSet中,再以“相等的”找另 一个作为键值去查找他们的时候,则根本找不到

不同类型的hashCode取值

  • 如果该域是布尔型的,计算(f?0:1)
  • 如果是char,short,byte或int,计算(int)f
  • 如果是long类型,计算(int)(f^(f>>>32))
  • 如果是float类型,计算Float.floatToIntBits(f)
  • 如果是double类型,计算Dobule.doubleToLongBits(f)
  • 如果该域是一个对象引用,递归调用hashCode
  • 如果该域是一个数组,则把每个元素当做单独的域来处理,对每个重要的元素计算一个散列码

Map集合比较:

  • HashMap的存入顺序和输出顺序无关。
  • LinkedHashMap 则保留了键值对的存入顺序。
  • TreeMap则是对Map中的元素进行排序。
  • 因为HashMap和LinkedHashMap 存储数据的速度比直接使用TreeMap 要快,存取效率要高。
  • 当完成了所有的元素的存放后,我们再对整个的Map中的元素进行排序。这样可以提高整个程序的运行的效率,缩短执行时间。

注意:TreeMap中是根据键(Key)进行排序的。而如果我们要使用TreeMap来进行正常的排序的话,Key 中存放的对象必须实现Comparable 接口。

Map常用方法:

  • Object put(Object key,Object value):用来存放一个键-值对Map中
  • Object remove(Object key):根据key(键),移除键-值对,并将值返回
  • void putAll(Map mapping) :将另外一个Map中的元素存入当前的Map中
  • void clear() :清空当前Map中的元素
  • Object get(Object key) :根据key(键)取得对应的值
  • boolean containsKey(Object key) :判断Map中是否存在某键(key)
  • boolean containsValue(Object value):判断Map中是否存在某值(value)
  • public Set keySet() :返回所有的键(key),并使用Set容器存放
  • public Collection values() :返回所有的值(Value),并使用Collection存放
  • public Set entrySet() :返回一个实现 Map.Entry 接口的元素 Set

集合遍历

    1. 增强for循环 for(Obj o:c){syso(o)}
    2. 使用iterator , Iterator it=c.iterator;
      while(it.hasNext()){Object o = it.next()}
    3. 普通循环:for(Iterator it=c.iterator();it.hasNext();){it.next() }

java.util.Map 查找表
Map是java中非常经典的数据结构之一

Map的结构看起来像是一个多行两列的表格,其中左列称为:Key
右列称为:Value
Map总是以Key-Value对的形式保存数据的。并且Map提供了根据Key获取其对应的Value的查找方法。

Map有一个要求:Key是不允许重复的(Key的equals方法决定)

常用实现类:java.util.HashMap
HashMap称为散列表或哈希表,使用散列算法实现的Map,是当今世界上查询速度最快的数据结构。其查询速度不受数据量影响,现如今所有网站的缓存都是用HashMap来实现。

        Map<String,Integer> map = new HashMap<>();
        /*
         * V put(K k,V v)
         * 将给定的key,value对保存到Map中
         * 若给定的Key在当前Map中已经存在,则是替换value
         * 操作,那么返回值就是原Key对应的Value
         * 否则返回值为null
         */
        Integer num = map.put("语文", 99);
        System.out.println(num); //null
        map.put("数学", 98);
        map.put("英语", 97);
        map.put("物理", 96);
        map.put("化学", 99);
        System.out.println(map); //{物理=96, 数学=98, 化学=99, 语文=99, 英语=97}    
        num = map.put("语文", 88);
        System.out.println(map); //{物理=96, 数学=98, 化学=99, 语文=88, 英语=97}
        System.out.println(num); //99
        
        /*
         * V get(Object key)
         * 根据给定的key获取对应的value,若给定的key在
         * Map中不存在,则返回值为null。
         */
        num = map.get("数学");
        System.out.println("数学:"+num); //数学:98
        num = map.get("体育");
        System.out.println("体育:"+num); //体育:null
        
        int size = map.size();
        System.out.println("size:"+size); //size:5
        
        /*
         * V remove(Object key)
         * 删除给定的key所对应的键值对
         * 返回值为该key对应的value。
         */
        num = map.remove("英语");
        System.out.println(map); //{物理=96, 数学=98, 化学=99, 语文=88}
        
        /*
         * boolean containsKey(Object key)
         * boolean containsValue(Object value)
         * 判断当前Map是否包含给定的key或value
         * 是否包含还是依据元素自身equals比较的结果
         */
        boolean ck = map.containsKey("语文");
        System.out.println("包含key:"+ck); //包含key:true    
        boolean cv = map.containsValue(97);
        System.out.println("包含value:"+cv); //包含value:false

 

 

Map的遍历:

  Map的遍历
* 遍历Map有三种方式:
* 遍历所有的key
* 遍历所有的key-value对
* 遍历所有的value(相对不常用)

        Map<String,Integer> map = new HashMap<>();
        map.put("语文", 99);
        map.put("数学", 98);
        map.put("英语", 97);
        map.put("物理", 96);
        map.put("化学", 99);
        System.out.println(map); //{物理=96, 数学=98, 化学=99, 语文=99, 英语=97}
        /*
         * Set keySet()
         * 将当前Map中所有的key以一个Set集合形式返回
         */
        Set<String> keySet = map.keySet();
        for(String key : keySet) {
            System.out.println("key:"+key); //key:物理  key:数学 ......
        }
        
        /*
         * Set<Entry> entrySet()
         * 将当前Map中每一组键值对一个Entry实例形式存放
         * 于Set集合后返回
         * 
         * java.util.Map.Entry
         * Entry的每一个实例用于表示Map中的一组键值对
         * 
         */
        Set<Entry<String,Integer>> entrySet = map.entrySet();
        
        for(Entry<String,Integer> entry:entrySet) {
            String key = entry.getKey();
            Integer value = entry.getValue();
            System.out.println(key+":"+value); //物理:96  数学:98....
        }
        
        /*
         * Collection values()
         * 将当前Map中所有的Value以一个Collection集合的
         * 形式返回。之所以没有用Set集合返回,是因为Map
         * 中的value是可以重复的。
         */
        Collection<Integer> values = map.values();
        for(Integer value : values) {
            System.out.println("value:"+value); //value:96  value:98.....
        }

 

 

总结:

技术图片

 

 

 

  1. ArrayList: 元素单个,效率高,多用于查询
  2. Vector: 元素单个,线程安全,多用于查询
  3. LinkedList:元素单个,多用于插入和删除
  4. HashMap: 元素成对,元素可为空
  5. shTable: 元素成对,线程安全,元素不可为空
HashMap和Hashtable的区别:

HashMap和Hashtable都是java的集合类,都可以用来存放java对象,这是他们的相同点
以下是他们的区别:

    1. 历史原因:
      Hashtable是基于陈旧的Dictionary类的,HashMap是java 1.2引进的Map接口的一个现实。
    2. 同步性:
      hashtable是同步的,这个类中的一些方法保证了Hashtable中的对象是线程安全的,而HashMap则是异步的,因此HashMap中的对象并不是线程安全的,因为同步的要求会影响执行的效率,所以如果你不需要线程安全的结合那么使用HashMap是一个很好的选择,这样可以避免由于同步带来的不必要的性能开销,从而提高效率,我们一般所编写的程序都是异步的,但如果是服务器端的代码除外。
    3. 值:
      HashMap可以让你将空值作为一个表的条目的key或value
      Hashtable是不能放入空值(null)的

 

ArrayList和Vector的区别
ArrayList与Vector都是java的集合类,都是用来存放java对象,这是他们的相同点,
区别:

  1. 同步性: Vector是同步的,这个类的一些方法保证了Vector中的对象的线程安全的,而ArrayList则是异步的,因此ArrayList中的对象并不 是线程安全的,因为同步要求会影响执行的效率,所以你不需要线程安全的集合那么使用ArrayList是一个很好的选择,这样可以避免由于同步带来的不必 要的性能开销。
  2. 数据增长: 从内部实现的机制来讲,ArrayList和Vector都是使用数组(Array)来控制集合中的对象,当你向两种类型中增加元素的时候,如果元素的数目超过了内部数组目前的长度他们都需要扩展内部数组的长度,Vector缺省情况下自动增长原来一倍的数组长度,ArrayList是原来的50%,所以最后你获得的这个集合所占的空间总是比你实际需要的要大,所以如果你要在集合中保存大量的数据,那么使用Vector有一些优势,因为你可以通过设置集合的初始大小来避免不必要的资源开销。

总结:

  1. 如果要求线程安全,使用Vector,Hashtable
  2. 如果不要求线程安全,使用ArrayList,LinkedList,HashMap
  3. 如果要求键值对,则使用HashMap,Hashtable
  4. 如果数据量很大,又要求线程安全考虑Vector

arraylist和linkedlist联系与区别:

  1. ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
  2. 对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。
  3. 对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。 这一点要看实际情况的。若只对单条数据插入或删除,ArrayList的速度反而优于LinkedList。但若是批量随机的插入删除数据,LinkedList的速度大大优于ArrayList. 因为ArrayList每插入一条数据,要移动插入点及之后的所有数据。

HashMap与TreeMap联系与区别:

  1. HashMap通过hashcode对其内容进行快速查找,而TreeMap中所有的元素都保持着某种固定的顺序,如果你需要得到一个有序的结果你就应该使用TreeMap(HashMap中元素的排列顺序是不固定的)。
  2. 在Map 中插入、删除和定位元素,HashMap是最好的选择。但如果您要按自然顺序或自定义顺序遍历键,那么TreeMap会更好。使用HashMap要求添加的键类明确定义了hashCode()和 equals()的实现。

两个map中的元素一样,但顺序不一样,导致hashCode()不一样。
同样做测试:
在HashMap中,同样的值的map,顺序不同,equals时,false;
而在treeMap中,同样的值的map,顺序不同,equals时,true,说明,treeMap在equals()时是整理了顺序了的。

 

以上是关于java集合详解的主要内容,如果未能解决你的问题,请参考以下文章

集合详解及代码测试

Java集合详解2:LinkedList和Queue

Java集合详解4:HashMap和HashTable

Java 集合类详解

Java集合详解7:HashSet,TreeSet与LinkedHashSet

Java集合详解5:深入理解LinkedHashMap和LRU缓存