Java基础知识学习 之 集合

本文详细介绍了Java集合框架,包括Collection接口、List接口、Set接口中的ArrayList、Vector、LinkedList、HashSet、TreeSet等子类,以及Map接口的HashMap、TreeMap和Properties类。文章讨论了集合特点、迭代器、泛型等核心概念,并提供了相关API的使用示例。此外,还涵盖了JDK5的新特性,如自动装箱拆箱、泛型、foreach循环等。
摘要由CSDN通过智能技术生成

集合

本部分为日常学习笔记,因整理方法尚不完善,部分内容或有待补充。
之后会逐一编辑完善,用作日常知识点记录与参考。

  • collection接口
    • List接口
      • ArrayList
      • Vector
        • Stack
    • Queue接口
      • Deque
        • LinkedList

集合特点

集合类解决什么问题?

在不知道集合中到底有多少个对象的情况下,对于一组对象进行操作,而不是某一个对象。

集合类的特点
  • 只能存储引用数据类型
  • 当存储空间不足时,能够进行自动扩容
数组和集合都是容器,它们之间有什么不同?

主要的区别存在于数据类型、长度、效率、API四个方面;

数组集合类
数据类型可以存储多种数据类型仅仅能够存储引用类型
长度一旦创建,长度不变空间不够能够自动扩容
效率较高,偏底层较低
API没什么API有丰富的API

Collection接口概述

Collection 层次结构 中的根接口。Collection 表示一组对象,这些对象也称为 collection 的元素。JDK不提供此接口的任何直接实现:它提供更具体的子接口(如 Set 和 List)实现。

  • Collection的子类接口为set、List
  • 有些子类集合允许重复有些不允许,有些子类集合是有序的有些是无序的
Collection API
  • 单个集合上进行操作
    • boolean add(E e)–添加元素e,确保集合中存在元素
    • boolean remove(Object o)–移除集合中指定元素,如果有重复的只会删除一个
    • void clear()–将集合中的内容置为空,但该集合对象依然存在
    • boolean contains(Object o)–查找集合中是否存在该元素,如果存在则返回true
    • boolean isEmpty()–判断该集合是否为空,如果是空则返回true
    • int size()–返回集合中元素的个数
  • 多个集合上进行操作
    • boolean addAll(Collection c)–将指定集合c中的所有元素都添加到集合当中,相当于两集合求并集
    • boolean removeAll(Collection c)
    • boolean containsAll(Collection c)
    • boolean retainAll(Collection c)
迭代器Iterator接口
迭代器的模型
迭代器的实现语法
迭代器的设计原理
  • 为什么Iterator定义成一个接口而不是一个类?
    1. 不同的集合数据接口可能不同,遍历方式也不一样,所以不能够再一个类当中实现对所有集合的遍历。
    2. 遍历是集合的基本操作,应该有一个统一的方法作为遍历的标准,因此需要一个抽象方法表示遍历。
    3. 因为迭代器与集合之间没有从属关系,所以将迭代器设置为接口而非抽象类。
  • 通过Iterator it=c.iterator();方式获取迭代器,其中it肯定是Iterator的实现子类对象,那么此迭代器是什么类型的?为什么要这样设计迭代器?
    1. 迭代器用来遍历集合,所以迭代器必须知道集合的数据结构,但集合的数据结构都是私有的;
    2. 一个类如何访问另一个类的私有成员?此处通过内部类来实现迭代器;
      • 局部内部类的对象属于该方法所有,遍历集合在多种方法中都可能用到,所以不能以局部内部类实现迭代器;
      • 静态内部类对象属于外部类而非外部类对象,也就是不依赖于集合对象,也不适合实现迭代器;
      • 成员内部类对象依赖于外部类对象存在,所以创建方式也是通过外部类对象进行创建。
      • 这属于设计模式中的【迭代器模式】;
使用迭代器的注意事项
  • 警惕ConcurrentModificationException异常
  • 不要使用while循环遍历集合,推荐使用for循环,最好使用foreach循环;

List接口

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

List子类
ArrayList
  • 特点
    • 底层数据结构是数组,查询快,增删慢
    • 不是同步的,所以线程不安全,但是没有锁效率高
  • 常用API:
    • 11
Vector 向量
  • 特点
    • 底层数据结构是数组,查询快,增删慢
    • 是同步的,线程安全
  • 常见API
    • 11
LinkeList

