这是一些我从《java
数据结构和算法》中选出来的,需要记住的东西。
一、javaAPI中的表
1.1 Collection接口
Collction
接口扩展了Iterable
接口,有一些基本的方法需要掌握:
public interface Collection<T> extends Iterable<T>{
int size();
boolean isEmpty();
void clear();
boolean contains(T x);
boolean add(T x);
boolean remove(T x);
java.util.Iterator<T> iterator();
}
1.2 Iterator接口
实现java.lang.Iterable
接口的集合必须提供一个iterator
的方法,该方法返回一个java.util.Iterator
对象,此接口的定义中有几个方法需要掌握:
public interface Iterator<T>{
boolean hasNext();
T next();
void remove();
}
说明:
- 此接口的
remove
方法的效率比Collection
中的remove
方法的效率要高; - 当使用此接口进行迭代的时候要注意,一定不要在迭代的过程中该表集合,比如使用
add、remove、clear
等方法。
1.3 List接口、ArrayList接口、LinkedList接口
-
List
接口中有几个基本的方法需要掌握:
public interface List<T> extends Collection<T>{
T get(int idx);
T set(int idx, T newVal);
void add(int idx, T x);
void remove(int idx);
ListIterator<T> listIterator(int pos);
}
ArrayList
的优点在于对get
和set
方法花费常数时间,缺点是插入和删除代价昂贵,除非是在其末端进行。LinkedList
的优点在于添加和删除都是花费常数时间,但是get和set方法花费较大。其提供了一些好用的方法:addFirst、removeFirst、addLast、removeLast、getFirst、getLast
。
1.4 ArrayList的实现
主要的细节有:
- 将保持基础数组,数组的容量,以及存储在其中的当前元素的个数;
- 将提供一种机制以改变基础数组的容量。通过获得一个新数组,将老数组拷贝到新数组中来改变数组的容量,允许虚拟机回收老数组;
- 将提供get和set实现
- 将提供基本的例程,如
size、isEmpty和clear、remove
,以及两种不同版本的add
。如果数组的大小和容量相同,那么这两个add
例程将增加容量。
import java.util.Iterator;
import java.util.NoSuchElementException;
public class MyArrayList<T> implements Iterable<T> {
private static final int DEFAULT_CAPACITY = 10;//链表的默认容量
private int theSize;//链表中当前元素的项数
private T[] theItems;//用于存储元素
public MyArrayList() {
clear();
}
public void clear(){
theSize = 0;
ensureCapacity(DEFAULT_CAPACITY);
}
public int size(){
return theSize;
}
public boolean isEmpty(){
return size() == 0;
}
//扩容
public void trimToSize(){
ensureCapacity(size());
}
public T get(int idx){
if(idx < 0 || idx >= size()){
throw new ArrayIndexOutOfBoundsException();
}
return theItems[idx];
}
public T set(int idx, T newVal){
if(idx < 0 || idx >= size()){
throw new ArrayIndexOutOfBoundsException();
}
T old = theItems[idx];
theItems[idx] = newVal;
return old;
}
public void ensureCapacity(int newCapactiy){
if(newCapactiy < theSize){
return ;
}
T[] old = theItems;
theItems = (T[]) new Object[newCapactiy];
for(int i = 0; i < size(); i++){
theItems[i] = old[i];
}
}
//在链表的末尾添加
public boolean add(T x){
add(size(), x);
return true;
}
//在链表的某个位置添加
public void add(int idx, T x){
//首先判断是否需要扩容
if(theItems.length == size()){
ensureCapacity(size() * 2 + 1);
}
//添加之前需要将idx后面元素依次向后移动,注意是从theSize开始
for(int i = theSize; i > idx; i--){
theItems[i] = theItems[i - 1];
}
theItems[idx] = x;
theSize++;
}
//删除,就是将元素向前移动,然后将当前元素项数减一
public T remove(int idx){
if(idx < 0 || idx >= size()){
throw new ArrayIndexOutOfBoundsException();
}
T removedItem = theItems[idx];
for(int i = idx; i < size() - 1; i++){
theItems[i] = theItems[i + 1];
}
theSize--;
return removedItem;
}
public Iterator<T> iterator() {
return new ArrayListIterator();
}
//实现Iterator接口
private class ArrayListIterator implements Iterator<T>{
private int current = 0;
//如果标记小于当前元素的项数,表示还有下一个元素
public boolean hasNext(){
return current < size();
}
public T next(){
if(!hasNext()){
throw new NoSuchElementException();
}
return theItems[current++];
}
public void remove(){
MyArrayList.this.remove(--current);
}
}
}
1.5 LinkedList类的实现
在设计方面,我们将需要提供三个类:
-
MyLinkedList
类本身,它包含到两端的链、表的大小以及一些方法 -
Node
类,它可能是一个私有的嵌套类。一个节点包含数据以及前一个节点的链和到下一个节点的链,还有一些适当的构造方法 -
LinkedListIterator
类,该类抽象了位置的概念,是一个私有类,并实现接口Iterator
。它提供了方法next、hasNext
和remove
的实现。
package cn.list;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.NoSuchElementException;
public class MyLinkedList<T> implements Iterable<T> {
private int theSize;//链表中元素个数
private int modCount = 0;//对链表改动的一个计数器
private Node<T> beginMarker;//链表起始标记(但是不属于链表的节点)
private Node<T> endMarker;//链表结束标记(但是不属于链表的节点)
public MyLinkedList(){
clear();
}
public void clear(){
//初始化开头和结束标记
beginMarker = new Node<T>(null, null, null);
endMarker = new Node<T>(null, beginMarker, null);
beginMarker.next = endMarker;
theSize = 0;
modCount++;
}
//链表的大小
public int size(){
return theSize;
}
public boolean isEmpty(){
return size() == 0;
}
//默认是在链表结尾添加
public boolean add(T x){
add(size(), x);
return true;
}
//在指定位置添加
public void add(int idx, T x){
addBefore(getNode(idx), x);
}
public T get(int idx){
return getNode(idx).data;
}
public T set(int idx, T newVal){
Node<T> p = getNode(idx);
T oldVal = p.data;
p.data = newVal;
return oldVal;//这里其实应该返回设置成功或者失败
}
public T remove(int idx){
return remove(getNode(idx));
}
private void addBefore(Node<T> p, T x){
//首先新节点的前缀指向p的前一个节点,而新节点的后缀指向p
Node<T> newNode = new Node<T>(x, p.prev, p);
//将p和p的前一个节点断开,让其指向新节点
newNode.prev.next = newNode;
//让p的前一个节点变成新节点
p.prev = newNode;
theSize++;
modCount++;
}
//删除节点
private T remove(Node<T> p){
p.next.prev = p.prev;
p.prev.next = p.next;
theSize--;
modCount++;
return p.data;
}
private Node<T> getNode(int idx){
Node<T> p;
if(idx < 0 || idx > size()){
throw new IndexOutOfBoundsException();
}
//如果给定的索引小于链表长度的一半,那么我们从前向后搜索
//否则从后向前搜索,提高效率
if(idx < size() / 2){
p = beginMarker.next;
for(int i = 0; i < idx; i++){
p = p.next;
}
}else{
p = endMarker;
for(int i = size(); i > idx; i--){
p = p.prev;
}
}
return p;
}
public Iterator<T> iterator(){
return new LinkedListIterator();
}
//迭代器
private class LinkedListIterator implements Iterator<T>{
//迭代开始时第一个节点为链表头标记的下一个节点
private Node<T> current = beginMarker.next;
//开始时让迭代器的计数器和链表的计数器,如果中途两者不想等,表明程序出现错误
private int expectedModCount = modCount;
private boolean okToRemove = false;
//当前节点指向链表结束标记表示到末尾了
public boolean hasNext(){
return current != endMarker;
}
public T next(){
if(modCount != expectedModCount){
throw new ConcurrentModificationException();
}
if(!hasNext()){
throw new NoSuchElementException();
}
T nextItem = current.data;
current = current.next;
//将此标记设置为true,即表示在迭代的时候不允许使用remove等方法改变链表
okToRemove = true;
return nextItem;
}
public void remove(){
if(modCount != expectedModCount){
throw new ConcurrentModificationException();
}
if(!hasNext()){
throw new NoSuchElementException();
}
MyLinkedList.this.remove(current.prev);
okToRemove = false;
expectedModCount++;
}
}
//节点类
private static class Node<T>{
private T data;//数据
private Node<T> prev;//本节点的前一个节点
private Node<T> next;//本节点的后一个节点
public Node(T data, Node<T> prev, Node<T> next) {
this.data = data;
this.prev = prev;
this.next = next;
}
}
}