【源码解析】Java NIO 包中的 ByteBuffer

文章目录

  • 1. 前言
  • 2. ByteBuffer 概述
  • 3. 属性
  • 4. 构造器
  • 5. 方法
    • 5.1 allocate 分配 Buffer
    • 5.2 wrap 映射数组
    • 5.3 slice 获取子 ByteBuffer
    • 5.4 duplicate 复刻 ByteBuffer
    • 5.5 asReadOnlyBuffer 创建只读的 ByteBuffer
    • 5.6 get 方法获取字节
    • 5.7 put 方法往 ByteBuffer 里面加入字节
    • 5.8 array 和 arrayOffset
    • 5.9 compact 切换写模式
    • 5.10 其他方法
  • 6. 大端序和小端序
  • 7. 小结


1. 前言

上一篇文章我们介绍了最底层的 Buffer,那么这篇文章就要介绍下 Buffer 的
比较核心的一个实现类 ByteBuffer,上一篇文章的地址如下:

  • 【源码解析】Java NIO 包中的 Buffer

2. ByteBuffer 概述

在这里插入图片描述
上面就是 Buffer 的继承结构,当然 Buffer 的子类肯定不会只有这么点,比如下面的图:
在这里插入图片描述
只不过上面图中就给了几个基本 Buffer 的实现类,可以看到几个重要的实现类 MappedByteBufferHeapByteBufferDirectByteBuffer 都是 ByteBuffer 的子类,这几个实现类也是我们要介绍的重点,只不过这篇文章我们先介绍 ByteBuffer。

ByteBuffer 是字节缓存,也是最常见的 Buffer,无论是缓存映射还是文件映射都有 ByteBuffer 的身影。上一篇文章中我们也说过,没有 ByteBuffer 之前,对于字节流一个一个处理都是比较繁琐的,有了 ByteBuffer 之后就可以一次处理一大批的数据,性能更加高效。

下面我们就来看下 ByteBuffer 这个类的庐山真面目。


3. 属性

final byte[] hb;

hb 是 ByteBuffer 中存储字节数据的数组,专门用于 HeapByteBuffer 中数据的存放,如果是直接内存 Buffer,那这个数组就不会存储数据。


final int offset;

offset 是 ByteBuffer 中第一个元素的起始位置,也可以说是存储元素的数组的第一个起始下标,一般都是从 0 开始。


boolean isReadOnly;

这个属性就是表示是否是只读的,如果一个 Buffer 是只读的,那么就不能修改,只能读取。

ByteBuffer 的属性比较简单,是因为指针都封装到底层 Buffer 了,所以到 ByteBuffer 这一层属性就没那么多了。


4. 构造器

ByteBuffer(int mark, int pos, int lim, int cap,byte[] hb, int offset){super(mark, pos, lim, cap);this.hb = hb;this.offset = offset;}ByteBuffer(int mark, int pos, int lim, int cap) {this(mark, pos, lim, cap, null, 0);}

无论是哪个构造器,都绕不过 markposlimitcap 这几个指标,就是 Buffer 里面的这四个参数。

那么这两个构造器不同的是参数上第一个构造器需要设置数组 hboffset。这其实就很明显了,调用第一个方法的其实就是创建 HeapByteBuffer,第二个方法则是 DirectByteBufferMappedByteBuffer 会调用。


5. 方法

因为 ByteBuffer 是抽象类,所以里面的所有方法几乎都留给了子类去实现,所以我这里就简单介绍下这个 ByteBuffer 里面的一些抽象方法以及这些方法的具体用途。

5.1 allocate 分配 Buffer

这个方法用于分配 HeapByteBuffer 和 DirectByteBuffer,其实就是直接 new 出来。

// 分配 HeapByteBuffer
public static ByteBuffer allocate(int capacity) {if (capacity < 0)throw new IllegalArgumentException();return new HeapByteBuffer(capacity, capacity);
}// 分配 DirectByteBuffer
public static ByteBuffer allocateDirect(int capacity) {return new DirectByteBuffer(capacity);
}

5.2 wrap 映射数组

这个方法用于将传入数组中的一部分数据或者是数组的全部数据映射成一个 HeapByteBuffer(转换),为什么不是 DirectByteBuffer 和 MappedByteBuffer 呢?当然是另外两个是直接内存了不受 JVM 管理了,所以传入的数组肯定不能映射成堆外的 Buffer

