Java集合框架

2022-09-19 17:36:39 浏览数 (1)

前言

资源整合来源与网络以及自身的学习总结,如有侵权请联系我删除~ 有写的不对的地方,请各位在下方评论指出,一起学习进步~

面向对象语言对事物的体现都是以对象的形式,为了方便对多个对象的操作,就要对对象进行存储。另一方面,使用Array存储对象方面具有一些弊端,而Java 集合就像一种容器,可以动态地把多个对象的引用放入容器中

引出

数组在内存存储方面的特点:

  • 数组初始化之后,长度就确定了(无法再次改变长度)
  • 数组声明的类型,就决定了进行元素初始化的类型

数组在存储数据方面的弊端

  • 数组初始化之后长度不可变,不便于扩展
  • 数组中提供的属性和方法较少,不便于进行增删改等操作,且效率低,同时无法直接获取存储元素的个数
  • 数组存储的数据是有序的,可以重复的—>存储数据的特点 单一

Java集合系统架构

Java集合类主要由两个根接口Collection和Map派生出来的

Collection派生出了三个子接口:

List

  • List接口为Collection子接口。List所代表的是有序的Collection
  • 用某种特定的插入顺序来维护元素顺序。用户可以对列表中每个元素的插入位置进行精确地控制,同时可以根据元素的整数索引(在列表中的位置,和数组相似,从0开始,到元素个数-1)访问元素,并检索列表中的元素,由于这些特性,List在Collection的基础上扩展了一些重要方法:

Set

  • Set是一种不包括重复元素的Collection。它维持自己的内部排序,所以随机访问没有任何意义。与List一样,它同样允许null的存在但是仅有一个
  • 由于Set接口的特殊性,所有传入Set集合中的元素都必须不同,同时要注意任何可变对象,如果在对集合中元素进行操作时,导致e1.equals(e2)==true,则必定会产生某些问题。

Queue

  • 队列是一种先进先出的数据结构,元素在队列末尾添加,在队列头部删除。Queue接口扩展自Collection,并提供插入、提取、检验等操作
  • 方法offer表示向队列添加一个元素,poll()与remove()方法都是移除队列头部的元素,两者的区别在于如果队列为空,那么poll()返回的是null,而remove()会抛出一个异常。方法element()与peek()主要是获取头部元素,不删除
  • 接口Deque,是一个扩展自Queue的双端队列,它支持在两端插入和删除元素,因为LinkedList类实现了Deque接口,所以通常我们可以使用LinkedList来创建一个队列。PriorityQueue类实现了一个优先队列,优先队列中元素被赋予优先级,拥有高优先级的先被删除

Map接口派生: Map代表的是存储key-value对的集合,可根据元素的key来访问value。

因此Java集合大致也可分成List、Set、Queue、Map四种接口体系

Collection接口

  • Collection 接口是 List、Set 和 Queue 接口的父接口,该接口里定义的方法既可用于操作 Set 集合,也可用于操作 List 和 Queue 集合。
  • JDK不提供此接口的任何直接实现,而是提供更具体的子接口(如:Set和List)实现。
  • 在 Java5 之前,Java 集合会丢失容器中所有对象的数据类型,把所有对象都当成 Object 类型处理;从 JDK 5.0 增加了泛型以后,Java 集合可以记住容器中对象的数据类型。
  • 在Java中所有实现了Collection接口的类都应该提供两套标准的构造函数,一个是无参,用于创建一个空的Collection,一个是带有Collection参数的有参构造函数,用于创建一个新的Collection,这个新的Collection与传入进来的Collection具备相同的元素

Collection重要方法

Collection接口为集合提供一些统一的访问接口(泛型接口),覆盖了向集合中添加元素、删除元素、以及协助对集合进行遍历访问的相关方法:

方法

功能

boolean add(E e)

确保此collection包含指定的元素

boolean addAll(Collection<?extendsE> c)

将指定collection中的所有元素都添加到此collection中

void clear()

移除此collection中的所有元素

boolean contains(Object o)

如果此collection包含指定的元素,则返回true

boolean containsAll(Collection<?> c)

如果此collection包含指定collection中的所有元素,则返回true

boolean isEmpty()

如果此collection不包含元素,则返回true

Iterator iterator()

