数组
通过使用数组,可以存储一连串的数据
在java中,可以存储所有的基本类型以及对象类型。
但是如果在代码中,需要频繁对数组中的元素进行添加和删除,特别是从数组的头部进行添加和删除,就会有如下的问题:
- 从头部添加时需要把所有的元素往后移,以腾出第一个位置插入新元素
- 从头部删除时,需要把所有的元素往前移。
//初始化一个数组
int[] a=new int[10];
a[0]=0;
a[1]=1;
a[2]=2;
//为了方便,使用currentSize来表示数组实际使用了多少
index=3;
//从头部插入一个元素-1,会使用如下方法
if(currentSize<10){
for(int i=currentSize;i>0;i--){
a[i]=a[i-1];
}
a[0]=-1;
}
//以上操作就是先把原先存在的数据往后移动,然后再插入头部
//当从头部删除一个元素时,使用方法如下
if(currentSize>0){
for(int i=0;i<currentSize;i++){
a[i]=a[i+1];
}
}
//以上操作就是把后面的元素往前移动
从代码可以看出,对数组的插入和删除操作麻烦,如果数组的容量非常大的话,那么循环次数将非常多。
还有一个问题就是,当数组已存储的元素个数达到初始化的容量,若要继续添加元素,就需要对数组进行扩容。如下代码
int[] a=new int[2];
a[0]=0;
a[1]=1;
//此时数组已经达到最大数量,需要进行扩容。
int[] b=new int[a.length*2];
for(int i=0;i<a.length;i++){
b[i]=a[i];
}
a=b;
//此时数组a的容量就扩大到原来的两倍
链表
为了避免使用数组带来的插入和删除的开销,需要保证元素可以不在内存地址上进行连续存储,否则添加或删除一个元素就可能需要整体移动。这是我们就以使用链表来存储数据。
结构
链表由一系列节点组成,这些节点在内存中不一定相连。在最简单的链表中,每一个节点均包含数据元素和该数据元素的后继的指针,将其称之为next。最后一个元素的next指向null。
如图:

//链表节点实现
class Node {
//使用Object类型,让节点能够保存任意类型的数据
private Object data;
private Node next;
public Node(Object data) {
this.data = data;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public Node getNext() {
return next;
}
public void setNext(Node next) {
this.next = next;
}
}
//链表使用
public class Test {
public static void main(String[] args) {
Node node1 = new Node(1);
Node node2 = new Node(2);
Node node3 = new Node(3);
node1.setNext(node2);
node2.setNext(node3);
node3.setNext(null);
//这样的链表就和数组 int[] a=new int[]{1,2,3}差不多,由于node3的后继不存在,所以node3指向null
}
}
我们可以通过intellij编辑器的断点进行查看:

关系就是node1->node2->node3->null
一般的,我们会为链表设置一个表头,它不存储数据,但是指向第一个元素
package list; class Node { //使用Object类型,让节点能够保存任意类型的数据 private Object data; private Node next; public Node() { } public Node(Object data) { this.data = data; } public Object getData() { return data; } public void setData(Object data) { this.data = data; } public Node getNext() { return next; } public void setNext(Node next) { this.next = next; } } public class Test { public static void main(String[] args) { //声明表头 Node head=new Node(); Node node1 = new Node(1); Node node2 = new Node(2); Node node3 = new Node(3); //直接指向node1,里面没有存储数据 head.setNext(node1); node1.setNext(node2); node2.setNext(node3); node3.setNext(null); } }
链表的插入和删除
插入
链表的插入很简单,比如要想n节点插入到s节点的后面,首先对链表进行遍历,找到s节点后,先让n.next=s.next,然后让s.next=n

public class Test {
public static void main(String[] args) {
Node head = new Node();
Node node1 = new Node(1);
Node node2 = new Node(2);
Node node3 = new Node(3);
head.setNext(node1);
node1.setNext(node2);
node2.setNext(node3);
node3.setNext(null);
//声明一个node4
Node node4 = new Node(4);
//要想得到某个指定的节点,就得对链表进行遍历,如果到了某个节点,其next指向为null,说明整个链表都遍历完毕,不存在指定的节点元素,所有可以使用while循环进行遍历
Node node = head;
while ((node = node.getNext()) != null) {
//等于2的时候,就是找到了节点node2
if (node.getData().equals(2)) {
//先让新节点node4指向node2的下一节点,也就是node3
node4.setNext(node2.getNext());
//然后让node2指向新节点node4
node2.setNext(node4);
//跳出循环
break;
}
}
}
}
使用debug工具进行查看:

删除
删除和插入类似,比如要想删除节点n,首先对链表进行遍历,找到s节点,满足s.next=n,然后让s.next=n.next ,n.ndex=null,节点n会由于虚拟机的垃圾回收机制被自动回收
public class Test {
public static void main(String[] args) {
Node head = new Node();
Node node1 = new Node(1);
Node node2 = new Node(2);
Node node3 = new Node(3);
head.setNext(node1);
node1.setNext(node2);
node2.setNext(node3);
node3.setNext(null);
//要想得到某个指定的节点,就得对链表进行遍历,如果到了某个节点,其next指向为null,说明整个链表都遍历完毕,不存在指定的节点元素,所有可以使用while循环进行遍历
Node node = head;
while ((node = node.getNext()) != null) {
//节点的next.data等于2的时候,就找到了需要被删除节点的前驱
if (node.getNext().getData().equals(2)) {
Node deleteNode = node.getNext();
//先让前驱指向后继
node.setNext(deleteNode.getNext());
//然后把删除节点的后继指向null
deleteNode.setNext(null);
//跳出循环
break;
}
}
}
}
debug查看

可以看到,该链表中不再包含2
总结
总的来说,数组和链表使用频率都大,下面总结下各自的优点和缺点
数组
优点
查询快,因为有下标
缺点
- 插入和删除需要移动大量元素
- 容量固定,当容量不够的时候,需要进行扩容
链表
优点
- 容量大,其大小和内存有关,因为节点都是不连续的
- 插入和删除方便
缺点
因为链表中的节点不是连续的,所以想要获取某个元素的时候需要对链表进行遍历和判断
使用
需要经常添加和删除元素时,用链表。元素不会经常变动,或者查询频率较高,用数组