public static ByteBuffer wrap(byte[] array,int offset, int length)
{try {return new HeapByteBuffer(array, offset, length);} catch (IllegalArgumentException x) {throw new IndexOutOfBoundsException();}
}public static ByteBuffer wrap(byte[] array) {return wrap(array, 0, array.length);
}

这里其实就是简单的创建一个 HeapByteBuffer。


5.3 slice 获取子 ByteBuffer

public abstract ByteBuffer slice();

slice() 方法用于创建一个新的 ByteBuffer 对象,其内容是当前 ByteBuffer 对象内容的一个共享子序列,啥意思呢?新创建的 ByteBuffer 对象和原始 ByteBuffer 对象之间的内容是共享的,但它们的位置(position)、限制(limit)和标记(mark)值是独立的。换句话说这两个 ByteBuffer 对象的底层数组是一样的,只是 Buffer 的几个标记不一样。

既然新建的 ByteBuffer 和原来的 ByteBuffer 共享一个内存空间,那也就意味新的 ByteBuffer 由下面的性质。

  • 共享内存

    1. 对当前 ByteBuffer 对象内容的任何修改将反映在新创建的 ByteBuffer 对象中,反之亦然
  • 状态独立

    1. 新创建的 ByteBuffer 对象的 position 将被设置为 0
    2. 新创建的 ByteBuffer 对象的 capacity 和 limit将等于当前 ByteBuffer 对象剩余的字节数
    3. 新创建的 ByteBuffer 对象的 mark 会被重置为 -1
  • 属性继承

    1. 当前 ByteBuffer 是什么类型(HeapByteBuffer 和 DirectByteBuffer),创建出来的 ByteBuffer 就是什么类型
    2. 如果当前 ByteBuffer 对象是只读的(read-only),则新创建的 ByteBuffer 对象也将是只读的

在这里插入图片描述
可以看到上面图中,slice 获取的 ByteBuffer 视图中 position 重新指向了 0 的位置,而 limit = 6,那么问题来了,既然 slice 之后获取的 ByteBuffer 重新设置了这几个指标,那么如何进行访问呢?

不知道大家还记得 ByteBuffer 中的 offset 吗?这个 offset 上面我们说过了就是 position 的偏移量,所以 slice 创建出来的子 ByteBuffer 可以通过 offset + position 来算出,比如在上面例子中 offset = 4
在这里插入图片描述
那下面我们还可以看个例子:

public static void byteBufferTest(){ByteBuffer byteBuffer = ByteBuffer.allocate(10);byteBuffer.put((byte) 1);byteBuffer.put((byte) 2);byteBuffer.put((byte) 3);byteBuffer.put((byte) 4);ByteBuffer slice = byteBuffer.slice();System.out.println(slice.arrayOffset()); // 4
}

在上面这个例子中,创建出来的 slice.offset = 4,那么下面我们接着往里面写入数据。

    slice.put((byte) 5);slice.put((byte) 6);slice.put((byte) 7);slice.put((byte) 8);System.out.println(Arrays.toString(byteBuffer.array()));

最后来看下输出的结果:
在这里插入图片描述
可以看到最后的输出结果就表明了对创建出来的 slice 添加数据也会影响到原来的 ByteBuffer,同时 slice 是在原来 ByteBuffer 的 position 后面继续操作,也能看到上面输出的 offset 就是调用 slice 方法时候的 position 值。


5.4 duplicate 复刻 ByteBuffer

public abstract ByteBuffer duplicate();

如果说上面的 slice 是从原来的 ByteBuffer 截取一段(共享地址)下来,这个方法就是完整复刻整个 ByteBuffer。也就是说它们的 offset,mark,position,limit,capacity 变量的值全部是一样的。
在这里插入图片描述
下面来看个例子,其实主要是看里面的 offset 是多少,可以看到 duplicate 就是完全复制一个 ByteBuffer,在里面可以

public static void byteBufferTest(){ByteBuffer byteBuffer = ByteBuffer.allocate(10);byteBuffer.put((byte) 1);byteBuffer.put((byte) 2);byteBuffer.put((byte) 3);byteBuffer.put((byte) 4);ByteBuffer duplicate = byteBuffer.duplicate();System.out.println(duplicate.arrayOffset()); // 0duplicate.put((byte) 5);duplicate.put((byte) 6);duplicate.put((byte) 7);duplicate.put((byte) 8);System.out.println(Arrays.toString(byteBuffer.array()));
}

