在 Java SE中,提供了数个收集对象的类,可以直接取用这些类,而不用重新打造类似的API。
1、Collection架构
Java SE提供了满足各种需求的API,在使用些API前,建议先了解其继承与接口操作架构,才能了解何时该采用哪个类,以及类之间如何彼此合作,而不会沦于死背API或抄写范例的窘境。 针对收集对象的需求, Java SE提供了 Collection API:
收集对象的行为,像是新增对象的add()方法、移除对象的remove()方法等,都是定义在java.util.Collection中。既然可以收集对象,也要能逐一取得对象,这就是java.lang.Iterable定义的行为,它定义了 iterator()方法返回java.uti1. Iterator操作对象,可以让你逐一取得收集的对象,详细操作方式,下一篇再做说明 收集对象的共同行为定义在collection中,然而收集对象会有不同的需求。如果希望收集时记录每个对象的索引顺序,并可依索引取回对象,这样的行为定义在java.util.List接口中。如果希望收集的对象不重复,具有集合的行为,则由java.util.Set定义。如果希望收集对象时以队列方式,收集的对象加入至尾端,取得对象时从前端,则可以使用java.util.Queue。如果希望对Queue的两端进行加入、移除等操作,则可以使用java util. Deque。
收集对象时,会依需求使用不同的接口操作对象。举例来说,如果想要收集时具有索引顺序,操作方式之一就是使用数组,而以组操作List的就是java.util.Arraylist。高度自定义的Java提供了AbstractCollection、AbstractList等,必要时可以继承AbstractCollection,操作自己的Collection,其他的API也类似。
2、List
List是一种Collection,可以收集对象,并以索引方式保留收集的对象顺序。 操作类有ArrayList,LinkedList。
1、ArrayList
数组在内存中会是连续的线性空间,根据索引随机存取时速度快,如果操作上有这类需求时,像是排序,就可使用 Arraylist,可得到较好的速度表现。 数组在内存中会是连续的线性空间,如果需要调整索引顺序时,会有较差的表现。例如若在已收集100对象的Arraylist中,使用可指定索引的add()方法,将对象新增到索引0位置,那么原先索引0的对象必须调整至索引1,索引1的对象必须调整至索引2,索引的对象必须调整至索引3。依此类推,使用 Arraylist做这类操作并不经济。 数组的长度固定也是要考虑的问题,在Arraylist内部数组长度不够时,会建立新数组,并将旧数组的参考指定给新数组,这也是必须耗费时间与内存的操作。为此, Arraylist有个可指定容量(Capacity)的构造函数,如果大致知道将收集的对象范围,事先建立足够长度的内部数组,可以节省以上所描述的成本。
2. Linkedlist
linkedlist在操作List接口时,采用了链接link结构。参考下面的可以更好理解link结构:
package coll_map;
public class LinkedListDemo {
private class Node {
Node(Object o) {
this.o = o;
}
Object o;
Node next;
}
private Node first;
public void add(Object elem) {
Node node = new Node(elem);
if (first == null) {
first = node;
} else {
append(node);
}
}
private void append(Node node) {
// TODO 自动生成的方法存根
Node last = first;
while (last.next != null) {
last = last.next;
}
last.next = node;
}
private int size() {
int count = 0;
Node last = first;
while (last != null) {
last = last.next;
count++;
}
return count;
}
public Object get(int index) {
checkSize(index);
return findElemOf(index);
}
private void checkSize(int index) throws IndexOutOfBoundsException {
int size = size();
if (index >= size) {
throw new IndexOutOfBoundsException(String.format("Index:%d,Size:%d", index, size));
}
}
private Object findElemOf(int index) {
int count = 0;
Node last = first;
while (count < index) {
last = last.next;
count++;
}
return last.elem;//???
}
}
在 simplelinkedlist内部使用Node封装新增的对象0,每次add()新增对象之后,将会形成链状结构目。 所以每次add()对象时,才会建立新的Node来保存对象,不会事先耗费内存,若调用size(),则从第一个对象,逐一参考下一个对象并计数,则可取得收集的对象长度。若想调用get()指定索引取得对象,则从第一个对象,逐一参考下一个对象并计数,则可取得指定索引的对象。 可以看出,想要指定索引随机存取对象时,链接方式都得使用从第一个元素开始查找下一个元素的方式,会比较没有效率,像排序就不适合使用链接操作的List。如果排序时,刚好必须将索引0与索引10000的素调换,效率就不高。 链接的每个元素会参考下一个元素,这有利于调整索引顺序。例如,若在已收集100对象的 simplelinkedlist中,操作可指定索引的add()方法,将对象新增到索引0位置。 新增的对象将建立Node实例封装,而frst(或上一节点的next)重新参考至新建的Node对象,新建Node的next则参考至下一node对象。因此,若收集的对象经常会有变动索引的情况,也许考虑链接方式操作的List会比较好,像是随时会有客户端登录或注销的客户端List,使用 Linkedlist会有比较好的效率
3、内容不重复的Set
同样是收集对象,在收集过程中若有相同对象,则不再重复收集,若有这类需求,可以使用set接口的操作对象。收集不重复单词:
package coll_map;
import java.util.*;
public class WordFind {
public static void main(String[] args) {
Scanner scan=new Scanner(System.in);
System.out.println("输入英文");
Set words =tokenSet(scan.nextLine());//调用tokenSet返回的是HashSet所有可以直接这样
System.out.printf("不重单词%d个:%s%n", words.size(),words);
}
private static Set tokenSet(String line) {
String[] tokens=line.split(" ");//根据空白切割出字符串,返回的是Spring[]
return new HashSet(Arrays.asList(tokens));
}
}
Arrays.asList ()方法返回List,而工List是一种Collection,因而可传给Hashset接受 Collection实例的构造函数,由于set的特性是不重复,因此若有相同单词,则不会再重复加入,最后只要调用set的size()方法,就可以知道收集的字符串个数, Hashset的 toString()操作,则会包括收集的字符串。
package coll_map;
import java.util.*;
class Student {
private String name;
private int ID;
Student(String name, int ID) {
this.name = name;
this.ID = ID;
}
@Override
public String toString() {
// TODO 自动生成的方法存根
return String.format("(%s,%s)", name, ID);
}
}
public class Students {
public static void main(String[] args) {
Set<Student> stus=new HashSet<>();
stus.add(new Student("gg1", 2017));
stus.add(new Student("gg2", 2018));
stus.add(new Student("gg3", 2019));
stus.add(new Student("gg2", 2018));
System.out.println(stus);
}
}
set没有将重复学生数据排除,因为你并没有告诉set,什么样的 Student实例才算是重复,以 Hashset为例,会使用对象的 hashcode()与 equals()来判断对象是否相同。 Hashset的操作概念是,在内存中开设空间,每个空间会有个哈希编码( Hash Code)。
这些空间称为哈希桶( Hash Bucket),如果对象要加入 Hashset,则会调用对象的 hashcode)取得哈希码,并尝试放入对应号码的哈希桶中,如果哈希桶中没对象,则直接放入,如果哈希桶中有对象会再调用对象的equa1s()进行比较。 如果同一个哈希桶中已有对象,调用该对象 equals()与要加入的对象比较,结果为 false,则表示两个对象非重复对象,可以收集,如果是true,表示两个对象是重复对象,则不予收集事实上不只有 Hashset,Java中许多要判断对象是否重复时,都会调用 hashcode()与 equals()方法,因此规格书中建议,两个方法必须同时操作。以前面范例而言,若操作了 hashcode与 equals方法,则重复的 Student将不会被收集。IDE一般可以自动生成。
4、Queue
如果希望收集对象时以队列方式,收集的对象加入至尾端,取得对象时从前端,则可 add以使用 Queue接口的操作对象。 Queue继承自 collection,所以也具有 collection的 remove)、 element()等方法,然而 Queue定义了自己的 offer()、poll()与peek()等方法,最主要的差别之一在于,add()、 remove()、element()等方法操作失败时会抛出异常,而offer、poll()、()与peek()等方法操作失败时会返回特定值。 如果对象有操作 Queue,并打算以队列方式使用,且队列长度受限,通常建议使用 offer()、po1l()与peek()等方法。 offer()方法用来在队列后端加入对象,成功会返回true失败则返回 false。Poll方法用来取出队列前端对象,若队列为空则返回null,peek用来取得(但不取出)队列前端对象,若队列为空则返回null。 前面提过 Linkedlist它不仅操作了List接口,也操作了 Queue的行为,所以可将 linkedlist当作队列来使用。Queue<T> name = new LinkedList<>();
package coll_map;
import java.util.*;
interface Request {
void execute();
}
public class RequestQueue {
public static void main(String[] args) {
Queue<Request> requests = new LinkedList<>();
offerRequestTo(requests);
process(requests);
}
static void offerRequestTo(Queue<Request> requests) {
for (int i = 1; i <= 5; i++) {
requests.offer(() -> System.out.printf("模拟产生数据:%f%n", Math.random())); // ----Lambda表达式----
}
}
private static void process(Queue<Request> requests) {
while (requests.peek() != null) {
Request request = requests.poll();
request.execute();
}
}
}
Deque
想对队列的前端与尾端进行操作,在前端加入对象与取出对象,在尾端加入对象与取出对象,Queue的子接口Deque就定义了这类行为。 Deque中定义 addfirst() removeFirst()、 getFirst()、 addlast()、 removelast()、 getLast( 等方法,操作失败时会抛出异常,而 offerfirst()、 polifirst(、 peekpirst()、 offerlast()、 polllast()、 peeklast()等方法,操作失败时会返回特定值 Qeue的行为与 Deque的行为有所重复,有几个操作是等义的:
Queue | Deque |
---|---|
add | addLast |
offer | offerLast |
remove | removeFirst |
poll | pollFirst |
element | getFirst |
peek | peekFirst |
使用ArrayDeque操作有限堆栈:
package coll_map;
import java.util.*;
public class Stack {
private Deque<Object> elems=new ArrayDeque<>();
private int capacity;
public Stack(int capacity) {
this.capacity=capacity;
}
public boolean push(Object o) {
if(isFull()) {
return false;
}
return elems.offerLast(o);
}
private boolean isFull() {
return elems.size()+1>capacity;
}
public Object pop() {
return elems.peekLast();
}
public int size() {
return elems.size();
}
public static void main(String[] args) {
Stack stack=new Stack(5);
stack.push("one");
stack.push("two");
stack.push("three");
System.out.println(stack.pop());
System.out.println(stack.pop());
System.out.println(stack.pop());
}
}