当前位置: 首页 > news >正文

北京网站建设方案飞沐最好的网站优化公司

北京网站建设方案飞沐,最好的网站优化公司,新东方培训机构官网,沈阳高端网站制作公司哪家好LinkedList 一个双向链表。 本身是基于链表进行封装的列表, 所以具备了链表的特性: 变更简单, 容量是无限的, 不必像数组提前声明容量等。 同时 LinkedList 支持存储包括 null 在内的所有数据类型。 1 链表 了解 LinkedList 之前, 我们需要先了解一下双向链的特点 单链表, 双…

LinkedList 一个双向链表。
本身是基于链表进行封装的列表, 所以具备了链表的特性: 变更简单, 容量是无限的, 不必像数组提前声明容量等。
同时 LinkedList 支持存储包括 null 在内的所有数据类型。

1 链表

了解 LinkedList 之前, 我们需要先了解一下双向链的特点

  1. 单链表, 双链表, 循环链表的定义, 可以看一下这个
  2. 链表内存是散乱的, 每一个元素存储本身内存地址的同时还存储下一个元素的地址
  3. 链表具备了增删快, 查找慢的特点
  4. LinkedList 是基于双向链表设计的, 所以具备了链表的特点

2 LinkedList 中每个节点的定义

private static class Node<E> {/** 当前节点的内容 */E item;/** 下一个节点 */Node<E> next;/** 上一个节点 */Node<E> prev;Node(Node<E> prev, E element, Node<E> next) {// 当前节点的数据 = elementthis.item = element;// 当前节点的后置节点 = nextthis.next = next;// 当前节点的前置节点 = prevthis.prev = prev;}
}

Node 节点中除了指向下一个节点的 next, 还包含指向上一个节点的 prev, 所以 LinkedList 是一个双向的链表。

3 LinkedList 中的几个属性

public LinkedList<E> {transient int size = 0;transient Node<E> first;transient Node<E> last;
}

3.1 size

表示当前链表中的节点个数。

3.2 first

整个双向链表的头节点

3.3 last

整个双向链表的尾节点

重要的就这个几个属性了

4 LinkedList 的构造方法

public LinkedList<E> {// 构造函数 1: 无参构造函数public LinkedList() {}// 构造函数 2: 给定一个 Collection 的构造public LinkedList(Collection<? extends E> c) {// 省略}
}

4.1 无参的构造函数

public LinkedList() {
}

就是单纯的声明 LinkedList 的实例, 没有其他的逻辑了

4.2 给定一个 Collection 的构造

public LinkedList(Collection<? extends E> c) {// 调用自身无参的构造函数this();// 将集合 c 添加到当前的链表中addAll(c);
}/*** 将集合对象全部添加到当前链表中*/
public boolean addAll(Collection<? extends E> c) {// 调用 addAll 重写方法在 size 的位置添加数据return addAll(size, c);
}/*** 指定位置的批量插入*/
public boolean addAll(int index, Collection<? extends E> c) {// 判断 0 <= index <= size, 不满足抛出异常checkPositionIndex(index);// 集合转为数组Object[] a = c.toArray();int numNew = a.length;// 需要插入的集合没有数据, 直接结束if (numNew == 0)return false;// pred: 新增的元素的将要插入的位置的前一个节点  // succ: 新增的元素的将要插入的位置的节点// 如果新增的元素的节点位置为 a, 那么 pred 就是 a 的前一位的节点, succ 就是 a 节点Node<E> pred, succ;// 需要添加的元素刚好追加在已有的后面的if (index == size) {succ = null;pred = last;} else {// 获取指定位置的节点succ = node(index);pred = succ.prev;}    for (Object o : a) {E e = (E) o;Node<E> newNode = new Node<>(pred, e, null);// 第一次创建时, 那么第一个节点就是 first节点if (pred == null)first = newNode;else// 将 pred 的下一个节点指向新创建的节点pred.next = newNode;  // 将 pred 改为当前节点, 方便下一个元素操作pred = newNode;    }   // succ 是为空的, 表示直接在已有的数据后面追加元素的话, 所以将最后节点设置为新增元素的最后一个元素的节点if (succ == null) {last = pred;} else {// 原有数据后半部分拼接到现在链表的后半部分pred.next = succ;succ.prev = pred;} // 当前的长度size += numNew;// 修改次数加1 modCount++;return true;
}/*** 校验 index 的值满足:  0 <= index <= size*/
private void checkPositionIndex(int index) {if (!isPositionIndex(index))throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}/** 判断 index 是否大于等于 0 并且小于等于 size*/
private boolean isPositionIndex(int index) {return index >= 0 && index <= size;
}/*** 获取指定位置的节点*/
Node<E> node(int index) {// 此次做了一个小优化: 当要查找的位置小于现有数据的一半, 从前往后找, 大于的话, 从后面开始找// size >> 1 相当于 size / 2if (index < (size >> 1)) {// 从前向后找Node<E> x = first;for (int i = 0; i < index; i++)x = x.next;return x;} else {// 从后向前找Node<E> x = last;for (int i = size - 1; i > index; i--)x = x.prev;return x;}
}

从代码可知

