上周一个数据库作业,用文件读写的方式来实现学生信息的读写,从而与数据库编程的方式进行对比。
在这个练习中,我主要是打算熟悉一下 java 的文件操作,因为我发现我学了 java 之后基本没有写过文件读写。
本文主要总结一下本练习用到的一些知识点,方便下次使用。
本文对应的 github 库
参考链接
题目
- 1.建两个文本文件
- 2.插入学生信息
- 3.查询学生对应的奖励
- 4.增删改信息
文件 1 学生基本信息
学号 |
姓名 |
性别 |
年龄 |
专业 |
位置 |
长度 |
2017901006 |
杨啸 |
男 |
21 |
软件工程 |
0 |
30 |
…… |
…… |
…… |
…… |
…… |
…… |
…… |
- 位置:奖励文件内对应的位置
- 长度:奖励文件内对应的奖励字段的长度
文件 2 奖励
记录学生获得的奖励
奖励 |
2011 校奖学金,2012 国家奖学金 |
2012 校优秀学生 |
结构
- model 包
- Gender 枚举类:枚举值为“男”和“女”
- StudentInfo 类:作为学生信息结点,存储学生信息的六个字段。后来写着写着觉得这个类名太长了,不该加 Info 后缀的,写起来怪怪的。
- StudentInfoSystem 类:用于实现整个系统的逻辑功能,包括录入信息,查找信息,保存数据,读取数据等。
- view 包
- ConsoleMenu 类:控制台菜单类,作为与用户交互的界面。
知识点总结
Gender 枚举类
在录入学生性别的时候,会涉及到输入什么的问题,输入 0 或 1?输入“男”或“女”?如果用整数或者字符串都可能会产生非法数据,导致一些问题,这些问题在很久以前我拿 C++写的时候就遇到过一些。
最好的方式不是用布尔类型这种二值类型,而是使用枚举类型,因为有可能用到别的值,比如 UNSET(未设置)。
在这次练习之前,我会的 java 枚举大概是下面这样:
1 2 3 4 5
| public enum Gender { MALE, FAMALE }
|
如同 C++枚举一样列出枚举值,然后当成常量一样使用。
后面变成了这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| package model;
public enum Gender { MALE("男"), FAMALE("女");
private String m_genderString; private Gender(String gender) { m_genderString = gender; }
public String getGenderString() { return m_genderString; }
public static Gender newGender(String genderString) { switch (genderString) { case "男": return Gender.MALE; case "女": return Gender.FAMALE;
default: break; } return null; }
}
|
百度了几篇博客之后(当时没收藏,找不到了),知道了枚举类一点点原理。
拿上面这个代码举例:写MALE("男")
其实像是为 Gender 类的构造方法传入一个值为“男”的字符串,而默认的构造方法是没有参数的,所以你需要写一个以字符串为参数的构造方法。
为了保留下这个传入的字符串,需要定义一个字符串类型的属性;为了获取它,再定义一个 getter。
这样做的目的是,在输出学生性别时,可以调用性别属性的getGenderString()
方法获取对应的字符串。
最下面那个newGender()
方法则是在录入学生性别时使用。
File 类
java 中的 File 类是对文件的抽象,它可以是文件,也可以是目录。
1 2
| File dir = new File(pathName); if(!dir.isDirectory()) return -1;
|
它拥有的方法主要是用于检测文件的信息的,比如它是文件还是目录,是否存在,绝对路径是什么等等,它不能直接读写文件(创建倒是可以)。
读写文件得需要其他类,这一部分的类太多了,我找了挺久才找到合适的类。
FileOutputStream 类
首先是保存数据到文件中。用到了这个类,它是文件输出流,以字节流的形式输出到文件。意思就是给他传入的参数是字节数组而不是字符串。不过字符串转字节数组非常简单,用字符串的getBytes()
就好。
打开文件输出流:
1 2 3 4 5 6 7 8
| File dir = new File(pathName); if(!dir.isDirectory()) return -1;
File infoFile = new File(pathName + "/StudentInfoList.csv");
if(!infoFile.exists()) infoFile.createNewFile();
FileOutputStream infoFOS = new FileOutputStream(infoFile);
|
随后拼接好学生信息的字符串,转换为字节数组,传入即可:
1 2
| byte[] infoBuf = studentInfoString.getBytes(); infoFOS.write(infoBuf);
|
最后记得 close
FileReader 类和 BufferedReader 类
一行一个学生信息,所以读取时打算直接 readline,需要BufferedReader
类,进而需要FileReader
类。
1 2 3 4 5
| BufferedReader bufReader = new BufferedReader(new FileReader(infoFile)); String tmpString = null; while((tmpString = bufReader.readLine()) != null) {
}
|
因为基本信息文件中保存的是奖励在奖励文件中的位置与长度,所以在读取时需要设置文件指针。
这个需要用到RandomAccessFile
类。
1
| RandomAccessFile randomAccessFile = new RandomAccessFile(rewardFile, "r");
|
设置文件指针到指定位置,读取对应长度的字节。
1 2 3 4 5
| randomAccessFile.seek(position); byte[] tmpBytes = new byte[1024]; randomAccessFile.read(tmpBytes,0, rewardLen); String rewardString = new String(tmpBytes);
|
写到这里我还遇到了一个问题,就是读取到字符串后,无法判断字符串是否为空,如果用rewardString.equals("")
或者``rewardString.isEmpty()`,发现字符串的长度是和我设置的缓冲区大小有关的。虽然全部是空白,但是并不为空字符串。用长度判断也不行。
后来找到了一个方法:
1
| String rewardString = new String(tmpBytes).trim();
|
trim()
可以去掉首尾空白,那么 1024 长度的空字符串就会变成 0 长度的普通的空字符串,就可以用刚刚的方式来判断学生是否有奖励了。
录入学生信息
java 的控制台输入我也是没弄过,这次就来试试。
会了之后发现蛮简单的。创建一个Scanner
对象,传入源输入流,要从控制台输入,所以输入流设置为System.in
。
1
| Scanner scanner = new Scanner(System.in);
|
需要读取一行数据,可以使用next()
或者nextLine()
方法,区别详情见百度
录入普通字符串搞定:
1 2 3 4 5
| System.out.println("请输入学生的学号"); String studentId = scanner.nextLine();
System.out.println("请输入学生的姓名"); String name = scanner.nextLine();
|
录入性别则需要判断合法性(其实上面的学号,姓名也需要判断合法性,不过不是本次练习的核心,就没弄了)
1 2 3 4 5 6 7
| String genderString = ""; Gender gender = null; do { System.out.println("请输入学生的性别,输入\"男\" 或者\"女\""); genderString = scanner.nextLine(); gender = Gender.newGender(genderString); }while(gender == null);
|
这里用了一个 do-while 循环,如果输入的不是枚举类里面有的值,就要求再次输入。
接着是年龄,年龄是一个正整数,需要nextInt()
来获取整数,如果输入的不是整数,就会抛出异常。处理完这个异常之后继续要求输入,直到输入正确的年龄为止。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| System.out.println("请输入学生的年龄"); int age = 0; while (true) { try { age = scanner.nextInt(); if(age<=0) { System.out.println("年龄必须是正数"); } else { break; } } catch (Exception e) { System.out.println("年龄必须是整数"); scanner.next(); } }
|
在输入完整数类型的年龄后,需要输入字符串类型的专业了,这里会遇到和 C++输入一样的情况,那就是你还没输入就当你输入了,最后得到的是空字符串。这是因为在输入整数后按下回车键确定时,这个换行符还留存在输入缓冲区,下一个nextLine()
将其当成了结束标志,从而结束字符串的输入。所以需要清空一下缓冲区,清空方式就是读取一个值并丢弃:
1 2 3 4
| scanner.nextLine();
System.out.println("请输入学生的专业"); String major = scanner.nextLine();
|
最后是录入学生的奖励,这个部分加深了我对“String 的相等比较不能用==
而要用equals()
”的知识点的印象。
1 2 3 4 5 6 7 8 9 10
| System.out.println("请输入学生的专业"); String major = scanner.nextLine();
System.out.println("请输入学生的奖励,每输入完一项换行,输入\"done\"结束输入"); String rewardString = ""; Vector<String> reward = new Vector<String>(); do { rewardString = scanner.nextLine(); if(!rewardString.equalsIgnoreCase("done")) reward.add(rewardString); } while (!rewardString.equalsIgnoreCase("done"));
|
核心代码
保存数据到文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
|
public int saveData(String pathName) {
try { File dir = new File(pathName); if(!dir.isDirectory()) return -1;
File infoFile = new File(pathName + "/StudentInfoList.csv"); File rewardFile = new File(pathName + "/Rewards.csv");
if(!infoFile.exists()) infoFile.createNewFile(); if(!rewardFile.exists()) rewardFile.createNewFile();
FileOutputStream infoFOS = new FileOutputStream(infoFile); FileOutputStream rewardFOS = new FileOutputStream(rewardFile);
int curPosition = 0; for(StudentInfo studentInfo:m_studentInfoList) {
String studentInfoString = String.format("%s,%s,%s,%d,%s,", studentInfo.getStudentId(), studentInfo.getName(), studentInfo.getGender().getGenderString(), studentInfo.getAge(), studentInfo.getMajor() );
String rewardString = String.join(",", studentInfo.getReward()); if(!rewardString.isEmpty()) { rewardString += "\n"; }
byte[] rewardBuf = rewardString.getBytes();
studentInfoString += String.format("%s,%s\n", curPosition,rewardBuf.length); curPosition += rewardBuf.length;
byte[] infoBuf = studentInfoString.getBytes();
infoFOS.write(infoBuf); rewardFOS.write(rewardBuf); }
System.out.printf("成功保存%d条记录到以下文件中:\n[%s]\n[%s]\n", m_studentInfoList.size(), infoFile.getCanonicalPath(), rewardFile.getCanonicalPath() );
rewardFOS.close(); infoFOS.close();
return m_studentInfoList.size();
} catch (IOException e) { e.printStackTrace(); }
return 0; }
|
从文件中加载数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
|
public int loadData(String pathName,boolean clearFlag) { try { if(clearFlag) m_studentInfoList.clear();
File dir = new File(pathName); if(!dir.isDirectory()) return -1;
File infoFile = new File(pathName + "/StudentInfoList.csv"); File rewardFile = new File(pathName + "/Rewards.csv");
BufferedReader bufReader = new BufferedReader(new FileReader(infoFile)); RandomAccessFile randomAccessFile = new RandomAccessFile(rewardFile, "r");
String tmpString = null; int counter = 0; while((tmpString = bufReader.readLine()) != null) {
String[] infoStrings = tmpString.split(",");
if(infoStrings.length != 7) { return -1; }
String studentId = infoStrings[0]; String name = infoStrings[1]; Gender gender = Gender.newGender(infoStrings[2]); if(gender == null) return -1;
int age = Integer.parseInt(infoStrings[3]); String major = infoStrings[4];
long position = Long.parseLong(infoStrings[5]); int rewardLen = Integer.parseInt(infoStrings[6]);
randomAccessFile.seek(position); byte[] tmpBytes = new byte[1024]; randomAccessFile.read(tmpBytes,0, rewardLen);
String rewardString = new String(tmpBytes).trim();
Vector<String> rewardList = null; if(!rewardString.isEmpty()) { rewardList = new Vector<String>(); String[] rewardArr = rewardString.split(","); for(String reward : rewardArr) { rewardList.add(reward); }
}
StudentInfo newStudentInfo = new StudentInfo(studentId, name, gender, age, major, rewardList);
m_studentInfoList.add(newStudentInfo); counter++; }
bufReader.close(); randomAccessFile.close();
return counter;
} catch (Exception e) { e.printStackTrace(); return -1; }
}
|