在这里插入图片描述


5.5 asReadOnlyBuffer 创建只读的 ByteBuffer

public abstract ByteBuffer asReadOnlyBuffer();

这个方法就是创建一个只读的 ByteBuffer,而如果写入数据的话会抛出 ReadOnlyBufferException 异常。


5.6 get 方法获取字节

get 方法就是 ByteBuffer 里面获取字节的方法,可以通过这个方法来获取 ByteBuffer 里面 position 位置的值,当然这个方法也有很多重载方法,其中我们也可以传入一个 byte[] 数组,然后把 ByteBuffer 里面的值传到数组里面。

public abstract byte get();// 获取指定下标下面的值
public abstract byte get(int index)// 从 offset 开始获取 length 个字节放到数组 dst 中
public ByteBuffer get(byte[] dst, int offset, int length) {// 检查指定 index 的边界,确保不能越界checkBounds(offset, length, dst.length);// 检查 ByteBuffer 是否有足够的转移字节if (length > remaining())throw new BufferUnderflowException();int end = offset + length;// 从 offset 开始获取 length 个字节转移到数组 dst 中for (int i = offset; i < end; i++)dst[i] = get();return this;
}// 将 ByteBuffer 全部放到 dst 中
public ByteBuffer get(byte[] dst) {return get(dst, 0, dst.length);
}

5.7 put 方法往 ByteBuffer 里面加入字节

// 往 position 位置设置字节 b,同时设置 position = position + 1
public abstract ByteBuffer put(byte b);// 往 index 设置字节 b,设置之后 position 不会改变
public abstract ByteBuffer put(int index, byte b);// 把 src 的所有字节放到当前的 ByteBuffer 里面
public ByteBuffer put(ByteBuffer src) {if (src == this)throw new IllegalArgumentException();if (isReadOnly())throw new ReadOnlyBufferException();int n = src.remaining();if (n > remaining())throw new BufferOverflowException();for (int i = 0; i < n; i++)put(src.get());return this;
}// 从 offset 开始,将 length 个字节设置到当前 ByteBuffer 中
public ByteBuffer put(byte[] src, int offset, int length) {// 检查指定 index 的边界,确保不能越界checkBounds(offset, length, src.length);// 检查 ByteBuffer 是否能够容纳得下if (length > remaining())throw new BufferOverflowException();int end = offset + length;// 从字节数组得 offset 处,转移 length 个字节到 ByteBuffer 中for (int i = offset; i < end; i++)this.put(src[i]);return this;
}// 传入一个字节数组,设置到当前 ByteBuffer 中
public final ByteBuffer put(byte[] src) {return put(src, 0, src.length);
}

上面几个方法都是 put 方法,就是往当前 ByteBuffer 里面设置数据的,不过要注意下,当调用 put(int index, byte b) 来设置字节,position 不会被修改。


5.8 array 和 arrayOffset

public final byte[] array() {if (hb == null)throw new UnsupportedOperationException();if (isReadOnly)throw new ReadOnlyBufferException();return hb;
}public final int arrayOffset() {if (hb == null)throw new UnsupportedOperationException();if (isReadOnly)throw new ReadOnlyBufferException();return offset;
}

这两个方法就是获取 Buffer 底层的数组和数组的第一个元素的偏移量,这个偏移量其实就是 Buffer 第一个元素的下标。

但是如果 Buffer 是只读的,那么就没办法获取,会抛出异常 ReadOnlyBufferException


5.9 compact 切换写模式

ByteBuffer 切换写模式之前已经介绍过一个方法了,就是 clear(),但是这里面有个问题,就是 clear 这个方法是直接把 position 设置为 0,也就是从头开始写入,如果在调用 clear 之前已经把数据读完了那当然没问题,但是如果还遗留一些数据,这样新写入的数据会把原来剩下那些没读取完的覆盖掉,比如看下面的例子:

