io流是很基础,很常用的工具。掌握好会很有用。
另,nio比io功能强大了不少,建议了解下。
为了方便演示,try catch,finally代码在这里不写了,实际中一定要加上。
相关的文件要先准备好。
inputstream是一个接口提供读的方法。
实际中要和FileInputStream配合
脉络
- InputStream
- InputStreamReader # 可以读char
- FileInputStream # 可以传入文件名
- Reader
- InputStreamReader
- FileReader
- BufferedReader
FileInputStream读取
例子代码:
public static void main(String[] args) throws Exception {
String userHome = System.getProperties().getProperty("user.home"); // 用户目录,如:C:\Users\chushiyun
String fileName = userHome+"/01.png"; // 文件路径
InputStream in = new FileInputStream(fileName);
byte[] bytes = new byte[100]; // 一次读取多少
int temp = 0; // 记录读取的byte数
// read() 读取一个字节
// read(bytes) 读满bytes字节
// read(bytes,5,20) 从5开始 读取20个字节
while ((temp = in.read(bytes,5,20)) != -1) { // 实际中别这么写
System.out.println(bytes);
}
}
单字节读取有个缺点,如果文件很大,要和磁盘做很多次交互,非常耗时。
多字节指定一次读取的字节,减少和io流的交互,速度会提高很多。
曾经做过测试1G文件测试:
单字节读取 30分钟没有读完,不等了。
100长度的bytes读取 24秒。
Reader
为什么要引入reader?
字节流很快,但有时我们需要要查看或操作读取的数据,还需要转换。
reader按字符来读取,可以直接展示。
Reader和InputStream最大的区别就是一个读取byte一个读取char。
public static void main(String[] args) throws Exception {
String userHome = System.getProperties().getProperty("user.home"); // 用户目录,如:C:\Users\chushiyun
String fileName = userHome+"/ttt.txt"; // 文件路径
Reader reader = new InputStreamReader(new FileInputStream(fileName));
char[] chars=new char[100]; // 100个字符的容器
int temp;
while ((temp = reader.read(chars,5,20)) != -1) { //实际中别这么写
System.out.println(chars);
}
}
按行读取文件和输出文件
代码:
public static void main(String[] args) {
try {
// 设置输出目录
String outputDirectory= "C:\\Users\\chushiyun\\Desktop\\xiaoxiang-output";
// 获取目录下所有文件
List<String> files = FileUtils.getFiles("C:\\Users\\chushiyun\\Desktop\\xiaoxiang-prd");
for (String file: files) {
// 获得File对象,当然也可以获取输入流对象
File fileInput = new File(file);
File fileOutput = new File(outputDirectory+"\\"+fileInput.getName());
BufferedReader bufferedReader = new BufferedReader(new FileReader(fileInput));
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(fileOutput));
String line = null;
while ((line = bufferedReader.readLine() )!=null ) {
logger.info("读取line的内容为: {}",line);
// 包含password,这个toLowerCase() 很精髓,因为配置文件里面命名很不规则,既有Password,也有passWord
// 不是注释
// 不包含ENC,防止重复编码
// 不包含salt值
if(line.toLowerCase().contains("password")
&& !line.startsWith("#")
&& !line.contains("ENC")
&& !line.contains("MazBcy1F6AwLwhkaPkg")) {
if (file.endsWith("yml")) {
line = JasyptStaticUtils.handlePasswordYml(line);
} else if (file.endsWith("properties")) {
line = JasyptStaticUtils.handlePasswordProperties(line);
}
bufferedWriter.write(line+"\r\n");
}else{
bufferedWriter.write(line+"\r\n"); // 原样输出
}
}
bufferedWriter.flush();
bufferedReader.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
读取目录下的所有文件(不递归所有目录)
代码:
public class FileUtils {
private static org.slf4j.Logger logger = LoggerFactory.getLogger(FileUtils.class);
public static List<String> getFiles(String directory) {
List<String> files = new ArrayList<String>();
File file = new File(directory);
File[] tempList = file.listFiles();
for (int i = 0; i < tempList.length; i++) {
if (tempList[i].isFile()) {
files.add(tempList[i].toString());
//文件名,不包含路径
//String fileName = tempList[i].getName();
}
if (tempList[i].isDirectory()) {
// 文件夹递归 这里不递归
}
}
logger.info("目录{} 的文件列表是: {}", directory,JSON.toJSON(files));
return files;
}
public static void main(String[] args) {
getFiles("C:\\Users\\chushiyun\\Desktop\\xiaoxiang-prd");
}
}
bufferWriter 为什么最后要flush一下
首先要知道bufferWriter的作用是什么?
提供缓冲,先将内容放到buffer中。这样减少和io的交互次数,提高性能。
bufferWriter什么时候把数据写到文件?
1、bufferWriter满了之后,会自动写。
2、调用bufferWriter.flush() 方法。
当while循环结束时,bufferWriter不一定是满的,所以要手动flush,将里面的数据写到文件里,否则少内容。
bufferWriter设置大小无效
设置了buffer的大小,但是没有效果,也不知道为什么?
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(fileOutput),2);
写文件无效
用idea或者eclipse的时候,有时会发现写不到文件里面。 这是什么原因呢?
如果用的class路径。 那么很有可能出现这个问题。 因为我们代码执行的目录是target,而我们idea中看到的是源码路径。 输出文件输出到target下了。 所以我们在idea中看不到。
字节流读取并写入到一个文件
代码:
public static void main(String[] args) {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream("D://a.jpg");
fos = new FileOutputStream("D://a-dest.jpg");
//io读数据的时候,数据存的位置(相当于传输数据的管子)
byte bs[] = new byte[1024];
int i = 0;
//read()方法返回的int类型,是表示数据下一个字节的字节码,如果已经到达流的最后面了,那就返回-1
while(i!=-1){
//read()的内容就写入新的文件
fos.write(bs,0,i);
i= fis.read(bs);
}
System.out.println("数据复制完成");
}
restTempalte访问rest服务(小文件一次性)
这种方式一次性读取,大文件的时候不好用。
代码:
@ResponseBody
@RequestMapping(value="/httpGetFile",name="http请求获取文件")
public String httpGetFile(HttpResponse response){
// 待下载的文件地址
String url = "http://test.download.net/api/download/17091547d75244e59d7450c73bddb581";
ResponseEntity<byte[]> rsp = restTemplate.getForEntity(url, byte[].class);
System.out.println("文件下载请求结果状态码:" + rsp.getStatusCode());
// 将下载下来的文件内容保存到本地
String targetPath = "D://7306.ofd";
try {
Files.write(Paths.get(targetPath), Objects.requireNonNull(rsp.getBody(),
"未获取到下载文件"));
} catch (IOException e) {
e.printStackTrace();
}
return "success";
}
restTemplate访问rest服务(大文件以流的方式)
流的方式读取,大文件也好用。
代码:
@ResponseBody
@RequestMapping(value="/httpGetBigFile",name="http请求获取大文件")
public String httpGetBigFile(HttpResponse response){
// 待下载的文件地址
String url = "http://test.download.net/api/docs/17091547d75244e59d7450c73bddb581";
// 文件保存的本地路径
String targetPath = "D://7306.ofd";
//定义请求头的接收类型
RequestCallback requestCallback = request -> request.getHeaders()
.setAccept(Arrays.asList(MediaType.APPLICATION_OCTET_STREAM, MediaType.ALL));
//对响应进行流式处理而不是将其全部加载到内存中
restTemplate.execute(url, HttpMethod.GET, requestCallback, clientHttpResponse -> {
Files.copy(clientHttpResponse.getBody(), Paths.get(targetPath));
return null;
});
return "success";
}
返回文件流(即下载)
原始的response返回文件流(response实现下载)
代码:
@ResponseBody
@GetMapping("/responseDownload")
public void responseDownload(HttpServletResponse response) throws IOException {
String fileName = "我的.jpg";
// 获取输入流
FileInputStream fis = new FileInputStream("d://" + fileName);
// 输出流
ServletOutputStream sos = response.getOutputStream();
response.setHeader("Content-Disposition","attachment;filename="+new String(fileName.getBytes(),StandardCharsets.ISO_8859_1));
// 输出
int len = 1;
byte[] b = new byte[1024];
while ((len = fis.read(b)) != -1) {
sos.write(b, 0, len);
}
fis.close();
sos.close();
}
那么,如果返回值设置为String,还可以下载吗?
实测可以,但是界面效果还是下载。返回的字符串不知道有什么用。
spring返回文件流(spring实现下载)
代码:
@ResponseBody
@RequestMapping(value="/downloadDemo",name="下载演示")
public ResponseEntity<byte[]> downloadDemo(HttpResponse response){
String fileName="我的.jpg";
File file = new File("d://"+fileName);
HttpHeaders headers = new HttpHeaders();
try {
headers.setContentDispositionFormData("fileName", new String(fileName.getBytes(),"ISO-8859-1"));
// headers.setContentDispositionFormData("fileName", fileName); // 如果不设置为ISO-8859-1的话,中文会乱码
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
ResponseEntity<byte[]> result = null;
try {
result = new ResponseEntity<byte[]>(FileUtil.readAsByteArray(file), headers,
HttpStatus.OK);
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
原始的response返回文件流的方式(jsonResult这种形式不好,仅供参考)
/*
* 下载文件
*/
@RequestMapping("/web/file/downloadFile")
public JsonResult downloadFile(@RequestParam(name = "filePath") String filePath, HttpServletResponse response) {
String methodName = "文件功能_下载文件";
JsonResult result = new JsonResult<>();
try {
log.info(methodName + "方法开始,filePath={}", filePath);
// 这里相当于一个完全的地址来下载了
CustomAssertUtils.isNotEmpty(filePath, "filePath不能为空");
File file = new File(filePath);
String fileName = filePathUtils.generateFileNameFromPath(filePath);
FileInputStream in = new FileInputStream(file);
response.setContentType("application/octet-stream");
response.setContentLength((int) file.length());
String headerKey = URLEncoder.encode(fileName, "UTF-8");
response.setHeader("Content-Disposition", "attachment; filename=\"" + headerKey + "\"");
// 输出流写入客户端
BufferedInputStream bis = new BufferedInputStream(in);
OutputStream out = response.getOutputStream();
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = bis.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
bis.close();
out.close();
log.info(methodName + "方法完成");
return result;
} catch (Exception e) {
log.error(methodName + "方法异常,error=", e);
return JsonResult.error("-1", methodName + "方法异常,error=" + e.getMessage());
}
}
注:文件名乱码的问题,用这行代码就可以解决。
String headerKey = URLEncoder.encode(fileName, "UTF-8");
为什么要编码呢? 因为文件名是在url中传给客户端的,不支持中文,所以要先编码,到达客户端(这里的客户端是浏览器)会自动进行解码。
下载文件(推荐)
1、正确方式应该只有两种,void或ResponseEntity
2、返回的格式只能是确定的一种
方案可以这样做
(1)成功时返回文件流,即下载文件
(2)失败时返回json报文,但是不是通过返回值定义的,而是response.write写的json结构体
@RequestMapping("/file/download")
public void download(HttpServletResponse response, String relativePath){
String methodName = "下载文件"; // 建议将这里的“上传文件”修正为“下载文件”
try {
// 1. 安全校验:将相对路径转换为绝对路径,并校验是否越权
File baseDir = new File(rootFilePath); // 允许的根目录
File targetFile = new File(baseDir, relativePath).getCanonicalFile();
if (!targetFile.exists() || !targetFile.getPath().startsWith(baseDir.getCanonicalPath())) {
// response.sendError(HttpServletResponse.SC_NOT_FOUND, "File not found or access denied");
throw new IllegalArgumentException("文件未找到或无权限");
}
// 2. 设置规范的响应头
response.setContentType("application/octet-stream");
String originalFileName = targetFile.getName();
String decodedFileName = URLDecoder.decode(originalFileName, StandardCharsets.UTF_8);
ContentDisposition disposition = ContentDisposition.attachment()
.filename(decodedFileName, StandardCharsets.UTF_8)
.build();
response.setHeader("Content-Disposition", disposition.toString());
response.setHeader("Content-Length", String.valueOf(targetFile.length()));
// 防止 Nginx 等反向代理缓存整个响应体
response.setHeader("X-Accel-Buffering", "no");
// 3. 使用 NIO 零拷贝技术进行高效流式传输
try (RandomAccessFile raf = new RandomAccessFile(targetFile, "r");
FileChannel channel = raf.getChannel();
OutputStream os = response.getOutputStream();
WritableByteChannel wbChannel = Channels.newChannel(os)) {
// transferTo 直接在内核空间传输,绕过 JVM 堆内存,性能极高
channel.transferTo(0, channel.size(), wbChannel);
} catch (IOException e) {
// 客户端中途取消下载会抛出异常,生产环境应捕获并优雅处理
log.warn("文件下载中断或失败: {}", targetFile.getName(), e.getMessage());
}
} catch (IllegalArgumentException e){
log.error(methodName + " 操作失败, error=", e);
IOResultUtils.error(response,HttpServletResponse.SC_INTERNAL_SERVER_ERROR,"-1",e.getMessage());
} catch (Exception e){
log.error(methodName + " 操作异常, error=", e);
IOResultUtils.error(response,HttpServletResponse.SC_INTERNAL_SERVER_ERROR,"-1",e.getMessage());
}
}
IOResultUtils代码见另外一篇笔记。
文件名fileName
主要有两部分:设置文件名、获取文件名。
设置文件名
关键点就一个,header里面不能传中文,所以要先转换为iso-8859-1编码。
response.setHeader("Content-Disposition","attachment;filename="
+new String(fileName.getBytes(),StandardCharsets.ISO_8859_1));
如何从文件流中获取文件名
常用的有两种方法:
URL url = new URL(downloadUrl);
conn = (HttpURLConnection)url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(20 * 1000);
final ByteArrayOutputStream output = new ByteArrayOutputStream();
IOUtils.copy(conn.getInputStream(),output);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(output.toByteArray());
String fileName = "";
String raw = conn.getHeaderField("Content-Disposition");
if (raw != null && raw.indexOf("=") > 0) {
fileName = raw.split("=")[1];
fileName = new String(fileName.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
}
方法二:
// 方法二
String newUrl = conn.getURL().getFile();
if (newUrl == null || newUrl.length() <= 0) {
newUrl = java.net.URLDecoder.decode(newUrl, "UTF-8");
int pos = newUrl.indexOf('?');
if (pos > 0) {
newUrl = newUrl.substring(0, pos);
}
pos = newUrl.lastIndexOf('/');
fileName = newUrl.substring(pos + 1);
}
MultipartFile获取文件名很方便
MultipartFile 那么获取文件名很简单,file.getOriginalFilename(); 就可以获取到。
所以上传文件的时候不用上传文件名,不用上传文件名,不用上传文件名。
multipartFile和file相互转换
根据file获取multipartFile:
MultipartFile multipartFile = new MockMultipartFile("copy"+file.getName(),file.getName(),ContentType.APPLICATION_OCTET_STREAM.toString(),fileInputStream);
multipartFile转换为file:
String fileName = file.getoriginalFilename();
byte[]filecontent = file.getBytes();
file.transferTo(new File("path/to/save/"+ fileName));
fileController 上传和根据url下载文件
/*
* 定制文件功能
*/
@RestController
@RequestMapping("/file/interface")
public class FileController {
private static Logger logger = LoggerFactory.getLogger(FileController.class);
@Value("${fileUploadPath:d:/data/upload/jxcustom}")
private String fileUploadPath;
@ResponseBody
@RequestMapping(value="/upload",name="文件上传")
public JsonResult upload(@RequestParam(value="file",required = false) MultipartFile file){
try {
JsonResult result = new JsonResult<>();
logger.info("定制文件功能_文件上传请求开始");
if(file==null || file.isEmpty()){
return JsonResult.error("-1","file是必填项");
}
// 保存文件
Path filePath = Paths.get(fileUploadPath + File.separator + file.getName());
logger.info("定制文件功能_文件上传,filePath={}",filePath.toString());
long copyResult = Files.copy(file.getInputStream(), filePath);
result=JsonResult.success(copyResult);
logger.info("定制文件功能_文件上传请求完成,result={}", JSON.toJSONString(result));
return result;
}catch (Exception e){
logger.error("定制文件功能_文件上传请求异常,error=",e);
return JsonResult.error("-1","定制文件功能_文件上传请求异常");
}
}
/*
* 下载byUrl
*/
@ResponseBody
@RequestMapping(value="/downloadByUrl",name="文件上传")
public JsonResult downloadByUrl(@RequestBody FileDownloadRequest request){
JsonResult result = new JsonResult<>();
String methodName="定制文件功能_下载byUrl";
InputStream inputStream = null;
OutputStream outputStream = null;
try {
logger.info(methodName+"请求开始,request={}",JSON.toJSONString(request));
if(StringUtils.isEmpty(request.getUrl())){
logger.info(methodName+"请求失败,url不能为空");
return JsonResult.error("-1",methodName+"请求失败,url不能为空");
}
if(StringUtils.isEmpty(request.getFileName())){
logger.info(methodName+"请求失败,fileName不能为空");
return JsonResult.error("-1",methodName+"请求失败,fileName不能为空");
}
URL url = new URL(request.getUrl());
//这里没有使用 封装后的ResponseEntity 就是也是因为这里不适合一次性的拿到结果,放不下content,会造成内存溢出
HttpURLConnection connection =(HttpURLConnection) url.openConnection();
//使用bufferedInputStream 缓存流的方式来获取下载文件,不然大文件会出现内存溢出的情况
inputStream = new BufferedInputStream(connection.getInputStream());
String path=fileUploadPath+File.separator +request.getFileName();
File file = new File(path);
if (file.exists()) {
file.delete();
}
outputStream = new FileOutputStream(file);
//这里也很关键每次读取的大小为5M 不一次性读取完
byte[] buffer = new byte[1024 * 1024 * 5];// 5MB
int len = 0;
while ((len = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, len);
}
connection.disconnect();
return result;
}catch (Exception e){
logger.error(methodName+"请求异常,error=",e);
return JsonResult.error("-1",methodName+"请求异常");
}finally {
IOUtils.closeQuietly(outputStream);
IOUtils.closeQuietly(inputStream);
logger.info(methodName+"请求关闭流完毕");
}
}
}
注:上传的功能是否好用先持保留态度,根据url下载实测可用。(解决了应急问题)
流式下载(零拷贝下载)
@RequestMapping("/file/download")
public JsonResult download(HttpServletResponse response, String relativePath){
String methodName = "下载文件"; // 建议将这里的“上传文件”修正为“下载文件”
JsonResult result = JsonResult.ok();
try {
// 1. 安全校验:将相对路径转换为绝对路径,并校验是否越权
File baseDir = new File(rootFilePath); // 允许的根目录
File targetFile = new File(baseDir, relativePath).getCanonicalFile();
if (!targetFile.exists() || !targetFile.getPath().startsWith(baseDir.getCanonicalPath())) {
response.sendError(HttpServletResponse.SC_NOT_FOUND, "File not found or access denied");
throw new IllegalArgumentException("文件未找到或无权限");
}
// 2. 设置规范的响应头
response.setContentType("application/octet-stream");
String originalFileName = targetFile.getName();
String decodedFileName = URLDecoder.decode(originalFileName, StandardCharsets.UTF_8);
ContentDisposition disposition = ContentDisposition.attachment()
.filename(decodedFileName, StandardCharsets.UTF_8)
.build();
response.setHeader("Content-Disposition", disposition.toString());
response.setHeader("Content-Length", String.valueOf(targetFile.length()));
// 防止 Nginx 等反向代理缓存整个响应体
response.setHeader("X-Accel-Buffering", "no");
// 3. 使用 NIO 零拷贝技术进行高效流式传输
try (RandomAccessFile raf = new RandomAccessFile(targetFile, "r");
FileChannel channel = raf.getChannel();
OutputStream os = response.getOutputStream();
WritableByteChannel wbChannel = Channels.newChannel(os)) {
// transferTo 直接在内核空间传输,绕过 JVM 堆内存,性能极高
channel.transferTo(0, channel.size(), wbChannel);
} catch (IOException e) {
// 客户端中途取消下载会抛出异常,生产环境应捕获并优雅处理
log.warn("文件下载中断或失败: {}", targetFile.getName(), e.getMessage());
}
return result;
} catch (IllegalArgumentException e){
log.error(methodName + " 操作失败, error=", e);
return JsonResult.fail("-1", "操作失败," + e.getMessage());
} catch (Exception e){
log.error(methodName + " 操作异常, error=", e);
return JsonResult.fail("-1", "操作异常,请联系系统管理员");
}
}
上传下载方案
1、通用方案,上传下载都在fileController实现 # 简单快速,不细分功能(如果要细分功能也可以在上传接口加功能字段)
2、上传在不同controller,下载传相对路径,共用fileController的download方法 # 细分功能
3、上传下载都用单独的文件系统,如docid # 需要单独的文件微服务,或建一张document表
其他
Content-Disposition 中的小细节
返回的结果是个数组还是字符串。
要分情况。
数组格式:
"Content-Disposition":["attachment;fileName=??.jpg"]
字符串格式:
"Content-Disposition":"attachment;fileName=??.jpg"
使用 fileName= 作为substring来判断可以吗?
一般可以,不过有时 fileName*= ,这种情况要考虑到。
httpclient获取文件流
HttpURLConnection conn = null;
try {
URL url = new URL(downloadUrl);
conn = (HttpURLConnection)url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(20 * 1000);
final ByteArrayOutputStream output = new ByteArrayOutputStream();
IOUtils.copy(conn.getInputStream(),output);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(output.toByteArray());
String fileName = "";
String raw = conn.getHeaderField("Content-Disposition");
if (raw != null && raw.indexOf("=") > 0) {
fileName = raw.split("=")[1];
fileName = new String(fileName.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
}
File destFile = new File("d://"+fileName);
logger.info("fileName:{}",fileName);
OutputStream out = new FileOutputStream(destFile);
byte [] bytes=new byte[1000];
while(byteArrayInputStream.read( bytes )!=-1){
//将缓存区中的内容写到afterfile文件中
out.write( bytes );
out.flush();
}
out.close();
} catch (Exception e) {
logger.error(e+"");
}finally {
try{
if (conn != null) {
conn.disconnect();
}
}catch (Exception e){
logger.error(e+"");
}
}
图片在浏览器展示和下载的区别
这两种用responseEntity都是可以的。
设置不同的contentType类型即可。
attachmeng是下载:
String filename = file.getName();
response.setHeader("Content-Type","text/plain");
response.addHeader("Content-Disposition","attachment;filename=" + new String(filename.getBytes(),"utf-8"));
response.addHeader("Content-Length","" + file.length());
inline是预览:
String filename = file.getName();
response.setHeader("Content-Type","text/plain");
response.addHeader("Content-Disposition","inline;filename=" + new String(filename.getBytes(),"utf-8"));
response.addHeader("Content-Length","" + file.length());
用原生的response预览:
OutputStream out= response.getOutputStream();
out.write(bytes);//将缓冲区的数据输出到浏览器
out.flush();
out.close();
文件入口的几种方式
主要是写接口的时候用到,整理下会更清晰。
这也是io流较复杂的原因之一,它的形态较多,每种都需要掌握。
1、filePath
本地调试的时候用这个,filePath一般传实际文件路径。
2、url
传入在线url地址,客户可以操作。
3、file
文件的形式。和filePath有点不同,一个是地址,一个直接是文件。
4、multipartFile
类似于界面的点文件上传,客户可以操作。
5、base64文件流
一般是中台服务接口,因为用户肯定不会直接操作base64。
6、inputstream
这应该算一个中间的形式,但是肯定也有这个口。
以上状态都是实际存在的,如果输入输出转换,那么实际是乘积的关系,确实有点多。
另外,有时还伴随着删除目录等操作,那么复杂度就更高了。
资源加载
见资源加载笔记。
filePath为什么难以理解
在不同的工具中,表示的意思也不一样。
例如在minio中,filePath和fileName是分开的。
在文件流中,new File(filePath+fileName) # 这个路径一般是全路径,因为java的file是支持目录也支持文件的。
看到了吧,所以要点就2点。
1、区分清楚filePath是路径还是实际文件。
2、区分清楚,该插件传的路径是dir还是文件名。
另外,不同场景还有些区分点,如磁盘路径前缀要以"/“开头,但是minio路径恰恰不以”/"开头,一定要区分开。
生成多级目录
这个实际特别简单,只有一个小小的注意点。
代码:
public static void main(String[] args) {
String dirs="d://data//a.txt";
File fileDir = new File(dirs);
// 是这样,执行这个方法就会穿件多级目录,但是需要确认这个路径是文件夹
// 如果计划是文件名 如 d://data/a.txt 会创建a.txt文件夹,这一点需要注意
boolean mkdirs = fileDir.mkdirs();
log.info("mkdirs={}",mkdirs);
}
用file.mkdirs()方法即可。
注:需要确定该file是文件夹的路径,如果带有文件名,也会创建为文件,这点需要注意。
如:
d:/data/file # /data/file文件夹。
d:/data/a.txt # a.txt也会创建为文件夹,可能和我们期望的不一致。
io流中诡异的404问题
遇到过一个问题,接口路径明明对,就是报错404,可太奇怪了。
这个可以下载文件:
{{url}}/mysite/api/file/download?relativePath=操作手册.docx
这个报错404:
{{url}}/mysite/api/file/download?relativePath=操作手册_777.docx
后来找到了问题:
if (!targetFile.exists() || !targetFile.getPath().startsWith(baseDir.getCanonicalPath())) {
// 关键就在这句行,response发送404错误,被spring拦截到了,所以返回的404
response.sendError(HttpServletResponse.SC_NOT_FOUND, "File not found or access denied");
throw new IllegalArgumentException("文件未找到或无权限");
}
解决方案:
弃用response.sendError()这种方式,改为自定义处理异常,再通过response.write()输出。
io流中没响应的问题
场景:
文件没下载,也没返回json。
后来找到问题了,httpStatus返回的是自定义的-1,servlet底层容器解析异常,所以就处理不正确。
解决方案:
1、错误码用http可以识别的错误码,如500
2、然后用response.write()输出自定义的jsonResult
印象中好像确实见过点下载既返回了报文,又下载了文件的
先说结论:
一次请求的返回结构固定的,不可能既返回报文,又下载文件。
如果确实看到了这个效果,可能是前端调用了两次请求:
例如导出excel
1、点导出先返回报文,里面包含下载地址
2、前端再下载这个文件
本文深入讲解Java中的IO流,包括字节流、字符流、文件读写、缓冲流的使用技巧,以及通过REST模板访问服务的多种方式。同时,探讨了文件流在不同场景下的应用,如文件复制、大文件下载和文件名处理。

2100

被折叠的 条评论
为什么被折叠?



