《Java面向对象程序设计》学习笔记——CSV文件的读写与处理
笔记汇总:《Java面向对象程序设计》学习笔记
笔记记录的不是非常详实,如果有补充的建议或纠错,请踊跃评论留言!!!
什么是CSV文件
CSV文件的定义
CSV 是英文 comma-separated values 的缩写,翻译为 “逗号分隔值“。
CSV 文件可以理解为以带逗号分隔(也可以是其他简单字符分割)的纯文本形式存储表格数据的文件。
CSV文件与Excel文件的关系
CSV文件和Excel文件有点像,但Excel是带格式的文件,且一个工作表里最多只可以存放 1048576 (2^10)行。
可以使用Excel打开CSV。但如果没有按照指定编码方式进行保存,用Excel打开CSV会乱码。
CSV文件的优点
-
方便数据交换
-
无最大行数的限制
CSV文件格式
格式规范
-
CSV文件第一行可以是字段名(也可以不写字段名直接写数据)
-
接下来每一行是字段所对应的值
-
不同 字段/数据 之间用 逗号 隔开
-
其他格式规范
比如:
// 通讯录.csv
姓名,手机,QQ,微信号
小新,13913000001,1819122001,xx9907
小亮,13913000002,1819122002,xiaoliang
小刚,13913000003,1819122003,gang1004
大刘,13913000004,1819122004,liu666
大王,13913000005,1819122005,jack_w
奇妙方程式,12345678910,229600398,qq229600398
// 统计demo.csv
下单,0,a,BCF
付款,0,a,BCF
发货,0,a,BCF
付款,0,a,BCF
收款,0,a,BCF
下单,0,a,BCF
付款,0,a,BCF
CSV文件的读写操作
注意事项
-
其实CSV文件有固定的规范,简单的用逗号分隔,并不是安全的解析方法
比如:有些值中有逗号,直接使用split分隔逗号会出问题
-
一般都是使用类库来对csv文件进行处理,比如python的csv库、java的open-csv库。
-
其实在读写时最好上锁。
-
但个人练习没啥大问题。
-
使用Java读写文件后记得调用方法关闭流。
读取方法
以下为Java中读取CSV文件的方法,程序打印所读内容
// test.csv
Id,SepalLengthCm,SepalWidthCm,PetalLengthCm,PetalWidthCm,Species
1,5.1,1.4,1.4,0.2,Iris-setosa
2,4.9,3,1.4,0.2,Iris-setosa
3,4.7,3.2,1.3,0.2,Iris-setosa
4,4.6,3.1,1.5,0.2,Iris-setosa
5,5,3.6,1.4,0.2,Iris-setosa
6,5.4,3.9,1.7,0.4,Iris-setosa
7,4.6,3.4,1.4,0.3,Iris-setosa
8,5,3.4,1.5,0.2,Iris-setosa
// Csv.java
package csv;import java.io.BufferedReader;
import java.io.FileReader;public class Csv {public static void main(String[] args) {String filepath = "D:\\CSV文件\\test.csv";String[][] alldata = null;try {// 1. 创建流// 创建FileReader和BufferedReader对象来读取文件内容FileReader filereader = new FileReader(filepath);BufferedReader buffereader = new BufferedReader(filereader);// 当然写在一起也行// BufferedReader buffereader = new BufferedReader(new FileReader(filepath));// 2. 计算文件中的行数int linenumber = 0;while (buffereader.readLine() != null) {linenumber++;}// 3. 将缓冲读取器重置为从文件开头读取filereader = new FileReader(filepath);buffereader = new BufferedReader(filereader);// 4. 用适当的维度初始化AllData数组alldata = new String[linenumber][];// 5. 读取内容,存储到内存(数组、Map或其他数据类型)中。使用split分隔逗号,还可以使用正则表达式进一步处理。String line;linenumber = 0;while ((line = buffereader.readLine()) != null) {// 按逗号分隔每行数据String[] data = line.split(",");// 第一种方式
// if (data.length > 0) {
// // 初始化内部数组
// alldata[linenumber] = new String[data.length];
// // 存入二维字符数组中
// for (int i = 0; i < data.length; i++) {
// alldata[linenumber][i] = data[i];
// }
// }// 第二种方式alldata[linenumber] = data;linenumber++;}// 6. 输出存储数据for (int i = 0; i < linenumber; i++) {System.out.print(alldata[i][0]);for (int j = 1; j < alldata[0].length; j++) {System.out.print(","+alldata[i][j]);}System.out.println();}// 7. 关闭FileReader流和BufferedReader流filereader.close();buffereader.close();} catch (Exception e) {// 处理异常情况e.printStackTrace();}}
}
// 输出
Id,SepalLengthCm,SepalWidthCm,PetalLengthCm,PetalWidthCm,Species
1,5.1,1.4,1.4,0.2,Iris-setosa
2,4.9,3,1.4,0.2,Iris-setosa
3,4.7,3.2,1.3,0.2,Iris-setosa
4,4.6,3.1,1.5,0.2,Iris-setosa
5,5,3.6,1.4,0.2,Iris-setosa
6,5.4,3.9,1.7,0.4,Iris-setosa
7,4.6,3.4,1.4,0.3,Iris-setosa
8,5,3.4,1.5,0.2,Iris-setosa
写入方法
以下为Java中读取CSV文件,写入新CSV文件的方法(读取方法的代码和上面一致,就省略)。
// Csv.java
package csv;import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;public class Csv {public static void main(String[] args) {String filepath = "D:\\CSV文件\\test.csv";String[][] alldata = null;try {// 接上面的代码,省略// 1. 创建流// 创建FileWriter和BufferedWriter对象来读取文件内容filepath = "D:\\CSV文件\\test1.csv";FileWriter filewriter = new FileWriter(filepath);BufferedWriter bufferwriter = new BufferedWriter(filewriter);// 当然写在一起也行
// BufferedWriter bufferwriter = new BufferedWriter(new FileWriter(filepath));// 2. 写入新文件for(String[] str:alldata) {String context = str[0];for (int i = 1; i < alldata[0].length; i++) {context = context + "," + str[i];}bufferwriter.write(context);bufferwriter.newLine(); // 换行}// 3. 关闭BufferedWriter流和FileWriter流,顺序不要搞反bufferwriter.close();filewriter.close();} catch (Exception e) {// 处理异常情况e.printStackTrace();}}
}
数据处理
数据处理其实不涉及IO流的知识,更多涉及正则表达式、字符串操作、循环、赋值等知识。
比如寻找最值、计算平均值、提取某个数据等。
正则表达式
匹配整数和小数
// 匹配整数或小数的字符串
// -? 表示可选的负号,匹配一个或零个负号。
// \\d+ 匹配一个或多个数字字符。
// (\\.\\d+)? 表示可选的小数部分,包括一个点号和一个或多个数字字符。
String regex = "-?(0|[1-9]\\d*)(\\.\\d+)?";if (s.matches(regex)) {···
}else {···
}
其他匹配方式欢迎补充
字符串操作
split() 方法分隔逗号
result = s.split(",");
equals() 方法判断字符串是否相等
if (s.equals(num)) {···
}else {···
}
trim() 方法去除字符串两端的空格或指定字符
String s = " 1 23 4 ";
s = s.trim();
System.out.print(s);
// 输出
1 23 4
其他方法欢迎补充
缺值处理
- 使用正则表达式统计出现次数
- 排序后取最大最小值
- 使用平均值、中位数等填充缺失数据
其他方法欢迎补充
练习案例
1. 读取 CSV 文件并按第一列分组计数
// 统计demo.csv
下单,0,a,BCF
付款,0,a,BCF
发货,0,a,BCF
付款,0,a,BCF
收款,0,a,BCF
下单,0,a,BCF
付款,0,a,BCF
// CsvDataGrouping.java
package csv;import java.io.BufferedReader;
import java.io.FileReader;
import java.util.HashMap;
import java.util.Map;public class CsvDataGrouping {public static void main(String[] args) {String filepath = "D:\\CSV文件\\统计demo.csv";try {// 创建一个BufferedReader对象来读取文件BufferedReader reader = new BufferedReader(new FileReader(filepath));// 创建一个Map来存储统计结果Map<String, Integer> countMap = new HashMap<>();String line;while ((line = reader.readLine()) != null) {// 按逗号分隔每行数据String[] data = line.split(",");if (data.length > 0) {// 去除首尾空格并取得关键字String key = data[0].trim();// 统计关键字出现次数countMap.put(key, countMap.getOrDefault(key, 0) + 1);}}// 输出统计结果for (Map.Entry<String, Integer> entry : countMap.entrySet()) {System.out.println(entry.getKey() + ": " + entry.getValue());}} catch (Exception e) {// 处理异常情况e.printStackTrace();}}
}
// 输出
下单: 2
收款: 1
发货: 1
付款: 3
代码大致说明
在这段代码中,我们首先定义了一个文件路径 filepath
,它指向要读取的CSV文件。然后我们使用 BufferedReader
和 FileReader
来创建一个读取文件的对象 reader
。
接下来,我们创建了一个 Map<String, Integer>
类型的对象 countMap
,用于统计每个关键字出现的次数。
然后,我们使用 reader.readLine()
逐行读取文件内容,将每一行按逗号分隔成字符串数组 data
。如果数组长度大于0,说明该行有数据,我们提取关键字 key
。然后使用 countMap.getOrDefault(key, 0) + 1
统计关键字出现的次数,并将结果存入 countMap
中。
最后,我们遍历 countMap
中的键值对,并打印出每个关键字及其出现的次数。
代码详细说明
上述代码是一个Java程序,用于读取指定路径下的CSV文件并统计每个关键字出现的次数。让我们逐步分析代码:
main
方法是程序的入口点,使用了public static void
修饰符,表示它是一个公共静态方法,不返回任何值,接受一个String
类型的数组作为参数。String filepath = "D:\\CSV文件\\统计demo.csv";
定义了一个字符串变量filepath
,用于存储CSV文件的路径。- 在
try-catch
块中,代码通过创建BufferedReader
对象来读取文件内容。使用new BufferedReader(new FileReader(filepath))
,传入一个FileReader
对象,该对象负责读取指定路径下的文件。 - 创建了一个
Map<String, Integer>
类型的变量countMap
,用于存储统计结果。HashMap
类实现了Map
接口,可以存储键值对,这里键的类型是String
,值的类型是Integer
。 - 进入一个
while
循环,读取文件的每一行数据,将读取的结果存储在line
变量中。循环条件为line = reader.readLine()
,当读取到的行不为空时继续循环。 - 在循环中,通过
line.split(",")
使用逗号分隔每一行的数据,并将结果存储在data
数组中。 - 如果
data
数组的长度大于0,表示当前行有数据。代码通过data[0].trim()
去除首尾空格并获取关键字,将结果存储在key
变量中。 - 通过
countMap.getOrDefault(key, 0)
获取countMap
中关键字key
对应的值,如果不存在则返回默认值0。 - 使用
countMap.put(key, countMap.getOrDefault(key, 0) + 1)
将关键字key
的出现次数加1,并将结果保存回countMap
中。 - 循环结束后,通过遍历
countMap
的每个条目,使用entry.getKey()
获取关键字,使用entry.getValue()
获取关键字出现的次数,并将结果打印到控制台。 - 在
catch
块中,代码捕捉并处理可能发生的异常情况,使用e.printStackTrace()
打印异常堆栈信息。
总体而言,这段代码读取指定路径下的CSV文件,统计每个关键字出现的次数,并将结果输出到控制台。
2. 读数据的第一行,输出所有的值、最值、指定数值出现的次数
// iris.csv
Id,SepalLengthCm,SepalWidthCm,PetalLengthCm,PetalWidthCm,Species
1,5.1,1.4,1.4,0.2,Iris-setosa
2,4.9,3,1.4,0.2,Iris-setosa
3,4.7,3.2,1.3,0.2,Iris-setosa
4,4.6,3.1,1.5,0.2,Iris-setosa
5,5,3.6,1.4,0.2,Iris-setosa
// IrisData.java
package csv;import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;public class IrisData {public static void main(String[] args) {// 创建文件对象,指向要读取的CSV文件File file = new File("D:\\CSV文件\\Iris.csv");String[] result = new String[10];// 匹配整数或小数的字符串// -? 表示可选的负号,匹配一个或零个负号。// \\d+ 匹配一个或多个数字字符。// (\\.\\d+)? 表示可选的小数部分,包括一个点号和一个或多个数字字符。String regex = "-?(0|[1-9]\\d*)(\\.\\d+)?";try {// 创建FileReader和BufferedReader对象来读取文件内容FileReader fr = new FileReader(file);BufferedReader br = new BufferedReader(fr);String string;int line = 0;// 逐行读取文件内容while ((string = br.readLine()) != null) {// 当行数为1时,将该行按逗号分割成字符串数组并存储在result中if (line == 1) {result = string.split(",");// 数组拷贝方法。从源数组中指定的范围复制一部分元素到一个新的数组中。// Arrays.copyOfRange(源数组, 起始位置, 终止位置)// 相当于复制下标1->最后的数组给原字符串,同时改变了原数组的长度result = Arrays.copyOfRange(result, 1, result.length);break;}line ++;}br.close();} catch (IOException e) {e.printStackTrace();}// 创建ArrayList对象用于存储符合条件的数字ArrayList<Double> numbers = new ArrayList<>();// 如果34行附近没有对数组进行截取拷贝操作// 则可以调整循环,使其在迭代结果数组时从索引1而不是0开始// 这种方式更为简单粗暴for (int i = 0; i < result.length; i++) {// 判断字符串是否匹配正则表达式,如果匹配则将其转换为Double类型并添加到numbers中if (result[i].matches(regex)) {numbers.add(Double.parseDouble(result[i]));}}// 打印列表中的所有数字for (int i = 0; i < numbers.size(); i++) {System.out.println(numbers.get(i));}// 输出指定数字在列表中出现的次数System.out.println("1.4 出现了 " + getCount(numbers, 1.4) + " 次");// 输出列表中的最大值和最小值System.out.println("max: " + getMaxAndMin(numbers)[0] + "\nmin: " + getMaxAndMin(numbers)[1]);}/*** 统计给定数值在列表中出现的次数** @param numbers 列表* @param num 给定数值* @return 出现次数*/public static int getCount(ArrayList<Double> numbers, Double num) {int count = 0;for (int i = 0; i < numbers.size(); i++) {if (numbers.get(i).equals(num)) {count ++;}}return count;}/*** 获取列表中的最大值和最小值** @param numbers 列表* @return 包含最大值和最小值的数组,索引0为最大值,索引1为最小值*/public static Double[] getMaxAndMin(ArrayList<Double> numbers) {Double[] max = {numbers.get(0), numbers.get(0)};for (int i = 0; i < numbers.size(); i++) {if (numbers.get(i) > max[0]) {max[0] = numbers.get(i);}if (numbers.get(i) < max[1]) {max[1] = numbers.get(i);}}return max;}
}
// 输出
5.1
1.4
1.4
0.2
1.4 出现了 2 次
max: 5.1
min: 0.2
代码大致说明
这段代码读取一个CSV文件,并对文件中的数据进行分析。首先,它读取文件的第二行数据(索引为1),将该行按逗号分割成字符串数组 result
,copyOfRange() 方法用于排除第一个元素,它表示ID列。然后存储在 numbers
列表中的元素必须满足正则表达式 regex
的匹配条件(即匹配一个浮点数),才会被添加到 numbers
列表中。
接下来,它使用两个辅助方法 getCount
和 getMaxAndMin
分别计算列表中指定数值出现的次数,以及列表中的最大值和最小值。
在 getCount
方法中,它遍历列表中的元素,如果元素与给定的数值相等,则计数器 count
自增。
在 getMaxAndMin
方法中,它首先将列表的第一个元素作为最大值和最小值的初始值。然后遍历列表中的元素,如果当前元素大于最大值,则更新最大值;如果当前元素小于最小值,则更新最小值。最后,它返回一个包含最大值和最小值的数组。
最后,它打印出列表中的所有数值,以及给定数值 1.4
在列表中出现的次数,以及列表中的最大值和最小值。
代码详细说明
给定的代码是一个Java程序,它从名为"Iris.csv"的CSV文件中读取数据,并对数据执行一些操作。让我们逐步分析代码:
- 代码开始导入必要的类和包。
- 定义了
IrisData
类,其中包含程序执行开始的主方法。 - 创建了一个
File
对象来表示CSV文件的位置。 - 初始化了一个字符串数组
result
,长度为10。该数组将存储从CSV文件中提取的值。 - 定义了一个正则表达式模式
regex
,用于匹配整数或小数字符串。 - 在try-catch块内,创建了
FileReader
和BufferedReader
来读取文件内容。 - 代码进入一个循环,使用
BufferedReader
的readLine()
方法逐行读取文件内容。 - 当行号为1时(行号从0开始),代码使用逗号分隔符拆分行,并将结果数组存储在
result
变量中。使用Arrays.copyOfRange()
方法来排除第一个元素,该元素表示ID列。 - 循环在读取第一行后中断。
- 关闭
BufferedReader
以释放系统资源。 - 创建一个名为
numbers
的ArrayList<Double>
,用于存储从result
数组中提取的数字。 - 循环遍历
result
数组,检查每个元素是否与正则表达式模式匹配。如果找到匹配项,将元素转换为Double
值并添加到numbers
列表中。 - 另一个循环用于打印
numbers
列表中的所有数字。 - 程序调用
getCount()
方法,将numbers
列表和值1.4作为参数传递,并打印1.4在列表中出现的次数。 - 程序调用
getMaxAndMin()
方法,将numbers
列表作为参数传递,并打印列表中的最大值和最小值。 getCount()
方法接受一个ArrayList<Double>
和一个Double
值作为参数,在列表中计算给定值出现的次数。getMaxAndMin()
方法接受一个ArrayList<Double>
作为参数,并返回一个包含列表中最大值和最小值的数组。
总体而言,该代码读取CSV文件,从特定行提取数值,将其存储在一个列表中,并执行一些操作,如计数出现次数和查找最大值和最小值。
3. 读第三个字段的所有值,输出所有的值、最值、指定数值出现的次数
其实就是读第三列的所有值,输出所有的值、最值、指定数值出现的次数。
CSV文件和上一题一样,省略
读文件方式和上一题差不多,省略相同代码,其实也就只是处理时稍微不一样而已
// 逐行读取文件内容
while ((string = br.readLine()) != null) {// 当行数大于0时,将该行按逗号分割成字符串数组并存储在result中if (line > 0) {String[] r = string.split(",");// 题目要求读第三列的所有值,即下标2的值result[line-1] = r[2];}line ++;
}
// 数组拷贝方法。从源数组中指定的范围复制一部分元素到一个新的数组中。
// Arrays.copyOfRange(源数组, 起始位置, 终止位置)
// 相当于复制下标1->最后的数组给原字符串,同时改变了原数组的长度
result = Arrays.copyOfRange(result, 0, line-1);br.close();
代码大致说明
这段代码读取一个CSV文件的行,跳过标题行,将每行按逗号分割,并将第三列的值存储在 result
数组中。然后,数组被重新调整大小以删除未使用的元素,并且关闭了 BufferedReader
。
代码详细说明
while
循环使用br.readLine()
逐行读取文件的内容,并将其赋值给变量string
。- 条件
string = br.readLine()
检查string
是否不为空,这意味着仍有待读取的行。 - 在循环内部,代码检查
line
是否大于 0。这确保跳过标题行(第 0 行),只处理数据行。 - 如果
line
大于 0,则代码使用逗号(,
)分隔string
,通过string.split(",")
得到一个值的数组。 - 将
r
数组中索引为 2 的值(表示第三列)赋值给result
数组的相应索引(result[line-1] = r[2]
)。 - 处理完每一行后,递增
line
变量的值。 - 一旦所有行都被处理完,代码使用
Arrays.copyOfRange()
方法创建一个名为result
的新数组,它的长度更小。 Arrays.copyOfRange()
方法从源数组中创建一个新数组,从指定的起始索引(0
)开始,到指定的结束索引(line-1
)结束。这有效地删除了result
数组中未使用的元素。- 更新后的
result
数组被重新赋值给result
变量。 - 最后,代码关闭了
BufferedReader
,以释放系统资源。
参考资料
b站视频
1分钟学java:统计csv文件中的数据:https://www.bilibili.com/video/BV1zX4y1Y7PJ
CSDN文章
java 操作文件及问题解决(csv):https://blog.csdn.net/weixin_43763430/article/details/125346370
致谢
感谢群成员 @电子鬼会吓到仿生人吗 提供的 部分代码 和 CSV文件