LinkedList集合源码分析

news2024/11/24 17:44:19

LinkedList集合源码分析

文章目录

  • LinkedList集合源码分析
  • 一、字段分析
  • 二、构造函数分析
  • 三、方法分析
  • 四、总结

在这里插入图片描述

  • 看到实现了Deque 就要联想到这个数据结构肯定是属于双端队列了。
  • Queue 表示队列,Deque表示双端队列。

一、字段分析

  • LinkedList 字段很少,就三个。
//表示实际存储的数据个数,即节点个数
transient int size = 0;
//指向最开始的节点
transient Node<E> first;
//指向最末尾的节点
transient Node<E> last;

// Node 是一个静态内部类,从这里也可以看出,LinkedList 是一个双向链表,因为它有一个指向下一个节点
//的 next 和一个指向上一个节点的 prev。
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;
        }
    }

二、构造函数分析

//就是创建了一个LinkedList 对象,字段值都是默认值。
//即 size = 0; first = null, last = null;
public LinkedList() {
    }

//使用集合作为参数创建 LinkedList,这个参数 c 得是 Collection 的子类才可以
public LinkedList(Collection<? extends E> c) {
        //调用无参构造函数
        this();
        //将 c 的元素全部添加到LinkedList中,接着往下看
        addAll(c);
    }

public boolean addAll(Collection<? extends E> c) {
		//此时的size 为 0
        return addAll(size, c);
    }

//然后这个方法的参数意义是:index 为要插入节点的位置,需要注意是往 index 这个节点的 后面!! 插入(尾差法),
//而不是前面插入,这很关键!!记住这点然后去分析。 参数 c 就是被插入的集合了。
public boolean addAll(int index, Collection<? extends E> c) {
		//检查index越界,检查逻辑也很简单  index >= 0 && index <= size,下面贴出了源码
        checkPositionIndex(index);
		//将被插入集合转换为数组
        Object[] a = c.toArray();
        //被插入集合的长度
        int numNew = a.length;
        //如果被插入集合是空的,则后面也没有元素可插了,到此为止。
        if (numNew == 0)
            return false;
		//创建了两个变量,用来方便操作链表。
		//pred:记录插入位置的上一个节点
		//succ:记录插入位置的下一个节点
		//熟悉双向链表,想必就知道为啥要这个两个变量了,为了方便连接。比如我们向index 位置插入了节点Node
		//则通过 pred.next = node; node.prev = pred; node.next = succ; succ.prev = node,这样双向连接起来。
        Node<E> pred, succ;
        //处理一特殊情况,就是插入位置后面没有节点了,那就是插入到最末尾,所以下个节点succ为空,上个节点pred为最后那个节点。
        if (index == size) {
            succ = null;
            pred = last;
        } else {
        	//处理一般情况了,这里返回的是index处节点的下一个节点,而不是index 处的节点,在联想我上面说的尾差法,
        	//succ 的变量意义是记录新插入节点的上一个节点,所以它现在要成为新节点的下一个节点了。
        	//该处源码看下文,同时贴出了测试结果。
            succ = node(index);
            //pred 才是index 出那个节点。现在充当新插入节点的上个节点
            //效果就是   pred -> <- succ    =>      pred -> <- new -> <- succ 
            pred = succ.prev;
        }
		
        for (Object o : a) {
            @SuppressWarnings("unchecked") E e = (E) o;
            //创建新节点
            Node<E> newNode = new Node<>(pred, e, null);
            //判断山个节点是否为null,即判断插入位置是不是正好插在头部了。
            if (pred == null)
                first = newNode;
            else
            //不是头部,则从上个节点开始,一个一个连接新节点,每次连接完,新节点则作为上个节点。
                pred.next = newNode;
            pred = newNode;
        }
		//新节点查完了,需要把后面的节点在后面补上
        if (succ == null) {
        	//后面没节点了,新的尾结点变成我们最后插入的节点了。
            last = pred;
        } else {
        	//后面还有节点,给连接上。
            pred.next = succ;
            succ.prev = pred;
        }
		//更新 size
        size += numNew;
        //更新版本号,迭代时候用,判断迭代过程中,LinkedList是否被篡改。
        modCount++;
        //插入成功!!!
        return true;
    }