  1. LinkedList 是基于双向链表实现了
  2. LinkedList 的数据是通过 first(节点) 和 last(节点) 和 size 三个共同维护的
  3. LinkedList 内部的数据通过泛型, 保持了自己的类型, 没有转为 Object。

5 LinkedList 几个常用的方法

5.1.1 添加数据

直接在链表末尾添加元素

// 直接添加一个元素
public boolean add(E e) {// 默认新增到链表的末尾linkLast(e);return true;
}/*** 将入参的元素添加到链表的末尾*/
void linkLast(E e) {// 获取链表的尾结点final Node<E> l = last;// 将数据封装为 Node 节点final Node<E> newNode = new Node<>(l, e, null);// 链表的尾结点等于新的节点last = newNode;// 如果链表一开始的尾结点为空, 表示链表一开始没有数据if (l == null)// 链表的头节点也等于当前的新节点first = newNode;else// 一开始的为节点有值, 将原来的尾结点的下一个节点设置为当前新的节点l.next = newNode;    // 链表个数 + 1size++;// 修改次数 + 1modCount++    
}

指定位置的插入

public void add(int index, E element) {// 判断 0 <= index <= size, 不满足抛出异常checkPositionIndex(index);// index == size 要插入的位置刚好在末尾if (index == size)linkLast(element);else // 插入到某个节点的前面linkBefore(element, node(index));    
}/*** 将元素插入到某个节点的前面*/
void linkBefore(E e, Node<E> succ) {// 获取某个节点的前置节点final Node<E> pred = succ.prev;final Node<E> newNode = new Node<>(pred, e, succ);// 设置插入位置的节点的前置节点为需要插入的新节点succ.prev = newNode;// 插入位置的节点的前置节点为空, 表示没有头节点if (pred == null)first = newNode;else// 需要插入的位置的前置节点的下一个节点为新节点pred.next = newNode;    // 个数加 1size++;// 修改次数 + 1modCount++;        
}

在头部插入

public void addFirst(E e) {linkFirst(e);
}/*** 在链表的头节点前新增元素*/ 
private void linkFirst(E e) {// 获取头节点final Node<E> f = first;final Node<E> newNode = new Node<>(null, e, f);// 头节点等于新节点first = newNode;// 原本的头节点为空, 表示没有数据if (f == null)// 设置尾节点为新节点last = newNode;else// 设置原本的头节点的前置节点为当前的节点f.prev = newNode;size++;modCount++;        
}

还有前面看到的

  1. 插入一个 Collection 的 addAll(Collection c)
  2. 指定位置的插入一个 Collection 的 addAll(int index, Collection c)

5.1.2 删除数据

直接删除 (默认删除第一个元素)

public E remove() {return removeFirst();
}/*** 删除链表第一个元素*/
public E removeFirst() {final Node<E> f = first;// 头节点为空if (f == null)throw new NoSuchElementException();return unlinkFirst(f);
}/*** 删除指定的节点*/
private E unlinkFirst(Node<E> f) {// 获取删除节点的数据final E element = f.item;// 找到删除元素的下一个元素final Node<E> next = f.next;// 清空删除元素的信息f.item = null;f.next = null;// 将头节点重新设置为删除元素的下一个元素first = next;// 如果原本删除元素就是没有后续元素时, 将最后的元素设置为 nullif (next == null)last = null;else// 将新的第一个元素的前置节点设置为 nullnext.prev = null;  // 元素个数 - 1    size--;// 修改次数 + 1modCount++;// 返回数据return element;          
}

指定位置的删除

public E remove(int index) {// 检测删除的位置是否正确checkElementIndex(index);return unlink(node(index));
}private void checkElementIndex(int index) {// 如果不满足  0 <= index < size, 就抛出异常if (!isElementIndex(index))throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}private boolean isElementIndex(int index) {return index >= 0 && index < size;
}E unlink(Node<E> x) {final E element = x.item;// 得到将要移除的元素的后置节点final Node<E> next = x.next;// 得到将要移除的元素的前置节点final Node<E> prev = x.prev;// 如果前置节点为空, 说明移除的为头节点, 重新设置头节点为删除节点的后置节点if (prev == null) {first = next;} else {// 将前置节点的下一个节点设置为后置节点prev.next = next;// 设置删除节点前置节点为 nullx.prev = null;}// 如果后置节点为空, 说明移除的为尾节点, 重新设置尾节点为删除节点的前置节点if (next == null) {last = prev;} else {// 将后置节点的前置节点设置为前置节点next.prev = prev;// 设置删除节点后置节点为 nullx.next = null;}// 将删除节点的数据设置为 nullx.item = null;size--;modCount++;return element;
}

指定删除元素

public boolean remove(Object o) {// 需要删除的节点为 nullif (o == null) {// 找到第一个为 null 的元素, 进行删除for (Node<E> x = first; x != null; x = x.next) {if (x.item == null) {unlink(x);return true;}}} else {for (Node<E> x = first; x != null; x = x.next) {// 找到节点的数据等于要删除的节点if (o.equals(x.item)) {unlink(x);return true;}}}
}

除了上面几个还有

