链表LinkedList
LinkedList 也像 ArrayList 一样实现了基本的 List 接口,但它在 List 中间执行插入和删除操作时比 ArrayList 更高效。然而,它在随机访问操作效率方面却要逊色一些。
LinkedList 还添加了一些方法,使其可以被用作栈、队列或双端队列(deque) 。在这些方法中,有些彼此之间可能只是名称有些差异,或者只存在些许差异,以使得这些名字在特定用法的上下文环境中更加适用(特别是在 Queue 中)。例如:
- getFirst() 和 element() 是相同的,它们都返回列表的头部(第一个元素)而并不删除它,如果 List 为空,则抛出 NoSuchElementException 异常。 peek() 方法与这两个方法只是稍有差异,它在列表为空时返回 null 。
- removeFirst() 和 remove() 也是相同的,它们删除并返回列表的头部元素,并在列表为空时抛出 NoSuchElementException 异常。 poll() 稍有差异,它在列表为空时返回 null 。
- addFirst() 在列表的开头插入一个元素。
- offer() 与 add() 和 addLast() 相同。 它们都在列表的尾部(末尾)添加一个元素。
- removeLast() 删除并返回列表的最后一个元素。
/**
* @author myf
*/
public class LinkedListFeatures {
public static void main(String[] args) {
LinkedList<Fruits> fruitsList = new LinkedList<>();
LinkedList<Fruits> nullList = new LinkedList<>();
fruitsList.add(new Fruits("test"));
fruitsList.add(new Fruits("apple"));
fruitsList.add(new Fruits("orange"));
fruitsList.add(new Fruits("banana"));
// NoSuchElementException
// System.out.println(nullList.getFirst());
// System.out.println(nullList.element());
// null
System.out.println(nullList.peek());
// Fruits{name='test'}
System.out.println(fruitsList.getFirst());
// Fruits{name='test'}
System.out.println(fruitsList.element());
// Fruits{name='test'}
System.out.println(fruitsList.peek());
// NoSuchElementException
// System.out.println(nullList.removeFirst());
// System.out.println(nullList.remove());
// null
System.out.println(nullList.poll());
// Fruits{name='test'}
System.out.println(fruitsList.removeFirst());
// Fruits{name='apple'}
System.out.println(fruitsList.remove());
// Fruits{name='orange'}
System.out.println(fruitsList.poll());
// [Fruits{name='banana'}]
System.out.println(fruitsList.toString());
fruitsList.addFirst(new Fruits("apple"));
// [Fruits{name='apple'}, Fruits{name='banana'}]
System.out.println(fruitsList.toString());
fruitsList.offer(new Fruits("test1"));
fruitsList.add(new Fruits("test2"));
fruitsList.addLast(new Fruits("test3"));
// [Fruits{name='apple'}, Fruits{name='banana'}, Fruits{name='test1'}, Fruits{name='test2'}, Fruits{name='test3'}]
System.out.println(fruitsList.toString());
fruitsList.removeLast();
// [Fruits{name='apple'}, Fruits{name='banana'}, Fruits{name='test1'}, Fruits{name='test2'}]
System.out.println(fruitsList);
}
}
堆栈Stack
堆栈是“后进先出”(LIFO)集合。它有时被称为叠加栈(pushdown stack),因为最后“压入”(push)栈的元素,第一个被“弹出”(pop)栈。经常用来类比栈的事物是带有弹簧支架的自助餐厅托盘。最后装入的托盘总是最先拿出来使用的。
Java 1.0 中附带了一个 Stack 类,结果设计得很糟糕(为了向后兼容,我们永远坚持 Java 中的旧设计错误)。Java 6 添加了 ArrayDeque ,其中包含直接实现堆栈功能的方法:
/**
* @author myf
*/
public class StackTest {
public static void main(String[] args) {
Deque<String> stringDeque = new ArrayDeque<>();
for (String s:"My dog has fleas".split(" ")){
stringDeque.add(s);
}
while (!stringDeque.isEmpty()){
System.out.println(stringDeque.pop());
}
}
}
即使我们是在实现一个堆栈,但我们仍必须将其声明为 Deque 。非常容易造成混淆。
我们可以写一个自己的Stack
/**
* @author myf
*/
public class Stack<T> {
private Deque<T> deque = new ArrayDeque<>();
public void push(T t){
deque.push(t);
}
public T peek(){
return deque.peek();
}
public T pop(){
return deque.pop();
}
public boolean isEmpty(){
return deque.isEmpty();
}
@Override
public String toString() {
return deque.toString();
}
}
这样我们在使用堆栈Stack时就可以使用我们自定义命名的这个堆栈了。
这里引入了使用泛型的类定义的最简单的可能示例。类名称后面的 告诉编译器这是一个参数化类型,而其中的类型参数 T 会在使用类时被实际类型替换。基本上,这个类是在声明“我们在定义一个可以持有 T 类型对象的 Stack 。” Stack 是使用 ArrayDeque 实现的,而 ArrayDeque 也被告知它将持有 T 类型对象。注意, push() 接受类型为 T 的对象,而 peek() 和 pop() 返回类型为 T 的对象。 peek() 方法将返回栈顶元素,但并不将其从栈顶删除,而 pop() 删除并返回顶部元素。
集合Set
Set 不保存重复的元素。 如果试图将相同对象的多个实例添加到 Set 中,那么它会阻止这种重复行为。 Set 最常见的用途是测试归属性,可以很轻松地询问某个对象是否在一个 Set 中。因此,查找通常是 Set 最重要的操作,因此通常会选择 HashSet 实现,该实现针对快速查找进行了优化。
Set 具有与 Collection 相同的接口,因此没有任何额外的功能,不像前面两种不同类型的 List 那样。实际上, Set 就是一个 Collection ,只是行为不同。(这是继承和多态思想的典型应用:表现不同的行为。)Set 根据对象的“值”确定归属性。
下面是使用存放 Integer 对象的 HashSet 的示例:
/**
* @author myf
*/
public class SetOfInteger {
public static void main(String[] args) {
Random rand = new Random(47);
Set<Integer> intset = new HashSet<>();
for(int i = 0; i < 10000; i++){
intset.add(rand.nextInt(30));
}
System.out.println(intset);
}
}
/* Output:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29]
*/
在 0 到 29 之间的 10000 个随机整数被添加到 Set 中,因此可以想象每个值都重复了很多次。但是从结果中可以看到,每一个数只有一个实例出现在结果中。
早期 Java 版本中的 HashSet 产生的输出没有可辨别的顺序。这是因为出于对速度的追求, HashSet 使用了散列,由 HashSet 维护的顺序与 TreeSet 或 LinkedHashSet 不同,因为它们的实现具有不同的元素存储方式。 TreeSet 将元素存储在红-黑树数据结构中,而 HashSet 使用散列函数。 LinkedHashSet 因为查询速度的原因也使用了散列,但是看起来使用了链表来维护元素的插入顺序。看起来散列算法好像已经改变了,现在 Integer 按顺序排序。但是,您不应该依赖此行为:
/**
* @author myf
*/
public class SetOfString {
public static void main(String[] args) {
Set<String> colors = new HashSet<>();
for(int i = 0; i < 100; i++) {
colors.add("Yellow");
colors.add("Blue");
colors.add("Red");
colors.add("Red");
colors.add("Orange");
colors.add("Yellow");
colors.add("Blue");
colors.add("Purple");
}
System.out.println(colors);
/* Output:
[Red, Yellow, Blue, Purple, Orange]
*/
Set<String> colors2 = new TreeSet<>();
for(int i = 0; i < 100; i++) {
colors2.add("Yellow");
colors2.add("Blue");
colors2.add("Red");
colors2.add("Red");
colors2.add("Orange");
colors2.add("Yellow");
colors2.add("Blue");
colors2.add("Purple");
}
System.out.println(colors2);
/* Output:
[Blue, Orange, Purple, Red, Yellow]
*/
// true
System.out.println(colors.contains("Yellow"));
}
}
HashSet,String 对象似乎没有排序。要对结果进行排序,一种方法是使用 TreeSet 而不是 HashSet 。
最常见的操作之一是使用 contains() 测试成员归属性