Java容器

这是我参与8月更文挑战的第14天,活动详情查看:8月更文挑战

Java框架内容

  1. Java集合框架提供了一套性能优良、使用方便的接口和类,它们位于java.util包中;

在这里插入图片描述

  1. Collection:存放的是单一值:

    1. api方法:

      1. add:要求必须传入的参数是Object对象,因此当传入基本类型数据的时候,包含了自动装箱拆箱的过程;
      2. addAll:添加另一个集合的元素到此集合中;
      3. clear:只是清空集合中的元素,但是此集合对象并没有被回收;
      4. contains:判断集合中是否包含指定元素;
      5. containsAll:判断集合中是否包含另一个集合;
      6. isEmpty:判断集合是否为空;
      7. retainAll:若集合中拥有另一个集合中的所有元素,返回true,否则返回else;
      8. size:返回当前集合的大小;
      9. toArray:将集合转换成数组;
      10. remove:删除指定元素;
      11. removeAll:删除集合元素。
    2. 特点:

      1. 可以存放不同类型的数据,而数组只能存放固定类型的数据;
      2. 当使用arraylist子类实现的时候,初始化长度是10;当长度不够时,会进行扩容。
  2. 小节:

    1. Collection:接口存储一组不唯一、无序的对象;
    2. List接口存储一组不唯一、有序(插入顺序)的对象;
    3. Set接口存储一组唯一,无序的对象;
    4. Map接口存储一组键值对象,提供key到value的映射。

List集接口的实现类

  1. List特点:

    1. 有序、不唯一(可重复);
    2. ArrayList实现了长度可变数组,在内存中分配连续的空间。
      1. 优点:遍历元素和随机访问元素的效率比较高
      2. 缺点:添加和删除需要移动大量元素,效率低,按照内容查询效率低。
  2. LinkedList采用链表方式存储方式:

    1. 优点:插入、删除元素效率比较高;
    2. 缺点:遍历和随机访问元素效率低下。
  3. Vector与ArrayList:

    1. Vector也是List接口的一个子类实现;
    2. Vector跟ArrayList一样;底层都是用数组实现的;
    3. 二者区别:
      1. ArrayList:线程不安全,效率高;Vector线程安全,效率低;
      2. ArrayList:在进行扩容的时候,是扩容的1.5倍;Vector扩容的时候扩容原来的2倍。
  4. Iterator迭代器:

    1. 三种循环方式:
      1. do……while;
      2. while;
      3. for:
        1. for还有一种增强方式,可简化循环的编写;
        2. 所有的集合类都默认实现了Iterable的接口,实现此接口意味着具备了增强for循环的能力,也就是for-each(比较常用);
        3. 方法:
          1. iterator():当使用iterator进行迭代的过程中如果删除其中的某个元素会报错,并发操作异常,因此,如果遍历的同时需要修改元素,建议使用listIterator;
            1. listIterator:提供了向前和向后两种遍历的方式,始终是通过cursor和lastret的指针来获取元素值及向下遍历索引。需要注意的是当使用向前遍历的时候必须要保证指针在迭代器的结果,否则无法获取结果值。
          2. foreach():该方法要求返回一个Iterator的接口实例对象;此接口包含了:hashNext();next()。

