老程序员分享:java容器体系(三)

简介: 老程序员分享:java容器体系(三)

  LinkedList 是 List 的又一种实现方法,首先看一下它的类图:


  LinkedList 继承自 AbstractSequentialList, 实现了Deque、Cloneable、Serializable 接口,同 ArrayList 一样,它包含了AbstractList 的所有的行为特征,但它的实现方式是链表,而且是双向链表。


1、成员变量


  LinkedList 提供了四个成员变量


transient int size = 0; // LinkedList 的容量


transient Node //代码效果参考:http://www.jhylw.com.cn/385825795.html

first; // 第一个节点

transient Node last; // 最后一个节点


protected transient int modCount = 0; // List结构化修改的次数(size大小发生改变的操作都会增加)


  可以看到,四个变量都使用了 transient 进行修饰,在序列化的时候这三个变量不会序列化。


  Node 是 LinkedList 的私有内部类,实现代码如下:


private static class Node {


E item; // 当前节点的元素


Node next; // 后一节点的引用地址


Node prev; // 前一节点的引用地址


Node(Node prev, E element, Node next) {


// 节点初始化时会带有三个参数,前一节点引用、当前节点元素和后一节点引用


this.item = element;


this.next = next;


this.prev = prev;


}


}


2、构造函数


public LinkedList() {


}


public LinkedList(Collection<? extends E> c) {


this();


addAll(c);


}


  LinkedList 提供了两个构造函数,一个是无参数构造函数;另一个是带有 Collection 类型参数的构造函数。可以看到,与ArrayList不同,LinkedList 并不需要设置初始容量。


3、实现自Deque的方法


  Deque 是一个队列接口,表明该类的实现类是一个双向队列,实现了 Queue。Queue 提供了 add(E e)、offer(E e)、remove()、poll()、element()、peek() 方法,Deque 提供了addFirst(E e)、addLast(E e)、offerFirst(E e)、offerLast(E e)、removeFirst()、removeLast()、pollFirst()、pollLast()、getFirst()、getLast()、peekFirst()、peekLast()等。


  Queue 提供的方法主要是队列头部取出以及尾部的加入方法,是FIFO(先入先出)的数据结构;Deque 在队列的头部和尾部均可以获取、添加、以及删除。下面分析一下具体方法的实现:


  (1)add(E e) 方法


public boolean add(E e) { // 在尾部添加一个元素,等同于 addLast(E e)


linkLast(e);


return true;


}


void linkLast(E e) {


final Node l = last;


final Node newNode = new Node(l, e, null);


last = newNode;


if (l == null) // 加入之前,last 未初始化


first = newNode; // 初始化 first,此时first = last


else


l.next = newNode;


size++; // 元素数量+1


modCount++; // 修改次数+1


}


  (2)offer(E e) 方法 其实使用的是add方法


public boolean offer(E e) {


return add(e);


}


   (3) remove() 方法


public E remove() { // 删除队列的第一个接地那


return removeFirst();


}


public E removeFirst() {


final Node f = first;


if (f == null) // first 未初始化,表示队列里无数据


throw new NoSuchElementException();


return unlinkFirst(f);


}


private E unlinkFirst(Node f) { // 删除第一个相等的元素


// assert f == first && f != null;


final E element = f.item;


final Node next = f.next;


f.item = null;


f.next = null; // help GC


first = next;


if (next == null) // 即此时first=null时,队列里没有元素,last也置为null


last = null;


else


next.prev = null; // 前驱节点释放


size--; // 队列元素数量-1


modCount++; // 结构化修改数量+1


return element;


}


  (4)poll() 方法


public E poll() { // 删除队列头部的第一个元素并返回


final Node f = first;


return (f == null) ? null : unlinkFirst(f);


}


  poll() 方法和 remove() 方法差不多,都是使用 unlinkFirst(E e) 删除头部的第一个元素,但是和 remove(E e) 不同的是,在队列为空的情况下调用 poll() 会返回 null,而 remove() 方法会抛出 NoSuchElementException 异常(其实是使用removeFirst(E e) 方法抛出的异常)。


  (5)element() 方法


