Java文件读取与下载

今天学了文件读取与下载的七种方法。

(每次在ide里新建了md,再进入typora里编辑,都会出现一点事故-输入失效,光标跳行)

这篇文章主要是一些代码解析。是什么?干什么用?怎么用?

法一:使用java.nio.file.Files读取文本

核心函数:Files.readAllBytes()

该方法适合读取小文件(相较于内存而言)内存消耗: 由于该方法一次性将文件的所有行读取到内存中,因此不适合处理非常大的文件,否则可能导致内存不足问题。

NIO:(New Input/Output) 是 Java 中用于处理非阻塞 I/O 操作的 API,提供了更加高效的文件和网络 I/O 处理能力

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
import java.io.IOException;
import java.nio.charset.StandardCharsets;//StandardCharsets 用于定义字符编码标准。
import java.nio.file.Files;//Files 提供静态方法用于文件操作。
import java.nio.file.Path;//
import java.nio.file.Paths;//Path 和 Paths 用于处理文件和目录路径。
import java.util.List;//List 用于存储文件中的每一行。
public class ReadFiles {
public static void main(String[] args) throws IOException {//
String fileName = "E:\\Java\\JWeb\\data\\test.txt";//使用Java 7中的Files类处理小文件,获取完整的文件数据
readUsingFiles(fileName);
}
private static void readUsingFiles(String fileName) throws IOException {
Path path = Paths.get(fileName);//使用Paths.get方法将文件路径字符串转换为Path对象。
byte[] bytes = Files.readAllBytes(path);//使用Files.readAllBytes方法将文件的内容读取到一个字节数组中。
System.out.println("使用File类读取文件.........");
System.out.println(bytes);//Java 的 byte[] 对象的 toString() 方法默认会返回对象的哈希码
System.out.println(new String(bytes));//将字节类型的数组转换为字符串类型然后输出

@SuppressWarnings("unused")//@SuppressWarnings("unused") 应用于该方法的声明,以抑制未使用方法的警告。
List<String> allLines = Files.readAllLines(path, StandardCharsets.UTF_8);//用于将指定文件的所有行读取为一个 List<String>,每一行作为列表中的一个元素,并以指定的字符编码读取文件内容。(自动转换)
System.out.println(allLines);
//System.out.println(new String(allLines, StandardCharsets.UTF_8));//因为 List<String> 不能直接转换为 String。new String() 构造函数需要一个字节数组而不是一个字符串列表。
System.out.println( (path));
String content=String.join("\n", allLines);
System.out.println(content);

}
}

法二:java.io.FileReader

适合处理小到中等大小的文本文件。对于大文件或需要支持不同编码的文件,可以考虑使用更高级的文件读取方式。

核心函数:

1
2
3
4
5
6
FileReader fr=new FileReader(path);
BufferedReader br=new BufferedReader(fr);
String line;
while((line=br.readLine())!=null){
System.out.println(line);
}

整体解析:

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
//读取一个文本文件并在控制台逐行打印出来
import java.io.BufferedReader;//BufferedReader 和 FileReader 用于读取文件
import java.io.File;//File 用于处理文件路径,
import java.io.FileReader;
import java.io.IOException;
public class ReadFileReader {
public static void main(String[] args) throws IOException {
String fileName = "E:\\Java\\JWeb\\data\\test.txt";
//使用FileReader读取,没有编码支持,效率不高
readUsingFileReader(fileName);
}
private static void readUsingFileReader(String fileName) throws IOException {
File file = new File(fileName);//File file = new File(fileName); 创建一个 File 对象,表示文件路径。
FileReader fr = new FileReader(file);// 使用 FileReader 创建一个文件读取对象fr。FileReader 只能读取字符流,不支持编码,效率较低。
//FileReader可接受file实例,也可接收String变量
System.out.println("Beginning");
System.out.println(fr.read());//读取一个字符,输出该字符的10进制数
BufferedReader br = new BufferedReader(fr);// BufferedReader 接收一个reader对象,包装 FileReader,提供缓冲功能,提高读取效率。
System.out.println(br.readLine());
String line;
System.out.println("使用FileReader读取文本文件......");
while((line = br.readLine()) != null){//BufferedReader 的当前缓冲区位置开始,读取字符,直到遇到行终止符(如 \n 或 \r\n)或到达流的末尾。readLine()
//逐行读取
System.out.println(line);
}
br.close();
fr.close();
}
}
//适合处理小到中等大小的文本文件。对于大文件或需要支持不同编码的文件,可以考虑使用更高级的文件读取方式,例如 InputStreamReader 和 BufferedReader 的组合来支持指定编码的读取。

