Fork me on GitHub

JavaSE| 集合

 

集合

l  Collection 层次结构中的根接口。Collection 表示一组对象,这些对象也称为 collection 的元素。一些 collection 允许有重复的元素,而另一些则不允许。一些 collection 是有序的,而另一些则是无序的。JDK 提供更具体的子接口(如 Set 和 List、Queue)实现。此接口通常用来传递 collection,并在需要最大普遍性的地方操作这些 collection。

l  List:有序的 collection(也称为序列)。此接口的用户可以对列表中每个元素的插入位置进行精确地控制。用户可以根据元素的整数索引(在列表中的位置)访问元素,并搜索列表中的元素。

l  Queue:队列通常(但并非一定)以 FIFO(先进先出)的方式排序各个元素。不过优先级队列和 LIFO 队列(或堆栈)例外,前者根据提供的比较器或元素的自然顺序对元素进行排序,后者按 LIFO(后进先出)的方式对元素进行排序。

l  Set:一个不包含重复元素的 collection。更确切地讲,set 不包含满足 e1.equals(e2) 的元素对 e1 和 e2,并且最多包含一个 null 元素。正如其名称所暗示的,此接口模仿了数学上的 set 抽象。

l  SortedSet进一步提供关于元素的总体排序 的 Set。这些元素使用其自然顺序进行排序,或者根据通常在创建有序 set 时提供的 Comparator进行排序。该 set 的迭代器将按元素升序遍历 set。提供了一些附加的操作来利用这种排序。

l  Map:将键映射到值(key,value)的对象。一个映射不能包含重复的键;每个键最多只能映射到一个值。 Map 接口提供三种collection 视图,允许以键集、值集或键-值映射关系集的形式查看某个映射的内容。映射顺序 定义为迭代器在映射的 collection 视图上返回其元素的顺序。某些映射实现可明确保证其顺序,如 TreeMap 类;另一些映射实现则不保证顺序,如 HashMap 类。

l  SortedMap进一步提供关于键的总体排序 的 Map。该映射是根据其键的自然顺序进行排序的,或者根据通常在创建有序映射时提供的 Comparator 进行排序。

数组是一个容器,已经可以存储多个对象,逻辑结构是线性的,顺序的存储结构; 

申请内存:一次申请一大段连续的空间,一旦申请到了,内存就固定了。

存储特点:所有数据存储在这个连续的空间中,数组中的每一个元素都是一个具体的数据(或对象),所有数据都紧密排布,不能有间隔。

数组的操作
    查询:每一个元素都有一个数值下标,可以通过下标瞬间定位到某个元素
    增加:
        先使用total变量辅助记录实际存储元素个数
        从尾部增加:数组名[total++]=新元素
        从其他位置插入:先把index位置开始所有元素后移,然后数组名    [index]=新元素
    删除:先把index后面的元素前移,然后数组名[total--]=null
    改:直接数组名[index]=新元素

数组优点
  按索引查询效率高

数组不足:(添加/ 删除效率低,都涉及到移动元素)
  1)一旦确定就没发修改,如果修改要负责扩容代码;
  2)无法直接获取实际存储了几个对象,需要类似于total的变量辅助。

 

集合:新的容器,只用来装对象,不能用来装基本数据类型的数据
 集合比数组
 (1)类型更丰富,它有各种特征的集合
 (2)无需程序员来编写“扩容”等代码,也不用通过"total"来记录实际的元素的个数

分为两大类:
  (1)Collection:存储一组对象,单身party
  (2)Map:存储“映射关系”的,存储“键值对” (key,value),情侣party,家庭party

Collection:最常用的两个子接口
   List:有序(添加的顺序)的可重复的
   Set:无序(添加的顺序)的不可重复

1. java.util.Collection接口

java.util.Collection:接口
  JDK 不提供此接口的任何直接 实现:它提供更具体的子接口(如 Set 和 List)实现
  
1、添加
 * boolean add(Object obj)
 * boolean addAll(Collection other):    
 *         当前集合 = 当前集合  ∪  other集合
Collection c = new ArrayList();  //多态引用ArrayList是List接口的实现类,List接口是Collection的子接口
 c.add( 对象 );  c.add(12);    //因为12会自动装箱类Integer的对象
 c1.add(c2);             //增加另外一个集合; 如果有重复元素,也会添加进去。集合可以有重复元素;
c.add(c2);//把C2当成一个整体看;  c.addAll(c2);//把c2中的元素,一个一个添加到c中
 

2、删除
 * boolean remove(Object o)  
 * boolean removeAll(Collection other) 
 *         当前集合 = 当前集合  - (当前集合 ∩ other)
 * void clear() 
 c.remove( );
//c.removeAll(c2);//会用c2中的元素,与c中的元素进行equals,如果相同的就把c集合中的元素删除,而c2中的元素不会改变不会被删除。如果移除成功就返回true,否则false


3、修改(没有提供)

4、查找:
 * boolean contains(Object o)  
 * boolean containsAll(Collection c);   c1.containsAll(c2); 如果c1中的元素全部都包含c2的元素,就返回true; c2是否是c1的子集。   
 *         判断c集合是否是当前集合的子集
 * boolean isEmpty() 

  删除、查找等要依据元素的equals方法,如果元素没有重写equals,那么equals就等同于==,要注意元素是否重写equals。

  像String,Integer...等重写了equals


5、获取元素的个数
 * int size() 

6、排序(没有提供)
 * 没有提供的原因,某些集合自带排序功能,而且Collections工具类有排序功能
 * 
7、遍历
 * Iterator<E> iterator() :获取集合自身的迭代器,用于遍历集合用的
 * Object[] toArray() 
 * 
    //用迭代器迭代集合;用于按条件删除或修改等; Iterator接口
        Iterator iter = c1.iterator();          A:boolean hasNext():是否还有元素可迭代
        Object obj = iter.next();          B:Object next() 取出下一个元素  
        for (Object object : c1) {              C:remove():删除刚刚迭代的元素
            System.out.print(object);       
        }
        
        //转成数组迭代;
        Object[] objArray = c1.toArray();
        for (Object object : objArray) {
            System.out.print(object);
        }
        //直接用增强for循环; 只是查找和查看,用它更简洁更快
        for (Object object : c1) {
            System.out.println(object);
        }
8、求两个集合的交集
 * boolean retainAll(Collection<?> c)  
          当前集合 = 当前集合 ∩ other
  c1.retainAll(c2); 
  1)如果两个集合元素完全一样 返回值为false;2)c1包含于c2中  返回值为false;3)其他 返回值为true。
    实际上该方法是指:如果集合c1中的元素都在集合c2中,则c1中的元素不做移除操作,反之如果只要有一个不在c2中则会进行移除操作。
    即:c1进行移除操作返回值为:true; 反之返回值则为false。
  boolean retainAll = c1.retainAll(c2);
  System.out.println(retainAll);
  System.out.println("c的有效元素的个数:" + c1.size());//如果存在交集,有相同元素,c1中只保留相同的元素;
  //如果不存在相同元素,则c1会变空;

 

2. java.util.Iterator接口