//判断 index 是否越界
private void checkPositionIndex(int index) {
        if (!isPositionIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
private boolean isPositionIndex(int index) {
        return index >= 0 && index <= size;
    }

//返回第 index + 1 处的节点
Node<E> node(int index) {
		//这里分情况讨论了,因为linkedList 查找元素只能一个一个的遍历,效率低下,为了提高效率,判断这个位置距离头部近,
		//还是尾部近,哪边近则从哪边开始遍历查找,从而减少遍历次数,提高效率。
        // assert isElementIndex(index);
        if (index < (size >> 1)) {
            Node<E> x = first;
            //这里从 i = 0,开始的,相当于是从fist开始(即第一个节点),往后数index 下,不就是index + 1 处的节点嘛
            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;
        }
    }

在这里插入图片描述

三、方法分析

  • 添加元素。LinkedList 添加方法的API还挺多,,核心逻辑都差不多。
//方法名看起来好像是和把 e 和头结点相连、事实上也确实差不多,是用 e 创建新的节点作为头结点,
//而原先的头结点就作为新头结点的下一个节点
private void linkFirst(E e) {
		//获取头结点
        final Node<E> f = first;
        //使用 e 创建新的节点,并且 pre = null, next = f
        final Node<E> newNode = new Node<>(null, e, f);
        //更新 fist变量 为 新的头结点
        first = newNode;
        //如果链表原先是没有节点的,那么头结点当然也是尾结点了。
        if (f == null)
            last = newNode;
        else
        	//否则尾结点不变,并且将老的头结点连接上新的头结点,因为前面效果是  new -> old,这里之后就是 new -> <- old
            f.prev = newNode;
        //节点个数+1    
        size++;
        //更新版本号,看到这个字段,应该想到一定是用在迭代器中了,并且是用来防止迭代过程中,
        //校验集合是否被修改,实时也确实如此。
        modCount++;
    }

    
    //前面分析了,那这个也一目了然了,使用e 创建新的节点作为尾结点。
    void linkLast(E e) {
    	//获取尾结点
        final Node<E> l = last;
        //使用e创建新的节点,并且pre = last , next = null,效果就是  l <- newNode -> null
        final Node<E> newNode = new Node<>(l, e, null);
        //更新 last 变量,指向新的尾结点
        last = newNode;
        //如果原先 链表中没有节点,那尾结点也是头结点。
        if (l == null)
            first = newNode;
        else
        	//否则,将老的尾结点连接上新的尾结点,效果变成 l -> <- newNode -> null
            l.next = newNode;
        //节点个数 + 1
        size++;
        //版本 + 1
        modCount++;
    }

    /**
     * Inserts element e before non-null Node succ.
     */
     //使用 e 创建新的节点,并且将该节点插入到 succ 节点之前。
    void linkBefore(E e, Node<E> succ) {
        // assert succ != null;
        //获取 succ 的上个节点
        final Node<E> pred = succ.prev;
        //使用 e 创建新的节点,并且是 prev = pred, next = succ, 效果就是   pred  <- newNode -> succ
        final Node<E> newNode = new Node<>(pred, e, succ);
        //变成 pred  <- newNode -> <- succ
        succ.prev = newNode;
        //如果succ就是头结点,那么新的节点插到它前面,那么新插入的节点不就是新的头结点了。
        if (pred == null)
            first = newNode;
        else
        	//succ不是头结点,效果是  pred  -> <- newNode -> <- succ
            pred.next = newNode;
        //节点个数 + 1    
        size++;
        //版本 + 1
        modCount++;
    }

//所有的添加相关私有方法说完了,我们常调用的api 就是基于这些操作来的。

//向链表中添加新节点,可见该方法是想链表的尾部添加新的节点
public boolean add(E e) {
		//上面分析过了,向尾部添加新节点。
        linkLast(e);
        return true;
    }

//向 index 处添加新节点,是向index处节点的尾部添加新的节点,尾差发。
public void add(int index, E element) {
		//检查 index 是否越界
        checkPositionIndex(index);
		//如果index处就是尾结点,那么直接在尾部插入新的节点
        if (index == size)
            linkLast(element);
        else
        	//处理不是尾结点的情况。node(index) 在构造方法时说过了,是返回 index + 1 出的节点,在index + 1的前面插入,
        	//不就是往 index 出的后面插入吗,所以是我们分析的没问题,逻辑一致的。
            linkBefore(element, node(index));
    }
    
//插入新的头节点
public void addFirst(E e) {
        linkFirst(e);
    }
//插入新的尾结点    
public void addLast(E e) {
        linkLast(e);
    }

//还有addAll 方法,在构造方法说过了。

//LinkedList 实现了 List 和 Queue 两个接口,前面是属于List集合相关操作的,LinkedList 也可以作为 队列使用,下面分析作为队列
//时相关的添加元素的方法

//向队列尾部添加元素
public boolean offer(E e) {
        return add(e);
    }
    
//向队列头部添加元素
public boolean offerFirst(E e) {
        addFirst(e);
        return true;
    }

//向队列尾部添加元素    
public boolean offerLast(E e) {
        addLast(e);
        return true;
    }

//向队列头部添加元素
public void push(E e) {
        addFirst(e);
    }
  • 获取元素方法。
//获取第index + 1 个节点,这个方法基本没用过。。
public E get(int index) {
		//检查是否越界
        checkElementIndex(index);
        return node(index).item;
    }

//获取头结点 (只是获取不删除),链表为空,则报错
public E getFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return f.item;
    }
    
//获取头结点 (只是获取不删除),链表为空,则报错
public E element() {
        return getFirst();
    }
    
//获取尾结点 (只是获取不删除)链表为空,则报错
public E getLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return l.item;
    }