返回在此collection的元素上进行迭代的迭代器(继承自Iterable,是能够使用增强型for(forEach)循环的保证)

boolean remove(Object o) removeAll(Collection sub)

从此collection中移除指定元素的单个实例,如果存在的话

int size()

返回此collection中的元素数

T[] toArray(T[] a)

返回包含此collection中所有元素的数组;返回数组的运行时类型与指定数组的运行时类型相同

boolean removeIf(Predicate<? super E> filter)

条件删除

结论:集合的contains方法和remove[removeAll]方法中是使用equals方法判断两个对象是否一致的 进一步推论: 集合中凡需要进行对象的比较时,都用对象的equals方法判断

Collection的遍历

代码语言:javascript复制
package top.serms.demo21;

import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Collectors;

/**
 * <p>
 * </p>
 *
 * @Author SerMs
 * @Email 1839928782@qq.com
 * @Date 2022/7/26 10:15
 */
public class Demo3Collection {
    public static void main(String[] args) {
        //Collection -> Array
        Collection<String> c = new HashSet<>();     //HashSet重写了toString所以能直接打印输出
        System.out.println(c);
        String[] ss = {"aa", "bb", "11", "33"};
        System.out.println("ss = "   Arrays.toString(ss));
        Collections.addAll(c, ss);
        System.out.println("数组转集合: "   c);

        System.out.println("遍历一 增强for(forEach循环)");
        for (String s : c) {
            System.out.print(s   " ");
        }
        System.out.println();

        System.out.println("遍历二 stream流中得forEach");
        c.stream().forEach(System.out::printf);
        System.out.println();

        System.out.println("遍历三 迭代器");
        Iterator<String> i = c.iterator();
        while (i.hasNext()) {
            System.out.print(i.next());
        }

        System.out.println("遍历四 自带得增强for");
        c.forEach(new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        });

    }
}

输出结果为:

代码语言:javascript复制
[]
ss = [aa, bb, 11, 33]
数组转集合: [aa, bb, 11, 33]
遍历一 增强for(forEach循环)
aa bb 11 33 
遍历二 stream流中得forEach
aabb1133
遍历三 迭代器
aabb1133
遍历四 自带得增强for
aa
bb
11
33

进程已结束,退出代码0

List集合

List代表了有序可重复集合,可直接根据元素的索引来访问。

List接口常用的实现类有:ArrayList、LinkedList、Vector。

常用方法 方法 功能 void add(int index, E element) 在列表的指定位置插入指定元素 E get(int index) 返回列表中指定位置的元素 E remove(int index) 移除列表中指定位置的元素 List subList(int fromIndex, int toIndex) 返回列表中指定的fromIndex(包括)和toIndex(不包括)之间的部分视图,返回类型不要进行任何操作 E set(int index, E element) 设置索引index位置的元素 int indexOf(Object o) 从头查找元素,返回索引,没找到返回-1 int lastIndexOf(Object o) 从后面往前面查找 void sort(Comparator<? super E> c) 排序(升序,降序,乱序) 由于列表有序并存在索引,因此除了增强for循环进行遍历外,还可以使用普通的for循环进行遍历 List集合特点

  • 集合中的元素允许重复
  • 集合中的元素是有顺序的,各元素插入的顺序就是各元素的顺序
  • 集合中的元素可以通过索引来访问或者设置
ArrayList

ArrayList是一个动态数组,也是我们最常用的集合,是List类的典型实现。 它允许任何符合规则的元素插入甚至包括null,每一个ArrayList都有一个初始容量(10),该容量代表了数组的大小。 随着容器中的元素不断增加,容器的大小也会随着增加,在每次向容器中增加元素的同时都会进行容量检查,当快溢出时,就会进行扩容操作。 所以如果我们明确所插入元素的多少,最好指定一个初始容量值,避免过多的进行扩容操作而浪费时间、效率。 ArrayList擅长于随机访问,同时ArrayList是非同步的,是一个非线程安全的列表 ArrayList的默认扩容扩展后数组大小为:(原数组长度*3)/2 1 ArrayList的JDK1.8之前与之后的实现区别?

  • **JDK1.7:**ArrayList像饿汉式,直接创建一个初始容量为10的数组
  • **JDK1.8:**ArrayList像懒汉式,一开始创建一个长度为0的数组,当添加第一个元素时再创建一个始容量为10的数组