(1)boolean hasNext() :是否有下一个元素需要迭代
(2)Object next() :取出下一个元素
(3)void remove() :删除刚刚迭代的元素

    Iterator iter = c.iterator();//在iterator()给我们创建了一个Iterator的实现类对象; iter的作用就是遍历、迭代集合;
        while(iter.hasNext()){
            Object object = iter.next();
            Student stu = (Student) object;
            if(stu.getScore() < 90){  //  if("李四".equals(obj))
                iter.remove();//调用的是迭代器自己的删除方法;  //而foreach不允许在遍历是添加、删除元素
            }
        }//要在while循环结束后再迭代查看它
        System.out.println("迭代删除后:---------------------");
        for (Object object : c) {
            System.out.println(object);
        }

 

一、增加一个接口:java.lang.Iterable接口
 * JDK1.5增加
 * 它有一个抽象方法:Iterator iterator() 
 * 实现这个接口允许对象成为 "foreach" 语句的目标

 * Collection从JDK1.5之后开始继承Iterable接口。

二、java.util.Iterator接口在哪里实现的?
 * //左边是Iterator接口,说明右边一定创建了一个Iterator接口的实现类对象,否则iterator调用方法是没有方法体可以执行
        Iterator iterator = list.iterator();
        
 * 跟踪源代码:list.iterator()
 *    public Iterator<E> iterator() {
        return new Itr();//Itr是一个内部类
    }

 * 跟踪每一种Collection的集合发现,所有的集合在内部都一个内部类来实现java.util.Iterator接口* 
 * 为什么?
 * (1)每一种集合的内部实现物理结构不一样,有的是数组有的是链表等,迭代方式不一样,无法给出统一的实现
 * (2)迭代的作用,为某个集合迭代,遍历元素,那么它只为某类集合服务,迭代时又需要访问集合的内部(private)的元素,
 * 所以我们设计为内部类更合适。
 * 
 * 我们可以把迭代器比喻成:公交车上的售票员,飞机上空姐,他只为本集合服务
 */

 

3. java.util.List接口

(比Collection多了和索引index相关的方法)


* 有序的 collection(也称为序列)。此接口的用户可以对列表中每个元素的插入位置进行精确地控制。
* 用户可以根据元素的整数索引(在列表中的位置)访问元素,并搜索列表中的元素。
* List是Collection的子接口,那么Collection中的方法它也有。* List增加了Collection接口没有的方法,和索引位置相关的方法:

 1、添加
* boolean add(Object obj):List系列的,默认添加到最后
* boolean addAll(Collection other):List系列的,默认添加到最后
* 
* boolean add(int index ,Object obj):在指定位置index处,插入一个元素obj
* boolean add(int index, Collection other):在指定位置index处,插入多个元素,other中的多个元素
*
2、删除
* boolean remove(Object o) ; 这个是指定元素。
* boolean removeAll(Collection other) 
* void clear() 
* 
* Object remove(int index):删除指定位置index的元素,删除的同时还返回了该元素,如果需要就接收,不需要就不接收。 //Object remove = list.remove(1);
*
3、修改(有提供)
* Object set(int index, Object element) :替换集合index位置的元为element,返回被替换的旧元素 ; lis.set( 1, 8 );
*
4、查找:
* boolean contains(Object o) 
* boolean containsAll(Collection c) 
* 判断c集合是否是当前集合的子集
* boolean isEmpty() 
* 
* int indexOf(Object o)    //int index = list.indexOf("李四");
* int lastIndexOf(Object o)  //查找最后一个元素的索引位置
* Object get(int index)   //list.get( 2 ) ;
*
5、获取元素的个数
* int size() 
* 
 7、遍历
* Iterator<E> iterator() :获取集合自身的迭代器,用于遍历集合用的
* Object[] toArray() 
* 增加了:
* ListIterator<E> listIterator():
 ListIterator和Iterator有什么不同?
Iterator只能从前往后遍历,而ListIterator可以从任意位置从前往后,从后往前。

        ListIterator iter = lis.listIterator();
        while(iter.hasNext()){
            Object nex = iter.next();
            System.out.println(nex);
        }
        
        ListIterator iterator = lis.listIterator(lis.size()); //从后边往前边迭代;    int previousIndex();还增加了迭代的同时:set和add
        while(iterator.hasPrevious()){
            Object pre = iterator.previous();
            System.out.println(pre);
        }
 8、截取列表的一部分
 List<E> subList(int fromIndex, int toIndex) 

System.out.println(lis.subList(0, 3)); //[1, 2, 3]     

List接口的实现类

java.util.List接口的实现类有:

  • java.util.ArrayList:动态数组
  • java.util.Vector:动态数组
  • java.util.LinkedList:链表
  • java.util.Stack:栈

① ArrayList和Vector

(它们底层都是数组--称为动态数组)

Vector:是旧版本,线程安全的,扩容机制为原来的2倍,支持旧版的Enumeration迭代器

ArrayList:是新版本,线程不安全的,扩容机制为原来的1.5倍,不支持Enumeration迭代器

ArrayList只用:foreach和Iterator或ListIterator
      只看,不改:foreach
      看,并且删除:从前往后:Iterator
      看,并且删除、插入,从前往后,或从后往前:ListIterator
 如果需要再细化:

    ArrayList在JDK1.7之后,如果new ArrayList(),一开始初始化为一个长度为0的空数组,在第一次添加元素时,初始化为容量为10的数组。
     Vector:new Vector()直接初始化为容量为10的数组。

ArrayList、    LinkedList、Vector三者的异同?
同: 3个类都实现了List接口,存储数据的特点相同,存储有序、可重复的数据
不同:    
Collection接口: 单列集合, 用来存储一个一个的对象
    List接口: 存储有序的、可重复的数据。 -> "动态"数组, 替换原有的数组
        ArrayList: 作为List接口的主要实现类, 线程不安全的, 效率高; 底层使用Object[] elementData存储
        Vector: 作为List接口的古老实现类, 线程安全的,效率低; 底层Object[] elementData存储
        LinkedList:对于频繁插入、删除操作,使用此类效率比ArrayList高; 底层使用双向链表存储。
ArrayList源码分析:
 JDK7:
    ArrayList list = new ArrayList();//底层创建了长度是10的object[]数组 elementData 
    list.add(123) //elementData[0] = new Integer(123);
    ..
    list.add(11) //如果此次的添加导致底层elementData数组容量不够,则扩容。
    默认情况下,扩容为原来容量的1.5倍,同时需要将原来数组中的数据复制到新的数组中。
 JDK8:
    ArrayList list = new ArrayList();//底层object[] elementData 初始化为{},并没有创建长度为10的数组;
    list.add(123) //第一次调用add()时,底层才创建了长度10的数组,并将数据123添加到 elementData[0]
    ..后序的添加和扩容操作与JDK7一样。
 jdk7中Arraylist的对象的创建类似于单列的饿汉式,而jdk8 Arraylist的对象的创建类似于单列的懒汉式,延迟了数组的创建,节省了内存。
 