//获取头结点 (只是获取不删除),支持链表为空,返回null
public E peek() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
    }


//获取头结点 (获取且删除),支持链表为空,返回null
public E poll() {
        final Node<E> f = first;
        return (f == null) ? null : unlinkFirst(f);
    }
  • 扩容方法:链表存储数据所需的内存空间不需要连续的,所以并不需要扩容。添加新元素时,直接连接上即可。

  • 移除元素方法:

//这个方法名看起来像是将头结点first断开,可是如果是这样的话,为啥要传参数呢,因为直接可以拿到变量 first。
//实时上,该方法作用就是移除 头结点first,并且这里的参数f就是头结点,这是个私有方法,内部调用之前,这个 f 就是
//被赋值 first,并且做了非空的判断,所以到这里的f 必不为空,且f 必为 First。这样简化了方法里的逻辑。不需要做其他额外判断。
private E unlinkFirst(Node<E> f) {
        // assert f == first && f != null;
        //获取 节点f 的值
        final E element = f.item;
        //获取 节点f 的下一个节点
        final Node<E> next = f.next;
        //将 节点f 的值设为 null
        f.item = null;
        //将 节点f 和 下一个节点断开, 效果为   f <- next
        f.next = null; // help GC
        //将 节点f 设置next,这样设置是为了将 prev 和 next 连接,从而剔除掉 节点f。
        first = next;
        //如果要删除的节点f是尾结点的话,即当前就只有一个节点,被删除了后,last 也要背设置为null 了。
        if (next == null)
            last = null;
        else
        	//还有其他节点的情况。  next节点 断开了指向 first。
            next.prev = null;
        //节点数量 -1    
        size--;
        //版本 + 1
        modCount++;
        //返回被删除的头结点的值
        return element;
    }

    /**
     * Unlinks non-null last node l.
     */
    //和上面同样的道理,作用:删除尾结点。且这里的 l 就是 last 尾结点。且必不为空。
    private E unlinkLast(Node<E> l) {
        // assert l == last && l != null;
        //获取尾结点的值
        final E element = l.item;
        //获取尾结点 的上一个节点,该节点后续要作为新的尾结点了
        final Node<E> prev = l.prev;
        //尾结点值设为null
        l.item = null;
        //尾结点断开指向上个节点
        l.prev = null; // help GC
        //设置新的尾结点,即prev
        last = prev;
        //如果链表只有一个节点,那么删除了,头结点也要设置为null了,因为整个链表没有节点了
        if (prev == null)
            first = null;
        else
        	//不止一个节点的情况。将新的尾结点的next 设置null
            prev.next = null;
        //节点个数 - 1
        size--;
        //版本 + 1
        modCount++;
        //返回要删除的节点值
        return element;
    }

    /**
     * Unlinks non-null node x.
     */
    //删除节点 x ,且 x 比不为null
    E unlink(Node<E> x) {
        // assert x != null;
        //x 节点的值
        final E element = x.item;
        //x 节点的上一个节点
        final Node<E> next = x.next;
        //x 节点的下一个节点
        final Node<E> prev = x.prev;
		//如果 x 节点就是头结点的话,那么新的头结点就是next了。
        if (prev == null) {
            first = next;
        } else {
        	//节点x节点不是头结点,所以上个节点指向 x节点 的下一个节点。
        	//prev -> <- x -><- next  =>    prev-> next  且    prev <- x -> <- next
            prev.next = next;
            //断开 x节点 指向上个节点, 变成  	prev-> next  且    x -> <- next
            x.prev = null;
        }
		//同样的如果删除的 x节点 是尾结点,那么上个 节点prev 就是新的尾结点了。
        if (next == null) {
            last = prev;
        } else {
        	//x 节点不是尾结点的情况。 变成  prev-> <- next  且    x ->  next
            next.prev = prev;
            //变成 prev-> <- next  且    x ->  null ,这样  节点x 就彻底断开了,并且prev 和 next连接上了。
            x.next = null;
        }
		//设置 x节点 的值为null。方便 gc
        x.item = null;
        //元素个数 - 1
        size--;
        //版本 + 1
        modCount++;
        //返回 被删除节点x 的值
        return element;
    }

