一、数组
存放同一种数据类型的多个元素的容器,通过索引(内存偏移量)进行元素的访问,数组的大小一旦确定就不能改变。数组其实也是线性表结构,在内存中数组的元素是紧挨着连续存储的。
特点:查询快、增删慢
举例:定义一个数组:int arr = new int[]{1, 2, 3, 4, 5};
- 想在2的后面插入一个新的元素11,这时候就需要定义一个新的数组arr2,数组arr2的长度是arr的长度+1,然后通过遍历数组arr,在2前面的元素还是按照之前的索引位置存储在数组arr2中,接着存储2这个元素,在元素2之后先存储11,剩下的元素一次把索引+1存储到数组arr2中。
- 把arr数组中的元素3删除,定义一个新的数组arr3,数组arr3的长度是arr的长度-1,然后通过遍历数组arr,在元素3之前的元素还是按照之前的索引位置存储到数组arr3中,元素3之后的元素索引依次-1。
通过以上的例子,可以看出,数组对元素的增删比较繁琐,速度慢,而查询的速度则较快。
二、链表
所谓的链表指的是节点和节点之间无需再内存中物理连续的存储,而是通过引用成员去指向下一个节点的位置,这样就可以在内存中动态的进行对象创建,链表存放元素的数量仅受堆内存大小的限制,且无需在创建链表的时候给定其容量大小。简单的一句话就是链表是由一条链子把多个节点连接起来组成的数据
特点:链表解决了数组增删慢和数组大小不可变更的问题,但是数组获取元素是通过内存偏移量(索引),链表是通过节点和节点之间的引用建立关系获取元素,因此数组的查询元素的速度要快于链表。
举例:在链表中有元素1, 2, 3, 4, 5等元素,它们在链表中的存储方式如图所示:
自定义一个链表结构类来存储数据。
节点类:
public class Node {
private String key;
private String value;
public Node next;
public Node() {
}
public Node(String key, String value) {
this.key = key;
this.value = value;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
@Override
public String toString() {
return "Node{" +
"key='" + key + '\'' +
", value='" + value + '\'' +
", next=" + next +
'}';
}
}
链表类:
public class LinkNode {
private Node head;
/**
* 添加一个节点
* @param key 键
* @param value 值
*/
public void addNode(String key, String value) {
// 判断key是否是null或者是空字符串
Pattern compile = Pattern.compile(" *");
Matcher matcher = compile.matcher(key);
boolean matches = matcher.matches();
if (key == null || matches) {
throw new IllegalArgumentException("key不能为null或者空字符串!");
}
// 如果head为null,表示此时列表为空,加进来的值就是head
if (head == null) {
head = new Node(key, value);
} else {
delNode(key);// 如果之前有相同的key存在,则删除之前的key(类似Map中相同的key,后者key中的value会覆盖前者key的value)
Node current = this.head;
while (current != null && current.next != null) {
current = current.next; // 查找到链表尾部
}
if (current != null) {
current.next = new Node(key, value); // 在尾部添加节点
} else {
head = new Node(key, value);
}
}
}
/**
* 根据key获取节点元素
* @param key 键
* @return
*/
public Node getNode(String key) {
if (this.head == null) {
return null;
}
Node current = this.head;
// 如果current等于null,说明该节点不存在
// 如果key等于current中的key,说明当前节点就是需要查找的节点,直接返回
while (current != null && !key.equals(current.getKey())) {
current = current.next;
}
return current;
}
/**
* 根据key删除节点元素
* @param key 键
* @return
*/
public boolean delNode(String key) {
if (head == null) {
return false;
}
boolean isSuccess = false;
if (key.equals(head.getKey())) { // 刚好删除的是头结点
this.head = this.head.next;
isSuccess = true;
} else {
Node parent = this.head;
Node current = this.head.next;
while (current != null && !key.equals(current.getKey())) {
current = current.next;
parent = parent.next;
}
if (current != null) {
parent.next = current.next;
isSuccess = true;
}
}
return isSuccess;
}
/**
* 在索引index处插入一个节点元素
* @param node 节点元素
* @param index 索引
* @return
*/
public boolean inertNode(Node node, int index) {
if (this.head == null && index == 0) {
this.head = node;
return true;
}
boolean isSuccess = false;
if (index == 0) { // 如果插入的位置是0,那么头结点就是要插入的节点
node.next = head;
head = node;
isSuccess = true;
} else {
Node parent = this.head;
Node current = this.head.next;
while (current != null && index-- > 1) {
current = current.next;
parent = parent.next;
}
if (current != null) {
parent.next = node;
node.next = current;
isSuccess = true;
}
}
return isSuccess;
}
@Override
public String toString() {
return "LinkNode{" +
"head=" + head +
'}';
}
}
测试类:
public class MainApp {
public static void main(String[] args) {
LinkNode linkNode = new LinkNode();
linkNode.addNode("k1", "v1");
linkNode.addNode("k2", "v2");
linkNode.addNode("k3", "v3");
linkNode.inertNode(new Node("insertKey", "insertValue"), 1);
System.out.println(linkNode);
}
}
三、栈
栈也是一种线性表(物理或逻辑连续的),栈顶和栈底是标识栈的两个端点。栈提供了“先进后出” 的数据结构。压入(push)和弹出(pop)是栈的两个基本操作。
示例:
自定义栈类:
public class CustomStack {
// 定义存储数据的数组
private Object[] stack;
// 栈的大小
private final int size;
// 指向栈顶的变量
private int top;
// 指向栈底的变量
private int buttom;
/**
* 初始化栈,初始化时指定栈的大小
*
* @param size
*/
public CustomStack(int size) {
this.size = size;
stack = new Object[size];
}
/**
* 判断栈是否为空
*/
public boolean isEmpty() {
// 栈顶指向栈底的时候,栈为空
return this.top == this.buttom;
}
/**
* 判断栈是否满
*
* @return
*/
public boolean isFull() {
// 当栈顶变量的大小和栈的大小一样时,栈就满了
return this.top == this.size;
}
/**
* 入栈(往栈中添加数据)
*
* @param object
*/
public void push(Object object) {
/*
先判断栈是否已满,未满则添加数据,
否则就抛异常提示栈已满
*/
if (!isFull()) {
/*
top++表示往栈顶移动,
因为top始终指向的是
第一个未使用的空间。
*/
stack[top++] = object;
} else {
throw new IllegalStateException("stack is full");
}
}
/**
* 出栈
*
* @return
*/
public Object pop() {
/*
判断栈是否为空,如果不为空
就出栈,为空就抛异常
*/
if (!isEmpty()) {
return stack[--top];
} else {
throw new IllegalStateException("stack is empty");
}
}
/**
* 获取栈的大小
* @return
*/
public int size() {
return this.size;
}
}
测试:
public class MainApp {
public static void main(String[] args) {
CustomStack stack = new CustomStack(5);
System.out.println("stack.isEmpty():" + stack.isEmpty());
System.out.println("stack.isFull():" + stack.isFull());
System.out.println("-------------------");
stack.push(1);
stack.push("aa");
System.out.println("stack.isEmpty():" + stack.isEmpty());
System.out.println("stack.isFull():" + stack.isFull());
System.out.println("-------------------");
stack.push('a');
stack.push(true);
stack.push(5);
System.out.println("stack.isEmpty():" + stack.isEmpty());
System.out.println("stack.isFull():" + stack.isFull());
System.out.println("-------------------");
for (int i = 0; i < stack.size(); i++) {
Object pop = stack.pop();
System.out.println(pop);
}
System.out.println("stack.isEmpty():" + stack.isEmpty());
System.out.println("stack.isFull():" + stack.isFull());
}
}
结果:for循环中的打印的结果顺序:5、true、a、aa、1,弹出栈的顺序跟入栈的顺序相反。
四、队列
队列也是一种线性表,对头和队尾是它的两个标志,入队(offer)和出队(poll)是队列的两个基本操作。队列提供了”先进先出“的数据结构。
示例(循环队列):
自定义队列类:
public class CustomQueue {
//定义队列的大小
private final int size;
//定义队列数组
private Object[] queue;
//队列头的位置
private int head;
//队尾位置
private int end;
/**
* 初始化队列
*
* @param size
*/
public CustomQueue(int size) {
this.size = size;
queue = new Object[size];
}
/**
* 判断队列是否为空
*
* @return
*/
public boolean isEmpty() {
return head == end;
}
/**
* 向后移动队首或者队尾的指向
*
* @param i
* @return
*/
public int next(int i) {
return (i + 1) % size;
}
/**
* 判断队列是否满了
*
* @return
*/
public boolean isFull() {
return next(end) == head;
}
/**
* 入队
*
* @param data
*/
public void offer(Object data) {
if (!isFull()) {
queue[end] = data;
end = next(end);
} else {
throw new IllegalStateException("queue is full");
}
}
/**
* 出队
*
* @return
*/
public Object poll() {
if (!(isEmpty())) {
Object result = queue[head];
head = next(head);
return result;
} else {
throw new IllegalStateException("queue is empty");
}
}
/**
* 队列的大小
*
* @return
*/
public int size() {
return size;
}
}
测试类:
public class MainApp {
public static void main(String[] args) {
CustomQueue customQueue = new CustomQueue(5);
System.out.println("customQueue.isFull():" + customQueue.isFull());
System.out.println("customQueue.isEmpty():" + customQueue.isEmpty());
System.out.println("------------------");
customQueue.offer(1);
customQueue.offer(2L);
customQueue.offer("aa");
customQueue.offer('a');
System.out.println("customQueue.isFull():" + customQueue.isFull());
System.out.println("customQueue.isEmpty():" + customQueue.isEmpty());
System.out.println("------------------");
for (int i = 0; i < customQueue.size() - 1; i++) {
Object poll = customQueue.poll();
System.out.println(poll);
}
System.out.println("customQueue.isFull():" + customQueue.isFull());
System.out.println("customQueue.isEmpty():" + customQueue.isEmpty());
}
}
从代码中可以看出,在定义队列的大小是5的时候,但是实际存储的数据个数要比定义的大小-1个。 是因为这是一个循环队列,必须要有一个空的来区分队头和队尾,在判断队列是否为空的时候,使用 的是头的位置等于尾的位置,因此在用for循环出队时,循环的次数应该小于队列的大小-1。从结果 可以看出,队列的存储方式就是先进先出。