简易内存池

一、题目描述

请实现一个简易内存池,根据请求命令完成内存分配和释放。

内存池支持两种操作命令,REQUEST和RELEASE,其格式为:

REQUEST=请求的内存大小 表示请求分配指定大小内存,如果分配成功,返回分配到的内存首地址;如果内存不足,或指定的大小为0,则输出error。

RELEASE=释放的内存首地址 表示释放掉之前分配的内存,释放成功无需输出,如果释放不存在的首地址则输出error。

注意:

  1. 内存池总大小为100字节。
  2. 内存池地址分配必须是连续内存,并优先从低地址分配。
  3. 内存释放后可被再次分配,已释放的内存在空闲时不能被二次释放。
  4. 不会释放已申请的内存块的中间地址。
  5. 释放操作只是针对首地址所对应的单个内存块进行操作,不会影响其它内存块。

二、输入输出描述

输入描述

  • 第一行:整数 N , 表示操作命令的个数,取值范围:0 < N <= 100。
  • 接下来N行:每行将给出一个操作命令,操作命令和参数之间用 "="分割。

输出描述

  • 请求分配指定大小内存时,如果分配成功,返回分配到的内存首地址;如果内存不足,或指定的大小为0,则输出error
  • 释放掉之前分配的内存时,释放成功无需输出,如果释放不存在的首地址则输出error。

三、示例

|----|-------------------------|
| 输入 | 2 REQUEST=10 REQUEST=20 |
| 输出 | 0 10 |
| 说明 | |

|----|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 输入 | 5 REQUEST=10 REQUEST=20 RELEASE=0 REQUEST=20 REQUEST=10 |
| 输出 | 0 10 30 0 |
| 说明 | 第一条指令,申请地址0~9的10个字节内存,返回首地址0 第二条指令,申请地址10~29的20字节内存,返回首地址10 第三条指令,释放首地址为0的内存申请,0~9地址内存被释放,变为空闲,释放成功,无需输出 第四条指令,申请20字节内存,09地址内存连续空间不足20字节,往后查找到3049地址,返回首地址30 第五条指令,申请10字节,0~9地址内存空间足够,返回首地址0 |

四、解题思路

  1. 核心思想

采用首次适配 策略 + 有序链表管理已占用内存 + 区间交集判断,实现内存的申请与释放管理。核心是用有序链表维护已占用内存块(含边界),从地址 0 开始遍历查找首个可用空闲区间(无交集)完成申请,按起始地址精准匹配完成释放,异常场景输出 error。

  1. 问题本质分析
  • 表层问题:按 REQUEST/RELEASE 指令模拟内存管理,输出申请地址或错误信息;
  • 深层问题:
  1. 内存区间管理:本质是有序区间的插入(申请)、删除(释放)与冲突判断(交集),有序链表是高效的存储结构;

  2. 申请策略选择:首次适配(从 0 开始查找)实现简单,符合常规内存管理的基础逻辑,无需复杂排序;

  3. 边界控制:通过预设[100,101]尾边界,限制最大可用地址,避免内存申请超出范围;

  4. 异常处理:申请时无可用区间、释放时无对应起始地址,均需输出 error,保证逻辑的完整性。

  5. 核心逻辑

  • 存储管理:用LinkedList有序存储已占用内存块(含尾边界[100,101]),保持地址从小到大排序,方便遍历查找;
  • 内存申请:
  1. 首次适配:从地址 0 开始,计算待申请区间的结束地址;
  2. 冲突判断:通过hasIntersection判断待申请区间与已占用区间是否无交集(无交集即可用);
  3. 插入与更新:可用则插入区间并输出起始地址,不可用则更新查找起始地址,无可用区间输出 error;
  • 内存释放:
  1. 精准匹配:遍历有序链表,寻找起始地址完全一致的已占用内存块;
  2. 删除与异常:找到则删除区间,未找到则输出 error;
  • 区间判断:通过比较两个区间的起始、结束地址,快速判断是否相交,为申请提供可用依据。
  1. 步骤拆解

  2. 初始化准备

    • 读取指令数量n和所有指令,拆分为 "操作类型 - 参数" 二维数组;
    • 初始化LinkedList存储已占用内存块,添加尾边界[100,101],限制最大可用地址。
  3. 遍历处理每条指令按指令类型分支处理:

    • 分支 A:内存申请(REQUEST)
    1. 解析申请大小size,初始化查找起始地址start=0,申请失败标记flag=true
    2. 遍历已占用内存块:a. 计算待申请区间的结束地址end=start+size-1,构建待申请区间[start, end];b. 调用hasIntersection判断与当前已占用区间是否无交集:
      • 无交集:插入待申请区间到链表,标记flag=false,输出start,退出遍历;
      • 有交集:更新start为当前已占用区间的结束地址 + 1,继续遍历;
    3. flag=true(未找到可用区间),输出error
    • 分支 B:内存释放(RELEASE)
    1. 解析释放起始地址addr,初始化释放失败标记flag=true
    2. 遍历已占用内存块,寻找起始地址等于addr的区间:
      • 找到:删除该区间,标记flag=false,退出遍历;
      • 未找到:继续遍历;
    3. flag=true(未找到对应区间),输出error
  4. 输出结果 按操作结果实时输出:申请成功输出起始地址,申请 / 释放失败输出error,释放成功无额外输出。