//将头结点删除
public E removeFirst() {
		//获取头结点
        final Node<E> f = first;
        //头结点为空则报错
        if (f == null)
            throw new NoSuchElementException();
        //将头结点断开
        return unlinkFirst(f);
    }

//删除尾结点
public E removeLast() {
		//获取尾结点
        final Node<E> l = last;
        //尾结点为空则报错
        if (l == null)
            throw new NoSuchElementException();
       	//将尾结点断开    
        return unlinkLast(l);
    }

//根据对象删除节点
public boolean remove(Object o) {
		//从这里也可以看出,LinkedList 是支持存储null和删除null 的。
		//如果链表中又重复的值,删除的是靠近头结点的那一个值,而不是都删除
		//处理 null 的情况,从头结点开始遍历
        if (o == null) {
            for (Node<E> x = first; x != null; x = x.next) {
                if (x.item == null) {
                	//断开节点x
                    unlink(x);
                    return true;
                }
            }
        } else {
        	//处理非 null 的前框,从头结点开始遍历
            for (Node<E> x = first; x != null; x = x.next) {
                if (o.equals(x.item)) {
                	//断开节点x
                    unlink(x);
                    return true;
                }
            }
        }
        //如果没找到这个值,则返回 false,删除失败
        return false;
    }

//删除 第 index+ 1 个节点 
public E remove(int index) {
        checkElementIndex(index);
        return unlink(node(index));
    }

//最为队列的相关方法 -------------------------------

//删除头结点
public E remove() {
        return removeFirst();
    }

//获取头结点值并删除,如果链表为空,则返回null    
public E pollFirst() {
        final Node<E> f = first;
        return (f == null) ? null : unlinkFirst(f);
    }

//获取尾结点值并删除,如果链表为空,则返回null
public E pollLast() {
        final Node<E> l = last;
        return (l == null) ? null : unlinkLast(l);
    }

//获取头结点的值,并删除头结点
public E pop() {
        return removeFirst();
    }
  • 迭代器:LinkedList 只提供了一个 ListItr 迭代器,并提供了迭代过程中操作集合的方法,LinkedList 的迭代器使用非常非常少,一般LinkedList 被用来作为队列,操作头部和尾部,迭代器部分的代码有兴趣的可以自己看看。

四、总结

  • LinkedList 使用双向链表来存储元素,不需要连续的内存空间来存储元素,所以不需要扩容。
  • 一般作为队列,直接操作头部和尾部。支持存储或删除相同的元素,且支持存储null,存储的元素有序。存在相同多个值是,会删除靠近头结点的值。
  • 查找元素只能遍历查找,效率慢O(n),删除,插入快O(1)。根据元素值查找,只能从头结点开始遍历。如果根据 位置查找,会从距离头结点或尾结点近的一方开始遍历。
  • 不是线程安全的。

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

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

