Java 集合
一.java 集合框架
1.1 将集合的接口与实现分离
简单地定义接口 每一个实现(循环数组或链表)都可以通过一个实现了Queue接口的类表示
可以使用接口类型存放集合的应用:Queue<Customer> expressLane = new CircularArrayQueue<>(100);
1.2 Collection接口 :集合类的基本接口是Collection接口,这个接口有两个基本方法:
boolean add(E element); Iterator<E> iterator();
...
add方法用于向集合添加元素,如果改变了集合就返回true,否则返回false。
iterator方法用于返回一个实现了Iterator接口的对象,可以使用这个迭代器对象依次访问集合中的元素。
1.3 iterator迭代器
Iterator接口包含4个方法,E为参数,E next(); boolean hasNext(); void remove(); default void forEachRemaining(Consumer<? super E> action);
next 方法反复调用可以逐个访问集合中的每个元素,但是为了确定有没有到达集合的末尾,需要在调用next之前调用hasNext方法。
如果迭代器对象还有多个供访问的元素,hasNext就返回ture。 遍历数组和字符串可以用for each循环效率更高。
java迭代器查找一个元素的唯一方法是调用next,在执行查找操作时,迭代器的位置随之向前移动,返回刚刚越过的那个元素的引用。
Iterator接口的remove()方法将会删除上次调用next()方法时返回的元素,因此,如果要删除一个元素,首先需要使用一次next()方法查找。
1.4 泛型实用方法
由于Collection与Iterator都是泛型接口,可以编写操作任何集合类型的实用方法。例如下面一个检测任意集合是否包含指定元素的泛型方法:
public static <E> boolean contains(Collection<E> c, Object obj) { for(E element : c) if (element.equals(obj)) returm true; return false; }
二. 具体的集合
Java类库中的集合有例如ArrayList,LinkedList等多种类型,除了以Map结尾的类,其他类都实现了Collection接口。
2.1 链表
与数组在中间删除或插入一个元素需要付出很大的代价相比,链表中间插入删除明显简单很多,Java中所有链表实际上都是双向链接的。
从链表中删除一个元素,只需要更新被删除元素附近的链接。
在元素无序的情况下,只对自然有序的集合使用的Iterator接口就没有add方法,集合类库提供了子接口ListIterator,其中包含add方法
interface ListIterator<E> extends Iterator<E> { void add(E element); ... }
这个方法不返回boolean类型的值,且有两个方法可以用来反向遍历链表。
E previous()
boolean hasPrevious()
set方法用新元素取代next或previous方法返回的上一个元素。
为了避免并发修改的异常,需要遵循规则:更具需要给容器附加许多的迭代器,但是这些迭代器只能读取列表。另外,再单独附加一个既能读又能写的迭代器。
get方法可以查看链表第n个元素,但是必须从头开始越过n-1个元素,因此需要采用整数索引访问元素时,通常不采用链表。
2.3 散列表 (二叉查找树)
有几种能够快速查找元素的数据结构,其缺点是无法控制元素出现的次序。
散列表(hash table)就是可以快速地查找所需要对象的数据结构,它为每个对象计算一个称为散列码(hash code)的整数,散列码由对象的实例域产生。
自定义类需要自己实现这个类的 hash code,同时要与equals的定义一致,如果(x)equals(y)返回true,那么x.hashCode()就必须与y.hashCode()具有相同的值。
散列值计算:每个列表被称为桶(bucket), 首先算出散列码,索引计算方式:“散列码/桶(bucket)总数 余 N” ,如果第N号桶没有其他元素,就可以将元素直接插入到桶中就可以了。
再散列:如果装填因子为0.75(默认值),这个表就会用双倍的桶数自动进行再散列,即创建一个双倍桶数的新表,并将所有的元素插入到新表中,然后丢弃原来的表。
实现散列表:Java集合类库提供了一个HashSet类,可以实现基于散列表的集,可以用add方法添加元素。
2.4 树集
TreeSet类:任意顺序将元素插入到集合中,在对集合进行遍历时,每个值将自动按照排序后的顺序呈现,与散列集相似但有所改进。
TreeSet类的特点:使用树结构排序,每次将一个元素添加到树中时,都被放置在正确的排序位置上,迭代器以排好序的顺序访问每个元素。
优点:查找新元素的正确位置平均需要 log₂n 次比较,部分情况快于数组或散列。 缺点:将元素添加到树中要比添加到散列表中慢。
实现树集:TreeSet()方法
注:要使用树集,必须实现Comparable 接口,或者构造集时必须提供一个Comparator
2.5 队列与双端队列
特点:有两个端头的队列,可以有效得在队列的头部和尾部同时添加或删除元素,不支持在队列中添加元素。
实现:Deque接口,ArrayDeque和LinkedList类实现。
2.6 优先级队列
优先级队列:元素可以按照任意的顺序插入,却总是按照排序的顺序进行检索。
优先级队列特点:使用堆( heap )数据结构,这是一个可以自我调整的二叉树。
典例:任务调度:任务以随机乱序加到队列中,每当启动一个新的任务,都会将优先级最高的任务从队列中删除(1一般设为最高优先级,即把最小的元素删除)
三.映射
映射(map): 映射是种数据结构,映射用来存放键/值对,如果提供了键,就能查找到值
3.1 基本映射操作
映射实现:HashMap 和 TreeMap ,是两个通用的实现
操作过程:首先将键/值对添加到映射中,然后,从映射中删除一个键,同时与之对应的值也被删除了;修改某一个键对应的值,并调用get方法查看这个值
3.2 更新映射项
更新映射项:正常情况下,可以得到与一个键关联的原值,完成更新,再放回更新后的值。不过,必须考虑一个特殊情况,即键第一次出现。
3.3 映射视图
映射的视图(view)——这是实现了Collection 接口或某个子接口的对象
keySet(); values(); entrySet();三种视图分别为键值,值集合,以及键/值对集
3.4 弱散列映射
WeakHashMap:使用弱引用保存键。作用当某个对象只能由WeakReference引用,WeakHashMap周期性检查队列,当一个弱应用进入队列,意味着这个键不再被使用,于是删除对应条
3.5 链接散列集与映射
LinkedHashSet和LinkedHashMap类用来记住插入元素项的顺序
3.6 枚举集与映射
EnumSet:一个枚举类型元素集的高效实现,没有公共的构造体,可以使用静态工厂的方法构造这个集
EnumMap:是一个键类型为枚举类型的映射
3.7 标识散列映射
类IdentituHashMap :类生成hashCode的方法是根据内存地址计算散列码,因此使用 == 不使用 equals。
特点:不同的键对象,即使内容相同,仍然被认为是不同的对象。在实现对象遍历算法时,可以用来跟踪每个对象的遍历情况。
四. 视图与包装器
视图(views) :可以获得其他的实现了Collection接口和Map接口的对象
例如:映射类的KeySet方法,KeySet方法返回一个实现Set接口的类对象,这个类的方法对原映射进行操作。
4.1 轻量级集合包装器
java中的视图,可以说其实就是一个具有限制的集合对象,只不过这里的不是集合对象,而是一个视图对象。例如:这里有一个Test类
Test[] tests = new Test[10];
List<Test> testList = Arrays.asList(tests);
这里的testList是一个视图对象,具有访问数组元素set,get的方法。但是如果调用改变数组的方法就会抛出异常。所以可以说视图对象可以说是具有限制的集合对象。
4.2 子范围
子范围(subrange)视图 ,例如将一个列表staff,从中取出第10~19个元素,可以使用subList方法来获得一个列表的子范围视图
4.3 不可修改的视图
Collections.unmodiifiableCollection 等方法:对现有集合增加一个运行时的检查,如果发现视图对集合进行修改,就抛出一个异常。同时保持集合为未修改的状态。
4.4 同步视图
为了保证多个线程访问集合时,集合不会被意外地破坏,因此,使用视图机制确保常规集合的线程安全。
Collection类的静态synchronizedMap 方法可以将任何一个映射表转换成具有同步访问方法的Map。
4.5 受查视图
受查视图:用来对泛型类型发生问题时提供调试支持
例子:List<String> safeStrings = Collection.checkedList(strings,string.class);
五.算法
泛型集合接口的优点:算法只需要实现一次
例如: public static <T extends Comparable> T max(Collection<T> c) //max方法实现为能够接收任何实现了Collection接口的对象
5.1 排序与混排
Collections类中的sort方法:可以对实现了List接口的集合进行排序
Collections类中的shuffle方法:随机的混排列表中元素的顺序
5.2 二分查找
二分查找:直接查看位于数组中间的元素,看一看是否大于要查找的元素,如果是,则用同样的方法在数组前半部分查找。不是,则在后半部分用同样的方法
优点:快捷,如果有1024个元素,可以在10次比较后定位元素(或者确认查找的元素在数组中是否存在),而线性查找需要比较512次,不存在时需要1024次。
Collections类的binarySearch方法:实现了这个算法,注意提供的集合必须是排好序的,否则算法将返回错误的答案。
注意:如果为binarySearch算法提供一个链表,它会自动地变为线性查找。
5.3 简单算法
Collection类中包含了几个简单且有用的算法,例如查找集合中最大元素的示例,这是为了方便编写和阅读代码
5.4 批操作
x.removeAll(y); x.retainAll(y);等
5.5 集合与数组的转换
Arrays.asList 包装器:可以将一个数组转换为集合
String[] values = ...; HashSet<String> staff = new HashSet<>(Array.asList(values));
toArray 方法:从集合得到数组
但是toArray方法返回的数组是一个Object[]数组,不能使用强制类型转换。因此,必须使用toArray方法的一个变体形式:
//提供一个所需类型而且长度为0的数组,返回的数组会创建为相同的数组类型 String[] values = staff.toArray(new String[0]); //可以构造一个指定大小的数组 staff.toArray(new String[staff.size()]);
5.6 编写自己的算法
编写自己的算法(即以集合作为参数的任何方法):应该尽可能地使用接口,而不是具体的实现。
六.遗留的集合
①Hashtable类:与HashMap类的作用一样,对同步性或与遗留代码的兼容性没要求,就用HashMap,需要并发访问则用 ConcurrentHashMap。
②枚举:遗留集合使用Enumeration接口对元素序列进行遍历。
③属性映射(property map):具有三大特性:键与值都是字符串、表可以保存到一个文件中,也可以从文件中加载、使用一个默认的辅助表。
使用范围:实现属性映射的Java平台类称为Properties。 属性映射通常用于程序的特殊配置选项
④栈(Stack)类:有push方法和pop方法,且Stack类扩展为Vector类。
⑤位集 (BitSet类):用于存放一个位序列(实际上称它位向量或位数组更贴切)
特点:BitSet类提供了一个便于读取,设置或清除各个位的接口,且位集存储位序列时,比使用Boolean对象的ArrayList更加高效。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!