LinkedList

LinkedList:双向链表,内部没有声明数组,而是定义了Node类型的first和last,用于记录首末元素。LinkedList还实现了Deque接口,可以当作双端队列来使用,也就是说,既可以当作“栈”使用,又可以当作队列使用。

同时,定义内部类Node,作为LinkedList中保存数据的基本结构。Node除了保存数据,还定义了两个变量:

prev变量记录前一个元素的位置 next变量记录下一个元素的位置 对于频繁的插入或删除元素的操作,建议使用LinkedList类,效率较高

  • 同样实现List接口的LinkedList与ArrayList不同,LinkedList是一个双向链表。所以它除了有ArrayList的基本操作方法外还额外提供了get,remove,insert方法在LinkedList的首部或尾部
  • 由于实现的方式不同,LinkedList不能随机访问,它所有的操作都是要按照双重链表的需要执行。在列表中索引的操作将从开头或结尾遍历列表(从靠近指定索引的一端)。这样做的好处就是可以通过较低的代价在List中进行插入和删除操作
  • 与ArrayList一样,LinkedList也是非同步的。如果多个线程同时访问一个List,则必须自己实现访问同步

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

代码语言:javascript复制
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 是一个古老的集合,JDK1.0就有了。大多数操作与ArrayList相同,区别之处在于Vector是线程安全的。 在各种list中,最好把ArrayList作为缺省选择。当插入、删除频繁时,使用LinkedList;Vector总是比ArrayList慢,所以尽量避免使用。

Stack
  • Stack继承自Vector,实现一个后进先出的堆栈
  • Stack提供5个额外的方法使得Vector得以被当作堆栈使用。基本的push和pop 方法,还有peek方法得到栈顶的元素,empty方法测试堆栈是否为空,search方法检测一个元素在堆栈中的位置
  • Stack刚创建后是空栈

Java List总结

ArrayList 优点: 底层数据结构是数组,查询快,增删慢。 缺点: 线程不安全,效率高 Vector 优点: 底层数据结构是数组,查询快,增删慢。 缺点: 线程安全,效率低 LinkedList 优点: 底层数据结构是链表,查询慢,增删快。 缺点: 线程不安全,效率高

Set集合

  • EnumSet:
    • 是枚举的专用Set。所有的元素都是枚举类型
  • HashSet:
    • HashSet堪称查询速度最快的集合,因为其内部是以HashCode来实现的。它内部元素的顺序是由哈希码来决定的,所以它不保证set的迭代顺序;特别是它不保证该顺序恒久不变
  • TreeSet:
    • 基于TreeMap,生成一个总是处于排序状态的set,内部以TreeMap来实现。它是使用元素的自然顺序对元素进行排序,或者根据创建Set 时提供的 Comparator 进行排序,具体取决于使用的构造方法 PS: 自然顺序 -> 元素实现了java.lang.Comparable

HashSet

HashSet是Set集合最常用实现类,是其经典实现。

HashSet底层数据结构采用哈希表实现,元素无序且唯一,线程不安全,效率高,可以存储null元素,元素的唯一性是靠所存储元素类型是否重写hashCode()和equals()方法来保证的,如果没有重写这两个方法,则无法保证元素的唯一性。

LinkedHashSet

底层数据结构采用链表和哈希表共同实现,链表保证了元素的顺序与存储顺序一致,哈希表保证了元素的唯一性。

TreeSet

底层数据结构采用二叉树来实现,元素唯一且已经排好序,唯一性同样需要重写hashCode和equals()方法,二叉树结构保证了元素的有序性。

Java Set总结

HashSet 底层其实是包装了一个HashMap实现的 底层数据结构是数组 链表 红黑树 具有比较好的读取和查找性能, 可以有null 值 通过equals和HashCode来判断两个元素是否相等 非线程安全 HashSet 具有以下特点:

  • 不能保证元素的排列顺序
  • HashSet 不是线程安全的
  • 集合元素可以是 null