五、代码实现

java 复制代码
import java.util.LinkedList;
import java.util.Scanner;

public class Main {
  // 输入获取
  public static void main(String[] args) {
    Scanner sc = new Scanner(System.in);

    int n = sc.nextInt();

    String[][] cmds = new String[n][2];
    for (int i = 0; i < n; i++) cmds[i] = sc.next().split("=");

    getResult(n, cmds);
  }

  // 算法入口
  public static void getResult(int n, String[][] cmds) {
    // used保存被占用的内存 [起始地址,结束地址],初始时有一个[100,101]作为尾边界限定
    LinkedList<Integer[]> used = new LinkedList<>();
    used.add(new Integer[] {100, 101});

    for (String[] cmd : cmds) {
      String key = cmd[0];
      String val = cmd[1];

      // 申请内存
      if ("REQUEST".equals(key)) {
        // 当指令为REQUEST时,对应值为要申请的内存的大小,即size
        int size = Integer.parseInt(val);

        // 我们默认从start=0位置开始检查可用内存区间
        int start = 0;
        boolean flag = true;

        for (int i = 0; i < used.size(); i++) {
          int end = start + size - 1;
          // 要申请的内存区间
          Integer[] range = {start, end};

          // 检查要申请的内存区间和已占有的内存区间是否交叉
          if (!hasIntersection(used.get(i), range)) {
            // 若不存在交叉,则将申请区间加入used中
            used.add(i, range);
            flag = false;
            // 并打印此时申请区间的起始位置
            System.out.println(start);
            break;
          } else {
            // 若存在交叉,则将变更要申请的内存区间的起始位置
            start = used.get(i)[1] + 1;
          }
        }

        // 一旦申请到内存,那么flag就会被赋值为false,否则就保持true,意味着每申请到内存,则打印error
        if (flag) System.out.println("error");
      }
      // 释放内存
      else {
        //  当指令为RELEASE时,值为要释放内存的起始地址addr
        int addr = Integer.parseInt(val);
        boolean flag = true;

        for (int i = 0; i < used.size(); i++) {
          // 到已占有内存中找起始位置是addr的,找到则将该区间从used中删除,表示解除占用
          if (used.get(i)[0] == addr) {
            used.remove(i);
            flag = false;
            break;
          }
        }

        // 一旦释放成功,则flag就会被置为false,否则就保持True,意味着没有内存释放,则打印error
        if (flag) System.out.println("error");
      }
    }
  }

  // 判断两个区间是否存在交集
  public static boolean hasIntersection(Integer[] range1, Integer[] range2) {
    int s1 = range1[0];
    int e1 = range1[1];

    int s2 = range2[0];
    int e2 = range2[1];

    if (s1 == s2) return true;
    else if (s1 < s2) return e1 >= s2;
    else return e2 >= s1;
  }
}
相关推荐
漫随流水9 小时前
leetcode算法(145.二叉树的后序遍历)
数据结构·算法·leetcode·二叉树
华如锦9 小时前
四:从零搭建一个RAG
java·开发语言·人工智能·python·机器学习·spring cloud·计算机视觉
Tony_yitao9 小时前
22.华为OD机试真题:数组拼接(Java实现,100分通关)
java·算法·华为od·algorithm
JavaGuru_LiuYu9 小时前
Spring Boot 整合 SSE(Server-Sent Events)
java·spring boot·后端·sse
2501_941875289 小时前
在东京复杂分布式系统中构建统一可观测性平台的工程设计实践与演进经验总结
c++·算法·github
爬山算法10 小时前
Hibernate(26)什么是Hibernate的透明持久化?
java·后端·hibernate
sonadorje10 小时前
梯度下降法的迭代步骤
算法·机器学习
彭于晏Yan10 小时前
Springboot实现数据脱敏
java·spring boot·后端
漫随流水10 小时前
leetcode算法(94.二叉树的中序遍历)
数据结构·算法·leetcode·二叉树
luming-0210 小时前
java报错解决:sun.net.utils不存
java·经验分享·bug·.net·intellij-idea