public static void clearTest(){ByteBuffer byteBuffer = ByteBuffer.allocate(10);byteBuffer.put((byte) 1);byteBuffer.put((byte) 2);byteBuffer.put((byte) 3);byteBuffer.put((byte) 4);// 切换读模式byteBuffer.flip();System.out.println(byteBuffer.get()); // 1System.out.println(byteBuffer.get()); // 2System.out.println(byteBuffer.get()); // 3// 切换写模式byteBuffer.clear();byteBuffer.put((byte) 1);byteBuffer.put((byte) 2);byteBuffer.put((byte) 3);byteBuffer.put((byte) 5);System.out.println(Arrays.toString(byteBuffer.array()));// [1, 2, 3, 5, 0, 0, 0, 0, 0, 0]
}

在上面例子中,首先我们往 ByteBuffer 里面设置了 1,2,3,4,然后切换到读模式,接着读取前三个数据,也就是 1,2,3,接着我们调用 clear() 方法切换到写模式,然后往里面写入 1,2,3,5,这时候我们就发现原来里面的 4 被 5 覆盖了。但是如果换成 compact 方法就不一样了。

public static void compactTest(){ByteBuffer byteBuffer = ByteBuffer.allocate(10);byteBuffer.put((byte) 1);byteBuffer.put((byte) 2);byteBuffer.put((byte) 3);byteBuffer.put((byte) 4);byteBuffer.flip();System.out.println(byteBuffer.get()); // 1System.out.println(byteBuffer.get()); // 2System.out.println(byteBuffer.get()); // 3byteBuffer.compact();byteBuffer.put((byte) 1);byteBuffer.put((byte) 2);byteBuffer.put((byte) 3);byteBuffer.put((byte) 5);System.out.println(Arrays.toString(byteBuffer.array()));// [1, 2, 3, 5, 0, 0, 0, 0, 0, 0]
}

调用 duplicate 之后,会把没有读取到的 4 放到 ByteBuffer 的前面,然后继续往后写入,所以 compact 这个方法切换写模式并不会覆盖没有读取完的数据。
在这里插入图片描述

当切换写模式之后,会先把 [position, limit) 挪到下标 0 开始的位置,然后设置 position = limit - position,就是设置 position = 4 - 3 = 1,后面会从 1 开始继续写入。


5.10 其他方法

上面就是比较常用的方法,下面剩下那些就是不常用的,可以看下下面的截图。
在这里插入图片描述


6. 大端序和小端序

ByteBuffer 里面还有一个重要的概念就是大端序和小端序,下面就来介绍下这个概念,我们先来随便看一个数字的二进制,比如数字 1234,二进制为:00000000 00000000 00000100 11010010

数字存储到计算机中有两种方式,一种是内存的低地址向高地址存储,一种是高地址向低地址存储,也就是下面的两种方式。

  1. 大端序(Big-Endian):数据的高位字节存储在内存的低地址,低位字节存储在内存的高地址。
  2. 小端序(Little-Endian):数据的低位字节存储在内存的低地址,高位字节存储在内存的高地址。

比如下面图中的存储:
在这里插入图片描述

在大端序中,数字 1234 的存储从 0 开始,高位存储到 0 的位置,依次类推,小端序则反过来。

在 JVM 中,堆的地址从下往上是从低到高的,对于大端序,读取数据的时候就是从高位开始读取,对于小端序则是从低位开始读取。

在 ByteBuffer 中则是通过一个变量 bigEndian 来表示这个 ByteBuffer 存储数据是大端序还是小端序。

boolean bigEndian = true;

同时也给了一个方法返回时大端序还是小端序。

public final ByteOrder order() {return bigEndian ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN;
}

当然除了上面两个方法,ByteBuffer 也提供了方法去设置大端序还是小端序。

public final ByteBuffer order(ByteOrder bo) {bigEndian = (bo == ByteOrder.BIG_ENDIAN);nativeByteOrder =(bigEndian == (Bits.byteOrder() == ByteOrder.BIG_ENDIAN));return this;
}

当然这里 ByteBuffer 只是指定大端序还是小端序,对于不同的字节序,从里面读取数据的时候的操作就不同,因为这里只是 ByteBuffer,如果是 IntBuffer 这种一次性读取四个字节的,就需要根据不同的字节序来判断要如何组成一个 int 了,我举个例子,还是下面这张图。
在这里插入图片描述

  • 如果是大端序,这时候从下标 0 - 4 存储的就是 int 高到底的字节,那么组合的方法就是:(arr[0] << 24) | (arr[1] << 16) || (arr[2] << 8) || arr[3]
  • 如果是小端序,这时候从下标 0 - 4 存储的就是 int 低到高的字节,那么组合的方法就是:(arr[0]) | (arr[1] << 8) || (arr[2] << 16) || (arr[3] << 24)