List 接口的链接列表实现。

  • 实现所有可选的列表操作,并且允许所有元素(包括 null)。
  • 这些操作允许将链接列表用作堆栈、队列或双端队列。
  • 不仅实现了List接口,而且实现了Deque接口
  • 特点:
    • 底层数据结构是链表,增删快,查找慢
    • 不同步,线程不安全,效率高
  • 构造方法
    • LinkedList() --构造空列表,链表在构造时不需要关注容量,只要有内存就可以一直扩容
    • LinkedList(Collection<? extends E> c) --以集合创建链表,顺序为迭代器顺序
Deque接口 双端队列
  • Deque可用来实现队列、栈以及双端队列等,都可以用Deque接口中的方法进行模拟;
  • Deque实际上是在队列两端都实现了“增删查”顶端元素的方法,通过选用两端合适的方法,可以对栈和队列进行模拟;
  • 工程上的栈都使用此接口,而不是Stack类;
  • 每种方法都存在两种形式:
    • 一种形式在操作失败时抛出异常,另一种形式返回一个特殊值(null 或 false,具体取决于操作)。
    • 插入操作的后一种形式是专为使用有容量限制的 Deque 实现设计的;在大多数实现中,插入操作不能失败。
抛出异常(头)特殊值(头)抛出异常(尾)特殊值(尾)
插入addFirst(e)offerFirst(e)addLast(e)offerLast(e)
移除removeFirst()pollFirst()removeLast()pollLast()
检查getFirst()peekFirst()getLast()peekLast()

模拟队列和栈,都是从以上这些方法中挑选相应的方法实现该数据结构效果。

Stack extends Vector
  • 常用API
    • pop
    • push
    • peek
    • int search(Object o) --返回对象在堆栈中的位置,以 1 为基数。
  • 为什么不推荐使用Stack,而推荐使用Deque?
    • stack与Vector是继承关系,所以Vector有的API都可以用,甚至可以在任意位置添加数据
    • 线程安全的,效率比较低
用Vector模拟一个栈的数据结构——组合技术

set

  • 不包含重复元素
  • 可以有序也可以无序
  • 最多包含一个null元素
  • 模仿了数学上的集合的概念
  • set是一个接口,我们常用的实现类为HashSet
HashSet——Set的实现类
  • 底层是HashMap
    • 哈希表(JDK底层以“数组+链表”进行实现),每一个数组元素中都存的是一个单链表
    • 哈希表当中存储的是“键值对”<key,value>,通过key来查找对应的value
    • hashmap中的key是唯一的
  • 不保证迭代顺序,特别是不保证该顺序恒久不变(无序)
    • 不保证恒久不变的意思是,在扩容或者其他特殊情况下会造成迭代顺序的改变
  • 允许存储null元素
  • 不同步
HashSet保证元素唯一的原理
  • Set与Map的关系是一个组合的关系,Set当中持有了Map对象!
  • Set存储的元素是作为Map的key!Map中的key是唯一的!
    • Map中链表中存储的实际是key值,所有的key值都指向一个叫做PRESENT的最简单的Object对象;
注意事项
  • HashSet依赖对象的hashCode()与equals()方法来判断两个对象是否是相等的;
  • HashSet去重的特性只有在添加的时候,才会进行验证,如果该对象已经添加进去,再修改HashSet中此对象的值,就不能够再保证集合中没有重复元素。
  • 千万不要修改HashSet中元素的属性值!
类HashMap<K,V>的API
  • V put(K key, V value)
    在此映射中关联指定值与指定键。
    • 通过key求出哈希映射之后的哈希值int hash=hash(key)
    • 求出对应哈希值所要存储的目标索引int index=hash%table.length
    • 根据索引找到对应的链表,遍历链表,找key是否已经存在于链表当中
      • 如果存在,更新key对应的value并返回原本的value
      • 如果不存在,添加一个结点,返回null
  • V get(Object key)
    返回指定key所映射的value;找不到key返回 null。
    • 根据key值计算出哈希码【hash()】
    • 根据哈希码算出索引index【index=hash%table.length】
    • 根据索引找到对应的链表【访问对应的数组元素】
    • 遍历链表,在链表中寻找对应的key值是否存在
  • V remove(Object key)
    从此映射中移除指定键的映射关系(如果存在)。
    • key–hash–index
    • 找到链表并遍历
      • 存在此key则删除该结点并返回value
      • 不存在则返回null值
  • 时间复杂度
    • 增删查都是O(L),L表示链表的长度
    • JDK想要求常数时间的时间复杂度,就要求将key平均分布在数组当中,使得每个链表都不会超过某一固定长度;
    • 此固定长度称为“加载因子”,加载因子的固定会造成HashMap的容量有限,解决方法只能是对数组扩容;
    • HashMap对数组扩容,就必须重新散列
