Day26 手撕各种集合底层源码(一)

news2024/11/29 4:51:05

Day26 手撕各种集合底层源码(一)

一、手撕ArrayList底层源码

1、概念: ArrayList的底层实现是基于数组的动态扩容结构。

2、思路:
1.研究继承关系
2.研究属性
3.理解创建集合的过程 – 构造方法的底层原理
4.研究添加元素的过程

3、关键源码:

成员变量

transient Object[] elementData; // 用于存储元素的数组
private int size; // ArrayList中元素的数量

构造方法

public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; // 初始容量为0的空数组
}

public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity]; // 指定初始容量的数组
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA; // 初始容量为0的空数组
    } else {
        throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
    }
}

添加元素方法(add)

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // 确保容量足够
    elementData[size++] = e; // 将元素添加到数组末尾
    return true;
}

private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); // 默认容量为10
    }
    ensureExplicitCapacity(minCapacity); // 确保容量足够
}

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    if (minCapacity - elementData.length > 0) {
        grow(minCapacity); // 扩容
    }
}

private void grow(int minCapacity) {
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1); // 扩容为原来的1.5倍
    if (newCapacity - minCapacity < 0) {
        newCapacity = minCapacity;
    }
    if (newCapacity - MAX_ARRAY_SIZE > 0) {
        newCapacity = hugeCapacity(minCapacity);
    }
    elementData = Arrays.copyOf(elementData, newCapacity); // 数组扩容
}

4、举例:

public static void main(String[] args) {//ArrayList<String> list = new ArrayList<>();ArrayList<String> list = new ArrayList<>(10000);
​	
​	list.add("aaa");
​	list.add("bbb");
​	list.add("ccc");
​	list.add("ddd");}

二、手撕LinkedList底层源码

1、概念: LinkedList的底层实现是基于双向链表的数据结构。

2、思路:

​ 1.研究继承关系
​ 2.研究属性
​ 3.理解创建集合的过程 – 构造方法的底层原理
​ 4.研究添加元素的过程

3、关键源码:

节点定义

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;
    }
}

成员变量

transient int size = 0; // LinkedList中元素的数量
transient Node<E> first; // 链表的头节点
transient Node<E> last; // 链表的尾节点

添加元素方法(add)

public boolean add(E e) {
    linkLast(e); // 将元素添加到链表末尾
    return true;
}

void linkLast(E e) {
    final Node<E> l = last;
    final Node<E> newNode = new Node<>(l, e, null); // 创建新节点
    last = newNode; // 将新节点设置为尾节点
    if (l == null) {
        first = newNode; // 如果链表为空,将新节点设置为头节点
    } else {
        l.next = newNode; // 将前尾节点的后继节点指向新节点
    }
    size++; // 元素数量加1
}

删除元素方法(remove)

public E remove() {
    return removeFirst(); // 删除链表的第一个节点
}

public E removeFirst() {
    final Node<E> f = first;
    if (f == null) {
        throw new NoSuchElementException();
    }
    return unlinkFirst(f); // 删除第一个节点
}

E unlinkFirst(Node<E> f) {
    final E element = f.item;
    final Node<E> next = f.next;
    f.item = null;
    f.next = null; // help GC
    first = next; // 将下一个节点设置为头节点
    if (next == null) {
        last = null; // 如果下一个节点为空,将尾节点置空
    } else {
        next.prev = null; // 将下一个节点的前驱节点置空
    }
    size--; // 元素数量减1
    return element;
}

4、举例:

	public static void main(String[] args) {
		
		LinkedList<String> list = new LinkedList<>();
		
		list.add("小浩");
		list.add("小威");
		list.add("小刘");
		
	}

三、手撕Stack底层源码

1、概念: Java中的Stack类是基于动态数组实现的后进先出(LIFO)栈结构。然而,需要注意的是,Java官方推荐使用Deque接口的实现类ArrayDeque来代替Stack类,因为Deque接口提供了更完善的栈操作方法,并且在性能上更优秀。

2、关键源码:

成员变量

private transient Object[] elementData; // 用于存储栈元素的数组
private int elementCount; // 栈中元素的数量

基本方法

public E push(E item) {
    addElement(item); // 将元素压入栈顶
    return item;
}

public synchronized E pop() {
    E obj;
    int len = size();
    obj = peek(); // 获取栈顶元素
    removeElementAt(len - 1); // 移除栈顶元素
    return obj;
}

public synchronized E peek() {
    int len = size();
    if (len == 0) {
        throw new EmptyStackException();
    }
    return elementAt(len - 1); // 获取栈顶元素
}

扩容方法

java复制代码private void ensureCapacity(int minCapacity) {
    if (minCapacity - elementData.length > 0) {
        grow(minCapacity); // 扩容
    }
}