那么这里就简单介绍下这两个概念,因为具体的实现是在子类中去完成的,这篇文章就先不介绍了。


7. 小结

这篇文章就先介绍到这了,这个 ByteBuffer 是比较重要的一个类,为什么要介绍这个类呢?因为后面我将会逐步开始学习并写一些 RocketMQ 的文章,但我们都知道像这种 RocketMQ 的中间件的内存存储都离不开文件映射,其中就离不开 MappedByteBuffer,所以要慢慢从最底层的 Buffer 开始学习,这样才知道当往文件里面写入数据的时候,到底是怎么写入的。





如有错误,欢迎指出!!!

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HTML5实现好看的中秋节网页源码

HTML5实现好看的中秋节网页源码 前言一、设计来源1.1 网站首页界面1.2 登录注册界面1.3 节日由来界面1.4 节日习俗界面1.5 节日文化界面1.6 节日美食界面1.7 节日故事界面1.8 节日民谣界面1.9 联系我们界面 二、效果和源码2.1 动态效果2.2 源代码 源码下载结束语 HTML5实现好看…

OA项目登录

导入依赖,下面的依赖是在这次OA登录中用到的 <!--web依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.sprin…

YangQG 面试题汇总

一、交叉链表 问题&#xff1a; 给你两个单链表的头节点 headA 和 headB &#xff0c;请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点&#xff0c;返回 null 。 解题思想&#xff1a; 双指针 备注&#xff1a;不是快慢指针&#xff0c;如果两个长度相…

【面试题】技术场景 4、负责项目时遇到的棘手问题及解决方法

工作经验一年以上程序员必问问题 面试题概述 问题为在负责项目时遇到的棘手问题及解决方法&#xff0c;主要考察开发经验与技术水平&#xff0c;回答不佳会影响面试印象。提供四个回答方向&#xff0c;准备其中一个方向即可。 1、设计模式应用方向 以登录为例&#xff0c;未…

WEB前端-3.2

目录 css 【例】飙升榜 【源码】 css 【例】飙升榜 【源码】 <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <meta name"viewport" content"widthdevice-width, initial-scale1.0"&g…

qml SpringAnimation详解

1. 概述 SpringAnimation 是 Qt Quick 中用于模拟弹簧效果的动画类。它通过模拟物体在弹簧力作用下的反应&#xff0c;产生一种振荡的动画效果&#xff0c;常用于模拟具有自然回弹、弹性和振动的动态行为。这种动画效果在 UI 中广泛应用&#xff0c;特别是在拖动、拉伸、回弹等…

新活动平台建设历程与架构演进

01 前言 历时近两年的重新设计和迭代重构&#xff0c;用户技术中心的新活动平台建设bilibili活动中台终于落地完成&#xff01;并迎来了里程碑时刻 —— 接过新老迭代的历史交接棒&#xff0c;从内到外、从开发到搭建实现全面升级&#xff0c;开启了活动生产工业化新时代&#…

Python学习(三)基础入门(数据类型、变量、条件判断、模式匹配、循环)

目录 一、第一个 Python 程序1.1 命令行模式、Python 交互模式1.2 Python的执行方式1.3 SyntaxError 语法错误1.4 输入和输出 二、Python 基础2.1 Python 语法2.2 数据类型1&#xff09;Number 数字2&#xff09;String 字符串3&#xff09;List 列表4&#xff09;Tuple 元组5&…

微信小程序用的SSL证书有什么要求吗?

微信小程序主要建立在手机端使用&#xff0c;然而手机又涉及到各种系统及版本&#xff0c;所以对SSL证书也有要求&#xff0c;如果要小程序可以安全有效的访问需要满足以下要求&#xff1a; 1、原厂SSL证书&#xff08;原厂封&#xff09;。 2、DV单域名或者DV通配符。 3、兼…

【excel】VBA简介(Visual Basic for Applications)

文章目录 一、基本概念二、语法2.1 数据类型2.11 基本数据类型2.12 常量2.13 数组 2.2 控制语句2.21 条件语句2.22 循环语句2.23 错误处理&#xff1a;On Error2.24 逻辑运算 2.3 其它语句2.31 注释2.32 with语句 2.4 表达式2.41 常见表达式类型2.42 表达式的优先级 2.5 VBA 的…