  1. 删除第一个 removeFirst()
  2. 删除最后一个 removeLast()
  3. 删除第一个符合的元素 removeFirstOccurrence(Object o)
  4. 删除最后一个符合的元素 removeLastOccurrence(Object o)

5.1.3 获取数据

获取指定位置的数据

public E get(int index) {// 确保 0 <= index < sizecheckElementIndex(index);// 定位到对应位置的节点的数据return node(index).item;
}

获取第一个的数据

public E getFirst() {final Node<E> f = first;if (f == null)throw new NoSuchElementException();return f.item;
}

获取最一个的数据

public E getLast() {final Node<E> l = last;if (l == null)throw new NoSuchElementException();return l.item;
}

5.1.4 更新数据

更新指定位置节点的属性值

public E set(int index, E element) {// 确保 0 <= index < sizecheckElementIndex(index);// 获取指定位置的节点数据Node<E> x = node(index);// 获取指定节点是旧数据E oldVal = x.item;// 更新指定节点的数据x.item = element;// 返回旧数据return oldVal;
}

6 LinkedList 补充

1. LinkedList 实现了 Serializable 接口, 但是他的属性都是设置为 transient ?

LinkedList 重写了序列化方法 writeObject 和反序列化方法 readObject。 在序列化中, 重新通过遍历所有节点, 把所有节点数据写入到 ObjectOutputStream。

2. LinkedList 支持 fail-fast 机制 ?

通过继承关系可以指定 LinkedList 也是实现了 AbstractList, 具备了 modCount, 在修改中也会增加 modCount 的值, 所以 LinkedList 也支持 fail-fast 机制。

3. LinkedList不是一个线程安全的集合 ?

LinkedList 是线程不安全的, 如果需要保证线程的安全性, 可以考虑使用 Collections.synchronizedList(Lise l) 函数返回一个线程安全的 LinkedList 类。

4. 不要用 for 遍历 LinkedList ?

遍历的代码

List<String> list2 = new LinkedList<>();
list.add("1");
list.add("2");
list.add("3");for (int i = 0; i < list2.size(); i++) {String item = list2.get(i);System.out.println(item);
}

源码中涉及的代码:

public class LinkedList {public E get(int index) {checkElementIndex(index);return node(index).item;}Node<E> node(int index) {if (index < (size >> 1)) {Node<E> x = first;for (int i = 0; i < index; i++)x = x.next;return x;} else {Node<E> x = last;for (int i = size - 1; i > index; i--)x = x.prev;return x;}}
}

如上: 我们通过 for 遍历 LinkedList,

  1. 第一次 get(0), node(0), 就是 first 节点
  2. 第二次 get(1), node(1), 就是 first -> node1
  3. 第三次 get(2), node(2), 就是 first -> node1 -> node2

每次通过 get() 就是需要从第一个节点一直找到对应的位置, 才能找到需要的节点。
所以遍历 LinkedList 可以使用 foreach (foreach 循环的原理就是迭代器) 或者迭代器。

5. LinkedList 还有其他的作用吗

LinkedList 实现了 Deque 接口, 所以 LinkedList 可以作为双端队列, 同时 LinkedList 的双向链表的特点, 还可以作为 Stack 使用, 但是 LinkedList 的这 2 个功能,
如果没有什么特殊的要求的话, 都可以使用 ArrayDeque 替代, 后者的性能更好。

http://www.yidumall.com/news/9701.html

相关文章:

  • wordpress 禁用 事件seo基础知识培训视频
  • 怎么开网站 第一步怎么做长沙官网seo收费
  • wordpress产品批量导入搜索引擎营销优化策略有哪些
  • 企业营销网站案例系统推广公司
  • 南昌知名网站建设百度快照推广一年要多少钱
  • 做网站劫持多少钱百度一下你就知道网页
  • wordpress新站都该设置些什么it培训机构哪家好
  • 长沙做企业网站网络广告策划书
  • php开发网站建设书籍重庆seo排名优化费用
  • 五和网站建设烟台seo
  • 面试个人简历范文及网站建设如何创建一个个人网站
  • 自助服务平台seoshanghai net
  • 自己怎么做装修网站黄金网站app视频播放画质选择
  • 网页编成网站最近新闻小学生摘抄
  • 服务类网站怎么做谷歌引擎搜索
  • 集团网站建设免费的网站平台
  • 漂亮购物网站欣赏江门关键词排名优化
  • 政府网站集约化建设告知书seo搜索引擎优化营销案例
  • 公司官网如何推广seo的作用是什么
  • dw做网站一般是多大的尺寸昆明网络推广
  • 用织梦做网站视频微博seo排名优化
  • 地区门户网站 建设攻略百度客户服务电话是多少
  • 静态网站怎么做滚动文字明天上海封控16个区
  • 互联网公司薪资待遇软文优化
  • pageadmin如何做网站深圳网络推广seo软件
  • 网站建设公司怎么盈利临沂seo推广外包
  • 怎么做一个网站多少钱百度pc端提升排名
  • 主题资源网站建设反思知乎营销平台
  • beyond歌迷做的网站app注册推广平台
  • 什么网站做简历比较好优化大师官方免费