相关文章

Vector Search和专用Search Nodes:现已正式发布

我们非常高兴地推出了 Atlas Vector Search 和 Search Nodes 的正式发布版本 (GA)&#xff0c;为 Atlas 平台增添了更多价值。 自从在公开预览版中发布 Atlas Vector Search 和带有 Search Nodes 的专用基础架构以来&#xff0c;我们注意到&#xff0c;对于使用向量优化搜索节…

【数据集】MSWEP(多源加权集合降水)再分析数据

MSWEP全球降水数据 数据概述数据下载参考数据概述 MSWEP(Multi-Source Weighted-Ensemble Precipitation)降水数据集是一种高分辨率、全球覆盖的降水数据产品,它融合了多种来源的降水信息,包括卫星遥感数据、雷达观测、地面气象站观测数据以及数值天气预报模型的输出。MSW…

C switch 语句

一个 switch 语句允许测试一个变量等于多个值时的情况。每个值称为一个 case&#xff0c;且被测试的变量会对每个 switch case 进行检查。 语法 C 语言中 switch 语句的语法&#xff1a; switch(expression){case constant-expression :statement(s);break; /* 可选的 */ca…

Redis线程模型解析

引言 Redis是一个高性能的键值对&#xff08;key-value&#xff09;内存数据库&#xff0c;以其卓越的读写速度和灵活的数据类型而广受欢迎。在Redis 6.0之前的版本中&#xff0c;它采用的是一种独特的单线程模型来处理客户端的请求。尽管单线程在概念上似乎限制了其扩展性和并…

C++ 之AVL树

AVL树 AVL树的基本概念AVL树的平衡因子、AVL树的旋转avl的双旋旋转的4种情况 AVL树的基本概念 AVL树的平衡因子、 AVL树的旋转 当平衡因子的高度差过大时&#xff0c;就要选择。所谓的选择其实也是一种压扁的操作 在本例中 新插入的蓝色结点使得不在平衡。 我们看上图就能…

【CSP试题回顾】201604-1-折点计数

CSP-201604-1-折点计数 解题代码 #include <iostream> #include <vector> #include <algorithm> using namespace std;int n, pointSum;int main() {cin >> n;vector<int>myData(n);for (int i 0; i < n; i){cin >> myData[i];}// 统…

深色系可视化界面看腻了,来点浅色系?安排,20页来了。

只要不放在大屏上展示&#xff0c;贝格前端工场还是非常推崇浅色系的可视化界面&#xff0c;把它作为配色的首选 。浅色系可视化界面具有以下几个优势&#xff1a; 清晰明了 浅色系界面通常使用明亮的颜色&#xff0c;如白色、浅灰色等&#xff0c;使界面元素更加清晰可见。这…

文件MD5校验码的安全性及重要性

title: 文件MD5校验码的安全性及重要性 date: 2024/3/6 18:13:20 updated: 2024/3/6 18:13:20 tags: MD5原理文件校验下载验证数据库一致性安全性保障计算方法MD5安全防护 文件MD5&#xff08;Message Digest Algorithm 5&#xff09;是一种常用的哈希算法&#xff0c;用于验证…

Docker部署(ruoyi案例接上篇Docker之部署前后端分离项目)实施必会!!!!

文章目录 Docker部署前端 Docker部署前端 接上篇博主已经部署好后端Docker部署后端&#xff0c;现在来讲解怎么部署前端 MySQL和redis是不依赖其他任何一个东西的&#xff0c; ruoyi-admin是因为你启动项目的时候是必须连接数据库的 现在去单独启动它 docker start ruoyi-a…

Python爬虫:设置随机 User-Agent

Python爬虫&#xff1a;设置随机 User-Agent 在Python中编写爬虫时&#xff0c;为了模拟真实用户的行为并防止被服务器识别为爬虫&#xff0c;通常需要设置随机的User-Agent。你可以使用fake-useragent库来实现这一功能。首先&#xff0c;你需要安装fake-useragent库&#xff…

