集合
本部分为日常学习笔记,因整理方法尚不完善,部分内容或有待补充。
之后会逐一编辑完善,用作日常知识点记录与参考。
- collection接口
- List接口
- ArrayList
- Vector
- Stack
- Queue接口
- Deque
- LinkedList
- Deque
- List接口
集合特点
集合类解决什么问题?
在不知道集合中到底有多少个对象的情况下,对于一组对象进行操作,而不是某一个对象。
集合类的特点
- 只能存储引用数据类型
- 当存储空间不足时,能够进行自动扩容
数组和集合都是容器,它们之间有什么不同?
主要的区别存在于数据类型、长度、效率、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定义成一个接口而不是一个类?
- 不同的集合数据接口可能不同,遍历方式也不一样,所以不能够再一个类当中实现对所有集合的遍历。
- 遍历是集合的基本操作,应该有一个统一的方法作为遍历的标准,因此需要一个抽象方法表示遍历。
- 因为迭代器与集合之间没有从属关系,所以将迭代器设置为接口而非抽象类。
- 通过
Iterator it=c.iterator();
方式获取迭代器,其中it肯定是Iterator的实现子类对象,那么此迭代器是什么类型的?为什么要这样设计迭代器?- 迭代器用来遍历集合,所以迭代器必须知道集合的数据结构,但集合的数据结构都是私有的;
- 一个类如何访问另一个类的私有成员?此处通过内部类来实现迭代器;
- 局部内部类的对象属于该方法所有,遍历集合在多种方法中都可能用到,所以不能以局部内部类实现迭代器;
- 静态内部类对象属于外部类而非外部类对象,也就是不依赖于集合对象,也不适合实现迭代器;
- 成员内部类对象依赖于外部类对象存在,所以创建方式也是通过外部类对象进行创建。
- 这属于设计模式中的【迭代器模式】;
使用迭代器的注意事项
- 警惕
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
- 通过key求出哈希映射之后的哈希值
- 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。
- E ceiling(E e) --更严格的是higher
- 查看TreeSet中的最大最小值
- E first()
返回此 set 中当前第一个(最低)元素。 - E last()
返回此 set 中当前最后一个(最高)元素。
- E first()
- 移除最大值最小值
- E pollFirst()
获取并移除第一个(最低)元素;如果此 set 为空,则返回 null。 - E pollLast()
获取并移除最后一个(最高)元素;如果此 set 为空,则返回 null。
- E pollFirst()
- 视图方法
- NavigableSet subSet(E fromElement, boolean fromInclusive, E toElement, boolean toInclusive)
返回此 set 的部分视图,其元素范围从 fromElement 到 toElement。boolean类型的参数标识是否包含此边界值,true包含,false不包含;
- NavigableSet subSet(E fromElement, boolean fromInclusive, E toElement, boolean toInclusive)
TreeSet练习
- 键盘录入5个学生信息及成绩,按照总分成绩对学生进行排名
- 思路:
- 学生类中包含所需成员属性
- TreeSet存储自定义类型Student
- 要么实现compareable接口,要么使用匿名内部类对象实现该类型之间的比较;
- 参考代码:???
JDK5新特性
自动装箱拆箱
静态导入
- import 导包:导入级别是具体的类,使得该类像是在这个包下定义的一样;
- static import 静态导入:导入级别是导入到方法,且只能够导入静态方法,作用是能够免去写静态方法之前的类名。
- 注意1:静态导入只能够导入静态方法,而不能够导入其他成员。
- 注意2:静态导入会使程序的可读性降低,不推荐使用。
static关键字的用法
- 修饰类,静态内部类
- 修饰成员变量
- 修饰成员方法
- 静态代码块,随着类加载执行,在构造方法之前执行完毕
- 静态导入
泛型
泛型通配符
- 泛型通配符要解决的问题:为集合提供类似数组的功能,但是同时要避免数组可能存在的问题
- 数组是一个可协变类型,但是泛型集合不是可协变的类型
- 数组的问题在于往
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)
- V put(K key, V value)
- 删
- 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
- Set<Map.Entry<K,V>> entrySet() 【遍历的最佳实践】
- 注意事项
- 要判断key是否存在,不能够通过
get(key)!=null
,必须通过containskey
方法,也就是说,不能够认为返回null的key都不存在; - 要遍历一个Map的时候,最好是使用
entrySet()
,因为只需要一次遍历就能够取到键值对;否则使用keySet
在拿到key之后还要进行一边查找;
- 要判断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算法)
- protected boolean removeEldestEntry(Map.Entry<K,V> eldest)
TreeMap
TreeSet可以看作是TreeMap的马甲,用的是key,所以他所有的key都指向一个用来占位的最简单的对象PRESENT!
- TreeMap特点
- 底层数据结构是红黑树
- 创建对象时,传入了比较器
comparator
就按照比较器规定的顺序进行排序; - 创建对象时,如果没有传入比较器,那么就按照内在顺序进行排序,必须实现
compareable
接口; - 不同步;
- 不能够存储null键,除非
comparator
中能够对null键进行比较;
- 注意事项
- TreeMap依赖于key的
compareTo()
方法,或者是传入comparator
对象的compare()方法 - 千万不要修改key的属性值
- Map的规则是对key的约束!
- TreeMap依赖于key的
- TreeMap特有的API
练习
Properties类
- 概述
- 表示一个(可以)持久的属性集,持久的意思是可以保存到磁盘或者网络上;
- 对象都是存在于内存当中的,而properties类对象可以保存在流中或者从流中获取;
- properties类属性列表中,每个键及其对应值都是一个字符串;
- 注意事项
- 不要使用HashTable里定义的方法添加键值对,因为可能添加进非String类型的数据。
properties
类继承自HashTable<Object,Object>
,所以它有可能使用put()或者putAll()方法,将非String类型的值存储进properties
; - 有可能存储非String类型的
properties
是“不安全”的,对其采用store()或者save()操作会失败。
- 不要使用HashTable里定义的方法添加键值对,因为可能添加进非String类型的数据。
properties类中特有的API
111