法三:java.io.BufferedReader(法二)

特征描述:

适用于处理大文件, 也支持编码。

BufferedReader 是同步的,因此可以安全地从多个线程完成对 BufferedReader 的读取操作。

BufferedReader 的默认缓冲区大小为: 8KB 。

核心函数:

1
2
3
4
FileInputStream fis=new FileInputStream(fileName);
InputStreamReader isr=new InputStreamReader(fis);
FileReader fr=new FileReader(fileName);
BufferedReader br=new BufferedReader(isr);

整体解析:(编码方式对文件读取的影响)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.io.*;
import java.nio.charset.StandardCharsets;
public class ReadBufferedReader {
public static void main(String[] args) throws IOException {
String fileName = "E:\\Java\\JWeb\\data\\test.txt";
//使用BufferedReader读取,逐行读取,并设置编码为UTF_8
readUsingBufferedReader(fileName);
}
private static void readUsingBufferedReader(String fileName)//
throws IOException {
File file = new File(fileName);
FileInputStream fis = new FileInputStream(file);//以原始的字节流读入文件中的内容
InputStreamReader isr = new InputStreamReader(fis, StandardCharsets.UTF_8);//以UTF_8解释fis输入的文件信息;并且该reader默认读取方式为utf-8.故可省略
BufferedReader br = new BufferedReader(isr);
String line;
System.out.println("使用BufferedReader读取文本文件......");
while((line = br.readLine()) != null){
//逐行读取
System.out.println(line);
}
br.close();
}
}

法四:Scanner

特征描述:

Scanner 类:

  1. 逐行读取文件或
  2. 某些java正则表达式读取文件
  3. 处理大文件

Scanner 类使用分隔符模式将其输入分解为标记,分隔符模式默认匹配空格。

然后可以使用各种下一种方法将得到的标记转换成不同类型的值。

Scanner 类不同步,因此不是线程安全的。

核心函数:

1
2
3
4
5
6
Scanner sc=new  Scanner(path);
String line;
while(sc.hasNextLine()){
line=sc.nextLine();
System.out.println(line);
}

法五:RandomAccessFile断点续传

特征描述:

随机流(RandomAccessFile)不属于IO流,支持对文件的读取和写入随机访问。

首先把随机访问的文件对象看作存储在文件系统中的一个大型 byte 数组,然后通过指向该 byte 数组的 光标或索引(即:文件指针 FilePointer)在该数组任意位置读取或写入任意数据。

断点续传是在下载或上传时,将下载或上传任务(一个文件或一个压缩包)人为的划分为几个部分,每 一个部分采用一个线程进行上传或下载,如果碰到网络故障,可以从已经上传或下载的部分开始继续上 传或者下载未完成的部分,而没有必要从头开始上传或者下载。

断点续传实现原理:

  1. 下载断开的时候,记录文件断点的位置position;
  2. 继续下载的时候,通过RandomAccessFile找到之前的position位置开始下载

note:

  1. 不支持编码后的文件

核心函数:

1
RandomAccessFile file = new RandomAccessFile(fileName, "r");

r:只读

rw:读写

rws :Open for reading and writing, as with “rw”, and also require that every update to the file’s content or metadata be written synchronously to the underlying storage device.

rwd:Open for reading and writing, as with “rw”, and also require that every update to the file’s content be written synchronously to the underlying storage device.

整体实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.io.IOException;
import java.io.RandomAccessFile;


public class MyRandomAccessFile {
public static void main(String[] args) throws IOException {
String fileName = "E:\\Java\\JWeb\\data\\test.txt";
RandomAccessFileReading(fileName);
}

private static void RandomAccessFileReading(String fileName) throws IOException {
RandomAccessFile randomAccessFile = new RandomAccessFile(fileName,"r");
System.out.println(randomAccessFile);
String line;
while((line=randomAccessFile.readLine())!=null){
System.out.println(line);
}
randomAccessFile.close();
}
}

法六:外部库 org.apache.commons.io.FileUtils.readFileToString()

特征描述:

  1. it might not be suitable for very large files, as it loads the entire file into memory 不适合大文件(相较内存而言)
  2. FileUtils.readFileToString() method to read the entire content of the file into a String.将整个文件读入内存
  3. 需要安装依赖项:Commons-io

核心函数:

1
System.out.println(FileUtils.readFileToString(file, StandardCharsets.UTF_8));

整体解析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