private void grow(int minCapacity) {
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1); // 扩容为原来的1.5倍
    if (newCapacity - minCapacity < 0) {
        newCapacity = minCapacity;
    }
    if (newCapacity - MAX_ARRAY_SIZE > 0) {
        newCapacity = hugeCapacity(minCapacity);
    }
    elementData = Arrays.copyOf(elementData, newCapacity); // 数组扩容
}

3、举例:

import java.util.Stack;

public class Test01 {
	/**
	 * 知识点:手撕Stack底层源码
	 */
	public static void main(String[] args) {
		
		Stack<String> stack = new Stack<>();
		
		stack.push("aaa");
		stack.push("bbb");
		stack.push("ccc");
		stack.push("ddd");
		
	}
}

四、单向链表

1、概念: 单向链表(Singly Linked List)是一种常见的链表数据结构,它由节点组成,每个节点包含数据和指向下一个节点的指针。单向链表的最后一个节点指向空值(null),表示链表的结束。

2、特点:

  1. 单向遍历:只能从头到尾遍历链表,无法反向遍历。
  2. 插入和删除:在已知节点的情况下,可以方便地进行节点的插入和删除操作。
  3. 空间开销:相对于数组,单向链表需要额外的空间来存储指针。

3、应用场景:

  • 需要频繁的插入和删除操作,且不需要反向遍历的场景。
  • 需要在已知节点的情况下进行插入和删除操作的场景。

4、关键源码:

成员变量

private transient Object[] elementData; // 用于存储栈元素的数组
private int elementCount; // 栈中元素的数量

基本方法

public E push(E item) {
    addElement(item); // 将元素压入栈顶
    return item;
}
public synchronized E pop() {
    E obj;
    int len = size();
    obj = peek(); // 获取栈顶元素
    removeElementAt(len - 1); // 移除栈顶元素
    return obj;
}
public synchronized E peek() {
    int len = size();
    if (len == 0) {
        throw new EmptyStackException();
    }
    return elementAt(len - 1); // 获取栈顶元素
}

扩容方法

private void ensureCapacity(int minCapacity) {
    if (minCapacity - elementData.length > 0) {
        grow(minCapacity); // 扩容
    }
}

private void grow(int minCapacity) {
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1); // 扩容为原来的1.5倍
    if (newCapacity - minCapacity < 0) {
        newCapacity = minCapacity;
    }
    if (newCapacity - MAX_ARRAY_SIZE > 0) {
        newCapacity = hugeCapacity(minCapacity);
    }
    elementData = Arrays.copyOf(elementData, newCapacity); // 数组扩容
}

5、举例:

import java.util.Iterator;
public class Test01 {
	/**
	 * 知识点:实现单向链表
	 */
	public static void main(String[] args) {
		
		UnidirectionalLinkedList<String> list = new UnidirectionalLinkedList<>();
		
		list.add("aaa");
		list.add("bbb");
		list.add("ccc");
		list.add("ddd");
		list.add("eee");
		
		Iterator<String> it = list.iterator();
		while(it.hasNext()){
			String element = it.next();
			System.out.println(element);
		}
		
	}
}
public class UnidirectionalLinkedList<E> {

	private Node<E> first;
	private Node<E> last;
	private int size;
	
	public void add(E e){
		
		Node<E> node = new Node<>(e, null);
		
		if(first == null){
			first = node;
		}else{
			last.next = node;
		}
		last = node;
		size++;
	}
	
	public Iterator<E> iterator(){
		return new Itr();
	}
	
	public class Itr implements Iterator<E>{

		private int cursor;
		private Node<E> node = first;
		
		@Override
		public boolean hasNext() {
			return cursor != size;
		}

		@Override
		public E next() {
			E item = node.item;
			node = node.next;
			cursor++;
			return item;
		}
		
	}
	
	public static class Node<E>{
		E item;
		Node<E> next;
		
		public Node(E item, Node<E> next) {
			this.item = item;
			this.next = next;
		}
	}
	
}

五、双向链表

1、概念: 双向链表(Doubly Linked List)是一种常见的链表数据结构,每个节点包含两个指针,分别指向前一个节点和后一个节点。与单向链表相比,双向链表可以支持双向遍历,提供了更多的灵活性 。

2、特点:

  1. 双向遍历:可以从头到尾或者从尾到头遍历链表,提供了更多的遍历方式。
  2. 插入和删除:在已知节点的情况下,可以更方便地进行节点的插入和删除操作。
  3. 空间开销:相对于单向链表,双向链表需要额外的空间来存储前驱节点的指针。

3、应用场景:

  • 需要频繁的插入和删除操作,且需要双向遍历的场景。
  • 需要在已知节点的情况下进行插入和删除操作的场景。

4、基本操作:

  1. 插入节点:在给定节点后或前插入新节点,需要更新前后节点的指针。
  2. 删除节点:删除给定节点,同样需要更新前后节点的指针。
  3. 遍历:可以从头到尾或者从尾到头遍历链表,获取节点的值或执行其他操作。