Set接口中实现的类

  1. Set接口存储一组唯一,无序的对象;

    1. 存入和取出的顺序不一定一致;
    2. 数据操作方法与List类似,Set接口不存在get方法。
    3. set不可以通过下标获取对应位置的元素的值,因为无序的特点。
  2. HashSet:采用Hashtable哈希表存储结构:

    1. 优点:添加速度快,查询速度快,删除速度快;
    2. 缺点:无序。
  3. LinkedHashSet:采用哈希表存储结构,同时使用链表维护次序;

  4. TreeSet:采用二叉树(红黑树)的存储结构:

    1. 优点:有序(排序后的升序),查询速度比list快;
    2. 缺点:查询速度没有HashSet快;
    3. TreeSet底层的实现是treemap,利用红黑树进行实现;
    4. 设置元素的时候,如果是自定义对象,会查找对象中的equals和hashcode方法,如果没有,比较的是地址,而无法进行属性值的比较,一次需要重写equals方法;
    5. 树中的元素是要默认进行排序操作的,如果是基本数据类型,自动比较,如果是引用类型,需要自定义比较容器;
  5. HashSet操作:

    1. HashSet如何保证元素的唯一性:

      1. 通过元素的两个方法,hashCode和equals方法来完成;如果元素的HashCode值相同,才会判断equals是否为true;如果元素的HashCode值不同,不会调用equals方法。
    2. 为什要重新定义equals、HashCode方法:

      1. 当equals方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码,入下:
        1. 当obj1.equals(obj2)为true时,obj1.hashCode() == obj2.hashCode()必须为true;
        2. 当obj1.hashCode() == obj2.hashCode()为false时,obj1.equals(obj2)必须为false
        3. 如果不重写equals,那么比较的将是对象的引用是否指向同一块内存地址,重写之后目的是为了比较两个对象的value值是否相等。特别指出利用equals比较八大包装对象(如int,float等)和String类(因为该类已重写了equals和hashcode方法)对象时,默认比较的是值,在比较其它自定义对象时都是比较的引用地址hashcode是用于散列数据的快速存取,如利用HashSet/HashMap/Hashtable类来存储数据时,都是根据存储对象的hashcode值来进行判断是否相同的。这样如果我们对一个对象重写了euqals,意思是只要对象的成员变量值都相等那么euqals就等于true,但不重写hashcode,那么我们再new一个新的对象,当原对象.equals(新对象)等于true时,两者的hashcode却是不一样的,由此将产生了理解的不一致,如在存储散列集合时(如Set类),将会存储了两个值一样的对象,导致混淆,因此,就也需要重写hashcode()。
  6. List的Contains(obj)方法和Set中Contains(obj)方法:

    1. 实际上,List调用contains(Object obj)方法时,会遍历List中的每一个元素,然后再调用每个元素的equals()方法去跟contains()方法中的参数进行比较,如果有一个元素的equals()方法返回true则contains()方法返回true,否则所有equals()方法都不返回true,则contains()方法则返回false。因此,重写了Course类的equals()方法,否则testListContains()方法的第二条输出为false。
    2. Set的Contains(obj)方法,当调用HashSet的contains(Object obj)方法时,其实是先调用每个元素的hashCode()方法来返回哈希码,如果哈希码的值相等的情况下再调用equals(obj)方法去判断是否相等,只有在这两个方法所返回的值都相等的情况下,才判定这个HashSet包含某个元素。因此,需重写Course类的hashCode()方法和equals()方法。
  7. Comparable接口(内部比较器):

    1. 所有可“排序”的类都实现了java.lang.Comparable接口,Comparable接口中只有一个方法:public int comparableTo(Object obj);该方法:

      1. 返回0:表示this == obj;
      2. 返回正数:表示this > obj;
      3. 返回负数:表示this < obj;
    2. 实现了Comparable接口的类通过实现comparaTo方法从而确定该类对象的排序方式。

    3. 外部比较器:

      1. 定义在另一个类中,实现对本类的比较,实现Comparator接口中compare方法。但是要将实现的类比较器传递到集合中。
    4. 区别:

      1. 外部比较器可以定义成一个工具类,此时所有需要比较的规则一致的话,可以复用,而内部比较器只有在存储当前对象的时候的可以使用;
      2. 如果两者存在,使用外部比较器(自定义类则只能使用内部比较器);
      3. 当使用比较器的时候不会调用equals方法。
  8. 当做一些集合统一操作的时候,需要保证集合的类型是统一的,此时需要泛型来进行限制:

    1. 优点:数据安全;

    2. 获取数据效率比较高;

    3. 使用:

      1. 在定义对象的时候,使用<>中设置合理的类型来进行实现。
    4. 泛型的高阶应用:

      1. 泛型类:在定义类的时候,在接口的名称后面添加<E、K、V、A、B>起到占位的作用,类中的方法的返回值类型和属性的类型都可以使用。
      2. 泛型接口:
        1. 在定义接口的时候,在接口的名称后添加<E,K,V,A,B>;
        2. 子类在进行实现的时候,可以不填写泛型的类型,此时在创建具体的子类对象的时候才决定使用什么类型;
        3. 子类在实现泛型接口的时候,只在实现父类的接口的时候指定父类的泛型类型即可,此时,测试方法中的泛型类型必须要跟子类保持一致。
      3. 泛型方法: 在定义方法的时候,指定方法的返回值和参数是自定义的占位符,可以是类名中的T,也可以是自定义的Q,只不过在使用Q的时候需要使用定义在返回值的前面。
      4. 泛型的上限:
      5. 泛型的下限:

Map分析(1.7)

  1. 在这里插入图片描述

  2. map存储的是k-v键值对映射的数据:

    1. HashMap:数据+链表(1.7版本);数组+链表+红黑树(1.8);
    2. LinkedHashMap:链表;
    3. TreeMap:红黑树。
  3. 基本api操作:

    1. 增加:

      1. put(k,v):添加元素。
    2. 查找:

      1. isEmpty:判断是否为空;
      2. size:返回map大小;
      3. containsKey;
      4. containsValue;
      5. get。
    3. 删除:

      1. clear 清空集合中的所有元素;
      2. remove删除指定元素。
    4. Map.entry:表示的是K-V组合的一组映射关系,key和value成组出现;

  4. hashmap跟hashtable的区别:

    1. hashmap线程不安全,效率比较高,hashtable线程安全,效率低;
    2. hashmap中key和value都可以为空,hashtable不允许为空。
  5. 哈希表的初始容量:2的n次幂;

    1. 为什么是2的n次幂:

      1. 方便进行&运算,提高运算效率,&运算比%运算效率高;hash & (initCapacity-1)
      2. 在扩容的时候涉及到元素迁移的过程,迁移的时候只需要判断二进制的前一位是0或者是1即可,如果是0,表示新数组和旧数组的下标位置不变,如果是1,只需要将索引的位置加上旧的数组长度值即为新数组的下标。
    2. 源码:

          /**
           * The default initial capacity - MUST be a power of two.
           */
          static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
      
          /**
           * The maximum capacity, used if a higher value is implicitly specified
           * by either of the constructors with arguments.
           * MUST be a power of two <= 1<<30.
           */
          static final int MAXIMUM_CAPACITY = 1 << 30; 
      
          /**
           * The load factor used when none specified in constructor.
           */
          static final float DEFAULT_LOAD_FACTOR = 0.75f; //加载因子
      
      //初始化
      /** * Constructs a new, empty hashtable with a default initial capacity (11) * and load factor (0.75). */
      public Hashtable() { 
          this(11, 0.75f);
      }
      
      复制代码

