slug
type
status
category
date
summary
tags
password
icon
学习重点 1:文件与“持久化”数据的概念
关键点:
- 程序结束后,变量里的数据会消失;
- 文件里的数据会长期保存(持久化),下次运行程序可以从文件中重新读回来;
- 好处:
- 不用每次手工输入大量数据(比如字典、游戏存档);
- 数据与代码分离 → 更灵活:换一个数据文件,程序不改或者只改很少。
- *小例子:**从
save.txt读一个“存档数字”
学习重点 2:File、Scanner 与 IOException(异常处理)
2.1 File 与 Scanner(File f)
要点:
File表示一个文件的路径(不负责读写本身);
Scanner可以接收一个File,从文件中逐步读数据;
- 需要导入的包:
示例:
2.2 FileNotFoundException 与 throws IOException
要点:
- 如果文件名错了 / 文件不存在,
new Scanner(myFile)会抛出FileNotFoundException;
FileNotFoundException是IOException的一种;
- Java 要求你要么处理异常(try/catch),要么声明抛出(
throws IOException)。
① AP CSA 常用写法:在方法头加
throws IOException② 更专业写法:
try/catch 捕获异常学习重点 3:Scanner 的常用方法与“读数字后换行问题”
需要掌握的方法:
- 构造器:
Scanner(File f)、new Scanner(System.in)
String nextLine()
String next()
int nextInt()
double nextDouble()
boolean nextBoolean()
boolean hasNext()
void close()
3.1 键盘输入示例(+ InputMismatchException)
如果要允许小数:
3.2 “读数字 + 再读一整行”的坑
nextInt()/nextDouble()只读数字,不吃掉换行符;
- 紧接着调用
nextLine()时会读到“空串”。
解决办法:数字后多加一个
nextLine() 把本行剩余的内容吃掉:学习重点 4:用 while (scan.hasNext()) 读完整个文件
要点:
- 使用
hasNext()来判断是否还有内容可读;
- 循环结束后记得
scan.close()。
示例:统计字典文件有多少行
学习重点 5:把文件内容存入数组
要点:
- 先声明一个足够大的数组(AP CSA 暂时不用动态扩容);
- 在
while循环中:读一行 → 存到数组 →i++。
示例:把前 10 行 Pokemon 存到数组并打印
学习重点 6:CSV 与 String.split(String delimiter) 切割一行数据
要点:
- CSV 是 Comma Separated Values → 每行是一个记录,字段用逗号隔开;
split(delimiter)把一整行按照分隔符拆成String[];
- 对于 Pokemon 文件:
data[0]= Numberdata[1]= Namedata[2]= Type 1data[7]= Speeddata[8]= PNG 图片 URL
简单示例:
Pokemon 一行数据切割示例:
学习重点 7:综合练习——随机打印一个 Pokemon 的名字与图片
对应课本里的
PokeImages.randomPokemon。完整示例:
学习重点 8:面向对象设计——从 CSV 建立对象数组
最后一层:每一行数据 → 一个对象。以
Pokemon 为例。8.1 设计 Pokemon 类
8.2 读取 CSV → Pokemon[] 数组,并写 findType
Activity 4.6.5.
我直接按代码从上到下一点点拆开讲👇
import java.io.*;- 引入
java.io这个包里所有类( 表示“全部”)。 - 里面有
File,IOException等等,后面会用到File来表示文件对象。
import java.util.*;- 引入
java.util包里的所有类。 - 里面有
Scanner,Random,ArrayList等。 - 这段代码里会用到
Scanner。
- 定义一个公共类
PokeImages,类名要和文件名PokeImages.java一致。
- 花括号
{表示这个类的代码从这里开始。
- 定义一个私有成员变量
filename,类型是String。
- 初始值是
"pokemon.csv",表示我们的数据文件名。
- 以后如果要换文件,只要改这里即可,其他代码不用动。
- 定义一个字符串数组
pokemonLines,长度为152。
- 这个数组用来存放
pokemon.csv文件中的每一行文字。
- 下标范围是
0到151。通常0行是表头(header),1~151是 151 只宝可梦。
- 上面一行是注释,说明接下来这个方法的作用:把文件内容读进
pokemonLines数组。
public int readFile()- 定义一个公开方法
readFile,返回值类型是int。 - 返回值会是读到了多少行。
throws IOException- 表示这个方法在读文件时可能会抛出 IO 异常(例如:找不到文件)。
- 谁调用这个方法,谁就要处理/继续抛出这个异常(稍后在
main里用throws IOException解决)。
- 创建一个
File对象myFile,参数是filename(也就是"pokemon.csv")。
- 相当于告诉 Java:我要操作这个名字叫 pokemon.csv 的文件。
- 使用
Scanner,并把myFile传进去,表示: - 我用
Scanner这个“扫描器”从文件中读数据,而不是从键盘输入。
- 定义计数器
i,从 0 开始。
- 用来记录当前读到第几行,同时也用来当数组下标。
scan.hasNext():如果还有下一行/下一段输入,就返回true。
while ( ... ):只要还有内容,就一直循环。
- 这行表示:只要文件里还有没读完的内容,就继续读。
scan.nextLine():从文件中读出一整行字符串。
- 把这一行存入数组
pokemonLines的第i个位置。
- 即:数组的一格 = 文件的一行。
- 读完一行后,
i加 1,表示下一行要存到数组的下一个位置。
- 也用来统计总共读了多少行。
- 对应
while的结束大括号——循环体结束。
- 打印出:“Read in X lines.”,
i就是读到的总行数。例如152行。
- 用完
Scanner要把它关掉,释放文件资源,好习惯。
return i;:把读到的行数返回给调用者。
}:readFile方法结束。
接下来是随机挑出一只宝可梦的函数:
- 定义一个方法
randomPokemon, - 返回类型是
void(没有返回值), - 参数
length:表示一共读到了多少行(刚才readFile()的返回值)。
- 注释:
- 要在
1到length - 1之间随机选一个整数。 - 因为第
0行是表头(列名),不是宝可梦数据,所以跳过0。
重点行,慢慢拆:
Math.random():产生一个0.0 <= 随机数 < 1.0的double。
Math.random() * (length - 1):- 变成
0.0 <= 值 < (length - 1)。
(int)(...):强制类型转换为int,小数部分会被直接截断(不是四舍五入)。- 结果是一个整数:
0到length - 2。
- 最后
+ 1: - 把范围平移成:
1到length - 1。
总结:
👉
randIndex 是一个在 [1, length-1] 之间的随机整数,不会选到 0,也不会越界。- 从数组
pokemonLines中,取出第randIndex行的文本,保存到line变量。
- 这行文本对应
pokemon.csv文件里的某一行数据,比如:
25,Pikachu,Electric,,35,55,40,90,https://...,Mouse Pokémon- 调用
line.split(","): - 按照英文逗号
,把这一行拆成多个小字段。 - 每个字段就是 CSV 文件里的一个列值。
- 返回值是
String[],所以用String[] data接住。
- 举例:
line = "1,Bulbasaur,Grass,Poison,45,49,49,45,https://...,A strange seed..."data[0] = "1"(编号)data[1] = "Bulbasaur"(名字)data[2] = "Grass"(Type 1)data[8] = "https://..."(图片链接)- 等等。
- 注释提醒:数组的第 1 个元素(
data[1])是宝可梦名字。
System.out.println("Random Pokémon: " + data[1]);- 打印类似:
Random Pokémon: BulbasaurRandom Pokémon: Pikachu
data[8]:根据给的 CSV 结构,这是图片 URL。
- 调用下面定义的
printHTMLimage方法,把这个 URL 传进去。
- 这个方法会在控制台打印出一段
<img>标签,用于网页显示图片。
randomPokemon方法结束。
接下来是
main 方法——程序入口:public static void main(String[] args):Java 程序的入口方法。
throws IOException:- 因为
main里会调用readFile(),而readFile()可能抛出IOException, - 所以
main也声明:我也可能抛出这个异常。 - 在简单教学代码里,直接这样写是最方便的做法。
- 创建一个
PokeImages类的对象obj。
- 之后通过
obj来调用非静态方法(readFile和randomPokemon)。
- 注释:说明下面这行是在调用
readFile()来把文件读入数组。
obj.readFile():- 实际执行读取文件,把每一行存到
pokemonLines数组里。 - 返回读到的行数,比如
152。
- 把这个返回值保存在
length变量中,后面要传给randomPokemon。
- 调用
obj.randomPokemon(length);: - 用刚才得到的
length(比如 152),在1到151之间随机选一只宝可梦并打印。
}:main方法结束。
最后是打印图片标签的方法:
- 注释:在 Runestone 这种在线学习平台里,这个方法会把图片显示出来。
public static void printHTMLimage(String url):- 静态方法,可以不用对象直接调用(但这里是从实例方法里调,也没问题,因为静态方法可以用类名或直接调用)。
- 参数
url是图片的链接。
System.out.print("<img src=" + url + " width=300px />");:- 在控制台输出一段 HTML:
"<img src=图片地址 width=300px />"- 在支持 HTML 渲染的环境(比如 Runestone),会把这段
<img>解释为一张图片,显示出来。
- 整个
PokeImages类到此结束。
小结(用一句话串起来)
readFile():- 从
pokemon.csv里一行一行读数据,存进pokemonLines[], - 并返回“读到的总行数”。
randomPokemon(length):- 在
1到length-1中随机选一行(跳过表头), - 按逗号
split成data[], - 打印名字
data[1], - 再用
printHTMLimage(data[8])打印出图片 HTML 标签。
main:- 创建对象 → 调用
readFile()→ 再调用randomPokemon()。
Project 4.6.6.
我直接按“代码块 + 解释”这样讲👇
1. 导包部分
import java.io.*;
引入
java.io 这个包里所有的类(File, IOException 等),为了做文件读写。import java.util.*;
引入
java.util 里所有的类(比如 Scanner, ArrayList, Random 等),这里我们会用到 Scanner。2. 定义 Pokemon 类(一个“模板”)
class Pokemon
定义一个类
Pokemon,表示“一个宝可梦”的数据结构。- 下面四行是成员变量 / 属性,都用
private修饰: private String name;—— 宝可梦的名字(如 "Pikachu")private String type1;—— 第一属性(如 "Electric")private String type2;—— 第二属性(有些宝可梦只有一个属性,这里可以是空字符串)private String imageFile;—— 图片的 URL 或文件名
private 表示外部类不能直接访问这些字段,必须通过方法(getters)访问,这是封装(encapsulation)的体现。3. Pokemon 的构造方法
public Pokemon(...)
这是构造方法,当你写
new Pokemon(...) 时会被调用。- 四个参数:
name,type1,type2,imageFile,用来给对象初始化。
this.name = name;- 左边
this.name是当前对象的字段 - 右边
name是方法参数 - 这行的意思是:把传进来的参数值赋给当前对象的属性。其他几行同理。
4. Getter 方法(访问属性)
public String getName()- 返回类型是
String - 作用:给外部代码提供访问
name的方式 return name;直接返回私有字段name。
- 为了配合作业说明中写的
getType(),这里专门提供一个方法getType(),实际返回的是type1。
- 也就是说,如果学生调用
p.getType()和p.getType1(),效果是一样的。
- 三个普通的 getter,分别返回
type1、type2和imageFile。
- 到这里,
Pokemon类结束。
5. PokemonArray 类的字段
public class PokemonArray:这是主类,负责:- 读 CSV 文件
- 创建
Pokemon对象数组 - 提供查找功能
private Pokemon[] pokemonArray = new Pokemon[152];- 声明并创建一个
Pokemon数组,长度为 152。 - 一开始里面全是
null,后面会用new Pokemon(...)填进去。
private String filename = "pokemon.csv";- 保存要读取的文件名,方便修改。
6. PokemonArray 的构造方法
public PokemonArray() throws IOException- 没有参数的构造方法。
throws IOException表示:这个构造方法里可能会抛出IOException异常(比如文件不存在),调用者需要处理或继续抛出。
readFile();- 一创建
PokemonArray对象,就自动读取文件,把pokemonArray填满(尽可能填)。
7. 读文件的方法 readFile()
public void readFile() throws IOException- 没有返回值,是一个“做事”的方法:从文件中读数据。
File myFile = new File(filename);- 创建一个
File对象,对应"pokemon.csv"这个文件。
Scanner scan = new Scanner(myFile);- 用
Scanner来从文件中按行读取文本。
int index = 0;- 用来记录当前读到第几个 Pokémon,对应数组的下标。
- 注释:如果文件有表头(header),跳过它。
if (scan.hasNextLine())检查文件中是否还有下一行。
scan.nextLine();读取这一行(表头),但不保存——相当于直接丢弃。
while (scan.hasNextLine() && index < pokemonArray.length)- 循环条件有两个:
- 文件还有下一行;
pokemonArray还没装满(index 没到 152)。
String line = scan.nextLine();- 读取一整行 CSV 文本,比如
"1,Bulbasaur,Grass,Poison,45,49,...,https://xxx.png,Description..."String[] parts = line.split(",");- 用逗号
,把这一行拆成字符串数组parts。 parts[0]是第 1 列,parts[1]第 2 列,以此类推。
- 注释说明了对 CSV 列的假设:
parts[0]: 编号parts[1]: 名字parts[2]: Type 1parts[3]: Type 2(某些可能为空)- 最后一列:图片 URL
String name = parts[1];—— 取第 2 列作为名字。
String type1 = parts[2];—— 取第 3 列作为第一属性。
String type2 = parts.length > 3 ? parts[3] : "";- 这里用了三元运算符
条件 ? 值1 : 值2: - 如果这一行至少有 4 列(
parts.length > 3),就取parts[3]作为type2 - 否则就让
type2等于空字符串""
String imageFile = parts[parts.length - 1];- 无论 CSV 有多少列,最后一列都被认为是图片 URL 或图片文件名。
pokemonArray[index] = new Pokemon(...);- 用刚刚拆出来的数据创建一个新的
Pokemon对象 - 把这个对象存到数组的
index位置上。
index++;- 处理完一个宝可梦,数组下标加 1,准备存下一个。
scan.close();- 关闭扫描器,释放文件资源。
8. findType 方法:查找并显示宝可梦的类型和图片
public String findType(String name)- 参数:
name——要查找的宝可梦名字(比如"Pikachu") - 返回值:找到的话返回它的类型(Type 1),找不到返回
null。
for (Pokemon p : pokemonArray)- 增强 for 循环,依次取出数组中的每一个
Pokemon对象。 - 每次循环,
p指向数组中的一个元素(可能是null)。
if (p != null && p.getName().equalsIgnoreCase(name))- 先判断
p不为null(因为数组可能没有填满)。 p.getName().equalsIgnoreCase(name):忽略大小写比较名字是否相同,比如"Pikachu"和"pikachu"也算匹配。
- 如果找到匹配:
String type = p.getType();- 调用刚才写的
getType(),返回的是type1。 System.out.println("Type: " + type);- 在控制台打印类型。
printHTMLimage(p.getImageFile());- 调用下面的静态方法,打印出 HTML
<img>标签(这在 Runestone 之类的环境里会显示图片)。 return type;- 把类型返回给调用者。
- 如果循环结束都没找到:
System.out.println("Pokemon " + name + " not found.");- 在控制台提示“没找到这个宝可梦”。
return null;—— 返回null表示未找到。
9. 打印 HTML 图片标签的方法
public static void printHTMLimage(String url)static:这个方法属于类本身,不需要创建PokemonArray对象就可以调用。- 参数
url是图片的地址(或文件路径)。
System.out.print(...)- 打印出一个 HTML
<img>标签,比如: - 在支持 HTML 渲染的环境(如 Runestone)中,这会显示图片。
<img src=https://play.pokemonshowdown.com/.../pikachu.png width=300px />10. main 方法:程序入口
public static void main(String[] args) throws IOException- Java 程序的入口。
- 这里也声明
throws IOException,因为创建PokemonArray的时候可能读文件出错。
PokemonArray obj = new PokemonArray();- 创建一个
PokemonArray对象。 - 构造方法里会自动调用
readFile(),把 CSV 读入数组。
System.out.println("Pikachu's type is " + obj.findType("Pikachu"));- 调用
obj.findType("Pikachu"): - 在数组中找到名字是 Pikachu 的那一条
- 在控制台打印
- 返回字符串
"Electric" - 外层的
System.out.println(...)最终会打印:
Project 4.6.7.
我按模块一小段代码一小段讲,这样比“真的逐行一行一行”更清晰,但每一行都会解释到 👍
1. 导入包
import java.io.*;
导入
java.io 包里所有的类(星号 * 表示“全部”),比如 File、IOException 等,后面用到读文件。import java.util.*;
导入
java.util 包里所有的类,比如 Scanner,后面用来扫描/读取文件内容。2. Pokemon 类——表示 CSV 中“一行记录”
- 注释说明:这个类表示
pokemon.csv文件中的“一条记录/一行”。
class Pokemon:定义一个名为Pokemon的类,它不是public,只能在同一个文件里使用。
- 注释:这些属性都来自于 CSV 文件的数据列。
private int number;
精灵图鉴编号(第一列 Number)。
private String name;
宝可梦名称。
private String type1;
主属性(比如 "Fire"、"Grass")。
private int total;
总种族值(Total base stats)。
private String imageFile;
图片文件名或 URL 地址(CSV 最后一列)。
2.1 构造方法
- 注释:构造器,用来创建
Pokemon对象,并给属性赋初值。
public Pokemon(...):当你写new Pokemon(...)时,就会调用这个方法。
this.number = number;
左边
this.number 是“这个对象的成员变量”;右边 number 是构造器参数,把参数的值传给成员变量。- 下面几行同理,分别给
name、type1、total、imageFile赋值。
2.2 Getter 方法
- 注释:下面是“取值方法”(getter),用来从外部读取私有属性。
public int getNumber():返回int类型,名字叫getNumber。
return number;:把当前对象的number字段返回。
同样地:
- 每个方法都返回对应的私有变量。
2.3 toString 方法
- 注释:重写
toString方法,这样在System.out.println(pokemonObj)时,会打印出比较漂亮的一行文字。
public String toString():返回一个字符串。
return "#" + number + ...:
拼接字符串,例子:
3. PokemonData 类——表示“整个数据集”
- 注释:这个类表示“整个数据集”的操作。
public class PokemonData:这个文件的主类,名字和文件名PokemonData.java一致。
- 注释:一个
Pokemon对象的数组,用来存所有宝可梦。
private Pokemon[] pokemonArray = new Pokemon[152];
声明并创建一个长度为 152 的数组,里面每个元素都是
Pokemon 类型。152 的含义:通常有 151 条数据 + 可能留一个位置,或者为防溢出,略多一点。
private int size = 0;
实际读入了多少个宝可梦,一开始是 0,读完文件后更新。
private String filename = "pokemon.csv";
文件名,之后用它来创建
File 对象。4. readData 方法——读 CSV、拆分、创建对象、存数组
- 这段是文档注释,描述方法功能:读取 CSV,拆分每一行,创建
Pokemon对象。
public void readData() throws IOException:
公共方法,无返回值,可能会抛出
IOException(例如文件不存在)。File myFile = new File(filename);
新建一个
File 对象,指向 pokemon.csv 文件。Scanner scan = new Scanner(myFile);
用
Scanner 来读取这个文件里的文本,一行一行读。- 注释:读并丢弃第一行(表头:Number,Pokemon,Type1,...)。
if (scan.hasNextLine()):如果还有一行可读(防止空文件)。
scan.nextLine();:读取这一行但不保存,只是跳过。
int index = 0;
用
index 指示当前要往数组 pokemonArray 的哪个位置存。while (scan.hasNextLine() && index < pokemonArray.length):
只要“还有下一行”并且“数组还没装满”,就继续。
String line = scan.nextLine();:读出当前行的完整文本。
String[] parts = line.split(",");:
把这一行按逗号
, 拆成一个字符串数组 parts。parts[0] 是第一列,parts[1] 是第二列,等等。- 这些注释说明 CSV 各列的顺序和含义。
int number = Integer.parseInt(parts[0]);
把
parts[0] 这一列(字符串)转换成 int,当作编号。String name = parts[1];:名字。
String type1 = parts[2];:主类型。
int total = Integer.parseInt(parts[4]);:把 Total 列转成整数。
String imageFile = parts[13];:最后一列是图片文件名/URL。
Pokemon p = new Pokemon(...);
用刚刚拆出来的字段构造一个新的
Pokemon 对象。pokemonArray[index] = p;
把这个对象存进数组的第
index 个位置。index++;:索引加 1,为下一条记录做准备。
while循环结束:说明文件读完,或者数组用满了。
size = index;
把实际读入的记录数保存下来,后面遍历数组的时候就用
size,而不是用数组长度 152。System.out.println("Read in " + size + " Pokemon.");
打印出读了多少条记录。
scan.close();
关闭
Scanner,释放资源。5. 找到总种族值最大的 Pokemon
- 文档注释:说明这个方法的功能是找出
total最大的宝可梦。
public void printPokemonWithMaxTotal():公共、无返回值。
- 如果
size == 0,说明你还没调用readData()或者文件是空的。
- 打印提示
No data loaded.之后return;,直接结束方法。
- 假设数组第 0 个就是目前“最大”的宝可梦,作为初始值。
for (int i = 1; i < size; i++):从第 1 个元素开始遍历到size-1。
if (pokemonArray[i].getTotal() > maxPoke.getTotal()):
如果当前宝可梦的
total 大于目前记录的最大值:maxPoke = pokemonArray[i];:把maxPoke更新为这个更大的宝可梦。
- 循环结束后,
maxPoke就是总种族值最大的那个。
- 打印说明文字。
System.out.println(maxPoke);:
这里会自动调用
maxPoke.toString(),显示我们刚才重写的那串格式化字符串。6. 打印所有指定类型的 Pokemon(可选方法)
- 注释:可选拓展,打印所有主属性是某个类型的宝可梦。
public void printAllOfType(String type):传入一个字符串参数,比如"Fire"。
System.out.println("All Pokemon of type: " + type);:先打印标题。
for (int i = 0; i < size; i++):遍历已经读入的所有宝可梦。
pokemonArray[i].getType1().equalsIgnoreCase(type):
忽略大小写比较,比如
"fire" 也能匹配 "Fire"。- 如果匹配,就打印这一只宝可梦对象(会调用它的
toString方法)。
7. main 方法——程序入口
public static void main(String[] args):Java 程序入口。
throws IOException:说明这个方法可能抛出IOException,不在里面捕获,直接交给 JVM 处理(如果出错会打印异常信息)。
PokemonData obj = new PokemonData();
创建一个
PokemonData 对象,用来操作整个数据集。- 注释说明:调用方法把数据读进来。
obj.readData();:读取pokemon.csv,创建Pokemon对象数组,并更新size。
- 注释:调用一个方法,对数据做一点“有意思”的操作。
obj.printPokemonWithMaxTotal();
找出总种族值最大的那只宝可梦并打印。
- 注释:额外功能,如果把下面这一行的注释去掉,就会打印所有主属性为
"Fire"的宝可梦。
// obj.printAllOfType("Fire");当前被注释掉了,不会执行。
}:结束main方法。
- 最后一个
}:结束PokemonData类。
雅思词汇
- 作者:现代数学启蒙
- 链接:https://www.math1234567.com/article/codeexplained46
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
相关文章