LinkedList源码分析:
    List<Object> list = new LinkedList<>();内部声明了Node类型的first和last属性,默认值为null
    list.add(123) //将123封装到Node中,创建了Node对象。
    其中,Node定义体现了LinkedList双向链表的说法。
    其中,Node定义为:
        private static class Node<E> {
        E item;
        Node<E> next;
        Node<E> prev;

        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }

 

② 动态数组与链表LinkedList区别

  它们底层的物理结构不同

  动态数组底层的实现是数组;
    优点:可以根据索引快速的遍历和查找元素
    缺点:长度不够,需要扩容,影响性能
      删除后,要移动元素,并且空出大量的空间,浪费空间
      需要开辟连续的存储空间
  LinkedList是链表结构。
    * 优点:有几个元素,占几个空间,不需要连续的空间
      * 添加时,比较快,不需要扩容
      * 删除:删除时,比较快,不需要移动元素;只要修改前后元素的关系就可以,和其他元素无关,无需移动
    缺点:无法索引快速定位,如果要按索引操作,效率比较低,从头遍历

③ Stack栈

一种“先进后出FILO”或“后进先出LIFO”数据结构的集合。

  Stack s = new Stack();
  (1)push(Object obj)       s.push("张三"); 
  (2)Object peek():查看栈顶元素是什么 Object peek = s.peek();
  (3)Object pop():取出栈顶元素

LinkedList:双向链表(即记录第一个结点first,又记录最后一个结点last)

队列Queue:先进先出(FIFO)

双端队列Deque:就可以从队头出去,也可以从队尾出去

   从JDK1.6开始LinkedList又实现了Deque接口。

   LinkedList把栈、队列、双向链表、双端队列所有方法都集为一身,只要调用对应的方法就可以实现各种需求。

 

自己设计一个MyArrayList,类似于ArrayList

MyArrayList对象作为容器使用:

添加、获取有效元素的个数、根据索引删除; 从索引位置,把右边的元素移动到左边;根据对象删除;根据对象查找下标;返回数组中实际存储的元素;判断某个元素是否存在

在指定位置添加,从index位置起它后边的元素都往右边移动;替换指定位置的元素,并返回被替换的旧元素;返回指定位置的元素

import java.util.Arrays;

public class MyArrayList {

    private Object[] arr;
    private int total;
    
    public MyArrayList() {
        super();
        //给数组初始化;
        arr = new Object[10];
    }

    public MyArrayList(Object[] arr, int total) {
        super();
        this.arr = arr;
        this.total = total;
    }
    
    //1. 添加方法
    public void add(Object obj){
        
        if(total == arr.length){ //判断数组是否满了,满了就扩容
            //扩容
            arr = Arrays.copyOf(arr, arr.length * 2);
        }
        arr[total++] = obj;
        
    }
    
    //2.获取有效元素的个数;
    public int size(){
        return total;
    }
    
    //3.根据索引删除; 从索引位置,把右边的元素移动到左边;
    public void remove(int index){
        if(index < 0 || index > total){
            throw new IndexOutOfBoundsException("下标越界了");
        }
        // 1,2,3,4,5,6,null ;要把index=1的小标删除,从index+1的位置移动,需要移动3,4,5,6
        System.arraycopy(arr, index+1, arr, index, total-index-1);
        arr[total-1] = null;
        total--;
    }
    //4.根据对象删除
    public void remove(Object obj){
        int index = indexOf(obj);//调用找下标的方法
        
        if(index != -1){
            remove(index);
        }
    }
    
    //5.根据对象查找下标
    public int indexOf(Object obj){
        int index = -1;
        if(obj == null){
            for(int i = 0; i < total; i++){
                if(obj == arr[i]){
                    index = i;
                    break;
                }
            }
        }else{
            for(int i = 0; i < total; i++){
                if(obj.equals(arr[i])){
                    index = i;
                    break;
                }
            }
        }
        return index;
    }
    
    //6.返回数组中实际存储的元素
    public Object[] toArray(){
        
        return Arrays.copyOf(arr, total);
        
    }
    //7.判断某个元素是否存在
    public boolean contains(Object obj){
        int index = indexOf(obj);
        return index != -1;
    }
    
    //8.在指定位置添加,从index位置起它后边的元素都往右边移动;
    public void add(int index, Object obj){
        if(index < 0 || index > total ){ 
            throw new IndexOutOfBoundsException("下标越界了");
        }
        if(total == index){
            //扩容
            arr = Arrays.copyOf(arr, arr.length * 2);
        } // 1,2,3,4,5,null;  total=5,在index=2的位置插入一个新的
        System.arraycopy(arr, index, arr, index+1, total-index);
        arr[index] = obj;
        total++;
        
    }
    
    //9.替换指定位置的元素,并返回被替换的旧元素
    public Object set(int index, Object value){
        if(index < 0 || index > total ){ 
            throw new IndexOutOfBoundsException("下标越界了");
        }
        Object old = arr[index];
        arr[index] = value;
        return old;
        
    }
    
    //10.返回指定位置的元素
    public Object get(int index){
        if(index < 0 || index > total ){ 
            throw new IndexOutOfBoundsException("下标越界了");
        }
        return arr[index];
    }    
    
}
View Code

单链表的添加和删除方法

package com.atguigu.testlink;

public class MyLinked { //单链表

    private Node first; //第一个节点的地址,节点就包含了数据data和next节点;
    private int total;
    
    class Node{
        
        Object data;
        Node next;
        public Node(Object data, Node next) {
            super();
            this.data = data;
            this.next = next;
        }
    }
    //1、添加方法
    public void add(Object obj){
        Node  newNode = new Node(obj, null); //1)创建新节点newNode,它就包含数据对象和记录下一节点的地址
        if(first == null){  //2)如果链表是空的;
            first = newNode; //
            
        }else{
            Node last = first;// 1)先找到最后一个节点的地址
            while(last.next != null){
                last = last.next;
            }  //如果为空了循环就退出来了,就把新节点赋值给它
            last.next = newNode; //2)然后最后一个节点.next = newNode; 
        }
        total++;
    }
    //2、获取有效元素个数
    public int size(){
        return total;
    }
    
    //返回链表所有的数据
    public Object[] toArray(){
        
        Object[] all = new Object[total];
        //遍历链表,把Node的data放到all[i]中;
        
        Node node = first; //
        for (int i = 0; i < total; i++) {
            all[i] = node.data;
            node = node.next;
        }
        return all;
        
    }
    
