一、Java集合框架
Java集合库将接口(interface)与实现(implementation)分离。
以队列说明:
队列接口是指可以在对列添加元素,在队头删除元素,并且可以查找队列中的元素。简称为“先进先出”。
队列(Queue)接口最简形式可能如下:
public interface Queue<E>
{
void element(E element);
E remove();
int size;
}
队列的实现都可以用一个实现了Queue接口的类表示,应当注意的是Java库中没有名为CircularArrayQueue、LinkedListQueue类,这里只是用来解释集合接口与实现在概念的区分?。在实际开发中数组队列(ArrayDeque<>())、列表队列(LinkedList<>())。
//实现Queue接口的类
public class CircularArrayQueue<E> implements Queue<E>
{
private int head;
private int tail;
private E[] elements;
CircularArrayQueue(int capacity){...}
public int add(E element){...}
public E remove(){...}
public int size(){...}
}
public class LinkedListQueue<E> implements Queue<E>
{
private Link head;
private Link tail;
private E[] elements;
LinkedListQueue(int capacity){...}
public int add(E element){...}
public E remove(){...}
public int size(){...}
}
而实际代码如下:
class xxx<E> implements java.util.Queue<E>
{
@Override
public int size() {
return 0;
}
@Override
public boolean isEmpty() {
return false;
}
@Override
public boolean contains(Object o) {
return false;
}
@Override
public Iterator<E> iterator() {
return null;
}
@Override
public Object[] toArray() {
return new Object[0];
}
@Override
public <T> T[] toArray(T[] a) {
return null;
}
@Override
public boolean add(E e) {
return false;
}
@Override
public boolean remove(Object o) {
return false;
}
@Override
public boolean containsAll(Collection<?> c) {
return false;
}
@Override
public boolean addAll(Collection<? extends E> c) {
return false;
}
@Override
public boolean removeAll(Collection<?> c) {
return false;
}
@Override
public boolean retainAll(Collection<?> c) {
return false;
}
@Override
public void clear() {
}
@Override
public boolean offer(E e) {
return false;
}
@Override
public E remove() {
return null;
}
@Override
public E poll() {
return null;
}
@Override
public E element() {
return null;
}
@Override
public E peek() {
return null;
}
}
队列有两种实现方式:循环数组(Arrayqueue);链表队列(LinkedList)。
java.util.Queue<Integer> queue=new ArrayDeque<>();//循环数组队列
java.util.Queue<Integer> queue1 = new LinkedList<>();//链表队列
循环数组是一个有界集合,容量有限,如果程序中所收集的对象数量没有上限最好利用链表实现。
一、Collection接口
在Java库中,集合类的基本接口是Collection接口。其接口的方法参考笔记十集合类
public interface Collection<E>
add方法若元素确实被改变返回ture,反之返回false,在set集合中添加元素,而这个元素已经在set中存在,则add方法没有实效,因为set集合中元素不能重复。
二、迭代器
Iterator接口包含四个方法
- E next()
- boolean hasNext()
- void remove()
- default void forEachRemaining(Consumer<? spuer E>action)
元素的访问
以此代码为例,利用Collection接口创建了一个ArrayList集合对象。
Collection<Integer> collection = new ArrayList<>();
Iterator<Integer> iterator = collection.iterator();
1、反复调用next方法即可逐个获取集合中的元素,但当集合末尾时next方法会抛出NoSuchElementException,所以在调用next方法时,先利用hasNext方法判断。
while(iterator.hasNext())
{
int it = iterator.next();
System.out.println(it);
}
2、也可以用foreach循环。
for(Integer element:collection)
System.out.println(element);
3、使用lambda表达式
iterator.forEachRemaining(element->{
while(iterator.hasNext())
element = iterator.next();
});
访问元素的顺序取决于集合类型,若迭代处理一个ArrayList则索引从0开始,每次加一,若迭代处理为HashSet元素,会按照一种基本上随机的顺序访问,无法预知各元素的顺序。
迭代原理:
Java迭代器查找一个元素只能使用next方法,当调用next方法时,迭代器会越过下一个元素,返回上一个元素的引用。因此可以认为Java迭代器位于两个元素之间。
元素的删除
注意注意注意!!!!此时是在迭代器中,而并非在集合类中。
Interator接口的remove方法会删除上次next方法时返回的对象。netx方法与remove方法存在依赖性。如果调用remove之前未调用next方法则是不合法的,将会抛出IllegalStateException异常。
即在删除指定元素时,应当先越过要删除的元素。
例、以队列为例,添加1、2、3删除1
public class Queue {
public static void main(String args[])
{
java.util.Queue<Integer> queue = new ArrayDeque<>();
queue.add(1);
queue.add(2);
queue.add(3);
Iterator<Integer> Queue_iterator = queue.iterator();
//echo(Queue_iterator);
Queue_iterator.next();
Queue_iterator.remove();
echo(Queue_iterator);
}
public static void echo(java.util.Iterator<Integer> E)
{
while(E.hasNext())
{
int element = E.next();
System.out.println(element);
}
}
}
二、集合框架的接口
集合有两个基本接口Collection、Map
Map集合包含一个映射,读取值时应使用get方法。无迭代器,未继承Collection接口。
List为一个有序集合,有两种访问方式:得迭代器、下标索引,其中下标索引被称为随机访问。
Set集合不能添加相同元素,不要求有相同顺序。适当使用equals方法检查。包含相同元素的Set 集合使用hashCode会返回相同的散列码。
三、具体集合
其中List列表接口的实现类有LinkedList (链表)、ArrayList(数组列表)。
一、链表(linked list)
使用动态ArrayList类与数组在删除或添加时候极大的降低效率。当其中一个元素删除或增加时,其后所有的元素都要移动。
在Java中所有的链表都是双向链表,即每个链接存放这前去与后继的引用。
例、实现链表集合,添加三个元素,并删除第二个元素
import java.util.Iterator;
import java.util.List;
import static cn.abc.nb.Queue.echo;
public class LinkedList {
public static void main(String args[])
{
var linkedList = new java.util.LinkedList<Integer>();
//等价于List<Integer> linkedList = new linkedList<Integer>();
linkedList.add(1);
linkedList.add(2);
linkedList.add(3);
Iterator<Integer> LinkedIterator = linkedList.iterator();
LinkedIterator.next();
LinkedIterator.next();
LinkedIterator.remove();
//迭代遍历后,再次遍历需要重新创建构造器对象
Iterator<Integer> iterator = linkedList.iterator();
echo(iterator);
}
}
//result 1 3
迭代器
迭代遍历后,再次遍历需要重新创建构造器对象
对于自然有序的集合使用迭代器添加元素才有意义。所以Iterator接口中没有add方法,所以利用子接口ListIterator。其中在集合库类有ListIterator子接口其中包含了
void add(E element) //这个方法默认会改变链表无返回值
E previous()
boolean hasPrevious()
这两个方法用来反向遍历
LinkedList类的listIterator方法实现了一个ListIterator的迭代器对象。以上述代码为例
ListIterator<Integer> iter = linkedlist.listItetor();
注意:一定要记得创建迭代器对象使用的是方法,而不是使用构造器。当用一个刚由lisItetor方法返回并指向表头的迭代器时,此时使用add方法,新的元素将变成表头。当迭代器越过最后一个元素时(hasNext==false),添加的元素将变成表尾。
如下:
利用迭代器增加表头、表尾元素
//使用ListIterator接口中的listIterator方法创建迭代器
ListIterator<Integer> listIterator_= linkedList.listIterator();
listIterator_.add(0);//表头增加
向表尾增加,next循环,当hasNext为空时增加元素即可。
while(listIterator_.hasNext())
listIterator_.next();
if (listIterator_.hasNext()==false)
listIterator_.add(4);
而在实际开发时,选用List类中的方法进行增加或删除操作。
二、数组列表(ArrayList)
ArrayList类也实现了List接口,并拥有了List接口中的方法,且ArrayList为动态数组。
综上:有序列表有两种访问方式:迭代访问、使用get()、set()随机访问。当查看元素却不知 其位置时即可使用迭代器访问。
三、集合(Set)
集合接口的实现类有HashSet(散列集)、TreeSet(树集)、EunmSet(枚举集)
使用列表可以根据索引而找到相应元素,而当元素位置未知且元素个数较多,这时时间复杂度较大,所以当不考虑元素的顺序时,可以构造集合来实现快速查找,但缺点是无法控制元素出现的次数,因为集合中的元素是无序的。
1、散列表与散列码
利用散列表可以快速的查找对象,散列表(hash table)也称为哈希表。散列表为每一个对象计算一个整数,称为散列码(hash code)散列码是由对象的实例字段得出的一个整数。不同的数据的对象将产生不同的散列码。
2、散列码的计算
所有要实现快速查找必须先实现散列表,使用hashCode方法生成散列码。但注意计算散列码时不应该影响类中的其他元素。
在Java中散列表利用数组链表实现。每个列表被称为桶(bucket)。要查找表中对象的位置,先要计算出它的散列码,再与桶的总数取余,所得到的结果就是保存这个桶的索引。即将这个元素保存在相应的桶内。不过有时会遇到桶已经被装填的情况,称为散裂冲突(hash collision)。
1、HashSet(散列集)
Java集合库中提供了一个HashSet类,它实现了基于散列表的集。可用add方法添加元素。contains方法查找一个元素是否已在集中。它只查看桶的元素,而不必查看集合中的所有元素。
散列集迭代器可以依次访问所有的桶,所以在不关心元素中的顺序时才使用HashSet。
例、输入20个字符添加到散列集中,使用迭代处理散列集中不同的单词,输出5个。
package cn.SetCollection;
import static cn.SetCollection.Queue.echo;
import java.util.Scanner;
import java.util.Set;
/**
* <h3>idea_test</h3>
* <p>散列集学习</p>
*
* @author nb
* @date 2023-01-13 12:36
**/
public class HashSet {
public static void main(String args[])
{
//创建Hashset集合类
var words = new java.util.HashSet<String>();
//输入
try(var in = new Scanner(System.in))
{
//直接使用hasNext方法死循环无法退出
while(in.hasNext()) {
String word= in.nextLine();
//System.out.println(word);
words.add(word);
}
}
//迭代器访问,输出20个字符串
java.util.Iterator<String> iterator = words.iterator();
for (int i = 0;i<5&&iterator.hasNext();i++)
System.out.println(iterator.next());
System.out.println("...");
}
}
2、TreeSet(树集)
TreeSet集合与HashSet集合相比,树集是一个有序集合。可以将任意元素插入到集合中。在对集合进行排序时,会以自然升序的输出。其排序是利用红黑树完成的。
将一个元素添加到树集要比添加到散列集慢,当查找元素时时间复杂度为log2 n。
四、队列、双端队列、优先队列
队列是一种在头部删除元素,在尾部添加元素的数据结构。双端队列(Deque)允许在队头与队尾添加或删除元素,但不允许在队列中间添加元素。
1、Queue(单端队列)
Queue接口被LinkedList类实现,我们可以把LinkedList当成Queue来用。
普通队列(一端进另一端出):
Queue queue = new LinkedList()
或Deque deque = new LinkedList()
2、Deque(双端队列)
Deque这个接口被ArrayDeque、LinkedList、LinkedBlockingDeque类中实现。且允许在队头队尾删除或添加元素。
双端队列(两端都可进出)
Deque deque = new LinkedList()
Deque<Integer> deque = new ArrayDeque<>();
例、构建一个双端队列,添加三个元素123,并删除队头、队尾与中间元素并判断是否为空。添加队头、队尾
package cn.SetCollection;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.LinkedList;
import static cn.SetCollection.Queue.echo;
/**
* <h3>idea_test</h3>
* <p>队列双向队列</p>
*
* @author nb
* @date 2023-01-13 17:23
**/
public class Quque {
public static void main(String args[])
{
//构建ArrayDeque对象
Deque<String> deque = new ArrayDeque<>();
//向队列添加元素123
AddToDeque(deque);
//删除队头队尾与中间元素
deque.remove();//先进先出删除队头
deque.remove("2");
deque.removeLast();//最后一个元素,删除队尾
if (deque.peek()==null)
{
System.out.println("当前队列为空");
AddToDeque(deque);
}
//删除之前元素,再次添加元素后输出
echo(deque);
}
public static void AddToDeque(java.util.Queue<String> deque)
{
{
String str = new String("1,2,3");
for (int i = 0; i < str.length()-2; i++) {
//以逗号分割字符串,并依次向队列添加元素
String element[] = str.split(",", 3);
deque.add(element[i]);
}
}
}
}
3、优先队列
优先队列(priority queue)中的元素可以按照任意顺序插入,但会按照有序的顺序进行检索。
无论何时调用remove方法,总会获得当前优先队列中最小的元素。如果使用迭代处理元素,则不需要进行排序。
优先队列使用了堆(heap)。堆是一个可以组织的二叉树,添加和删除操作可以让最小的元素移动到根。
while(!PriorityQueue.isEmpty())
System.out.println(PriorityQueue.remove());
循环使用remove方法,可将其从小到大排序。