【web网页制作】html+css网页制作企业网站办公用品主题(3页面)【附源码】

企业网站目录 涉及知识写在前面一、网页主题二、网页效果Page1、主页Page2、用品展示Page3、关于我们 三、网页架构与技术3.1 脑海构思3.2 整体布局3.3 技术说明书 四、网页源码4.1 主页模块源码4.2 源码获取方式 作者寄语 涉及知识 办公用品企业主题HTML网页制作&#xff0c;…

进程调度算法(先来先服务/短作业优先)代码实现

最近在复习408操作系统进程时&#xff0c;决定用代码模拟调度算法来熟悉详细过程! 选择两个好写点的算法进行练习!!! 以下代码使用c语言。优先队列和队列直接使用STL容器!!! 引入头文件 #include<iostream> #include<queue> #include<map> using namespace …

【漏洞复现】ShopXO任意文件读取漏洞

Nx01 产品简介 ShopXO是一套开源的企业级开源电子商务系统&#xff0c;包含PC、H5、微信小程序、支付宝小程序、百度小程序等多个终端&#xff0c;遵循Apache2开源协议发布&#xff0c;基于ThinkPHP5.1框架研发。该系统具有求实进取、创新专注、自主研发、国内领先企业级B2C电商…

基于stm32的电压采样与质量检测系统

150基于stm32的电压采样与质量检测系统[proteus仿真] 电压检测系统这个题目算是课程设计和毕业设计中常见的题目了&#xff0c;本期是一个基于stm32的电压采样与质量检测系统 需要的源文件和程序的小伙伴可以关注公众号【阿目分享嵌入式】&#xff0c;赞赏任意文章 2&#xf…

RK356X RK3588 单独编译kernel 与烧录

RK356X RK3588 单独编译kernel 与烧录 可以快速提高我们开发与调试速度 网上可查到的方法如下&#xff1a; RK3568 Android12&#xff1a; 1.添加kernel-4.19/makekernel.sh #!/bin/sh make -j24 ARCHarm64 CC../prebuilts/clang/host/linux-x86/clang-r416183b/bin/clang …

信呼OA普通用户权限getshell方法

0x01 前言 信呼OA是一款开源的OA系统&#xff0c;面向社会免费提供学习研究使用&#xff0c;采用PHP语言编写&#xff0c;搭建简单方便&#xff0c;在中小企业中具有较大的客户使用量。从公开的资产治理平台中匹配到目前互联中有超过1W的客户使用案例。 信呼OA目前最新的版本是…

裸机编程的几种模式、架构、缺陷

目录 裸机编程模式/架构 1&#xff1a;初始化代码的编写 裸机编程模式/架构 2&#xff1a;轮询模式 裸机编程模式/架构 3&#xff1a;轮询加中断执行模式 裸机编程模式/架构 4&#xff1a;中断定时器主循环的前后台架构 裸机编程模式/架构 5&#xff1a;前后台 状态机架构…

c1-周考2

c1-第二周 9月-技能1.一个岛上有两种神奇动物&#xff0c;其中神奇鸟类2个头3只脚&#xff0c;神奇兽类3个头8只脚。游客在浓雾中看到一群动物&#xff0c;共看到35个头和110只脚&#xff0c;求可能的鸟类和兽类的只数2.构建一个长度为5的数组&#xff0c;并且实现下列要求3.构…

外包干了10天,技术退步明显。。。。。

先说一下自己的情况&#xff0c;本科生&#xff0c;2019年我通过校招踏入了南京一家软件公司&#xff0c;开始了我的职业生涯。那时的我&#xff0c;满怀热血和憧憬&#xff0c;期待着在这个行业中闯出一片天地。然而&#xff0c;随着时间的推移&#xff0c;我发现自己逐渐陷入…

Linux系统——SElinux

目录 前言 一、SELinux 的作用及权限管理机制 1.SELinux 的作用 1.1DAC 1.2MAC 1.3DAC 和 MAC 的对比 2.SELinux 基本概念 2.1主体&#xff08;Subject&#xff09; 2.2对象&#xff08;Object&#xff09; 2.3政策和规则&#xff08;Policy & Rule&#xff09;…