    //4、删除
    public void remove(Object obj){
        if(first == null){  //1)链表是空的,直接结束
            return;
        }else{
            //2)要删除的就是第一个节点;//判断first是否是被删除目标
            boolean isRemove = false;  
            if(obj == null && first.data == null){ //分为obj是否为空
                isRemove = true;
            }else if(obj != null && obj.equals(first.data)){
                isRemove = true;
            }
            if(isRemove == true){ //isRemove是true说明第一个结点就是要删除的结点
                first = first.next;
            }else{                  //说明第一个结点不是要删除的目标结点
                Node lastNode = first;
                Node node = lastNode.next;
                Node removeNode = null;
//                遍历查找第二个结点到最后一个结点是否有被删除的目标结点
                while(node.next != null){//把条件修改为node!=null,因为node = lastNode.next,说明node为空的话,lastNode就是最后一个节点,我们已经全部都找过
                    //分为obj是否为空
                    if(obj == null){
                        //如果node.data也为空,那么node就是被删除结点
                        if(node.data == null){
                            removeNode = node;
                            break;
                        }else{  //否则node和lastNode都往右移动
                            lastNode = node;
                            node = node.next;
                        }
                        
                    }else{
                        if(obj.equals(node.data)){
                            removeNode = node;
                            break;
                        }else{
                            lastNode = node;
                            node = node.next;
                        }
                    }
                }
                if(node.next == null){ //最后一个节点
                    if(obj == null){
                        if(node.data == null){
                            removeNode = node;
                        }
                    }else{
                        if(obj.equals(node.data)){
                            removeNode = node;
                        }
                    }
                    
                }
                if(removeNode == null){
                    return;
                }else{
                    lastNode.next = removeNode.next;
                    removeNode.data = null;//使得它尽快被回收
                    removeNode.next = null;//使得它尽快被回收
                }    
            }
        }
        total--;
            
    }
    
}
View Code
public class TestMyLinked {

    public static void main(String[] args) {
        MyLinked my = new MyLinked();
        System.out.println(my.size()); //第一个节点为空,first == null; total==0
        my.add("李四");//first:
                        data:hash==0、value[0]李 [1]四;
                        next:null
        my.add("王五");//first:
                        data:hash==0、value[0]李 [1]四;
                        next: data:hash==0、value[0]王 [1]五;
                              next:null
        
        my.add("赵六");//first:
                        data:hash==0、value[0]李 [1]四;
                        next: data:hash==0、value[0]王 [1]五;
                              next:data:hash==0、value[0]赵 [1]六;
                                   next:null
                
        
        my.add("Hello");
        System.out.println(my.size());
        my.remove("赵六");

        System.out.println(my.size());
    }

}

 

4. java.util.Set接口

(Collection另一个接口)

一个不包含重复元素的 collection

  • 更确切地讲,set 不包含满足 e1.equals(e2) 的元素对 e1 和 e2,并且最多包含一个 null 元素。
  • 正如其名称所暗示的,此接口模仿了数学上的 set 抽象。
  • Set的方法都是继承自Collection。

Set的实现类们

① HashSet

对于HashSet而言,它是基于HashMap,哈希表来实现的,底层采用HashMap来保存元素;

不保证set 的迭代顺序,元素是完全无序的,它的顺序是由hash值来决定的。允许使用null元素。

HashSet:元素的存储和是否重复要依赖于元素的:hashCode()和equals()方法。

仅仅是存储不重复的元素,不是key value结构,HashSet里面的HashMap的value则存储了一个PRESENT,它是一个静态的Object对象,因此HashSet也是非线程安全的,HashSet就是个简化的HashMap。

在HashSet中添加一个新元素时, 其实这个值是存储在底层Map的key中,而众所周知,HashMap的key值是不能重复的, 所以这里就可以达到去重的目的了。

 

根据 seriesId 和 listMonth 这两个字段去重,必须重写equals和hashCode方法。
@Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof NewListedCarDTO)) return false;

        NewListedCarDTO that = (NewListedCarDTO) o;

        if (seriesId != that.seriesId) return false;
        if (listYear != that.listYear) return false;
        return listMonth == that.listMonth;
    }
 @Override
    public int hashCode() {
        int result = (int) (seriesId ^ (seriesId >>> 32));
        result = 31 * result + listYear;
        result = 31 * result + listMonth;
        return result;
    }

② TreeSet

元素是按照“大小”顺序排列的(字母小的在前),和添加顺序无关。

TreeSet:元素的存储和是否重复要依赖于元素的比较大小的方法:int compareTo(Object o)或 指定的比较器的int compare(Object o1,Object o2)

    @Test
    public void test(){
        HashSet set = new HashSet();/*set.add("kk");
        set.add("alex");
        set.add("dd");
        set.add("java");
        set.add("java");*///String重写了hashCode()和equals()方法,所以只能添加进去一个
              //String实现了java.lang.Comparable接口
        

    TreeSet set = new TreeSet();
        set.add(2);
        set.add(1);  //Integer实现了java.lang.Comparable接口
        set.add(8);//Integer也重写了hashCode()和equals()方法,所以只能添加进去一个
        set.add(8);
        
        for (Object object : set) {
            System.out.println(object);
        }
    }
     TreeSet set = new TreeSet();
      set.add(new Student(2, "kk", 99));
      set.add(new Student(3, "alx", 69));
      set.add(new Student(4, "hk", 69)); //失败的原因,Student没有java.lang.Comparable,无法添加;
                          //Student cannot be cast to java.lang.Comparable
      for (Object object : set) {
        System.out.println(object);
        }

③ LinkedHashSet

LinkedHashSet是HashSet的子类,但是比HashSet要多一个“记录元素添加顺序的功能”,因此它添加和删除的效率比HashSet相对低。

当你既想要元素不重复,又想要按照添加的顺序时,选择LinkedHashSet。如果你只是想要不重复,那么选择HashSet,如果你执行想要添加顺序,选ArrayList等。

String 、Integer都重写了hashCode()和equals()方法。

  • LinkedHashSet 是 Set 的一个具体实现,其维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,该迭代顺序可为插入顺序或是访问顺序。
  • LinkedHashSet 继承与 HashSet,并且其内部是通过 LinkedHashMap 来实现的。有点类似于LinkedHashMap 其内部是基于 Hashmap 实现一样
  • 如果我们需要迭代的顺序为插入顺序或者访问顺序,那么 LinkedHashSet 是需要首先考虑的。

TreeSet:
  (1) 按照元素的自然顺序排列:要求元素必须实现java.lang.Comparable接口
     例如:String实现了java.lang.Comparable接口,它是按照字符的Unicode编码值来排序。

    (2) 按照指定的定制比较器的规则排序,要求指定一个java.util.Comparator接口的实现类对象
               例如:专门为比较String对象,定制了一个比较器java.text.Collator实现了java.util.Comparator接口

实现Comparable接口

public class TestMap2 {
    @Test
    public void test(){
        TreeSet set = new TreeSet();
        set.add(new Student(2,"kk",800));
        set.add(new Student(1,"gk",100));
        set.add(new Student(4,"alx",180));
        
        for (Object object : set) {
            System.out.println(object);
        }
}

class Student implements Comparable{
    private int id;
    private String name;
    private int score;
    
    public Student() {
        super();
    }
    public Student(int id, String name, int score) {
        super();
        this.id = id;
        this.name = name;
        this.score = score;
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }

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

    public int getScore() {
        return score;
    }

    public void setScore(int score) {
        this.score = score;
    }

    @Override
    public String toString() {
        return "Student [id=" + id + ", name=" + name + ", score=" + score + "]";
    }