Map(1.8)

  1. 数组+链表+红黑树;

    1. 链表的长度大于等于7时,会将链表转换为红黑树存储结构;而当红黑树的节点小于6时,会将红黑树转换为列表。在这里插入图片描述

          /**
           * The bin count threshold for using a tree rather than list for a
           * bin.  Bins are converted to trees when adding an element to a
           * bin with at least this many nodes. The value must be greater
           * than 2 and should be at least 8 to mesh with assumptions in
           * tree removal about conversion back to plain bins upon
           * shrinkage.
           */
          static final int TREEIFY_THRESHOLD = 8;
      
          /**
           * The bin count threshold for untreeifying a (split) bin during a
           * resize operation. Should be less than TREEIFY_THRESHOLD, and at
           * most 6 to mesh with shrinkage detection under removal.
           */
          static final int UNTREEIFY_THRESHOLD = 6;
      
      复制代码
  2. put方法:

    1. 扰动函数:减少哈希碰撞的概率;
          /**
           * Computes key.hashCode() and spreads (XORs) higher bits of hash
           * to lower.  Because the table uses power-of-two masking, sets of
           * hashes that vary only in bits above the current mask will
           * always collide. (Among known examples are sets of Float keys
           * holding consecutive whole numbers in small tables.)  So we
           * apply a transform that spreads the impact of higher bits
           * downward. There is a tradeoff between speed, utility, and
           * quality of bit-spreading. Because many common sets of hashes
           * are already reasonably distributed (so don't benefit from
           * spreading), and because we use trees to handle large sets of
           * collisions in bins, we just XOR some shifted bits in the
           * cheapest possible way to reduce systematic lossage, as well as
           * to incorporate impact of the highest bits that would otherwise
           * never be used in index calculations because of table bounds.
           */
          static final int hash(Object key) {
              int h;
              return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
          }
      复制代码
  3. resize方法:

    1. 存在对数据覆盖问题;

          /**
           * Initializes or doubles table size.  If null, allocates in
           * accord with initial capacity target held in field threshold.
           * Otherwise, because we are using power-of-two expansion, the
           * elements from each bin must either stay at same index, or move
           * with a power of two offset in the new table.
           *
           * @return the table
           */
          final Node<K,V>[] resize() {
              ……
          }
      复制代码

Collections工具类

  1. Collections提供的静态方法:
    1. addAll():批量添加;
    2. sort():排序;
    3. fill():替换;
    4. shuffle:随机排序;
    5. reverse():逆序;
    6. binarySearch():二分查找。先排序再查找。

Arrays工具类

  1. Arrays提供了数据操作的工具类,包含很多方法;
    1. 集合和数组之间的转换,使用较多asList方法。

常见问题

  1. 集合与数组的比较:
    1. 数组不是面向对象的,存在明显的缺陷,集合弥补了数组的一些缺点,比数组更灵活实用,可大大提高软件开发效率,而且不同的集合框架类可适用不同场合:
      1. 数组能存放基本数据类型和对象,而集合只能存放对象;
      2. 数组容易固定无法动态改变,集合类容量动态改变;
      3. 数组无法判断其中实际有多少元素,length只告诉了数组的容量,而集合的size()可以确切的知道元素的个数。
      4. 集合有多种实现方式和不同使用场合,不像数组仅采用顺序表方式;
      5. 集合以类的形式存在,具有封装,继承,多态等类的特性,通过简单的方法和属性即可实现各种复杂的操作,大大提高了软件的开发效率。
  2. Collection和Collections的区别:
    1. Collection是是java提供的集合接口,存储不唯一,无序的对象。它有两个子接口List和Set;
    2. Collections专门用来操作集合类,它提供了一系列的静态方法实现对各种集合的搜索、排序、线程安全化等操作。
  3. ArrayList和LinkedList的联系和区别:
    1. ArrayList实现了长度可变的数组,在内存中分配连续的空间。遍历元素和随机访问元素效率比较高;
    2. LinkedList采用链表存储方式。插入、删除元素效率比较高。
  4. Vector和ArrayList的联系和区别:
    1. 实现原理相同,功能相同,都是长度可变的数组结构,很多时候可以互用。
    2. 两者的区别如下:
      1. Vector是早期的JDK接口,ArrayList是替代Vector的新接口;
      2. Vector线程安全,ArrayList重速度轻安全,线程非安全;
      3. 容量需要扩容时,Vector默认增长一倍,ArrayList增长50%(1.5+1)。
  5. HashMap和Hashtable的联系和区别:
  6. 实现原理相同,功能相同,底层都是哈希表结构,查询速度快,在很多情况下可以互用;
  7. 两者的主要区别如下:
    1. Hashtable是早期的JDK接口,HashMap是新版的JDK提供的接口;

    2. HashTable继承Dictionary类,HashMap实现Map接口;

    3. Hashtable是线程安全的,HashMap线程非安全;

    4. Hashtable不允许null值,HashMap允许null值。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享