public E element() { // 返回头部节点的元素


return getFirst();


}


public E getFirst() {


final Node f = first;


if (f == null) // 队列为空时,抛出异常


throw new NoSuchElementException();


return f.item;


}


  (6)peek() 方法


public E peek() {


final Node f = first;


return (f == null) ? null : f.item;


}


  peek() 和 element() 方法都是返回队列的头部节点,队列为空时,peek() 会返回 null ,element() 会抛出 NoSuchElementException 异常。


  以上是Queue 提供的方法,其实还有 Collection 当中的方法,后面再介绍,下面介绍 Dueue 的方法。


  (1)addFirst(E e)


public void addFirst(E e) { // 在队列的头部添加元素


linkFirst(e);


}


private void linkFirst(E e) {


final Node f = first;


final Node newNode = new Node(null, e, f);


first = newNode;


if (f == null) // 队列为空时,新加入节点是last,first为null


last = newNode;


else


f.prev = newNode;


size++;


modCount++;


}


  (2)addLast(E e)


public void addLast(E e) { // 等同于add(E e)和offer(E e),只是没有返回值


linkLast(e);


}


  (3)offerFirst(E e)


public boolean offerFirst(E e) { // 等同于addFirst(E e),有返回值


addFirst(e);


return true;


}


  (4)offerLast(E e)


public boolean offerLast(E e) { // 等同于addLast(E e),有返回值


addLast(e);


return true;


}


  (5)push(E e)


public void push(E e) {


addFirst(e);


}


  (6)pop(E e)


public E pop() { // 删除队列头部元素并返回,队列为空会抛出异常


return removeFirst();


}


4、其他方法


  (1)get(int index)方法


public E get(int index) { // 获取指定位置的元素


checkElementIndex(index);


return node(index).item;


}


private void checkElementIndex(int index) {


if (!isElementIndex(index)) // 判断是否 0<=index

throw new IndexOutOfBoundsException(outOfBoundsMsg(index));


}


private boolean isElementIndex(int index) {


return index >= 0 && index [span style="color: rgba(0, 0, 0, 1)"> size;


}


private String outOfBoundsMsg(int index) {


return "Index: "+index+", Size: "+size;


}


Node node(int index) { // 这个才是真正的获取指定位置元素的方法,获取的是引用


// assert isElementIndex(index);


if (index < (size ] 1)) { // index < size/2,从头部开始遍历,否则从尾部开始遍历


Node x = first;


for (int i = 0; i < index; i++)


x = x.next;


return x;


} else {


Node x = last;


for (int i = size - 1; i > index; i--)


x = x.prev;


return x;


}


}


  (2)set(int index,E element)


public E set(int index, E element) {


checkElementIndex(index);


Node x = node(index); // 将获取到指定位置的引用返回


E oldVal = x.item;


x.item = element;


return oldVal;


}


  (3)add(int index, E element)


public void add(int index, E element) {


checkPositionIndex(index);


if (index == size)


linkLast(element); // 从尾部添加元素


else


// 获取指定位置元素的引用,其前驱节点改为新元素


linkBefore(element, node(index));


}


void linkBefore(E e, Node succ) {


// assert succ != null;


final Node pred = succ.prev;


final Node newNode = new Node(pred, e, succ);


succ.prev = newNode;


if (pred == null)


first = newNode;


else


pred.next = newNode;


size++;


modCount++;


}


  (4)indexOf(Object o)


public int indexOf(Object o) { // 从头部遍历,返回头一个等于输入参数的元素位置


int index = 0;


if (o == null) { // 为 null 时,判断元素等于null,否则调用equals判断元素相等


for (Node x = first; x != null; x = x.next) {


if (x.item == null)


return index;


index++;


}


} else {


for (Node x = first; x != null; x = x.next) {


if (o.equals(x.item))


return index;


index++;


}


}


return -1;


}


  (5)itrator() 方法


  LinkedList 中本身没有 itrator() 的方法体,方法体在其父类 AbstractSequentialList 中,它实际使用的也是 AbstractList 的实现。


  (6)listItrator() 方法


  当然 ListedList 作为 AbstractList 的子孙类,同样提供了 listItrator() 方法,既可以向前遍历,也可以向后遍历。其返回类型 Iterator 的实现方式也是内部类:


// 无参数的实现方式在 AbstractList 中已经实现


public ListIterator listIterator(int index) {


checkPositionIndex(index);


return new ListItr(index);


}


private class ListItr implements ListIterator {


private Node lastReturned;


private Node next;


private int nextIndex;


private int expectedModCount = modCount;


ListItr(int index) {


// assert isPositionIndex(index);


next = (index == size) ? null : node(index); // 下一节点位置


nextIndex = index;


}


public boolean hasNext() {


return nextIndex [span style="color: rgba(0, 0, 0, 1)"> size;


}


public E next() {


checkForComodification();


if (!hasNext())


throw new NoSuchElementException();


lastReturned = next;


next = next.next;


nextIndex++;


return lastReturned.item;


}


public boolean hasPrevious() {


return nextIndex > 0<span style="color: rg

相关文章
|
8月前
|
Java 虚拟化 容器
(Java)Java里JFrame窗体的基本操作(容器布局篇-1)
容器 容器,我的理解是可以包容其他东西的玩意。它可以是一个盒子,可以是一个虚拟化的物品,可只要能包裹住其他存在质体的东西,那么都可以称作是容器。例如:JPanel组件和JScollPane组件两者都是容器也是组件。 既然有容器,那么容器中的布局就必不可少了。不然不规矩的摆放物品,人类看不习惯,我也看不习惯 ???? 本篇内容,将说明java JFrame窗体里容器中几类布局。 说明:所有在JFrame窗体里的容器布局都会使用setLayout()方法,采用的布局参数都将放进这个方法里 绝对布局 调用窗体容器
226 1
|
人工智能 Kubernetes Java
回归开源,两位 Java 和 Go 程序员分享的开源贡献指引
Higress是一个基于Istio和Envoy的云原生API网关,支持AI功能扩展。它通过Go/Rust/JS编写的Wasm插件提供可扩展架构,并包含Node和Java的console模块。Higress起源于阿里巴巴,解决了Tengine配置重载及gRPC/Dubbo负载均衡问题,现已成为阿里云API网关的基础。本文介绍Higress的基本架构、功能(如AI网关、API管理、Ingress流量网关等)、部署方式以及如何参与开源贡献。此外,还提供了有效的开源贡献指南和社区交流信息。
2338 33
|
Java 程序员 应用服务中间件
【高薪程序员必看】万字长文拆解Java并发编程!(2 2-2)
📌 核心痛点暴击:1️⃣ 面了8家都被问synchronized锁升级?一张图看懂偏向锁→重量级锁全过程!2️⃣ 线程池参数不会配?高并发场景下这些参数调优救了项目组命!3️⃣ volatile双重检测单例模式到底安不安全?99%人踩过的内存可见性大坑!💡 独家亮点抢先看:✅ 图解JVM内存模型(JMM)三大特性,看完再也不怕指令重排序✅ 手撕ReentrantLock源码,AQS队列同步器实现原理大揭秘✅ 全网最细线程状态转换图(附6种状态转换触发条件表)
269 0
|
存储 缓存 Java
【高薪程序员必看】万字长文拆解Java并发编程!(5):深入理解JMM:Java内存模型的三大特性与volatile底层原理
JMM,Java Memory Model,Java内存模型,定义了主内存,工作内存,确保Java在不同平台上的正确运行主内存Main Memory:所有线程共享的内存区域,所有的变量都存储在主存中工作内存Working Memory:每个线程拥有自己的工作内存,用于保存变量的副本.线程执行过程中先将主内存中的变量读到工作内存中,对变量进行操作之后再将变量写入主内存,jvm概念说明主内存所有线程共享的内存区域,存储原始变量(堆内存中的对象实例和静态变量)工作内存。
379 0
|
12月前
|
存储 缓存 安全
Java 集合容器常见面试题及详细解析
本文全面解析Java集合框架,涵盖基础概念、常见接口与类的特点及区别、底层数据结构、线程安全等内容。通过实例讲解List(如ArrayList、LinkedList)、Set(如HashSet、TreeSet)、Map(如HashMap、TreeMap)等核心组件,帮助读者深入理解集合容器的使用场景与性能优化。适合准备面试或提升开发技能的开发者阅读。
222 0
|
12月前
|
缓存 Java API
Java 集合容器实操技巧与案例详解
本教程基于Java 8+新特性和现代开发实践,深入讲解Java集合容器的实操技巧。通过具体场景演示Stream API数据处理、ConcurrentHashMap并发控制、LinkedHashMap实现LRU缓存、TreeSet自定义排序等高级特性。同时涵盖computeIfAbsent优化操作、EnumMap专用集合使用、集合统计与运算(交集、并集、差集)等内容。代码示例丰富,助力掌握高效编程方法。[点击获取完整代码](https://pan.quark.cn/s/14fcf913bae6)。
297 0
|
网络协议 Java 大数据
【高薪程序员必看】万字长文拆解Java并发编程!(1)
📌 核心痛点暴击:1️⃣ 面了8家都被问synchronized锁升级?一张图看懂偏向锁→重量级锁全过程!2️⃣ 线程池参数不会配?高并发场景下这些参数调优救了项目组命!3️⃣ volatile双重检测单例模式到底安不安全?99%人踩过的内存可见性大坑!💡 独家亮点抢先看:✅ 图解JVM内存模型(JMM)三大特性,看完再也不怕指令重排序✅ 手撕ReentrantLock源码,AQS队列同步器实现原理大揭秘✅ 全网最细线程状态转换图(附6种状态转换触发条件表)
211 0
|
安全 Java 程序员
【高薪程序员必看】万字长文拆解Java并发编程!(2 2-1)
🔥【高薪程序员必看】万字长文拆解Java并发编程!面试官看了直呼内行,90%人不知道的线程安全骚操作!💻🚀《16个高频面试灵魂拷问+底层源码暴击》🔥👉戳这里看如何用1个月经验吊打3年程序员!📌 核心痛点暴击:1️⃣ 面了8家都被问synchronized锁升级?一张图看懂偏向锁→重量级锁全过程!2️⃣ 线程池参数不会配?高并发场景下这些参数调优救了项目组命!3️⃣ volatile双重检测单例模式到底安不安全?99%人踩过的内存可见性大坑!
187 0
|
缓存 安全 Java
【高薪程序员必看】万字长文拆解Java并发编程!(3-1):并发共享问题的解决与分析
活锁:多个线程相互影响对方退出同步代码块的条件而导致线程一直运行的情况。例如,线程1的退出条件是count=5,而线程2和线程3在其代码块中不断地是count进行自增自减的操作,导致线程1永远运行。内存一致性问题:由于JIT即时编译器对缓存的优化和指令重排等造成的内存可见性和有序性问题,可以通过synchronized,volatile,并发集合类等机制来解决。这里的线程安全是指,多个线程调用它们同一个实例的方法时,是线程安全的,但仅仅能保证当前调用的方法是线程安全的,不同方法之间是线程不安全的。
224 0
|
Java 程序员
【高薪程序员必看】万字长文拆解Java并发编程!(3-2):并发共享问题的解决与分析
wait方法和notify方法都是Object类的方法:让当前获取锁的线程进入waiting状态,并进入waitlist队列:让当前获取锁的线程进入waiting状态,并进入waitlist队列,等待n秒后自动唤醒:在waitlist队列中挑一个线程唤醒:唤醒所有在waitlist队列中的线程它们都是之间协作的手段,只有拥有对象锁的线程才能调用这些方法,否则会出现IllegalMonitorStateException异常park方法和unpark方法是LockSupport类中的方法。
225 0