    @Override
    public int compareTo(Object o) {   //自然排序,按照员工的
        Student stu = (Student) o;
        //return this.score - stu.score; //按照成绩排序
        //return this.id - stu.id;         //自然顺序,按照学生id的编号排序
        return this.name.compareTo(stu.getName()); //String有它自己的排序方法
    }

}

指定一个comparator接口的实现类对象

public class TestMap2 {        
    @Test
    public void Test1(){
        TreeSet set = new TreeSet(new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                Student stu1 = (Student) o1;
                Student stu2 = (Student) o2;            
                if(stu1.getScore() > stu2.getScore()){
                    return 1;
                }else if(stu1.getScore() < stu2.getScore()){
                    return -1;
                }else{
                    return 0;
                }        //因为double类型不精确,建议使用如下方法比较大小
                /*long d1 = Double.doubleToLongBits(stu1.getScore());
                long d2 = Double.doubleToLongBits(stu2.getScore());
                if(d1 > d2){
                    return 1;
                }else if(d1 < d2){
                    return -1;
                }else{
                    return 0;
                }*/
            }
        });
        set.add(new Student(2,"kk",800));
        set.add(new Student(1,"gk",100));
        set.add(new Student(4,"alx",180));
        
        for (Object object : set) {
            System.out.println(object);
        }
    }
    
}

 java.text.Collator类型比较字符串,可指定语言比较

//指定一个实现了java.util.Comparator接口的实现类的对象
//而且这个比较器还要比较两个“字符串”的大小
//java.text.Collator类型就符合要求
  Collator instance = Collator.getInstance(Locale.CHINA);//Locale.CHINA指定语言环境
        TreeSet set = new TreeSet(instance);
        set.add("张三");
        set.add("李四");
        set.add("王五");
        for (Object object : set) {
            System.out.println(object);
        }

5. java.util.Map接口

将键映射到值的对象。一个映射不能包含重复的键;每个键最多只能映射到一个值(一个key只能对应一个value, 一个key, put多个value以最后put的为准; )。

Map接口的常用方法:
 1、添加
  put(Object key, Object value)
 * 任意Java的引用数据类型都可以作为key和value的类型,但是最常见的是Integer和String比较多的作为key的类型。
 * putAll(Map map):把另一个map集合的映射关系添加到当前Map中

    Map map = new HashMap();
    map.put("smile", 78);
    map.put("kris", 78); //value可以一样,key不能一样;

 2、获取映射关系的对数
 * int size()  //map.size();

 3、查找
 * boolean containsKey(Object key)  
 * boolean containsValue(Object value) 
 * V get(Object key) :根据key获取value;  map.get("key");
 * boolean isEmpty() 

 4、删除
 * V remove(Object key) :根据key移除一对映射关系,并且返回对应的value
 * void clear()  

 5、遍历
   (1)Set keySet():因为Set不允许有重复元素,因为Map的所有key也是要求不允许重复的。

    Set keys = map.keySet();
    for (Object object : keys) {
      System.out.println(object);
      }

   (2)Collection values()  因为value是可以重复的,所以这里不能用Set

    Collection values = map.values();
      for (Object object : values) {
        System.out.println(object);
        }

  (3)Set entrySet()
   因为key不重复,所以entry是不重复的
   Entry类型是Map中的一个内部接口类型,映射项(键-值对)。它的实现类在Map的实现类中,以内部类的形式存在。
     Set entrySet = map.entrySet();
      for (Object object : entrySet) { //Entry接口,可以拿到K, V getKey() getValue()

        System.out.println(object);
           }

  //Can only iterate over an array or an instance of java.lang.Iterable
  //只有数组和Collection系列的集合
  //map不能直接foreach

Map接口的实现类

Map结构的理解:
  Map中的key:无序、不可重复的,使用Set存储所有的key   -->key所在的类要重写equals()和hashCode() (以HashMap为例)
  Map中的value:无序的、可重复的,使用Collection存储所有的value -->value所在的类重写equals()
   一个键值对:key - value构成了一个Entry对象。
   Map中的Entry:无序的、不可重复的,使用Set存储所有的entry;

① HashMap哈希表

Map的主要实现类,线程不安全的,效率高,存储null的key 和value

如何确定key的不重复和顺序的:依据key的 hashCode 方法和 equals 方法

② LinkedHashMap

保证在遍历map元素时,可以按照添加的顺序实现遍历;原因在原有的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素;对于频繁的遍历操作,此类执行效率高于HashMap。

LinkedHashMap是HashMap的子类,比HashMap多维护了“添加”的顺序。

③ TreeMap

保证按照添加的key-value对进行排序,实现排序遍历。要按照key进行排序:自然排序、定制排序,底层使用红黑树。

如何确定key的不重复和顺序的:依据key的自然顺序或定制比较器的规则

因此要求要么key类型实现了java.lang.Comparable接口,并重写int compareTo(Object obj)

要么在创建TreeMap时指定定制比较器对象(它是实现java.util.Comparator接口,并重写int compare(Object o1, Object o2)的对象)

④ Hashtable哈希表

古老的实现类,线程安全的,效率低;不能存储null的key和value 。

如何确定key的不重复和顺序的: hashCode 方法和 equals 方法

⑤ Properties

常用来处理配置文件,key和value都是String类型。

Properties是Hashtable的子类,是一种特殊的集合,特殊在它的key和value的类型都是String

HashMap和Hashtable都是哈希表:

(判断两个key相等的标准:两个key的hashcode值相等且equals方法返回true)

 Hashtable:旧版的,线程安全的,不允许key和value为null值;

 HashMap:新版的,线程不安全的,允许key和value为null值;

关系:

HashSet和HashMap:HashSet其实就是一个HashMap,通过HashMap实现
  添加到HashSet中的元素是作为HashMap的key,他们的value是共享了一个PRESENT(是Object类型的常量对象)

TreeSet和TreeMap: TreeSet其实就是一个TreeMap
  添加到TreeSet中的元素是作为TreeMap的key,他们的value是共享了一个PRESENT(是Object类型的常量对象)

LinkedHashSet和LinkedHashMap:LinkedHashSet其实就是一个LinkedHashMap

 面试题:1.Hash的底层实现原理? 2.HashMap和HashTable的异同 ? 3. CurrentHashMap与Hashtable的异同

练习

@Test
    public void test1(){
        TreeMap map = new TreeMap();
        //第一组
        ArrayList list = new ArrayList();
        list.add(new Student1("kris", 99));
        list.add(new Student1("jing", 89));
        list.add(new Student1("smile", 79));
        map.put("第一组", list);
        
        //第二组
        ArrayList list2 = new ArrayList();
        list2.add(new Student1("张三",56));
        list2.add(new Student1("李四",78));
        map.put("第二组", list2);
        
        //第三组
        ArrayList list3 = new ArrayList();
        list3.add(new Student1("王五",99));
        list3.add(new Student1("嘿嘿",45));
        map.put("第三组", list3);

        Set entrySet = map.entrySet();
        /*for (Object object : entrySet) {
            System.out.println(object);
        }*/
        double max = -1;
        for (Object object : entrySet) {
            Entry entry = (Entry) object;  // Entry类型是Map中的一个内部接口类型,映射项(键-值对)。它的实现类在Map的实现类中,以内部类的形式存在
            Object group = entry.getKey();
            System.out.println(group);
            
            System.out.print("该组学员有:");
            ArrayList listvalue = (ArrayList)entry.getValue();
            for (Object stu : listvalue) {
                System.out.println(stu);
                
                Student1 s = (Student1) stu;
                if(s.getScore() > max){
                    max = s.getScore();
                }
            }
        }System.out.println("最高分为:" + max);
    }
    