public class MyReadCommonsIo {
public static void main(String[] args) throws IOException {
String fileName = "E:\\Java\\JWeb\\data\\test.txt";
ReadCommonsIoReading(fileName);
}

private static void ReadCommonsIoReading(String fileName) throws IOException {
System.out.println(FileUtils.readFileToString(new File(fileName), StandardCharsets.UTF_8));

}
}

法七:Files.readString()

整体实现:

1
2
3
4
5
6
7
8
9
10
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

public class MyreadString {
public static void main(String[] args) throws IOException {
String fileName = "E:\\Java\\JWeb\\data\\test.txt";
System.out.println(Files.readString(Path.of(fileName)));
}
}

Web工程

1
2
3
4
http://127.0.0.1:8080/readUsingRandomAccessFile?fileName=E:\Java\JWeb\data\test.txt
http://127.0.0.1:8080/readUsingRandomAccessFile?fileName=E:/Java/JWeb/data/test.txt
http://127.0.0.1:8080/readUsingFiles?fileName=E:/Java/JWeb/data/test.txt
//注意访问用正斜杠
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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
package org.example.pfilereader;

import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
//import org.springframework.stereotype.Controller; 和 import org.springframework.web.bind.annotation.RequestMapping;
// 是Spring MVC的核心注解,分别用于声明控制器和映射HTTP请求。
import org.apache.commons.io.FileUtils;

import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Scanner;

@Controller
//接收用户输入并处理相应的业务逻辑,然后返回视图或直接返回数据。
//任何带有 @Controller 注解的类被Spring识别为一个Web控制器。
@ResponseBody
//用于将控制器方法的返回值直接作为HTTP响应体返回给客户端。读取文件内容并返回到浏览器的需求
public class ReadFilesController {

@RequestMapping("/readUsingFiles")
//用 @RequestMapping 或类似的注解来映射特定的URL请求到控制器方法。
public String readUsingFiles(String fileName, HttpServletResponse response) throws IOException {
Path path= Paths.get(fileName);
byte[] bytes= Files.readAllBytes(path);
System.out.println("使用File类处理小文件,获取完整的文件数据…………");

@SuppressWarnings("unused")
List<String> allLines=Files.readAllLines(path, StandardCharsets.UTF_8);

//response.reset();//response 代码部分可以将文件内容以附件形式下载,而不是直接在浏览器中显示。
//response.setContentType("application/octet-stream");
//response.setHeader("Content-Disposition", "attachment; filename="+ URLEncoder.encode(fileName,"UTF-8"));

//上面部分注释掉之后,文件内容直接·在·浏览器中显示
System.out.println(new String(bytes));
return new String(bytes);
}

@RequestMapping("/readUsingFileReader")
public void readUsingFileReader(String fileName, HttpServletResponse response) throws IOException {
File file=new File(fileName);
FileReader fr=new FileReader(file);
//FileReader 默认使用平台的默认字符编码(例如,Windows 上是 Windows-1252,Linux 上通常是 UTF-8),不能指定其他编码方式。
//需注意文件的字符编码与你的系统默认编码一致,
BufferedReader br=new BufferedReader(fr);
String line;
System.out.println("使用FileReader读取文本文件......");

response.reset();
//发送响应之前清除响应的缓冲区和HTTP头信息
//如果在调用 response.reset() 之前已经写入了一些数据到响应缓冲区,这些数据将会被丢弃。
response.setContentType("application/octet-stream");
//"application/octet-stream" 是一种 MIME 类型,表示任意的二进制数据。
//主要作用
//告知浏览器处理二进制数据:内容类型被设置为 "application/octet-stream" 时,浏览器不会尝试直接显示内容(例如在浏览器中显示图片或播放视频),而是将其视为一种二进制文件。
//触发文件下载:由于 "application/octet-stream" 不指定特定的文件类型,浏览器通常会弹出一个下载对话框,提示用户保存文件。这使得该 MIME 类型常用于提供文件下载功能,尤其是当你不知道或不确定文件的具体类型时。
response.setHeader("Content-Disposition", "attachment; filename="+ URLEncoder.encode(fileName,"UTF-8"));
//Content-Disposition 是一个 HTTP 响应头,用于指示浏览器如何处理响应内容。
// 当值为 "attachment" 时,浏览器会将内容视为附件,并提示用户下载,而不是在浏览器中直接显示。
//URLEncoder.encode(fileName,"UTF-8") 用于对文件名进行 URL 编码。这样可以确保文件名中的特殊字符(如空格、非 ASCII 字符等)在通过 HTTP 协议传输时不会出现问题。

PrintWriter pw=response.getWriter();
while((line=br.readLine())!=null){
System.out.println(line);
pw.println(line);
}
br.close();
fr.close();
}

@RequestMapping("/readBufferedReader")
public void readBufferedReader(String fileName, HttpServletResponse response) throws IOException {
File file=new File(fileName);
FileInputStream fis=new FileInputStream(file);
InputStreamReader isr=new InputStreamReader(fis);
//InputStreamReader 是一个字节流到字符流的桥梁,它将字节流(InputStream)解码为字符流。
// 通过在 InputStreamReader 中指定字符编码(如 UTF-8),精确控制如何解码字节流中的字符
BufferedReader br=new BufferedReader(isr);
String line;
response.reset();
response.setContentType("application/octet-stream");
//让浏览器直接显示文件(例如图片或PDF),而不是提示下载,可以设置 Content-Disposition 为 "inline":
response.addHeader("Content-Disposition", "inline; filename="+ URLEncoder.encode(fileName,"UTF-8"));
PrintWriter pw=response.getWriter();
while((line=br.readLine())!=null){
System.out.println(line);
pw.println(line);
}
br.close();
}
@RequestMapping("/readScanner")
public void readScanner(String fileName, HttpServletResponse response) throws IOException {
Path path= Paths.get(fileName);
Scanner scanner=new Scanner(path);
System.out.println("使用Scanner读取文本文件.....");
response.reset();
response.setContentType("application/octet-stream");
response.addHeader("Content-Disposition", "attachment; filename="+ URLEncoder.encode(fileName,"UTF-8"));
PrintWriter pw=response.getWriter();

while (scanner.hasNextLine()){
pw.println(scanner.nextLine());
System.out.println(scanner.nextLine());
}
scanner.close();
}

@RequestMapping("/readUsingRandomAccessFile")
public void readUsingRandomAccessFile(String fileName, HttpServletResponse
response) throws IOException{
RandomAccessFile file = new RandomAccessFile(fileName, "r");
String str;
response.addHeader("Content-Disposition", "attachment; filename="+ URLEncoder.encode(fileName,"UTF-8"));
PrintWriter out = response.getWriter();
while ((str = file.readLine()) != null) {
System.out.println("使用RandomAccessFile来实现断点续传读取/下载文件......");
System.out.println(str);
out.print(str);
}
file.close();
}
@RequestMapping("/readUsingCommonsIo")
public String readUsingCommonsIo(String fileName,HttpServletResponse
response) throws IOException {
File file = new File(fileName);
response.reset();
response.setContentType("application/octet-stream");
response.addHeader("Content-Disposition", "attachment; filename=" +
URLEncoder.encode(fileName, "UTF-8"));
System.out.println("使用Commons-io读取文件......");
System.out.println(FileUtils.readFileToString(file,
StandardCharsets.UTF_8));
return FileUtils.readFileToString(file, StandardCharsets.UTF_8);
}
}