5、代码理解:

import java.util.Iterator;
import com.qf.bidirectional_linked_list.BidirectionalLinkedList.Node;

public class Test01 {
	/**
	 * 知识点:实现双向链表
	 */
	public static void main(String[] args) {
		
		BidirectionalLinkedList<String> list = new BidirectionalLinkedList<>();
		
		list.add("aaa");
		list.add("bbb");
		list.add("ccc");
		list.add("ddd");
		list.add("eee");
		
		//正序遍历
		Iterator<String> it = list.iterator();
		while(it.hasNext()){
			String element = it.next();
			System.out.println(element);
		}
		
		System.out.println("-----------------------");

		//倒序遍历
		Node<String> node = list.getLast();
		while(node != null){
			System.out.println(node.item);
			node = node.prev;
		}
		
	}
}

public class BidirectionalLinkedList<E> {

	private Node<E> first;
	private Node<E> last;
	private int size;
	
	public void add(E e){
		
		Node<E> l = last;
		
		Node<E> node = new Node<>(l,e, null);
		
		if(first == null){
			first = node;
		}else{
			last.next = node;
			
		}
		last = node;
		size++;
	}
	
	
	public Node<E> getLast() {
		return last;
	}

	public Iterator<E> iterator(){
		return new Itr();
	}
	
	public class Itr implements Iterator<E>{

		private int cursor;
		private Node<E> node = first;
		
		@Override
		public boolean hasNext() {
			return cursor != size;
		}

		@Override
		public E next() {
			E item = node.item;
			node = node.next;
			cursor++;
			return item;
		}
	}
	
	public static class Node<E>{
		Node<E> prev;
		E item;
		Node<E> next;
		
		public Node(Node<E> prev,E item, Node<E> next) {
			this.prev = prev;
			this.item = item;
			this.next = next;
		}
	}
	
}

LinkedList理解图
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1556270.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Linux】图文详解Xshell远程连接服务器:以Amazon EC2 VPS为例

文章目录 问题描述解决方案Q&A 问题描述 本地cmd或powershell使用ssh -i “your.pem” user_nameip_address是可以登录Amazon EC2云服务器的。 然而&#xff0c;当使用XShell以SSH加载PEM文件方式登录亚马逊EC2云服务器&#xff0c;一直出现输入密码的问题&#xff0c;如…

FPGA亚稳态学习总结

首先是组合逻辑电路考虑的是竞争冒险&#xff0c;冒险会产生毛刺。重点研究如何去毛刺 时序逻辑电路考虑的是时序不满足会产生的亚稳态问题&#xff1a;如何考量时序满不满足呢&#xff1f;根据不同的场景又有不同的说法。 时序分析的两组基本概念 建立时间与保持时间 1.在…

使用Node.js常用命令提高开发效率

Node.js是一个基于Chrome V8引擎的JavaScript运行时环境&#xff0c;广泛用于构建服务器端应用程序和命令行工具。Node.js提供了丰富的命令和工具&#xff0c;可以帮助开发者更高效地开发应用程序。在日常开发中&#xff0c;除了Node.js本身的核心功能外&#xff0c;npm&#x…

加密/ 解密 PDF:使用Python为PDF文档设置、移除密码

在数字化时代&#xff0c;文档的安全性变得越来越重要。特别是对于包含敏感信息的PDF文件&#xff0c;确保其不被未经授权的人员访问或修改是至关重要的。本文将介绍如何使用Python在PDF文档中设置密码&#xff0c;以及如何移除已经设置的密码。 目录 PDF加密基础知识 Pytho…

QT 最近使用的项目配置文件

目录 1 QT 最近使用的项目配置文件所在路径 2 QtCreator.ini 1 QT 最近使用的项目配置文件所在路径 C:\Users\your username\AppData\Roaming\QtProject QtCreator.ini最好先备份一份 2 QtCreator.ini ProjectExplorer 下面的 RecentProjects\FileNames RecentProjects\…

【公示】2023年度青岛市级科技企业孵化器拟认定名单

根据《青岛市科技企业孵化器管理办法》&#xff08;青科规〔2023〕1号&#xff09;&#xff08;以下简称《管理办法》&#xff09;、《关于开展2023年度市级科技企业孵化器认定申报工作的通知》&#xff0c;经申报受理、区市推荐、形式审查、专家评审及现场核查等程序&#xff…

十七、InnoDB 一次更新事务的执行过程

一、InnoDB的一次更新事务是怎么实现的&#xff1f; InnoDB的一次更新事务涉及到多个组件和步骤&#xff0c;包括Buffer Pool、BinLog、UndoLog、RedoLog以及物理磁盘。 下面是一次完整的事务更新操作过程&#xff1a; 1. 加载数据到缓存中&#xff08;Buffer Pool&#xff0…