6. HashMap底层实现

    
HashMap的底层实现原理? 以jdk7为例:
HashMap map = new HashMap();
在实例化以后,底层创建了长度是16的一维数组Entry[] table    
    ...可能已经执行过很多次put...
    map.put(key1, value1):
    首先,调用key1所在类的hashCode()计算key1哈希值,此哈希值经过某种算法计算以后,得到Entry数组中的存放位置。  
    如果此位置上的数据为空,此时key1-value1添加成功。 --情况1 
    如果此位置上的数据不为空,意味着此位置存在一个或多个数据(以链表形式存在),比较key1和已经存在的一个或多个数据的哈希值:
        如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1添加成功。 --情况2 
        如果key1的哈希值和已经存在的某一个数据(key2-value2)的哈希值相同,继续比较:调用key1所在类的equals(key2)方法
            如果equals()返回false:此时key1-value1添加成功。 --情况3 
            如果equals()返回true:使用value1替换value2.
    情况2和情况3:此时key1-value1和原来的数据以链表的方式存储。
    
    在不断添加过程中,会涉及到扩容问题,默认的扩容方式:扩容为原来容量的2倍,并将原有的数据复制过来。
    
    jdk8和jdk7在底层实现方面的不同:
     1.new HashMap() 底层没有创建一个长度16的数组
     2.jdk8底层的数组是 Node[], 而非Entry[]
     3.首次调用put()方法时, 底层创建长度16的数组
     4.jdk7底层结构只有 数组+链表; jdk8底层结构:数组 + 链表 + 红黑树 
        当数组的某一个索引位置上的元素以链表形式存放的数据个数 > 8且当前数组长度 > 64时,此时索引位置上的所有数据改为红黑树存储。
        
    

 

存储到HashMap中的映射关系(key,value),其中的key的hashCode值和equals方法非常重要。

单向链表,如果查找n个元素中的最后一个元素会遍历n遍;如5个元素,查找5遍;

而二叉树分left和right,5个元素查找最后一个则只查找3遍;

 

* HashMap的底层实现:
  HashMap map = new HashMap(); * 1、简单版 * JDK1.7:HashMap的底层实现是:数组+单向链表 * JDK1.8:HashMap的底层实现是:数组+单向链表/红黑树 为什么要红黑树? * 红黑树:一个自平衡的二叉树 * 当结点多了用红黑树,少了用链表 * 因为少的话用红黑树太复杂,多了话用红黑树可以提高查询效率。 * 红黑树:(自动调整根结点,保证左右两边的结点数差不多),它会左旋,右旋来实现。 2、复杂v1.0版 * JDK1.7:HashMap的底层实现是:数组(初始长度为16)+单向链表 * 每一个对映射关系的存储位置: * 存储的位置:存储的位置和顺序是否重复,是依据key的hashCode()和equal()决定的; (1)先计算key的hash值,通过hashCode()就可以得到, (2)再用hash值经过各种(异或^)操作,得到一个int数字,目的是使得更均匀的分布在数组的每一个元素中。 (3)再用刚才的int值,与table数组的长度-1(int & table.length)做一个“按位与&"运算,目的是得到一个[i] 因为数组的长度是2的n次方,长度-1的数的二进制是前面都是0,后面都是1,任何数与它做“&”,结果一定是[0,该数]之间 为什么会出现链表? * (1)两个不同的key对象,仍然可能出现hashCode值一样 * (2)就算hashCode不一样,经过刚才的计算,得到[i]是一样的 * (3)而我们的数组的元素table[i]本来只能放一个元素,那么现在有多个元素需要存储到table[i]中,只能把这几个元素用链表连接起来 * 简单比喻: * y = f(x) * 两个不一样的x,可能得到一样的y; 那么存储到HashMap中的是什么样的元素呢? * (1)首先是Map.Entry类型:映射项(键-值对)。 * (2)其次HashMap有一个内部类HashMap.Entry实现了Map.Entry接口类型 * 内部类Entry由四部分组成: * (1)key * (2)value * (3)下一个Entry:next * (4)hash值计算的整数值,为了后面查询快一点 如何避免重复?(hash值取的是整数在内存中做比较比较快,而equals重写可能会比较很多次) * 如果两个key的hash值是一样的,还要调用一下equlas()方法,如果返回true,就会把新的value替换旧的value。 * 如果两个key的hash值是一样的,还要调用一下equlas()方法,如果返回false,就会把新的Entry连接到旧的Entry所在链表的头部(first) * 如果两个key的hash值是不一样的,但是[i]是一样的,就会把新的Entry连接到旧的Entry所在链表的头部(first) * 如果两个key的hash值是不一样的,并且[i]不一样的,肯定存在不同table[i]中 * 我们把table[i]称为“桶bucket”。 回忆:两个对象的hash值: * (1)如果两个对象是“相等”,他们的hash值一定是一样 * (2)如果两个对象的hash值是一样,他们可能是相同的对象,也可能是不同的对象;。 * * 3、复杂追踪源代码v2.0版 (1)什么时候扩容 * 当①元素的个数达到“阈值”,②并且新添加的映射关系计算出来的table[i]位置是非空的情况下,再扩容;(如果table[i]是空的就不扩容直接放进去,若是非空说明是连接到别的下面了;) * 默认加载因子 (0.75):DEFAULT_LOAD_FACTOR,也可自己手动赋值 * 阈值 = table.length * 加载因子(loadFactor)
* 第一次阈值:16 * 0.75 = 12 默认初始容量:DEFAULT_INITIAL_CAPACITY:16 (1<<4即2^4) 第二次阈值:32 * 0.75 = 24 * ... * * HashMap中table的长度有一个要求:必须是2的n次方(如果你给的值不是2^n,它也会给你纠正一个最接近的值),是为了让2^n-1的二进制是11111... (2)跟踪一下put方法的源代码 * 第一步:先判断table是否为空数组,如果table是空的,会先把table初始化为一个长度为16的数组(在默认的构造器中创建);
        如果你指定的长度不是2的n次方,会往上纠正为最接近的2的n次方(int capacity = roundUpToPowerOf2(toSize)),
        并且把阈值threshold = table.length * 加载因子(loadFactor) = 12。 * 第二步:如果key是null,(空的就直接添加进去) 首先确定的位置是table[0], * 如果原来table[0]已经有key为null的Entry,用新的value替换旧的value * 如果原来table[0]没有key为null的Entry,那么创建一个新的Entry对象,作为table[0]桶下面的链表的头,原来的那些作为它next。 * * 第三步:如果key不是null(要先看看key是否相同),先计算hash(key),用key的hashCode值,通过hash()函数算出一个int值称为"hash" * 第四步:通过刚才的“hash”的int值与table.length-1做&运算,得到一个下标index,表示新的映射关系即将存入table[index] * 第五步:循环判断table[index]位置是否为空,并且是否有Entry的key与我新添加的key是否“相同”,如果相同,就用新的value替换旧的value * 第六步:添加新的映射关系 * (1)判断是否要扩容: * 当元素的个数达到“阈值”,并且新添加的映射关系计算出来的table[i]位置是非空的情况下,table再扩容为原来的2倍长 * 如果扩容了,那么要重新计算hash和index * (2)把新的映射关系创建为一个Entry的对象放到table[index]的头部。 */