练习
  • HashSet存储自定义对象
    • 需要重写hashCode()与equals()方法
    • hashCode方法中协定,如果两个对象equals,他们一定要生成完全相等的hashCode;
    • Map中判断两个key是否相同,首先使用hashCode进行判断,其次判断两个对象是同一对象,最后判断是否equals;
LinkeHashSet
  • HashSet的子类,迭代顺序可预知的哈希表;
  • LinkeHashSet底层是HashMap&双向链表
    • HashMap保证了元素的唯一性
    • 双向链表定义了迭代的顺序,按照的是元素的插入顺序,且该顺序不受元素访问和重新插入的影响(就是按照第一次的顺序)
  • 其内部维护类一个在所有条目的双向链表【在原本HashSet的物理结构上为每个结点加链】
    • 遍历时就从head开始,沿着双向链表进行遍历即可;
    • 实现是在键值对Entry当中添加前后指针两个引用;
    • 插入的时候按照HashMap,便利的时候按照双向链表进行遍历
  • 不同步
使用场景

使用LinkedHashSet实现LRU算法的话,可以实现O(1)时间复杂度的查找和排重;

TreeSet——Set的实现类
概述——有序集合TreeSet
  • 基于TreeMap实现,TreeMap底层使用的是红黑树;
  • 如果创建对象时,没有传入comparator对象,则使用元素的自然顺序对元素进行排序;
  • 创建对象时传入了comparator对象,就根据创建set时提供的Comparator排序;
  • 不能够存储null元素,除非在comparator中定义了null的比较规则;
  • 线程不同步;
自然顺序——Comparable接口顺序
  • public interface Comparable此接口强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序,类的 compareTo 方法被称为它的自然比较方法。

  • 实现此接口的对象列表(和数组)可以通过 Collections.sort(和 Arrays.sort)进行自动排序。实现此接口的对象可以用作有序映射中的键或有序集合中的元素,无需指定比较器。

通过TreeSet存储自定义对象
  • 没有实现comparable接口且不提供comparator的类对象,因不能完成比较,不能够存储到TreeSet当中;
  • 实现comparable时,首先比较主要元素;主要元素比较不出结果时,对次要元素进行比较;
  • 如果一个类既没有实现compareable接口,又不能够更改此类的定义,是否TreeSet就不能存储该类型的元素?
    • 不,可以通过TreeSet的另一个指定比较器的构造方法来完成元素存储;
    • 在构造Treeset时,通过匿名内部类对象在构造方法中传入比较器,在比较器中指明该类型方法的比较方式;
  • 【注意】千万不要修改TreeSet的元素属性值!原因与HashSet一样,再修改之后不会再进行检查,不能重新维持有序;
TreeSet如何保证元素的唯一性

看源码要按照一条思路去看,不要从上到下按顺序齐看。

  • TreeSet元素唯一性主要依赖于底层的TreeMap元素唯一;
    • 其源码就是在BST的基础上添加了“是否使用比较器”以及“自动平衡”两个功能,其他插入逻辑是一致的。
TreeSet特有API
  • 查给定元素的上下界
    • E ceiling(E e) --更严格的是higher
      返回此 set 中大于等于给定元素的最小元素;如果不存在这样的元素,则返回 null。
    • E floor(E e) --更严格的是lower
      返回此 set 中小于等于给定元素的最大元素;如果不存在这样的元素,则返回 null。
  • 查看TreeSet中的最大最小值
    • E first()
      返回此 set 中当前第一个(最低)元素。
    • E last()
      返回此 set 中当前最后一个(最高)元素。
  • 移除最大值最小值
    • E pollFirst()
      获取并移除第一个(最低)元素;如果此 set 为空,则返回 null。
    • E pollLast()
      获取并移除最后一个(最高)元素;如果此 set 为空,则返回 null。
  • 视图方法
    • NavigableSet subSet(E fromElement, boolean fromInclusive, E toElement, boolean toInclusive)
      返回此 set 的部分视图,其元素范围从 fromElement 到 toElement。boolean类型的参数标识是否包含此边界值,true包含,false不包含;
TreeSet练习
  • 键盘录入5个学生信息及成绩,按照总分成绩对学生进行排名
  • 思路:
    • 学生类中包含所需成员属性
    • TreeSet存储自定义类型Student
    • 要么实现compareable接口,要么使用匿名内部类对象实现该类型之间的比较;
  • 参考代码:???