LinkedHashSet 继承HashSet,本质是LinkedHashMap实现 底层数据结构由哈希表(是一个元素为链表的数组)和双向链表组成。 有序的,根据HashCode的值来决定元素的存储位置,同时使用一个链表来维护元素的插入顺序 非线程安全,可以有null 值 LinkedHashSet 是 HashSet 的子类 LinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置,但它同时使用双向链表维护元素的次序,这使得元素看起来是以插入顺序保存的。 LinkedHashSet插入性能略低于 HashSet,但在迭代访问 Set 里的全部元素时有很好的性能。 LinkedHashSet 不允许集合元素重复。 优点:对于频繁的遍历操作,LinkedHashSet效率高于HashSet TreeSet 是一种排序的Set集合,实现了SortedSet接口,底层是用TreeMap实现的,本质上是一个红黑树原理 排序分两种:自然排序(存储元素实现Comparable接口)和定制排序(创建TreeSet时,传递一个自己实现的Comparator对象) 正常情况下不能有null值,可以重写Comparable接口 局可以有null值了。

Map的常用实现类

Map用于保存具有映射关系的数据,Map里保存着两组数据:key和value,它们都可以使任何引用类型的数据,但key不能重复。

Map与Collection并列存在。用于保存具有映射关系的数据:key-value Map 中的 key 和 value 都可以是任何引用类型的数据 Map 中的 key 用Set来存放,不允许重复,即同一个 Map 对象所对应的类,须重写hashCode()和equals()方法 常用String类作为Map的“键” key 和 value 之间存在单向一对一关系,即通过指定的 key 总能找到唯一的、确定的 value Map接口的常用实现类:HashMap、TreeMap、LinkedHashMap和Properties。其中,HashMap是 Map 接口使用频率最高的实现类

HashMap

  • HashMap是基于哈希表的Map接口的非同步实现,继承自AbstractMap,AbstractMap是部分实现Map接口的抽象类
  • 在之前的版本中,HashMap采用数组 链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里(和我们在之前自行实现的哈希表相同)。但是当链表中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低。而JDK1.8中,HashMap采用数组 链表 红黑树(一种平衡搜索二叉树)实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间
  • 和Vector类似,Map体系也有一个自JDK1.2之前遗留的集合工具:Hashtable,它的操作接口和HashMap相同,和HashMap的区别在于:Hashtable是线程安全的,而HashMap是非线程安全的
  • 重要常量 DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16 MAXIMUM_CAPACITY : HashMap的最大支持容量,2^30 DEFAULT_LOAD_FACTOR:HashMap的默认加载因子 TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树 UNTREEIFY_THRESHOLD:Bucket中红黑树存储的Node小于该默认值,转化为链表 MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量。(当桶中Node的数量大到需要变红黑树时,若hash表容量小于MIN_TREEIFY_CAPACITY时,此时应执行resize扩容操作这个MIN_TREEIFY_CAPACITY的值至少是TREEIFY_THRESHOLD的4倍。) table:存储元素的数组,总是2的n次幂 entrySet:存储具体元素的集 size:HashMap中存储的键值对的数量 modCount:HashMap扩容和结构改变的次数。 threshold:扩容的临界值,=容量*填充因子 loadFactor:填充因子

HashMap在JDK1.7和JDK1.8中的对比

总结:JDK1.8相变化: HashMap map = new HashMap();//默认情况下,先不创建长度为16的数组 当首次调用map.put()时,再创建长度为16的数组 数组为Node类型,在jdk7中称为Entry类型 形成链表结构时,新添加的key-value对在链表的尾部(七上八下) 当数组指定索引位置的链表长度>8时,且map中的数组的长度> 64时,此索引位置上的所有key-value对使用红黑树进行存储。

LinkedHashMap

  • LinkedHashMap继承自HashMap,它主要是用链表实现来扩展HashMap类,HashMap中条目是没有顺序的,但是在LinkedHashMap中元素既可以按照它们插入的顺序排序,也可以按它们最后一次被访问的顺序排序

TreeMap

  • TreeMap基于红黑树数据结构的实现,键值可以使用Comparable或Comparator接口来排序。TreeMap继承自AbstractMap,同时实现了接口NavigableMap,而接口NavigableMap则继承自SortedMap。SortedMap是Map的子接口,使用它可以确保图中的条目是排好序的
  • 在实际使用中,如果更新Map时不需要保持图中元素的顺序,就使用HashMap,如果需要保持Map中元素的插入顺序或者访问顺序,就使用LinkedHashMap,如果需要使图按照键值排序,就使用TreeMap