@SuppressWarnings("all")
public class TestHashMapSource17 {
    @Test
    public void test1(){
        HashMap map = new HashMap();
        
        map.put(1, "哈哈");
        map.put(2, "嘻嘻");
        map.put(3, "呵呵");
        map.put(4, "嘿嘿");
    }
    
    @Test
    public void test2(){
        HashMap map = new HashMap();
        
        map.put( "哈哈",1);
        map.put( "嘻嘻",2);
        map.put("呵呵",3);
        map.put("嘿嘿",4);
    }
    
    @Test
    public void test3(){
        HashMap map = new HashMap();
        
        map.put(new MyData(1), "哈哈");
        map.put(new MyData(2), "嘻嘻");
        map.put(new MyData(3), "呵呵");
        map.put(new MyData(4), "嘿嘿");
    }
    
    @Test
    public void test4(){
        HashMap map = new HashMap();
        for (int i = 1; i <= 35; i++) {
            map.put(i, "第" + i + "个value值");
        }
    }
    
    @Test
    public void test5(){
        HashMap map = new HashMap();
        for (int i = 1; i <= 35; i++) {
            map.put(new MyData(i), "哈哈" + i);
        }
    }
    
}

class MyData{
    private int num;

    public MyData(int num) {
        super();
        this.num = num;
    }

    @Override
    public int hashCode() {
        /*final int prime = 31;
        int result = 1;
        result = prime * result + num;
        return result;*/
        return 1;//所有对象的hash值都是1,这个时候肯定会出现链表
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        MyData other = (MyData) obj;
        if (num != other.num)
            return false;
        return true;
    }
    
}
View Code

 

 

 

 

 * JDK1.8:HashMap的底层实现是:数组+链表/红黑树
 * 
 * 1、复杂回答v1.0
jdk1.7和jdk1.8的异同点: * (1)映射关系的类型 TreeNode-->Entry-->Node-->Object * 添加到HashMap1.8种的映射关系的对象是HashMap.Node类型(原来是HashMap.Entry),
 或者是HashMap.TreeNode类型。 * (2)映射关系添加到table[i]中时,如果里面是链表,新的映射关系是作为原来链表的尾部; JDK1.7是只要是新添加的就放在首位; * “七上八下”:JDK1.7在上,JDK1.8在下。 为什么要在下面? 因为如果是红黑树,那么是在叶子上,保持一致,都在下面。 * (3)扩容的时机不同 * 第一种扩容:元素个数size达到阈值threshod = table.length * 加载因子 并且table[i]是非空的;(这种情况(都存在数组中均匀分布,不涉及链表)跟JDK1.7是一样的;) * 第二种扩容:当某个桶table[index]下面的结点的总数原来已经达到了8个,这个时候,要添加第9个时(由16变成32),会检查 * table.length是否达到64(基本上我们的元素很少达到64个),如果没达到就扩容; 如果添加第10个时,也会检查table.length是否达到64,如果没达到就扩容。 * 为什么? 因为一旦扩容,所有的映射关系要重新计算hash和index,一计算原来table[index]下面(单向链表)可能就没有8个,或新的映射关系也可能不在table[index]* 如果链表特别长是不利于查找的,如果均匀分布在数组中查找会很快(通过hash马上就找到了);这样就可能均匀分布。 ---------------------------------------------------------------------- * (4)什么时候从链表HashMap$Node(hash key next value)变成红黑树HashMap$TreeNode(left parent right) * 当table[index]下面结点的总数已经达到8个,并且table.length也已经达到64,(添加第11个时)那么再把映射关系添加到table[index]下时,就会把原来的链表修改红黑树 * (5)什么时候会从红黑树变为链表? * 当删除映射关系时table[index]下面结点的总数少于6个,会把table[index]下面的红黑树变回链表。 * 2、put的源代码v2.0 * 第一步:计算key的hash,用了一个hash()函数,目的得到一个比hashCode更合理分布的一个int值;(return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);) * 第二步:如果table是空的,那么把table初始化为长度为16的数组,阈值初始化为= 默认的长度16 * 默认的加载因子0.75 = 12    DEFAULT_INITIAL_CAPACITY:默认的初始化容量16    DEFAULT_LOAD_FACTOR:默认加载因子 0.75 * 第三步:查看table[index]是否为空,如果为空,就直接(添加到数组这种情况)new一个Node放进去      index = hash的int值 & table.length-1 * 第四步:不为空,先查看table[index]的根节点的key是否与新添加的映射关系的key是否“相同”(单向链表这种情况),如果相同,就用新的value替换原来的value。 * 第五步:如果table[index]的根节点的key与新添加的映射关系的key不同,     还要看table[index]根结点的类型是Node还是TreeNode类型,     如果是Node类型,那么就查看链表下的所有节点是否有key与新添加的映射关系的key是否“相同”,如果相同,就用新的value替换原来的value。     如果是TreeNode类型,那么就查看红黑树下的所有叶子节点的key是否与新添加的映射关系的key是否“相同”,如果相同,就用新的value替换原来的value。    * 第六步:如果没有找到table[index]有结点与新添加的映射关系的key“相同”,那么     如果是TreeNode类型,那么新的映射关系就创建为一个TreeNode,连接到红黑树中      如果是Node类型,那么要查看链表的结点的个数,是否达到8个,如果8个,并且table.length小于64(最小树化容量),那么先扩容,一旦扩容就会进行重写计算。     TREEIFY_THRESHOLD:树化阈值8,达到TREEIFY_THRESHOLD - 1 = 7时就准备树化;     MIN_TREEIFY_CAPACITY:最小树化容量64    UNTREEIFY_THRESHOLD:反树化,从树变为链表的阈值6

  几个常量和变量值的作用:

    ①默认负载因子static final float DEFAULT_LOAD_FACTOR = 0.75f;

    ②负载因子final float loadFactor;

   ③阈值int threshold;当size达到threshold阈值时,会扩容;

    ④树化阈值static final int TREEIFY_THRESHOLD = 8;

      该阈值的作用是判断是否需要树化,树化的目的是为了提高查询效率;当某个链表的结点个数达到这个值时,可能会导致树化。

     ⑤树化最小容量值static final int MIN_TREEIFY_CAPACITY = 64;当某个链表的结点个数达到8时,还要检查table的长度是否达到64,如果没有达          到,先扩容解决冲突问题

   ⑥反树化阈值static final int UNTREEIFY_THRESHOLD = 6;
@SuppressWarnings("all")
public class TestHashMapSource18 {
    @Test
    public void test1(){
        HashMap map = new HashMap();
        map.put(1, "哈哈");
        map.put(2, "嘻嘻");
        map.put(3, "呵呵");
        map.put(4, "嘿嘿");
    }
    