JDK5新特性

自动装箱拆箱
静态导入
  • import 导包:导入级别是具体的类,使得该类像是在这个包下定义的一样;
  • static import 静态导入:导入级别是导入到方法,且只能够导入静态方法,作用是能够免去写静态方法之前的类名。
    • 注意1:静态导入只能够导入静态方法,而不能够导入其他成员。
    • 注意2:静态导入会使程序的可读性降低,不推荐使用。
static关键字的用法
  1. 修饰类,静态内部类
  2. 修饰成员变量
  3. 修饰成员方法
  4. 静态代码块,随着类加载执行,在构造方法之前执行完毕
  5. 静态导入
泛型
泛型通配符
  • 泛型通配符要解决的问题:为集合提供类似数组的功能,但是同时要避免数组可能存在的问题
    • 数组是一个可协变类型,但是泛型集合不是可协变的类型
      • 数组的问题在于往Object[]传入了String[]引用之后,不能够再往其中添加其他类型数据。
    • 可协变类型方便代码的书写,泛型集合则不适用于此种变化,<>里头写啥就是啥,定死了。
  • 泛型通配符种类
    • 泛型通配符<?>
      任意类型,如果没有明确,那么就是Object以及任意的Java类了
    • ? extends E
      向下限定,E及其子类
    • ? super E
      向上限定,E及其父类
泛型擦除
  • 泛型擦除
    • 泛型信息只是对编译器有用,当编译器把源代码编译成字节码文件之后,会擦除泛型信息。
    • 意味着jvm拿到的都是已经确定类型的信息,不会接触到泛型信息。
  • 泛型擦除之后的类型
    • 如果是 E ,擦除之后会变为Object类型
    • 如果是?extends A,擦除之后会变成A类型
    • 如果是?super A,擦除之后会变成Object类型。
  • 泛型擦除之后的影响
    • 能够使用反射,绕过泛型类型检查,对泛型集合进行操作
    • 因为反射是在拿到字节码文件的基础上起作用的,而字节码文件中已经没有泛型信息,所以能够绕过其进行操作。
Collection接口的 T[] toArray(T[] a) 方法
  • 作用
    • 返回包含此 collection 中所有元素的数组;返回数组的运行时类型与指定数组的运行时类型相同。
  • 特性
    • 能容纳
    • 不能容纳
foreach循环
  • foreach循环的格式
for(元素数据类型 变量 : 数组或者Collection集合) {
	使用变量即可,该变量就是元素
}
  • foreach循环底层实现
    • 数组,实际上还是在原本的for循环方式进行处理
    • 集合,对于实现了public interface Iterable<T> 接口的对象都能够产生迭代器,进而使用此循环方式
      • collection接口继承自 Iterable接口,所以要求所有的集合都必须能够被遍历
  • foreach循环的缺点
    • 不能获取索引信息
    • 不能够修改集合和数组(即不能使用迭代器的修改API)
  • 建议:尽量使用foreach循环,简洁易懂
可变长参数
  • 可变长参数:完成传入任意数量的参数
    如果用数组完成多个参数的传入,那么就会导致程序可读性变差,造成维护困难
  • 格式:修饰符 返回值类型 方法名(数据类型… 变量名){}
  • 注意:
    • 这里的变量其实是一个数组
    • 可变长参数只能位于最后,一个方法当中只能有一个可变长参数。
    • 如果允许多个可变长参数,(A…,B…),若B为A的子类,则编译器无法区分到底是哪个,此种情况下需要自己封装。
    • 如果一个方法需要多个类似可变长参数的效果,那么除了最后一个之外,其余的封装成数组即可。
  • Arrays工具类中的一个方法
    public static List asList(T… a)
    • 数组–>集合 asList
    • 集合–>数组 ToArray
  • asList注意事项
    • 只能查找和修改元素,不能够对转换出的集合进行“增删操作”,在源码中相关方法会直接抛异常
    • 因为asList是一个视图方法,目标是维护此集合与底层数组一致,修改了集合的值也会导致数组值的修改

集合–Map<K,V>接口

Map接口概述
  • Map是一个顶层接口,为“映射”之意
    • Collection是iterable的子接口,所有的collection都是能够迭代的
  • Map<k,v>就是将key映射到value;
  • Map中不能够包含重复的key;
  • 一个key仅仅能够映射到一个值,不能是一对多的关系;