WeakHashMap

  • WeakHashMap实现了Map接口,是HashMap的一种实现,他使用弱引用作为内部数据的存储方案,WeakHashMap可以作为简单缓存表的解决方案,当系统内存不够的时候,垃圾收集器会自动的清除没有在其他任何地方被引用(不具备任何强引用、软引用)的键值对
  • 需要注意,WeakHashMap是主要通过expungeStaleEntries这个方法的来实现引用清除的。基本上只要对WeakHashMap的内容进行访问就会调用这个函数,从而达到清除其内部不在为外部引用的条目。但是如果预先生成了WeakHashMap,而在GC以前又不曾访问该WeakHashMap,那就不能释放内存了

Properties

  • Properties 类是 Hashtable 的子类,该对象用于处理属性文件
  • 由于属性文件里的 key、value 都是字符串类型,所以 Properties 里的 key 和 value 都是字符串类型
  • 存取数据时,建议使用setProperty(String key,String value)方法和getProperty(String key)方法

HashMap、HashTable、TreeMap的区别总结

TreeMap:基于红黑树实现。 HashMap:基于哈希表实现。 HashTable:和 HashMap 类似,但它是线程安全的,这意味着同一时刻多个线程可以同时写入HashTable 并且不会导致数据不一致。它是遗留类,不应该去使用它。现在可以使用 ConcurrentHashMap 来支持线程安全,并且 ConcurrentHashMap 的效率会更高,因为ConcurrentHashMap 引入了分段锁。 LinkedHashMap:使用双向链表来维护元素的顺序,顺序为插入顺序或者最近最少使用(LRU)顺序。

一,强引用 Object obj = new Object(); //只要obj还指向Object对象,Object对象就不会被回收 obj = null; //手动置null 只要强引用存在,垃圾回收器将永远不会回收被引用的对象,哪怕内存不足时,JVM也会直接抛出OutOfMemoryError,不会去回收。如果想中断强引用与对象之间的联系,可以显示的将强引用赋值为null,这样一来,JVM就可以适时的回收对象了 二,软引用 软引用是用来描述一些非必需但仍有用的对象。在内存足够的时候,软引用对象不会被回收,只有在内存不足时,系统则会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会抛出内存溢出异常。这种特性常常被用来实现缓存技术,比如网页缓存,图片缓存等。 在 JDK1.2 之后,用java.lang.ref.SoftReference类来表示软引用。 三,弱引用 弱引用的引用强度比软引用要更弱一些,无论内存是否足够,只要 JVM 开始进行垃圾回收,那些被弱引用关联的对象都会被回收。在 JDK1.2 之后,用 java.lang.ref.WeakReference 来表示弱引用。 四,虚引用 虚引用是最弱的一种引用关系,如果一个对象仅持有虚引用,那么它就和没有任何引用一样,它随时可能会被回收,在 JDK1.2 之后,用 PhantomReference 类来表示,通过查看这个类的源码,发现它只有一个构造函数和一个 get() 方法,而且它的 get() 方法仅仅是返回一个null,也就是说将永远无法通过虚引用来获取对象,虚引用必须要和 ReferenceQueue 引用队列一起使用。

Collections工具类

(操作数组的工具类:Arrays) Collections 是一个操作 Set、List 和 Map 等集合的工具类 Collections 中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作,还提供了对集合对象设置不可变、对集合对象实现同步控制等方法

Collection 和 Collections的区别

  • Collections是个java.util下的类,是针对集合类的一个工具类,提供一系列静态方法,实现对集合的查找、排序、替换、线程安全化(将非同步的集合转换成同步的)等操作。
  • Collection是个java.util下的接口,它是各种集合结构的父接口,继承于它的接口主要有Set和List,提供了关于集合的一些操作,如插入、删除、判断一个元素是否其成员、遍历等。

Collections常用方法 排序操作:(均为static方法) reverse(List):反转 List 中元素的顺序 shuffle(List):对 List 集合元素进行随机排序 sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序 sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序 swap(List,int, int):将指定 list 集合中的 i 处元素和 j 处元素进行交换 查找、替换 Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素 Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素 Object min(Collection) Object min(Collection,Comparator) int frequency(Collection,Object):返回指定集合中指定元素的出现次数 void copy(List dest,List src):将src中的内容复制到dest中 boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换List 对象的所有旧值

0 人点赞