    @Test
    public void test2(){
        HashMap map = new HashMap();
        
        map.put( "哈哈",1);
        map.put( "嘻嘻",2);
        map.put("呵呵",3);
        map.put("嘿嘿",4);
    }
    
    @Test
    public void test3(){
        HashMap map = new HashMap();
        
        map.put(new MyData(1), "哈哈");
        map.put(new MyData(2), "嘻嘻");
        map.put(new MyData(3), "呵呵");
        map.put(new MyData(4), "嘿嘿");
    }
    
    @Test
    public void test4(){
        HashMap map = new HashMap();
        for (int i = 1; i <= 35; i++) {
            map.put(i, "第" + i + "个value值");
        }
    }
    
    @Test
    public void test5(){
        HashMap map = new HashMap();
        for (int i = 1; i <= 35; i++) {
            map.put(new MyData(i), "哈哈" + i);
        }
    }
    
}

class MyData{
    private int num;

    public MyData(int num) {
        super();
        this.num = num;
    }

    @Override
    public int hashCode() {
        /*final int prime = 31;
        int result = 1;
        result = prime * result + num;
        return result;*/
        return 1;//所有对象的hash值都是1,这个时候肯定会出现链表
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        MyData other = (MyData) obj;
        if (num != other.num)
            return false;
        return true;
    }
    
}
View Code

 

    @Test
    public void test1(){
        HashMap<Integer, String> map = new HashMap<>();
        map.put(1, "kris");
        map.put(2, "alex");
        map.put(3, "smile");
    }

put的过程:

 [1],int算完的hash=1 & 15 --> 1,所以下标为1

以字符串作为key,数字为value;----->>

 

 

 

 

 

HashMap和TreeMap、LinkedHashMap的内部实现类型,每个版本分别是什么?

HashMap的key的hashCode值的作用是什么?hash()方法的作用是什么?
  hashCode值的作用是为了计算table[index]索引位置,协助equals方法保证key的不重复。
  hash()方法的作用是得到一个int值,这个int值可以使得映射关系更均匀的分布在table数组中。

HashMap的key的equals()方法的作用是什么?
  key的equals()方法的作用是确保key的不重复

 HashMap和CurrentHashMap的区别?

HashMap的初始容量和加载因子会影响它的性能;初始容量默认值是16,加载因子(是hash表在增加前可以达到多满的尺度)默认值是0.75,容量(hash表中桶的数量);

当hash表中容量 > 16*0.75=12,会进行扩容resize;

HashMap的寻址方式:对于新插入或者读取的数据,key-->hash值对数组长度取模,结果作为它在数组中查找的index,取模的代价在计算机中比较高,所以数组长度要是2^n;

key的hash值 和2^-1做与运行,结果与取模操作是相同的;

HashMap是线程不安全的;体现在resize时可能出现死循环,使用迭代器;扩容时创建一个新的是原来容量2倍的新数组,将原来的重新插入到新数组;

多线程rehash就可能出现问题;

currentHashMap底层仍然是数组+链表,与hashmap不同的是它的外层不是一个大的数组,而是一个三维的数组,每个数组包含与hashmap类似的链表;

在读取某个key的hash值,对Segment的N位取模得到该key属于哪个segment;接着就像操作hashmap时操作segment,保证不同的值分布均匀,segment时继承JUC里边的

可以很方便的对segment上锁;

不同点:线程安全;hashmap允许key value为空,而currenthashmap则不允许;hash不允许在遍历时修改,currenthashmao运行并且时可见的;

java8废弃了分段锁,并且使用了一个大的数组,为了提高碰撞、寻址做了性能优化;数组长度默认是8,链表换成了红黑树,寻址时间复杂度由On变成OLogn,key的hash与数组长度取模得到key在数组中索引;

 jdk1.8

 

7. 集合工具类

 java.util.Collections:
 * (1)public static <T> boolean addAll(Collection<? super T> c,T... elements)
  将后面可变参数传递的实参对象全部都添加到c集合中
 * (2)public static <T> int binarySearch(List<? extends Comparable<? super T>> list,T key)
   在list集合中,查找key的索引
 * (3)public static <T> void copy(List<? super T> dest, List<? extends T> src)  -- >>既有上限又有下线,T相当于是它俩的交集。
  把src的集合中的元素复制到dest集合
 * (4)public static <T extends Object & Comparable<? super T>> T min(Collection<? extends T> coll)
  在coll集合中找最小值
 * 这里为什么要写Object?因为当泛型擦除时,按照上限的第一个类型处理,泛型是JDK1.5才有的,
  JDK1.5之前,没有泛型这个方法,返回值类型是按照Object处理的,为了当泛型擦除时与低版本保持一致
 * (5)public static <T extends Comparable<? super T>> void sort(List<T> list)
  这个T的要求必须实现Comparable接口,也可以是T的父类实现了Comparable接口
 * (6)public static <T> void sort(List<T> list, Comparator<? super T> c)
   要指定定制比较器,这个定制比较器中的T,必须是T或T的父类
 * 
 * (7)synchronizedXxx开头的方法:表示把一个集合对象转成线程安全的
 * (8)unmodifiableXxx开头的方法:表示把一个集合对象变成只读的
 * 
 * java.util.Arrays
 * public static <T> List<T> asList(T... a):把可变参数的实参,都放到一个List的集合并返回

 

@Test
    public void test1(){
        ArrayList<String> list1 = new ArrayList<>();
        ArrayList<Object> list2 = new ArrayList<>(); //传它的父类Object也可以
    
        Collections.addAll(list1, "Hello", "World");
        
        int binarySearch = Collections.binarySearch(list1, "Hello");
        //这个集合的泛型(1)看T的类型(2)它必须实现java.lang.Comparable接口; 因为String实现了Comparable方法
        
        System.out.println(binarySearch);//0
        
        
        for (String object : list1) {
            System.out.println(object);
        }
    }
    
    @Test
    public void test2(){
        List<Number> src = new ArrayList<>();
        List<Number> dest = new ArrayList<>();
        List<Object> dest2 = new ArrayList<>();//看把Number放到Object或者Integer中行不行
//        List<Integer> dest1 = new ArrayList<>();//错误的
        Collections.copy(dest, src); //把src复制到dest

        
        ArrayList<String> list = new ArrayList<>();
        ArrayList<Object> list2 = new ArrayList<>(); //因为Object没有实现Comparable接口
        ArrayList<Integer> list3 = new ArrayList<>();
//        ArrayList<xx> list = new ArrayList<>();
        Collections.min(list);
    
    }
    
    public void test3(){
        List<String> list = Arrays.asList("Hello", "kris","java");
        //list.add("k"); //UnsupportedOperationException
        //这个返回的是一个内部的Arrays.ArrayList,不是我们平时的ArrayList它是只读的。
        for (String string : list) {
            System.out.println(string);
        }
        
    
    }
View Code

 

posted @ 2018-12-06 19:35  kris12  阅读(325)  评论(0编辑  收藏  举报
levels of contents