Map接口提供的API
  • 增/改
    • V put(K key, V value)
      将指定的值与此映射中的指定键关联,不仅可以用作新增,还可以用作修改;
    • void putAll(Map<? extends K,? extends V> m)
    • void clear()
    • V remove(Object key) 删key
    • boolean containsKey(Object key)
    • boolean containsValue(Object value)
      如果此映射将一个或多个键映射到指定值,则返回 true。
    • V get(Object key)
  • 获取集合属性
    • int size()
    • boolean isEmpty()
  • 遍历此集合
    • Set<Map.Entry<K,V>> entrySet() 【遍历的最佳实践】
      返回此映射中包含的映射关系(即所有映射项)的 Set 视图。
    • Set keySet() --所有key的set
      返回此映射中包含的键的 Set 视图
    • Collection values()
      因为值有重复的可能,所以返回的是collection
  • 注意事项
    • 要判断key是否存在,不能够通过get(key)!=null,必须通过containskey方法,也就是说,不能够认为返回null的key都不存在;
    • 要遍历一个Map的时候,最好是使用entrySet(),因为只需要一次遍历就能够取到键值对;否则使用keySet在拿到key之后还要进行一边查找;
如何对Map进行遍历
Map.Entry<k,v>
  • Map接口的内部接口,表示映射项(键值对);
    • K getKey()
    • V getValue()
    • V setValue(V value)

Map的常用子类

HashMap

HashMap依赖key对象的equals()方法以及hashCode()方法,用来判定是否重合,以决定是否添加进Map;

  • 特点
    • 除了“能够存储null”以及“线程不同步”之外,HashMap与HashTable基本上是相同的;
    • 不保证迭代顺序,特别是不保证该顺序恒久不变(扩容变);
    • 允许null键以及null键
HashMap构造方法
  • HashMap()
    • 构造一个具有默认初始容量 (16) 和默认加载因子 (0.75) 的空 HashMap。
  • HashMap(int initialCapacity)
    • 构造一个带指定初始容量和默认加载因子 (0.75) 的空 HashMap。
    • 在保证加载因子的情况下,保证初始情况下能够存储initialCapacity个元素;
  • HashMap(int initialCapacity, float loadFactor)
    • 构造一个带指定初始容量和加载因子的空 HashMap。
  • HashMap(Map<? extends K,? extends V> m)
    • 构造一个映射关系与指定 Map 相同的新 HashMap。
注意事项
  • HashMap的规则实际上都是对key产生约束,value是通过key可以获取的关联目标;
  • 存储完成之后,千万不要修改HashMap中key的属性值;
  • HashMap依赖key对象的equals()方法以及hashCode()方法;
HashMap与HashTable异同
  • 共同点:底层都是由哈希表构成
  • 区别
    • HashTable不允许null键和null值;
    • HashTable是同步的,也就是线程安全的;
LinkeHashMap——HashMap的子类
  • LinkedHashMap特征
    • Map接口的哈希表和链表实现,具有可预知的迭代顺序;
    • 链表定义了迭代顺序,迭代顺序就是键值对的插入顺序;
    • 不同步
  • 特有的API
    • protected boolean removeEldestEntry(Map.Entry<K,V> eldest)
      如果此映射移除其最旧的条目,则返回 true。(主要用于实现LRU算法)
TreeMap

TreeSet可以看作是TreeMap的马甲,用的是key,所以他所有的key都指向一个用来占位的最简单的对象PRESENT!

  • TreeMap特点
    • 底层数据结构是红黑树
    • 创建对象时,传入了比较器comparator就按照比较器规定的顺序进行排序;
    • 创建对象时,如果没有传入比较器,那么就按照内在顺序进行排序,必须实现compareable接口;
    • 不同步;
    • 不能够存储null键,除非comparator中能够对null键进行比较;
  • 注意事项
    • TreeMap依赖于key的compareTo()方法,或者是传入comparator对象的compare()方法
    • 千万不要修改key的属性值
    • Map的规则是对key的约束!
  • TreeMap特有的API
练习
Properties类
  • 概述
    • 表示一个(可以)持久的属性集,持久的意思是可以保存到磁盘或者网络上;
    • 对象都是存在于内存当中的,而properties类对象可以保存在流中或者从流中获取;
    • properties类属性列表中,每个键及其对应值都是一个字符串;
  • 注意事项
    • 不要使用HashTable里定义的方法添加键值对,因为可能添加进非String类型的数据。properties类继承自HashTable<Object,Object>,所以它有可能使用put()或者putAll()方法,将非String类型的值存储进properties;
    • 有可能存储非String类型的properties是“不安全”的,对其采用store()或者save()操作会失败。
properties类中特有的API

111

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值