大学课程项目中的记忆深刻 Bug —— 一次意外的数组越界
开头
在编程的世界里,每一行代码都像是一个小小的宇宙,承载着开发者的心血与智慧。然而,即便是最精心编写的代码,也难免会遇到那些突如其来的 bug,它们就像是潜伏在暗处的小怪兽,时不时跳出来捣乱。
在我的大学生涯中,有一次特别难忘的经历,让我深刻体会到了编程的挑战与乐趣。那是在一个数据结构与算法课程的期末项目中,我们遇到了一个令人头疼的数组越界错误。
今天,我想分享这段经历,希望能给正在编程道路上前行的你带来一些启示和思考。
引言
在大学计算机科学课程中,编程作业和项目是检验学生理解和应用知识的重要环节。然而,编程过程中难免会遇到各种 bug,这些 bug 有时会让人抓狂,但也成为了宝贵的学习经验。本文将分享我们在一个课程项目中遇到的一次令人难忘的数组越界错误,以及我们是如何解决这个问题的。
背景
我们正在完成一门数据结构与算法课程的期末项目,项目要求实现一个简单的图书管理系统。系统需要支持图书的添加、删除、查询等功能。为了提高效率,我们决定使用数组来存储图书信息。
初始代码
我们最初的代码如下所示:
public class BookManager {private Book[] books;private int count;public BookManager(int initialCapacity) {books = new Book[initialCapacity];count = 0;}public void addBook(Book book) {if (count == books.length) {expandArray();}books[count] = book;count++;}private void expandArray() {int newCapacity = books.length * 2;Book[] newBooks = new Book[newCapacity];for (int i = 0; i < count; i++) {newBooks[i] = books[i];}books = newBooks;}public Book getBook(int index) {return books[index];} }
发现问题
在项目开发过程中,我们进行了多次功能测试,大部分功能都能正常运行。然而,在一次全面的测试中,我们发现当添加大量图书后,系统偶尔会出现崩溃的情况。具体表现为程序突然终止,没有任何错误提示。
初步排查
我们首先怀疑是内存问题,因为数组存储了大量的数据。我们使用了一些调试工具(如 IntelliJ IDEA 的调试器)来查看程序运行时的内存状态,但没有发现明显的内存泄漏或溢出问题。
接着,我们仔细检查了代码逻辑,特别是添加图书的部分。我们发现,添加图书时会调用一个函数来扩展数组的大小,以容纳更多的图书。我们怀疑问题可能出在数组扩展的逻辑上。
定位问题
为了进一步排查问题,我们在关键代码段添加了日志输出,记录每次添加图书时的数组大小和索引值。通过日志,我们发现了一个重要的线索:在某些情况下,数组的索引值超过了数组的实际大小,导致了数组越界错误。
具体来说,我们在扩展数组时忘记更新数组的最大容量,导致后续的添加操作试图访问超出数组范围的内存地址。
问题重现
为了更好地理解问题,我们编写了一个简单的测试用例来重现问题:
public class Main {public static void main(String[] args) {BookManager manager = new BookManager(2);manager.addBook(new Book("Book 1"));manager.addBook(new Book("Book 2"));manager.addBook(new Book("Book 3")); // 这里会导致数组越界manager.addBook(new Book("Book 4"));for (int i = 0; i < 4; i++) {System.out.println(manager.getBook(i).getTitle());}} }
运行上述代码,我们发现程序在添加第三本书时抛出了 ArrayIndexOutOfBoundsException 异常。
解决问题
找到问题的根源后,我们立即对代码进行了修复。具体步骤如下:
- 更新数组容量:在扩展数组时,不仅要分配更大的内存空间,还要更新数组的最大容量变量。
- 增加边界检查:在添加图书时,增加边界检查,确保索引值不超过数组的最大容量。
- 单元测试:编写详细的单元测试,模拟各种边界情况,确保代码的健壮性。
以下是修复后的代码:
public class BookManager {private Book[] books;private int capacity;private int count;public BookManager(int initialCapacity) {books = new Book[initialCapacity];capacity = initialCapacity;count = 0;}public void addBook(Book book) {if (count == capacity) {expandArray();}books[count] = book;count++;}private void expandArray() {int newCapacity = capacity * 2;Book[] newBooks = new Book[newCapacity];for (int i = 0; i < count; i++) {newBooks[i] = books[i];}books = newBooks;capacity = newCapacity; // 更新数组的最大容量}public Book getBook(int index) {if (index < 0 || index >= count) {throw new IndexOutOfBoundsException("Index out of bounds");}return books[index];} }
测试验证
为了确保问题已经解决,我们重新运行了之前的测试用例:
public class Main {public static void main(String[] args) {BookManager manager = new BookManager(2);manager.addBook(new Book("Book 1"));manager.addBook(new Book("Book 2"));manager.addBook(new Book("Book 3")); // 不再抛出异常manager.addBook(new Book("Book 4"));for (int i = 0; i < 4; i++) {System.out.println(manager.getBook(i).getTitle());}} }
运行结果如下:
Book 1 Book 2 Book 3 Book 4
反思与总结
这次数组越界错误让我们深刻认识到:
- 边界检查的重要性:在处理数组和其他数据结构时,一定要注意边界条件,防止越界错误。
- 代码复审:定期进行代码复审,可以帮助我们及早发现潜在的问题。
- 单元测试:编写详细的单元测试,确保代码的正确性和健壮性。
- 调试工具的使用:熟练掌握调试工具,可以在问题发生时快速定位和解决问题。
每一个 bug 都是一次成长的机会。通过这次经历,我不仅提升了编程技能,也更加深刻地认识到了代码质量和测试的重要性。
希望我的分享能够帮助其他大学生避免类似的错误,共同提升编程水平。
结尾
每一次挫折都是成长的契机,每一个 bug 都是通往成功的阶梯。通过这次难忘的数组越界错误,我们不仅学会了如何更细致地处理边界条件,还深刻认识到了代码复审和单元测试的重要性。编程之路虽然充满挑战,但正是这些挑战让我们变得更加坚强和智慧。希望我们的故事能够激励每一位编程爱好者,勇敢面对困难,不断追求卓越。正如编程大师所说:“代码不仅仅是工具,更是表达思想的艺术。”愿你在编程的旅途中,不仅能写出高效的代码,更能创作出属于自己的精彩篇章。
相关文章:
大学课程项目中的记忆深刻 Bug —— 一次意外的数组越界
开头 在编程的世界里,每一行代码都像是一个小小的宇宙,承载着开发者的心血与智慧。然而,即便是最精心编写的代码,也难免会遇到那些突如其来的 bug,它们就像是潜伏在暗处的小怪兽,时不时跳出来捣乱。 在我…...
html数据类型
数据类型是字面含义,表示各种数据的类型。在任何语言中都存在数据类型,因为数据是各式各样。 1.数值类型 number let a 1; let num 1.1; // 整数小数都是数字值 // 数字肯定有个范围 正无穷大和负无穷大 // Infinity 正无穷大 // -Infinity 负…...
Kotlin Multiplatform 未来将采用基于 JetBrains Fleet 定制的独立 IDE
近期 Jetbrains 可以说是动作不断,我们刚介绍了 IntelliJ IDEA 2024.3 K2 模式发布了稳定版支持 ,而在官方最近刚调整过的 Kotlin Multiplatform Roadmap 优先关键事项里,可以看到其中就包含了「独立的 Kotlin Multiplatform IDE,…...
Redis中常见的数据类型及其应用场景
五种常见数据类型 Redis中的数据类型指的是 value存储的数据类型,key都是以String类型存储的,value根据场景需要,可以以String、List等类型进行存储。 各数据类型介绍: Redis数据类型对应的底层数据结构 String 类型的应用场景 常…...
代理IP在后端开发中的应用与后端工程师的角色
目录 引言 代理IP的基本概念和工作原理 代理IP在后端开发中的应用 网络爬虫与数据采集 负载均衡与性能优化 安全防护与隐私保护 后端工程师在使用代理IP时面临的挑战 结论 引言 在数字化时代,网络技术的飞速发展极大地推动了各行各业的发展。其中ÿ…...
工作流和流程引擎有什么区别?
在企业的数字化转型中,如何提升效率、优化业务流程是每个管理者都在思考的问题。而在这个过程中,工作流(Workflow)和流程引擎(Process Engine)这两个术语频频出现,成为企业流程自动化和智能化的…...
【SpringBoot】27 拦截器
Gitee仓库 https://gitee.com/Lin_DH/system 介绍 拦截器:拦截器是 Spring 框架提供的核心功能之一,主要用来拦截用户请求,在指定方法前后,根据业务需要执行预先设定的代码。 拦截器允许开发人员提前预定义一些逻辑,…...
AI对开发者的影响,以及传统软件开发 与 AI参与的软件开发区别
AI 大模型,尤其是像 GPT-4、BERT 这样的语言模型,正以深远的影响改变着软件开发流程。传统的软件开发流程通常依赖开发人员进行代码编写、测试、调试等工作,但随着 AI 技术的进步,AI 可以承担越来越多的任务,自动化和优…...
HBase Java基础操作
Apache HBase 是一个开源的、分布式的、可扩展的大数据存储系统,它基于 Google 的 Bigtable 模型。使用 Java 操作 HBase 通常需要借助 HBase 提供的 Java API。以下是一个基本的示例,展示了如何在 Java 中连接到 HBase 并执行一些基本的操作,…...
关于一次开源java spring快速开发平台项目RuoYi部署的记录
关于一次开源java spring快速开发平台项目RuoYi部署的记录 本次因为需要一些练习环境,想要快速搭建一个javaweb 项目作为练习环境,经过查询和实验找到一个文档详细,搭建简单,架构也相对比较新的开源项目RuoYi。 项目介绍…...
【AI编程实战】安装Cursor并3分钟实现Chrome插件(保姆级)
Cursor介绍 https://www.cursor.com/ 一句话介绍:AI代码编辑器,当前最火的AI编程器 软件下载与安装 下载 打开Cursor官网下载,会根据操作系统的差别进行选择 https://www.cursor.com/ 这里下载的内容很小,是个安装器&#x…...
【Chatgpt】如何通过分层Prompt生成更加细致的图文内容
如何通过分层Prompt生成更加细致的图文内容 利用ChatGPT和类似的生成式AI模型,通过分层Prompt设计可以生成更具层次感和细节的图文内容。分层Prompt的核心在于将需求分解成多层次的指令,从宏观到微观逐步细化,最终形成高质量的内容输出。 一…...
中间件--laravel进阶篇
laravel版本11.31,这中间件只有3种,分别是全局中间件,路由中间件,控制器中间件。相比thinkphp8,少了一个应用中间件。 一、创建中间件 laravel创建中间件可以使用命令的方式创建,非常方便。比如php artisan make:middleware EnsureTokenIsValid。EnsureTokenIsValid是中间…...
【vue】vue中.sync修饰符如何使用--详细代码对比
.sync修饰符作用 .sync修饰符是一个语法糖,可以简化父子组件通信操作,当子组件想改变父组件数值时,父组件只需要使用.sync修饰符,子组件使用props接收属性,再使用this.$emit(update:属性, 值);就可以实现子组件更新父…...
repmgr安装及常用运维指令
简介 repmgr 由 EDB 与其他个人和组织的贡献一起开发,安装部署相对较为简单 安装 repmgr官网上传对应的安装到服务器上 安装前/etc/hosts IP映射、始终同步、免密通信本文忽略 repmgr的安装相对较为简单,目前repmgr-5仅仅支持到postgresql-15 postgresql必要参数…...
RedHat系统配置静态IP
1、执行nmtui命令进入字符配置界面如下图所示 2、选择编辑连接进入 3、选择编辑进入后,将IPv4设置为手动模式后,选择显示后进行ip地址、网关、DNS的配置,配置完成后选择确定退出编辑 4、进入主界面后选择启用连接进入后,选择启用&…...
nvm和nrm的安装与使用
NVM相关请跳转: Node版本管理器nvm的安装与使用 nrm 的安装与使用 nrm(NPM Registry Manager)是一个用于管理和切换 NPM 源的工具。它允许你在多个 NPM 源之间快速切换,以提高包管理的速度和效率。以下是 nrm 的安装和使用方法&…...
10大核心应用场景,解锁AI检测系统的智能安全之道
随着工业化和自动化的快速推进,高风险作业场景的安全管理需求日益增加。思通数科AI检测系统以深度学习、计算机视觉和多模态数据融合技术为基础,通过智能化监控和实时反馈,为企业提供全面的作业安全和流程管理解决方案。本文将详细解读该系统…...
香豆烤馍:传统美食中的烟火记忆
食家巷香豆烤馍,承载着甘肃人的乡愁与记忆。它那朴实的外表下,蕴含着丰富的口感和深厚的文化底蕴。烤馍的制作过程充满了烟火气息。选用优质的面粉,经过发酵、揉制等多道工序,再放入传统的烤炉中慢慢烘烤。这个过程需要经验丰富的…...
金融量化交易模型的探索与发展
随着全球金融市场的不断变化与技术进步,量化交易逐渐成为机构和个人投资者的重要选择。作为数据驱动的交易方式,量化交易通过科学建模和技术手段,有效提升了交易效率与决策精准度。本文将探讨金融量化交易模型的创新探索与未来发展方向。 量化…...
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする 1、前言(1)情况说明(2)工程师的信仰2、知识点(1) にする1,接续:名词+にする2,接续:疑问词+にする3,(A)は(B)にする。(2)復習:(1)复习句子(2)ために & ように(3)そう(4)にする3、…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院挂号小程序
一、开发准备 环境搭建: 安装DevEco Studio 3.0或更高版本配置HarmonyOS SDK申请开发者账号 项目创建: File > New > Create Project > Application (选择"Empty Ability") 二、核心功能实现 1. 医院科室展示 /…...
STM32标准库-DMA直接存储器存取
文章目录 一、DMA1.1简介1.2存储器映像1.3DMA框图1.4DMA基本结构1.5DMA请求1.6数据宽度与对齐1.7数据转运DMA1.8ADC扫描模式DMA 二、数据转运DMA2.1接线图2.2代码2.3相关API 一、DMA 1.1简介 DMA(Direct Memory Access)直接存储器存取 DMA可以提供外设…...
基于数字孪生的水厂可视化平台建设:架构与实践
分享大纲: 1、数字孪生水厂可视化平台建设背景 2、数字孪生水厂可视化平台建设架构 3、数字孪生水厂可视化平台建设成效 近几年,数字孪生水厂的建设开展的如火如荼。作为提升水厂管理效率、优化资源的调度手段,基于数字孪生的水厂可视化平台的…...
从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(九)
设备树移植 和uboot设备树修改的内容同步到kernel将设备树stm32mp157d-stm32mp157daa1-mx.dts复制到内核源码目录下 源码修改及编译 修改arch/arm/boot/dts/st/Makefile,新增设备树编译 stm32mp157f-ev1-m4-examples.dtb \stm32mp157d-stm32mp157daa1-mx.dtb修改…...
全志A40i android7.1 调试信息打印串口由uart0改为uart3
一,概述 1. 目的 将调试信息打印串口由uart0改为uart3。 2. 版本信息 Uboot版本:2014.07; Kernel版本:Linux-3.10; 二,Uboot 1. sys_config.fex改动 使能uart3(TX:PH00 RX:PH01),并让boo…...
tree 树组件大数据卡顿问题优化
问题背景 项目中有用到树组件用来做文件目录,但是由于这个树组件的节点越来越多,导致页面在滚动这个树组件的时候浏览器就很容易卡死。这种问题基本上都是因为dom节点太多,导致的浏览器卡顿,这里很明显就需要用到虚拟列表的技术&…...
深入浅出深度学习基础:从感知机到全连接神经网络的核心原理与应用
文章目录 前言一、感知机 (Perceptron)1.1 基础介绍1.1.1 感知机是什么?1.1.2 感知机的工作原理 1.2 感知机的简单应用:基本逻辑门1.2.1 逻辑与 (Logic AND)1.2.2 逻辑或 (Logic OR)1.2.3 逻辑与非 (Logic NAND) 1.3 感知机的实现1.3.1 简单实现 (基于阈…...
WPF八大法则:告别模态窗口卡顿
⚙️ 核心问题:阻塞式模态窗口的缺陷 原始代码中ShowDialog()会阻塞UI线程,导致后续逻辑无法执行: var result modalWindow.ShowDialog(); // 线程阻塞 ProcessResult(result); // 必须等待窗口关闭根本问题:…...
【LeetCode】算法详解#6 ---除自身以外数组的乘积
1.题目介绍 给定一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O…...