HTML5通过api实现拖放效果 dataTransfer对象

dataTransfer对象 说明&#xff1a;dataTransfer对象用于从被拖动元素向放置目标传递字符串数据。因为这个对象是 event 的属性&#xff0c;所以在拖放事件的事件处理程序外部无法访问 dataTransfer。在事件处理程序内部&#xff0c;可以使用这个对象的属性和方法实现拖放功能…

【实现报告】学生信息管理系统(链表实现)

目录 实验一 线性表的基本操作 一、实验目的 二、实验内容 三、实验提示 四、实验要求 五、实验代码如下&#xff1a; &#xff08;一&#xff09;链表的构建及初始化 学生信息结构体定义 定义元素类型 链表节点结构体定义 初始化链表 &#xff08;二&#xff09;…

【AI】命令行调用大模型

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 【AI】命令行调用大模型引入正文初始化项目撰写脚本全局安装 成果展示 【AI】命令…

Linux——共享内存

Linux——共享内存 什么是共享内存共享内存原理Linux下共享内存的接口创建/获取共享内存&#xff1a;shmgetftok函数 映射共享内存到进程地址空间&#xff1a;shmat 解除共享内存映射&#xff1a;shmdt删除共享内存段&#xff1a;shmctl 利用共享内存进行通信 我们之前学习了匿…

香港服务器怎么看是CN2 GT线路还是CN2 GIA线路?

不知道有没有小伙伴们注意过&#xff0c;很多人在租用香港服务器的时候都习惯性选择 CN2 线路&#xff1f;仿佛香港服务器是否采用 CN2 线路成为个人企业选择香港服务器的一个标准。其实&#xff0c;香港服务器有CN2、优化直连(163)、BGP多线(包含了国际和国内线路)&#xff0c…

Unity 刮刮乐(优化极简)

废话不多说上代码&#xff0c;上图片&#xff0c;欢迎对Unity有兴趣的伙伴和我一起探讨学习 using UnityEngine; using UnityEngine.UI;public class ScratchCardWithSpriteRenderer : MonoBehaviour {// 公开背景和遮罩的Sprite Renderer组件public SpriteRenderer background…

java复原IP 地址(力扣Leetcode93)

复原IP 地址 力扣原题链接 问题描述 有效 IP 地址正好由四个整数&#xff08;每个整数位于 0 到 255 之间组成&#xff0c;且不能含有前导 0&#xff09;&#xff0c;整数之间用 ‘.’ 分隔。 例如&#xff1a;“0.1.2.201” 和 “192.168.1.1” 是有效 IP 地址&#xff0c…

爬取b站音频和视频数据,未合成一个视频

一、首先找到含有音频和视频的url地址 打开一个视频&#xff0c;刷新后&#xff0c;找到这个包&#xff0c;里面有我们所需要的数据 访问这个数据包后&#xff0c;获取字符串数据&#xff0c;用正则提取&#xff0c;再转为json字符串方便提取。 二、获得标题和音频数据后&…

基于LSB(最低有效位)的图像水印算法,Matlab实现

博主简介&#xff1a; 专注、专一于Matlab图像处理学习、交流&#xff0c;matlab图像代码代做/项目合作可以联系&#xff08;QQ:3249726188&#xff09; 个人主页&#xff1a;Matlab_ImagePro-CSDN博客 原则&#xff1a;代码均由本人编写完成&#xff0c;非中介&#xff0c;提供…

【蓝桥杯第十四届省赛B】(部分详解)

【01串的熵】 https://www.lanqiao.cn/problems/3498/learning/?subject_code1&group_code4&match_num14&match_flow1&origincup #include <iostream> #include<cmath> using namespace std; int main() {double n23333333;double sum0;for(int…

【跟着CHATGPT学习硬件外设 | 04】ADC

本文根据博主设计的Prompt由CHATGPT生成&#xff0c;形成极简外设概念。 &#x1f680; 1. 概念揭秘 1.1 快速入门 模数转换器&#xff08;ADC&#xff0c;Analog-to-Digital Converter&#xff09;是一种将模拟信号转换为数字信号的电子设备。模拟信号通常表示物理测量的连…

Learning To Count Everything

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 摘要Abstract文献阅读&#xff1a;学习数一切东西1、研究背景2、提出方法3、模块详细3.1、多尺度特征提取模块3.2、密度预测模块 4、损失函数5、性能对比6、贡献 二…

mybatis标签解析教程

mybatis标签解析 标签结构 我们在mapper的xml文件中&#xff0c;使用动态SQL&#xff0c;那么这些标签<where>、<if>、<set>、<ForEach>、<Choose>、<Trim> 等是怎么解析的呢&#xff1f;我们先看包的结构 包结构中&#xff0c;script…