Java 集合(底层解析)

Java 集合(底层解析)

  • 使用数组存储对象具有一些弊端,而Java集合就像一种容器,可以动态的把多个对象的引用放入容器中
  • 数组在内存中存储的特点:
    • 数组初始化以后,长度就确定了
    • 数组声明的类型,就决定了进行元素初始化的类型
  • 数组在存储数据方面的弊端:
    • 数组初始化以后,长度就不可变了,不便于扩展
    • 数组中提供的属性和方法少,不便于进行添加,删除,插入等操作,且效率不高,同时无法直接获取存储元素的个数
    • 数组存储的数据是有序的,可以重复的。 存储数据的特点单一
  • Java集合类可以用于存储数量不等的多个对象,还可用于保存具有映射关系的关联数组

1、Collection 接口

单列数据,定义了存取一组对象的方法的集合

iterator() :返回 Iterator 接口的实例,用于遍历集合元素

  • Iterator 对象称为迭代器(设计模式的一种),主要用于遍历Collection 集合中的元素
  • 迭代器模式的定义为:提供一种方法访问一个容器(container)对象中各个元素,而又不需暴露该对象的细节。迭代器模式,就是为容器而生。
  • Collection接口继承了Iterable接口,该接口有一个iterator() 方法,那么所有实现了Collection接口的集合类都有一个iterator()方法,用于返回一个实现了Iterator接口的对象
  • Iterator 仅用于遍历集合,Iterator 本身并不提供承装对象的能力。如果需要创建Iterator对象,则必须有一个被迭代的集合
  • 集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认指针都在集合的第一个元素之前。

1.1 List

元素有序、可重复的集合。

  • ArrayList:作为List的主要实现类,线程不安全的,效率高,底层使用数组存储

    // 底层分析
    // JDK 1.7
    ArrayList list = new ArrayList();// 底层创建了长度是10的Object[]数组
    list.add(123)...;// 如果此次的添加导致底层elementData数组容量不够,则扩容
    // 默认情况下,扩容为原来容量的1.5倍,同时将原有数组中的数据复制到新的数组中
    // 开发中建议使用带参的构造器:指定初始长度,避免不必要的扩容,增加效率
    
    // JDK 1.8
    ArrayList list = new ArrayList();// 底层Object[] elementData初始化为{},并没有创建长度为10的数组
    list.add(123);// 第一次调用add()时,底层才创建了长度为10的数组,并将数据添加到elementData数组中,后期扩容一样
    // 延迟了数组的创建,节省内存·
    
  • LinkedList:底层使用双向链表存储,对于频繁的插入、删除操作,比ArrayList效率高

    // 底层分析
    LinkedList list = new LinkedList();// 内部声明了Node类型的first和last属性,默认值为null
    list.add(123);// 将123封装到Node中,创建了Node对象。
    // 其中,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;
        }
    }
    
  • Vector:线程安全的,效率低,底层使用数组存储

    // Vector底层源码分析:通过构造器创建对象时,底层都是创建了长度为10的数组
    // 在扩容方面,默认扩容为原来数组的2倍。
    

1.2 Set

元素无序、不可重复的集合。

以HahSet为例:

  1. 无序性:不等于随机性。存储的数据在底层数组(初始长度16,当使用率超过0.75,则扩容为原来的2倍)中并非按照数组索引的顺序添加,而是根据数据的哈希值决定的
  2. 不可重复性:保证添加的元素按照equals() 判断时,不能返回true。即:相同的元素只能添加一个
  3. 向Set中添加的数据,其所在的类一定要重写hashCode()和equals()方法:相等的对象必须有相等的散列码(哈希值)

添加元素的过程:以HashSet为例

首先通过散列算法确定元素需添加在数组中的具体位置,如果该位置上有元素了,则比较哈希值,如果哈希值不一样,则该元素以链表的形式添加在该位置,如果哈希值一样,则通过equals()方法比较,如果还是一样则不会添加到集合

  • HashSet:Set接口的主要实现类,线程不安全,可以存储null值

  • LinkedHashSet:作为HashSet的子类,遍历其内部数据时,可以按照添加的顺序遍历

    将数据封装到了Node节点中,对于频繁的遍历操作,LinkedHashSet效率高于HashSet

  • TreeSet:可以按照添加对象的指定属性,进行排序。

    1. 向TreeSet中添加的数据,要求是相同类的对象

    2. 两种排序:自然排序和定制排序

      class User{
          String name;
          int age;
      }
      // 自然排序 需要排序的类需要实现Comparable接口,重写compareTo()方法
      @Override
      public int compareTo(Object o){
          if(o instanceof User){
              User user = (User)o;
              int compare = -this.name.compareTo(user.name);//按照姓名排序
              if(compare != 0){
                  return compare;
              }else{
                  return Integer.compare(this.age,user.age);// 同名再按照年龄排序
              }
          }else{
              throw new RuntimeException("输入的类型不匹配")
          }
      }
      // 自然排序中,比较两个对象是否相同的标准为:compareTO() 返回0,不再是equals().
      
      // 定制排序
      public void test(){
          Comparator<User> com = (o1,o2) -> {
      		if (o1 != null && o2 != null){
      			return Integer.compare(o1.getAge(), o2.getAge());// 按照年龄,年龄一样就不能添加
      		} else {
      			throw new RuntimeException("传参不能为空");
      		}
      	};
      	Set set = new TreeSet(com);
      }
      

2、Map 接口

双列数据,保存具有映射关系“key-value”的集合

Map 中的key:无序的、不可重复的,使用Set存储所有的key ;key所在的类要重写equals() 和 hashCode()

Map 中的value:无序的、可重复的,使用Collection存储所有的value

一个键值对:key-value构成了一个Entry对象。

Map 中的entry:无序的、不可重复的,使用set存储所有的entry

  • HashMap:作为Map的主要实现类;线程不安全,效率高;可存储null的key-value

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

    // 源码中相较于HashMap
    static class Entry<K,V> extends HashMap.Node<K,V>{
        Entry<K,V> before,after;// 能够记录添加的元素的先后顺序
        Entry(int hash,K key,V value,Node<K,V> next){
            super(hash,key,value,next);
        }
    }
    
  • TreeMap:可按照key对集合进行排序,实现排序遍历。底层使用红黑树

    // 向TreeMap中添加key-value,要求key必须是同一个类创建的对象,因为要按照key进行排序
    // 也是自然排序和定制排序
    
  • HashTable:作为Map的古老实现类;线程安全,效率低;不可存储null的key-value

  • Properties:作为Hashtable的子类,常用于处理配置文件,key和value都是String类型的

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值