其他

回顾一下java的基础类型:

image-20240808103559902

List:List 的特点是存取有序,可以存放重复的元素

List 类型可转化为 ArrayList 类型,不可转化为LinkedList类型

:movie_camera:ArrayList 类型:

  1. ArrayList 实现了 List 接口,并且是基于数组实现的
  2. ArrayList 在数组的基础上实现了自动扩容

LinkedList类型:链表

  1. LinkedList 是由双向链表实现的,不支持随机存取,只能从一端开始遍历,直到找到需要的元素后返回;
  2. 任意位置插入和删除元素都很方便,因为只需要改变前一个节点和后一个节点的引用即可,不像 ArrayList 那样需要复制和移动数组元素;
  3. 因为每个元素都存储了前一个和后一个节点的引用,所以相对来说,占用的内存空间会比 ArrayList 多一些。

线程安全

当多个线程同时访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那就称这个对象时线程安全的。

指某个函数、函数库在多线程环境中被调用时,能够正确地处理多个线程之间的公用变量,使程序功能正确完成。

编程语言的动静强弱:

image-20240808103701867

public、protected、private

public:包内、包外.可以被所有其他类所访问

protected:包内.自身,子类及同一个包中类可以访问

private:本类,只能被自己访问和修改

参考链接

https://javabetter.cn/nio/moxing.html java的学习教程,看起来很精致

https://cloud.tencent.com/developer/article/1332131

https://javabetter.cn/gongju/choco.html#%E5%B0%8F%E7%BB%93 值得学习的好工具:Chocolatey:一款 GitHub 星标 8.2k+ 的 Windows 命令行软件管理器