回溯算法汇总

1.回溯算法 回溯是递归的副产品&#xff0c;只要有递归就会有回溯。 回溯的本质是穷举&#xff0c;穷举所有可能&#xff0c;然后选出我们想要的答案&#xff0c;如果想让回溯法高效一些&#xff0c;可以加一些剪枝的操作&#xff0c;但也改不了回溯法就是穷举的本质。 回溯…

【黑马程序员三国疫情折线图——json+pyechart=数据可视化】

json数据在文末 将海量的数据处理成我们肉眼可以进行分析的形式&#xff0c;数据的可视化&#xff0c;可以分为两个步骤&#xff1a; 数据处理&#xff1a;利用三方网站厘清json层次格式化&#xff0c;再对文件的读取、检查是否符合JSON规范以及规范化、JSON格式的转化&#…

Node.js——fs(文件系统)模块

个人简介 &#x1f440;个人主页&#xff1a; 前端杂货铺 &#x1f64b;‍♂️学习方向&#xff1a; 主攻前端方向&#xff0c;正逐渐往全干发展 &#x1f4c3;个人状态&#xff1a; 研发工程师&#xff0c;现效力于中国工业软件事业 &#x1f680;人生格言&#xff1a; 积跬步…

注册中心如何选型?Eureka、Zookeeper、Nacos怎么选

这是小卷对分布式系统架构学习的第9篇文章&#xff0c;第8篇时只回答了注册中心的工作原理的内容&#xff0c;面试官的第二个问题还没回答&#xff0c;今天再来讲讲各个注册中心的原理&#xff0c;以及区别&#xff0c;最后如何进行选型 上一篇文章&#xff1a;如何设计一个注册…

XS5037C一款应用于专业安防摄像机的图像信号处理芯片,支持MIPI和 DVP 接口,内置高性能ISP处理器,支持3D降噪和数字宽动态

XS5037C是一款应用于专业安防摄像机的图像信号处理芯片&#xff0c;支持MIPI和 DVP 接口&#xff0c;最 大支持 5M sensor接入。内置高性能ISP处理器&#xff0c;支持3D降噪和数字宽动态。标清模拟输出支 持960H&#xff0c;高清模拟输出支持HDCCTV 720P/1080P/4M/5M。高度集成…

【2024年华为OD机试】 (A卷,100分)- 租车骑绿岛(Java JS PythonC/C++)

一、问题描述 题目描述 部门组织绿岛骑行团建活动。租用公共双人自行车&#xff0c;每辆自行车最多坐两人&#xff0c;最大载重 M。 给出部门每个人的体重&#xff0c;请问最多需要租用多少双人自行车。 输入描述 第一行两个数字 m、n&#xff0c;分别代表自行车限重&#…

网络安全-kail linux 网络配置(基础篇)

一、网络配置 1.查看网络IP地址&#xff0c; 我的kail&#xff1a;192.168.15.128 使用ifconfig查看kail网络连接情况&#xff0c;ip地址情况 又复制了一台kail计算机的IP地址。 再看一下windows本机&#xff1a;使用ipconfig进行查看&#xff1a; 再看一下虚拟机上的win7I…

shell基础使用及vim的常用快捷键

一、shell简介 参考博文1 参考博文2——shell语法及应用 参考博文3——vi的使用 在linux中有很多类型的shell&#xff0c;不同的shell具备不同的功能&#xff0c;shell还决定了脚本中函数的语法&#xff0c;Linux中默认的shell是 / b in/ b a s h &#xff0c;流行的shell…

【LeetCode】:删除回文子数组【困难】

class Solution { public:// 思考:能否用滚动数组进行优化int minimumMoves(vector<int>& arr) {// 定义状态dp[i][j]为i-j的最小步数int n arr.size();vector<vector<int>> dp(n, vector<int>(n, 1e9 7));// 可以把这 1 次理解为一种 最小操作单…

极大似然估计笔记

一、原理 我们拿到样本数据需要进行有参估计时&#xff0c;需要假设样本的服从某一分布&#xff0c;因此通过给定某种样本的分布&#xff0c;利用样本来拟合分布参数的过程就是极大似然法。给定一个概率分布 D&#xff0c;假定概率密度函数为 f &#xff0c;以及一个分布参数 θ…