《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 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 entry : countMap.entrySet()) {
		        System.out.println(entry.getKey() + ": " + entry.getValue());
		    }
		} catch (Exception e) {
		    // 处理异常情况
		    e.printStackTrace();
		}
	}
}
// 输出
下单: 2
收款: 1
发货: 1
付款: 3

代码大致说明

在这段代码中,我们首先定义了一个文件路径 filepath,它指向要读取的CSV文件。然后我们使用 BufferedReaderFileReader 来创建一个读取文件的对象 reader

接下来,我们创建了一个 Map 类型的对象 countMap,用于统计每个关键字出现的次数。

然后,我们使用 reader.readLine() 逐行读取文件内容,将每一行按逗号分隔成字符串数组 data。如果数组长度大于0,说明该行有数据,我们提取关键字 key。然后使用 countMap.getOrDefault(key, 0) + 1 统计关键字出现的次数,并将结果存入 countMap 中。

最后,我们遍历 countMap 中的键值对,并打印出每个关键字及其出现的次数。

代码详细说明

上述代码是一个Java程序,用于读取指定路径下的CSV文件并统计每个关键字出现的次数。让我们逐步分析代码:

  1. main方法是程序的入口点,使用了public static void修饰符,表示它是一个公共静态方法,不返回任何值,接受一个String类型的数组作为参数。
  2. String filepath = "D:\\CSV文件\\统计demo.csv"; 定义了一个字符串变量 filepath,用于存储CSV文件的路径。
  3. try-catch 块中,代码通过创建 BufferedReader 对象来读取文件内容。使用 new BufferedReader(new FileReader(filepath)),传入一个 FileReader 对象,该对象负责读取指定路径下的文件。
  4. 创建了一个 Map 类型的变量 countMap,用于存储统计结果。HashMap 类实现了 Map 接口,可以存储键值对,这里键的类型是 String,值的类型是 Integer
  5. 进入一个 while 循环,读取文件的每一行数据,将读取的结果存储在 line 变量中。循环条件为 line = reader.readLine(),当读取到的行不为空时继续循环。
  6. 在循环中,通过 line.split(",") 使用逗号分隔每一行的数据,并将结果存储在 data 数组中。
  7. 如果 data 数组的长度大于0,表示当前行有数据。代码通过 data[0].trim() 去除首尾空格并获取关键字,将结果存储在 key 变量中。
  8. 通过 countMap.getOrDefault(key, 0) 获取 countMap 中关键字 key 对应的值,如果不存在则返回默认值0。
  9. 使用 countMap.put(key, countMap.getOrDefault(key, 0) + 1) 将关键字 key 的出现次数加1,并将结果保存回 countMap 中。
  10. 循环结束后,通过遍历 countMap 的每个条目,使用 entry.getKey() 获取关键字,使用 entry.getValue() 获取关键字出现的次数,并将结果打印到控制台。
  11. 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 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 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 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 列表中。

接下来,它使用两个辅助方法 getCountgetMaxAndMin 分别计算列表中指定数值出现的次数,以及列表中的最大值和最小值。

getCount 方法中,它遍历列表中的元素,如果元素与给定的数值相等,则计数器 count 自增。

getMaxAndMin 方法中,它首先将列表的第一个元素作为最大值和最小值的初始值。然后遍历列表中的元素,如果当前元素大于最大值,则更新最大值;如果当前元素小于最小值,则更新最小值。最后,它返回一个包含最大值和最小值的数组。

最后,它打印出列表中的所有数值,以及给定数值 1.4 在列表中出现的次数,以及列表中的最大值和最小值。

代码详细说明

给定的代码是一个Java程序,它从名为"Iris.csv"的CSV文件中读取数据,并对数据执行一些操作。让我们逐步分析代码:

  1. 代码开始导入必要的类和包。
  2. 定义了IrisData类,其中包含程序执行开始的主方法。
  3. 创建了一个File对象来表示CSV文件的位置。
  4. 初始化了一个字符串数组result,长度为10。该数组将存储从CSV文件中提取的值。
  5. 定义了一个正则表达式模式regex,用于匹配整数或小数字符串。
  6. 在try-catch块内,创建了FileReaderBufferedReader来读取文件内容。
  7. 代码进入一个循环,使用BufferedReaderreadLine()方法逐行读取文件内容。
  8. 当行号为1时(行号从0开始),代码使用逗号分隔符拆分行,并将结果数组存储在result变量中。使用Arrays.copyOfRange()方法来排除第一个元素,该元素表示ID列。
  9. 循环在读取第一行后中断。
  10. 关闭BufferedReader以释放系统资源。
  11. 创建一个名为numbersArrayList,用于存储从result数组中提取的数字。
  12. 循环遍历result数组,检查每个元素是否与正则表达式模式匹配。如果找到匹配项,将元素转换为Double值并添加到numbers列表中。
  13. 另一个循环用于打印numbers列表中的所有数字。
  14. 程序调用getCount()方法,将numbers列表和值1.4作为参数传递,并打印1.4在列表中出现的次数。
  15. 程序调用getMaxAndMin()方法,将numbers列表作为参数传递,并打印列表中的最大值和最小值。
  16. getCount()方法接受一个ArrayList和一个Double值作为参数,在列表中计算给定值出现的次数。
  17. getMaxAndMin()方法接受一个ArrayList作为参数,并返回一个包含列表中最大值和最小值的数组。

总体而言,该代码读取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

代码详细说明

  1. while 循环使用 br.readLine() 逐行读取文件的内容,并将其赋值给变量 string
  2. 条件 string = br.readLine() 检查 string 是否不为空,这意味着仍有待读取的行。
  3. 在循环内部,代码检查 line 是否大于 0。这确保跳过标题行(第 0 行),只处理数据行。
  4. 如果 line 大于 0,则代码使用逗号(,)分隔 string,通过 string.split(",") 得到一个值的数组。
  5. r 数组中索引为 2 的值(表示第三列)赋值给 result 数组的相应索引(result[line-1] = r[2])。
  6. 处理完每一行后,递增 line 变量的值。
  7. 一旦所有行都被处理完,代码使用 Arrays.copyOfRange() 方法创建一个名为 result 的新数组,它的长度更小。
  8. Arrays.copyOfRange() 方法从源数组中创建一个新数组,从指定的起始索引(0)开始,到指定的结束索引(line-1)结束。这有效地删除了 result 数组中未使用的元素。
  9. 更新后的 result 数组被重新赋值给 result 变量。
  10. 最后,代码关闭了 BufferedReader,以释放系统资源。

参考资料

b站视频

1分钟学java:统计csv文件中的数据:https://www.bilibili.com/video/BV1zX4y1Y7PJ

CSDN文章

java 操作文件及问题解决(csv):https://blog.csdn.net/weixin_43763430/article/details/125346370

致谢

感谢群成员 @电子鬼会吓到仿生人吗 提供的 部分代码 和 CSV文件

你可能感兴趣的:(java,学习,笔记)