1 数据结构
数据结构就是研究数据的逻辑结构和物理结构以及它们之间相互关系,并对这种结构定义相应的运算,而且确保经过这些运算后所得到的新结构仍然是原来的结构类型。
数据的逻辑结构指反映数据元素之间的逻辑关系,而与他们在计算机中的存储位置无关:
- 集合(数学中集合的概念):数据结构中的元素之间除了“同属一个集合” 的相互关系外,别无其他关系;
- 线性结构:数据结构中的元素存在一对一的相互关系;
- 树形结构:数据结构中的元素存在一对多的相互关系;
- 图形结构:数据结构中的元素存在多对多的相互关系。
数据的物理结构/存储结构:是描述数据具体在内存中的存储(如:顺序结构、链式结构、索引结构、哈希结构)等,一种数据逻辑结构可表示成一种或多种物理存储结构。
数据结构和算法是一门完整并且复杂的课程,那么我们今天只是简单的讨论常见的几种数据结构,让我们对数据结构与算法有一个初步的了解。
2 动态数组
2.1 动态数组的特点
逻辑结构:线性的
物理结构:顺序结构
申请内存:一次申请一大段连续的空间,一旦申请到了,内存就固定了。
存储特点:所有数据存储在这个连续的空间中,数组中的每一个元素都是一个具体的数据(或对象),所有数据都紧密排布,不能有间隔。
例如:整型数组
例如:对象数组
2.2 动态数组的基本操作
与数据结构相关的数据操作:
- 插入
- 删除
- 修改
- 查找
- 遍历
public interface Container<E> extends Iterable<E>{
void add(E e);
void insert(int index,E value);
void delete(E e);
void delete(int index);
E update(int index, E value);
void update(E old, E value);
boolean contains(E e);
int indexOf(E e);
E get(int index);
Object[] getAll();
int size();
}
2.3 动态数组实现
import java.util.Arrays;
import java.util.Iterator;
import java.util.NoSuchElementException;
public class MyArrayList<E> implements Container<E>{
private Object[] all; // 初始化一个内部数组,用来储存元素
private int total; // 集合中的元素个数
public MyArrayList(){
all = new Object[5]; // 内部数组初始化长度默认为5
}
@Override
public void add(E e) {
ensureCapacityEnough();
all[total++] = e;
}
// 校验是否需要扩容
private void ensureCapacityEnough() {
if(total >= all.length){
all = Arrays.copyOf(all, all.length*2); // 长度扩容为原来的两倍
}
}
@Override
public void insert(int index, E value) {
//是否需要扩容
ensureCapacityEnough();
addCheckIndex(index);
if(total-index>0) {
System.arraycopy(all, index, all, index+1, total-index);
}
all[index]=value;
total++;
}
// 校验外界传入的下标是否越界
private void addCheckIndex(int index) {
if(index<0 || index>total){
throw new IndexOutOfBoundsException(index+"越界");
}
}
@Override
public void delete(E e) {
int index = indexOf(e);
if(index==-1){
throw new NoSuchElementException(e+"不存在");
}
delete(index);
}
@Override
public void delete(int index) {
checkIndex(index);
if(total-index-1>0) {
System.arraycopy(all, index+1, all, index, total-index-1);
}
all[--total] = null;
}
private void checkIndex(int index) {
if(index<0 || index>total){
throw new IndexOutOfBoundsException(index+"越界");
}
}
@Override
public E update(int index, E value) {
checkIndex(index);
E oldValue = get(index);
all[index]=value;
return oldValue;
}
@Override
public void update(E old, E value) {
int index = indexOf(old);
if(index!=-1){
update(index, value);
}
}
@Override
public boolean contains(E e) {
return indexOf(e) != -1;
}
@Override
public int indexOf(E e) {
int index = -1;
if(e==null){
// 需要查找的内容为null,则必须使用==判断
for (int i = 0; i < total; i++) {
if(e == all[i]){
index = i;
break;
}
}
}else{
// 如果查找的内容不为null,则使用equals判断。因为==会判断内存地址,不符合要求
for (int i = 0; i < total; i++) {
if(e.equals(all[i])){
index = i;
break;
}
}
}
return index;
}
@SuppressWarnings("unchecked")
@Override
public E get(int index) {
checkIndex(index);
return (E) all[index];
}
@Override
public Object[] getAll() {
return Arrays.copyOf(all, total);
}
@Override
public int size() {
return total;
}
@Override
public Iterator<E> iterator() {
return new Itr();
}
private class Itr implements Iterator<E>{
private int cursor;
@Override
public boolean hasNext() {
return cursor!=total;
}
@SuppressWarnings("unchecked")
@Override
public E next() {
return (E) all[cursor++];
}
@Override
public void remove() {
MyArrayList.this.delete(--cursor);
}
}
}
2.4 动态数组测试
import java.util.Arrays;
import java.util.Iterator;
import org.junit.Test;
public class TestMyArrayList {
@Test
public void test01(){
MyArrayList<String> my = new MyArrayList<String>();
my.add("hello");
my.add("java");
my.add("world");
my.add("atguigu");
my.add("list");
my.add("data");
System.out.println("元素个数:" + my.size());
Object[] all = my.getAll();
System.out.println(Arrays.toString(all));
my.insert(2, "尚硅谷");
System.out.println("元素个数:" + my.size());
all = my.getAll();
System.out.println(Arrays.toString(all));
}
@Test
public void test02(){
MyArrayList<String> my = new MyArrayList<String>();
my.add("hello");
my.add("java");
my.add("world");
my.add("atguigu");
my.add("list");
my.add("data");
my.delete(1);
System.out.println("元素个数:" + my.size());
Object[] all = my.getAll();
System.out.println(Arrays.toString(all));
my.delete("atguigu");
System.out.println("元素个数:" + my.size());
all = my.getAll();
System.out.println(Arrays.toString(all));
}
@Test
public void test03(){
MyArrayList<String> my = new MyArrayList<String>();
my.add("hello");
my.add("java");
my.add("world");
my.add("atguigu");
my.add("list");
my.add("data");
String update = my.update(3, "尚硅谷");
System.out.println("元素个数:" + my.size());
System.out.println("被替换的是:" + update);
Object[] all = my.getAll();
System.out.println(Arrays.toString(all));
my.update("java", "Java");
System.out.println("元素个数:" + my.size());
System.out.println("被替换的是:java");
all = my.getAll();
System.out.println(Arrays.toString(all));
}
@Test
public void test04(){
MyArrayList<String> my = new MyArrayList<String>();
my.add("hello");
my.add("java");
my.add("world");
my.add("atguigu");
my.add("list");
my.add("data");
System.out.println(my.contains("java"));
System.out.println(my.indexOf("java"));
System.out.println(my.get(0));
}
@Test
public void test05(){
MyArrayList<String> my = new MyArrayList<String>();
my.add("hello");
my.add("java");
my.add("world");
my.add("atguigu");
my.add("list");
my.add("data");
for (String string : my) {
System.out.println(string);
}
}
@Test
public void test06(){
MyArrayList<String> my = new MyArrayList<String>();
my.add("hello");
my.add("java");
my.add("world");
my.add("atguigu");
my.add("list");
my.add("data");
Iterator<String> iterator = my.iterator();
while(iterator.hasNext()) {
String next = iterator.next();
if(next.length()>4) {
iterator.remove();
}
}
for (String string : my) {
System.out.println(string);
}
}
}
2.5 Java核心类库中的动态数组
Java的List接口的实现类中有两个动态数组的实现:Vector和ArrayList。
1、ArrayList与Vector的区别?
它们的底层物理结构都是数组,我们称为动态数组。
- ArrayList是新版的动态数组,线程不安全,效率高,Vector是旧版的动态数组,线程安全,效率低。
- 动态数组的扩容机制不同,ArrayList扩容为原来的1.5倍,Vector扩容增加为原来的2倍。
- 数组的初始化容量,如果在构建ArrayList与Vector的集合对象时,没有显式指定初始化容量,那么Vector的内部数组的初始容量默认为10,而ArrayList在JDK1.6及之前的版本也是10,而JDK1.7之后的版本ArrayList初始化为长度为0的空数组,之后在添加第一个元素时,再创建长度为10的数组。
- Vector因为版本古老,支持Enumeration 迭代器。但是该迭代器不支持快速失败。而Iterator和ListIterator迭代器支持快速失败。如果在迭代器创建后的任意时间从结构上修改了向量(通过迭代器自身的 remove 或 add 方法之外的任何其他方式),则迭代器将抛出 ConcurrentModificationException。因此,面对并发的修改,迭代器很快就完全失败,而不是冒着在将来不确定的时间任意发生不确定行为的风险。
2、源码分析
(1)Vector源码分析
public Vector() {
this(10);//指定初始容量initialCapacity为10
}
public Vector(int initialCapacity) {
this(initialCapacity, 0);//指定capacityIncrement增量为0
}
public Vector(int initialCapacity, int capacityIncrement增量为0) {
super();
//判断了形参初始容量initialCapacity的合法性
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
//创建了一个Object[]类型的数组
this.elementData = new Object[initialCapacity];//默认是10
//增量,默认是0,如果是0,后面就按照2倍增加,如果不是0,后面就按照你指定的增量进行增量
this.capacityIncrement = capacityIncrement;
}
//synchronized意味着线程安全的
public synchronized boolean add(E e) {
modCount++;
//看是否需要扩容
ensureCapacityHelper(elementCount + 1);
//把新的元素存入[elementCount],存入后,elementCount元素的个数增1
elementData[elementCount++] = e;
return true;
}
private void ensureCapacityHelper(int minCapacity) {
// overflow-conscious code
//看是否超过了当前数组的容量
if (minCapacity - elementData.length > 0)
grow(minCapacity);//扩容
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;//获取目前数组的长度
//如果capacityIncrement增量是0,新容量 = oldCapacity的2倍
//如果capacityIncrement增量是不是0,新容量 = oldCapacity + capacityIncrement增量;
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
//如果按照上面计算的新容量还不够,就按照你指定的需要的最小容量来扩容minCapacity
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//如果新容量超过了最大数组限制,那么单独处理
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//把旧数组中的数据复制到新数组中,新数组的长度为newCapacity
elementData = Arrays.copyOf(elementData, newCapacity);
}
public boolean remove(Object o) {
return removeElement(o);
}
public synchronized boolean removeElement(Object obj) {
modCount++;
//查找obj在当前Vector中的下标
int i = indexOf(obj);
//如果i>=0,说明存在,删除[i]位置的元素
if (i >= 0) {
removeElementAt(i);
return true;
}
return false;
}
public int indexOf(Object o) {
return indexOf(o, 0);
}
public synchronized int indexOf(Object o, int index) {
if (o == null) {//要查找的元素是null值
for (int i = index ; i < elementCount ; i++)
if (elementData[i]==null)//如果是null值,用==null判断
return i;
} else {//要查找的元素是非null值
for (int i = index ; i < elementCount ; i++)
if (o.equals(elementData[i]))//如果是非null值,用equals判断
return i;
}
return -1;
}
public synchronized void removeElementAt(int index) {
modCount++;
//判断下标的合法性
if (index >= elementCount) {
throw new ArrayIndexOutOfBoundsException(index + " >= " +
elementCount);
}
else if (index < 0) {
throw new ArrayIndexOutOfBoundsException(index);
}
//j是要移动的元素的个数
int j = elementCount - index - 1;
//如果需要移动元素,就调用System.arraycopy进行移动
if (j > 0) {
//把index+1位置以及后面的元素往前移动
//index+1的位置的元素移动到index位置,依次类推
//一共移动j个
System.arraycopy(elementData, index + 1, elementData, index, j);
}
//元素的总个数减少
elementCount--;
//将elementData[elementCount]这个位置置空,用来添加新元素,位置的元素等着被GC回收
elementData[elementCount] = null; /* to let gc do its work */
}
(2)ArrayList源码分析
JDK1.6:
public ArrayList() {
this(10);//指定初始容量为10
}
public ArrayList(int initialCapacity) {
super();
//检查初始容量的合法性
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
//数组初始化为长度为initialCapacity的数组
this.elementData = new Object[initialCapacity];
}
JDK1.7
private static final int DEFAULT_CAPACITY = 10;//默认初始容量10
private static final Object[] EMPTY_ELEMENTDATA = {};
public ArrayList() {
super();
this.elementData = EMPTY_ELEMENTDATA;//数组初始化为一个空数组
}
public boolean add(E e) {
//查看当前数组是否够多存一个元素
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
if (elementData == EMPTY_ELEMENTDATA) {//如果当前数组还是空数组
//minCapacity按照 默认初始容量和minCapacity中的的最大值处理
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
//看是否需要扩容处理
ensureExplicitCapacity(minCapacity);
}
//...
JDK1.8
private static final int DEFAULT_CAPACITY = 10;
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;//初始化为空数组
}
public boolean add(E e) {
//查看当前数组是否够多存一个元素
ensureCapacityInternal(size + 1); // Increments modCount!!
//存入新元素到[size]位置,然后size自增1
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
//如果当前数组还是空数组
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//那么minCapacity取DEFAULT_CAPACITY与minCapacity的最大值
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
//查看是否需要扩容
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;//修改次数加1
// 如果需要的最小容量 比 当前数组的长度 大,即当前数组不够存,就扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;//当前数组容量
int newCapacity = oldCapacity + (oldCapacity >> 1);//新数组容量是旧数组容量的1.5倍
//看旧数组的1.5倍是否够
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//看旧数组的1.5倍是否超过最大数组限制
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//复制一个新数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
public boolean remove(Object o) {
//先找到o在当前ArrayList的数组中的下标
//分o是否为空两种情况讨论
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {//null值用==比较
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {//非null值用equals比较
fastRemove(index);
return true;
}
}
return false;
}
private void fastRemove(int index) {
modCount++;//修改次数加1
//需要移动的元素个数
int numMoved = size - index - 1;
//如果需要移动元素,就用System.arraycopy移动元素
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//将elementData[size-1]位置置空,让GC回收空间,元素个数减少
elementData[--size] = null; // clear to let GC do its work
}
public E remove(int index) {
rangeCheck(index);//检验index是否合法
modCount++;//修改次数加1
//取出[index]位置的元素,[index]位置的元素就是要被删除的元素,用于最后返回被删除的元素
E oldValue = elementData(index);
//需要移动的元素个数
int numMoved = size - index - 1;
//如果需要移动元素,就用System.arraycopy移动元素
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//将elementData[size-1]位置置空,让GC回收空间,元素个数减少
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
public E set(int index, E element) {
rangeCheck(index);//检验index是否合法
//取出[index]位置的元素,[index]位置的元素就是要被替换的元素,用于最后返回被替换的元素
E oldValue = elementData(index);
//用element替换[index]位置的元素
elementData[index] = element;
return oldValue;
}
public E get(int index) {
rangeCheck(index);//检验index是否合法
return elementData(index);//返回[index]位置的元素
}
public int indexOf(Object o) {
//分为o是否为空两种情况
if (o == null) {
//从前往后找
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
public int lastIndexOf(Object o) {
//分为o是否为空两种情况
if (o == null) {
//从后往前找
for (int i = size-1; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
for (int i = size-1; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
3 链式存储结构
逻辑结构:有线性的和非线性的
物理结构:不要求连续的存储空间
存储特点:数据必须封装到“结点”中,结点包含多个数据项,数据值只是其中的一个数据项,其他的数据项用来记录与之有关的结点的地址。
例如:以下列出几种常见的链式存储结构(当然远不止这些)
链表
单链表
单链表结点:
class Node{
Object data; // 该节点的数据
Node next; // 指向下一个节点
public Node(Object data, Node next) {
this.data = data;
this.next = next;
}
}
单链表:
public class OneWayLinkedList<E>{
private Node<E> head;//头结点
private int total;//记录实际元素个数
private static class Node<E>{
E data;
Node<E> next;
Node(E data, Node<E> next) {
this.data = data;
this.next = next;
}
}
}
双链表
双链表结点:
class Node{
Node prev; // 指向上一个节点
Object data; // 当前节点的内容
Node next; // 指向下一个节点
public Node(Node prev, Object data, Node next) {
this.prev = prev;
this.data = data;
this.next = next;
}
}
双向链表:
public class LinkedList<E>{
private Node<E> first;//头结点
private Node<E> last;//尾结点
private int total;//记录实际元素个数
private static class Node<E>{
Node<E> prev;
E data;
Node<E> next;
Node(Node<E> prev, E data, Node<E> next) {
this.prev = prev;
this.data = data;
this.next = next;
}
}
}
二叉树
二叉树实现基本结构
class Node{
Node parent;
Node left;
Object data;
Node right;
public Node(Node parent,Node left, Object data, Node right) {
this.parent = parent;
this.left = left;
this.data = data;
this.right = right;
}
}
二叉树
public class BinaryTree<E>{
private Node<E> root;
private int total;
private static class Node<E>{
Node<E> parent;
Node<E> left;
E data;
Node<E> right;
public Node(Node<E> parent, Node<E> left, E data, Node<E> right) {
this.parent = parent;
this.left = left;
this.data = data;
this.right = right;
}
}
}
二叉树分类
- 满二叉树: 除最后一层无任何子节点外,每一层上的所有结点都有两个子结点的二叉树。 第n层的结点数是2的n-1次方,2的n次方-1
- 完全二叉树: 叶结点只能出现在最底层的两层,且最底层叶结点均处于次底层叶结点的左侧。
- 平衡二叉树:平衡二叉树(Self-balancing binary search tree)又被称为AVL树(有别于AVL算法),且具有以下性质:它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树, 但不要求非叶节点都有两个子结点 。平衡二叉树的常用实现方法有红黑树、AVL、替罪羊树、Treap、伸展树等。 最小二叉平衡树的节点的公式如下 F(n)=F(n-1)+F(n-2)+1 这个类似于一个递归的数列,可以参考Fibonacci(斐波那契)数列,1是根节点,F(n-1)是左子树的节点数量,F(n-2)是右子树的节点数量。
例如:斐波那契数列(Fibonacci):1,1,2,3,5,8,13…
规律:除了第一个和第二个数以外,后面的数等于前两个数之和,
f(0) =1,
f(1) = 1,
f(2) = f(0) + f(1) =2,
f(3) = f(1) + f(2) = 3,
f(4) = f(2) + f(3) = 5
…
f(n) = f(n-2) + f(n-1);
二叉树的遍历
- 前序遍历:中左右
- 中序遍历:左中右
- 后序遍历:左右中
前序遍历:ABDHIECFG
中序遍历:HDIBEAFCG
后序遍历:HIDEBFGCA
4 单链表
4.1 单链表的实现
逻辑结构:单向链表
物理结构:链式顺序结构
package com.atguigu.test06;
public class OneWayLinkedList<E>{
private Node<E> head; // 记录头部元素
private int total; // 记录元素个数
private static class Node<E>{
E data; // 该节点数据内容
Node<E> next; // 指向下一个节点
Node(E data, Node<E> next) {
this.data = data;
this.next = next;
}
}
public void add(E e) {
Node<E> newNode = new Node<>(e,null); // 创建元素
if(head==null){
// 表示该数组中还没有元素
head = newNode;
}else{
Node<E> node = head;
// 从头部元素开始遍历,找到最后一个元素。因为只有最后一个元素的next为null
while(node.next!=null){
node = node.next;
}
node.next = newNode; // 将最后一个元素的next指向newNode,也就实现了尾部追加
}
total++;
}
public void delete(E e) {
Node<E> node = head;
Node<E> find = null;
Node<E> last = null;
if(e==null){
// 如果要删除的元素内容就是null
while(node!=null){
// 从头部开始循环遍历
if(node.data==null){ // null必须用==去判断
// 找到了内容为null的元素
find = node;
break;
}
last = node; // 记录当前的node,因为走到这里,node马上会变为下一个元素。这样last就可以表示上一个元素
node = node.next;
}
}else{
// 如果删除的元素内容不为null
while(node!=null){
if(e.equals(node.data)){
find = node;
break;
}
last = node;
node = node.next;
}
}
if(find != null){
// 表示在前面遍历的过程中找到了需要删除的元素
if(last==null){
// 表示要删除的元素就是第一个元素
head = find.next;
}else{
last.next = find.next; // 将上一个元素的next指向需要删除元素的下一个元素,这样就把需要删除的元素从链表中去除了
}
total--;
find.data = null; //将find节点的内容都置为空,方便垃圾回收器回收
find.next = null;
}
}
public void update(E old, E value) {
Node<E> node = head;
Node<E> find = null;
if(old==null){
while(node!=null){
if(node.data==null){
find = node;
break;
}
node = node.next;
}
}else{
while(node!=null){
if(old.equals(node.data)){
find = node;
break;
}
node = node.next;
}
}
if(find != null){
find.data = value;
}
}
public boolean contains(E e) {
return indexOf(e) != -1;
}
public int indexOf(E e) {
int index = -1;
if(e==null){
int i=0;
for(Node<E> node = head; node!=null; node=node.next ){
if(node.data==e){
index=i;
break;
}
i++;
}
}else{
int i=0;
for(Node<E> node = head; node!=null; node=node.next ){
if(e.equals(node.data)){
index=i;
break;
}
i++;
}
}
return index;
}
public Object[] getAll() {
Object[] all = new Object[total];
Node<E> node = head;
for (int i = 0; i < all.length; i++,node = node.next) {
all[i] = node.data;
}
return all;
}
public int size() {
return total;
}
}
4.2 单链表的测试
import java.util.Arrays;
import org.junit.Test;
public class TestOneWayLinkedList {
@Test
public void test01(){
OneWayLinkedList<String> my = new OneWayLinkedList<String>();
my.add("hello");
my.add("java");
my.add("world");
my.add("atguigu");
my.add("list");
my.add("data");
System.out.println("元素个数:" + my.size());
Object[] all = my.getAll();
System.out.println(Arrays.toString(all));
}
@Test
public void test02(){
OneWayLinkedList<String> my = new OneWayLinkedList<String>();
my.add("hello");
my.add("java");
my.add("world");
my.add("atguigu");
my.add("list");
my.add("data");
my.delete("hello");
System.out.println("元素个数:" + my.size());
Object[] all = my.getAll();
System.out.println(Arrays.toString(all));
my.delete("atguigu");
System.out.println("元素个数:" + my.size());
all = my.getAll();
System.out.println(Arrays.toString(all));
my.delete("data");
System.out.println("元素个数:" + my.size());
all = my.getAll();
System.out.println(Arrays.toString(all));
}
@Test
public void test03(){
OneWayLinkedList<String> my = new OneWayLinkedList<String>();
my.add("hello");
my.add("java");
my.add("world");
my.add("atguigu");
my.add("list");
my.add("data");
my.update("java", "Java");
System.out.println("元素个数:" + my.size());
Object[] all = my.getAll();
System.out.println(Arrays.toString(all));
}
@Test
public void test04(){
OneWayLinkedList<String> my = new OneWayLinkedList<String>();
my.add("hello");
my.add("java");
my.add("world");
my.add("atguigu");
my.add("list");
my.add("data");
System.out.println(my.contains("java"));
System.out.println(my.indexOf("java"));
}
@Test
public void test05(){
OneWayLinkedList<String> my = new OneWayLinkedList<String>();
my.add("hello");
my.add("java");
my.add("world");
my.add("atguigu");
my.add("list");
my.add("data");
for (String string : my) {
System.out.println(string);
}
}
}
5 双链表
Java中有双链表的实现:LinkedList,它是List接口的实现类。
LinkedList源码分析
int size = 0;
Node<E> first;//记录第一个结点的位置
Node<E> last;//记录最后一个结点的位置
private static class Node<E> {
E item;//元素数据
Node<E> next;//下一个结点
Node<E> prev;//前一个结点
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
public boolean add(E e) {
linkLast(e);//默认把新元素链接到链表尾部
return true;
}
void linkLast(E e) {
final Node<E> l = last;//用l 记录原来的最后一个结点
//创建新结点
final Node<E> newNode = new Node<>(l, e, null);
//现在的新结点是最后一个结点了
last = newNode;
//如果l==null,说明原来的链表是空的
if (l == null)
//那么新结点同时也是第一个结点
first = newNode;
else
//否则把新结点链接到原来的最后一个结点的next中
l.next = newNode;
//元素个数增加
size++;
//修改次数增加
modCount++;
}
public boolean remove(Object o) {
//分o是否为空两种情况
if (o == null) {
//找到o对应的结点x
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);//删除x结点
return true;
}
}
} else {
//找到o对应的结点x
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);//删除x结点
return true;
}
}
}
return false;
}
E unlink(Node<E> x) {//x是要被删除的结点
// assert x != null;
final E element = x.item;//被删除结点的数据
final Node<E> next = x.next;//被删除结点的下一个结点
final Node<E> prev = x.prev;//被删除结点的上一个结点
//如果被删除结点的前面没有结点,说明被删除结点是第一个结点
if (prev == null) {
//那么被删除结点的下一个结点变为第一个结点
first = next;
} else {//被删除结点不是第一个结点
//被删除结点的上一个结点的next指向被删除结点的下一个结点
prev.next = next;
//断开被删除结点与上一个结点的链接
x.prev = null;//使得GC回收
}
//如果被删除结点的后面没有结点,说明被删除结点是最后一个结点
if (next == null) {
//那么被删除结点的上一个结点变为最后一个结点
last = prev;
} else {//被删除结点不是最后一个结点
//被删除结点的下一个结点的prev执行被删除结点的上一个结点
next.prev = prev;
//断开被删除结点与下一个结点的连接
x.next = null;//使得GC回收
}
//把被删除结点的数据也置空,使得GC回收
x.item = null;
//元素个数减少
size--;
//修改次数增加
modCount++;
//返回被删除结点的数据
return element;
}
LinkedList数组新增的方法
相比较与ArrayList,LinkedList底层采用双端循环链表,有头部和尾部的概率。所以增加了头部、尾部的增删查改方法
- public void addFirst(E e) { }:在头部添加元素
- public void addLast(E e) {}:在尾部增加元素
- public E getFirst() { }: 获取头部元素
- public E getLast() {}:获取尾部元素
- public E removeFirst() {}:删除头部元素
- public E removeLast() {}:删除尾部元素
链表与动态数组的区别
-
动态数组底层的物理结构是数组,因此根据索引访问的效率非常高,但是根据索引的插入和删除效率不高,因为涉及到移动元素,而且添加操作时可能涉及到扩容问题,那么就会增加时空消耗。
-
链表底层的物理结构是链表,因此根据索引访问的效率不高,但是插入和删除的效率高,因为不需要移动元素,只需要修改前后元素的指向关系即可,而链表的添加不会涉及到扩容问题。