有关Java中的IO1-字节流和File类
有关Java中的IO(1) –字节流和File类
学习目标
● 掌握常用的File类常用的方法
● 掌握字节字符流的基本使用方法
1.File
1.1为什么要了解File
● 因为数据很重要所以我们要把数据永久化/持久化存储。
● 之前开发都把数据存入了 内存
- ● 存储内存
- 优势
- 性能快
● 弊端 : 程序结束,数据消失了。不能保证数据的持久化
● 数据只有存在计算机磁盘/缓存中 将数据持久化。
● 我们有必要学习对目录以及文件的相关操作。
1.2 File的概念
● File类位于java.io包下,表示文件或者目录,也就是说如果希望在程序中操作文件或者目录都可以通过File类来完成;
● 一个File对象代表硬盘或网络中可能存在/或者不存在的一个文件或者目录
● File类能做到新建、删除、重命名文件或者目录;
1.3 层级
public class File extends Object implements Serializable, Comparable<File>{
}
//代表磁盘/网络里面任意一个存在/不存在的文件/文件夹的抽象标识。
1.4 常用构造
有关文件路径
● window的路径分隔符使用“\”,而Java中的“\”表示转义字符,所以在Windows中表示路径,需要用两个反斜杠。
或者直接使用“/”也可以,Java程序支持将“/”当成平台无关的路径分隔符。或者直接使用File.separator常量值表示;
● 绝对路径:从盘符开始的路径,这是一个完整的路径;例如 “E:\java\io\resources\a.txt” 就是个绝对路径;
● 相对路径:相对于项目目录的路径,一种简洁的写法,开发中经常使用。
1. File(String pathname);//根据指定文件路径/目录路径(路径可以存在 也可以不存在)创建File类对象
2. File(String parent, String child);//根据指定的父级目录路径以及指定的子级文件/目录路径创建File类对象
//1. parent 父级目录路径
//2. child 子级文件/目录路径
3. File(File parent, String child);
方法 | 描述 |
---|---|
public File(String pathname) | 根据给定的 路径名字符串 创建新的 File实例。 |
public File(String parent, String child) | 根据 父路径名字符串和子路径名字符串创建新的 File实例。 |
public File(File parent, String child) | 从父路径对应的File对象和子路径名字符串 创建新的 File实例。 |
private static void demo2() {
File file = new File("D:\\demo\\a.txt");
File file1 = new File("d:\\demo", "a.txt");
File parentDirectory = new File("D:\\demo");
File file2 = new File(parentDirectory, "a.txt");
File file3 = new File("/a");// E:\\
File file4 = new File("src/com/java/file/FileDemo.java");// E:\\
}
1.5 常用属性
static String pathSeparator
static char pathSeparatorChar
//代表的是磁盘中系统环境变量的分隔符。windows: ; unix: :
static String separator ;
static char separatorChar ;
//代表路径盘符分割 windows: \ unix: /
//路径拼接
private static void demo1() {
System.out.println(File.pathSeparator);
System.out.println(File.pathSeparatorChar);
System.out.println(File.separator);
System.out.println(File.separatorChar);// \
}
1.6 常用方法
1.6.1 作为文件
方法 | 描述 |
---|---|
public String getName() | 返回由此File表示的文件名称。 |
public long length() | 返回由此File表示的文件的长度。 如果此路径名表示一个目录,则返回值是不确定的。 |
public long lastModified() | 返回File对象对应的文件或目录的最后修改时间(毫秒值)。 |
public boolean exists() | 此File表示的文件是否实际存在。 |
public boolean isFile() | 此File表示的是否为文件。 |
public boolean isHidden() | 此File表示的是否为隐藏文件。 |
public boolean canExecute() | 测试应用程序是否可以执行此抽象路径名表示的文件。 |
public boolean canRead() | 测试应用程序是否可以读取此抽象路径名表示的文件。 |
public boolean canWrite() | 测试应用程序是否可以修改此抽象路径名表示的文件。 |
public boolean delete() | 删除指定文件 |
public boolean setLastModified(long time) | 修改最后修改文件的时间 |
public boolean setReadOnly() | 修改文件为只读 |
public boolean createNewFile() | 当且仅当具有该名称的文件尚不存在时,创建一个新的空文件。(一定要保证父级目录存在) |
public boolean renameTo(File dest) | 重新命名此抽象路径名表示的文件或目录。但是此方法行为的许多方面都是与平台有关的:重命名操作无法将一个文件从一个文件系统移动到另一个文件系统。 |
public String getParent() | 返回此抽象路径名父目录的路径名字符串 |
public File getParentFile() | 返回此抽象路径名父目录的抽象路径名 |
public String getAbsolutePath() | 返回文件的绝对路径 |
public String getPath() | 返回文件的相对路径 |
private static void demo3() {
//当File代表的是文件的时候
File file = new File("D:\\demo\\a.txt");
System.out.println(file);
//获得文件/目录的相关属性的信息
System.out.println("文件名称:" + file.getName());
System.out.println("相对路径:" + file.getPath());
System.out.println("绝对路径:" + file.getAbsolutePath());
System.out.println("获得父级目录路径:" + file.getParent());
System.out.println("获得父级目录对象:" + file.getParentFile());
System.out.println("获得文件大小(字节):" + file.length());
System.out.println("获得上一次操作文件的时间:" + file.lastModified());//毫秒数
System.out.println("获得上一次操作文件的时间:" + new Date(file.lastModified()));
// 将毫秒数转换成具体的日期类对象 Date LocalDate LocalDateTime
//判断
System.out.println("判断文件是否存在:" + file.exists());
//手动创建文件
//System.out.println(file.createNewFile());// 1. try...catch...finally 2. throws
System.out.println(file.isFile());
System.out.println(file.isDirectory());
System.out.println(file.canRead());
System.out.println(file.isHidden());
System.out.println(file.setReadOnly());//更改为只读文件
//删除文件
System.out.println(file.delete());
}
private static void demo5() {
File file = new File("./src/user.txt");//相对于当前的项目 day17
System.out.println(file.exists());
System.out.println(file.getAbsolutePath());
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
1.6.2 作为目录
方法 | 描述 |
---|---|
public String getName() | 返回由此File表示的目录名称。 |
public boolean exists() | 此File表示的目录是否实际存在。 |
public boolean isDirectory() | 判断File对象是否是目录 |
public boolean mkdir() | 创建由此File表示的目录。 |
public boolean mkdirs() | 创建多级目录 |
public File getParentFile() | 返回此抽象路径名父目录的抽象路径名 |
public String getAbsolutePath() | 返回目录的绝对路径 |
public String getPath() | 返回目录的相对路径 |
public String[] list() | 获得File目录中的一级子文件名称或目录名称。 |
public File[] listFiles() | 获得File目录中的一级子文件对象或目录对象。 |
public File[] listFiles(FileFilter filter) | 返回所有满足指定过滤器的文件和目录。如果给定 filter 为 null,则接受所有路径名。否则,当且仅当在路径名上调用过滤器的 FileFilter.accept(File pathname)方法返回 true 时,该路径名才满足过滤器。 |
private static void demo6() {
//File 代表目录-----> 目录路径
File directory = new File("/demo", "b/c/d/e");
System.out.println(directory.getName());
System.out.println(directory.getPath());
System.out.println(directory.getAbsolutePath());
System.out.println(directory.getParent());
//目录不存在 要创建一个目录
if (!directory.exists()) {
//System.out.println(directory.mkdir());
System.out.println(directory.mkdirs());//创建>=1目录
}
System.out.println(directory.isDirectory());
//System.out.println(directory.exists());
//System.out.println(directory.delete()); 只能删除空目录
}
private static void demo7() {
//一般创建的目录都是以当前时间为准
//获得当前此刻的时间: 目录名称
String dateStr = LocalDate.now().toString();
File dir = new File("src" + File.separator + dateStr);
System.out.println(dir.mkdirs());
}
private static void demo8() {
File dir = new File("E:\\workspace\\JavaTest");
//查询展示指定目录下的所有的子级资源
//删除dir里面后缀是txt所有的文件----> file.delete()
String[] list = dir.list();//获得1级资源名称(目录名/文件名)
System.out.println(Arrays.toString(list));
File[] files = dir.listFiles();//获得1级资源对象(目录对象/文件对象)
System.out.println(Arrays.toString(files));
}
1.7 递归-斐波那契数列
● 什么是方法的递归?
● 方法的递归是指在一个方法的内部调用自身的过程。 递归必须要有结束条件,不然就会陷入无限递归的状态,永远无法结束调用;
public static void main(String[] args) {
File dir = new File("E:\\workspace\\JavaTest\\demo");
//System.out.println(dir.delete());
//删除目录 只能删除空目录
deleteFileTxt(dir);
}
- 案例: 删除指定目录所有的文件的txt资源
private static void deleteFileTxt(File parentDirectory) {
//删除的文件后缀是txt
for (File child : childFiles) {
//child 可能是子级目录 也可能是子级文件
if (child.isFile()) {
//if (child.getName().endsWith("txt")) child.delete();
} else {
//是目录
//继续查询子集 继续执行删除txt
//递归
deleteFileTxt(child);
}
}
}
public static void main(String[] args) {
File dir = new File("E:\\workspace\\day17\\demo");
listChild(dir,"|-");
}
private static void listChild(File dir, String s) {
String[] childNameArray = dir.list();
for (String name : childNameArray) {
System.out.println(s + name);
//如果是目录 需要再次递归
//创建一个File 作为目录对象
File child = new File(dir, name);
if (child.isDirectory()) listChild(child, "| " + s);
}
}
2.IO
2.1 为什么要学习IO?
● 由于我们在开发中,需要将数据持久化存储。必须存储磁盘或者缓存进行维护,因此我们学习了File类相关功能。
● 但是File类只能获得文件或者目录相关的属性,不能读写文件存储的数据。
● 数据持久存储,就离不开2个功能: 1.将数据存储文件 2.获得文件数据
● 也就是要实现读写功能,这个时候必须使用IO实现。
2.2 概念
● I input 输入 O output 输出
● 数据的传输,可以看做是一种数据的流动,按照流动的方向,分为输入input 和输出output ,即流向Java程序的是输入流,流出Java程序的是输出流;
● Java中I/O操作主要是指使用java.io包下的类与接口进行输入、输出操作。
● 输入也叫做读取数据,输出也叫做作写出数据。
● File对象不能直接读取和写入数据,如果要操作数据,需要IO流。
● File对象好比是到水库的“路线地址”以及水库的描述信息(包括水库的大小、位置、建造时间等),要“存取”里面的水到你“家里”,需要“管道”,IO流就好比是管道。
2.3 流的分类:
- 从传输方向上看:
- 输入流: 将本地磁盘数据传输至JVM
- 输出流: 将JVM数据传输到本地磁盘
- 从传输单位上看:
- 字节流: 以字节为单位进行传输, 可以传输任意类型的文件, 如文本|图片|视频|音频等
- 字符流: 以字符为单位记性传输, 只能传输文本类型的文件, 如.txt | .java | .excel等
- 从传输功能上看:
- 节点流: 是具备实际传输功能的流
- 过滤流: 不具备实际传输作用, 功能为给节点流添加附加功能|增强节点流传输能力
2.4 有关字节流
● 对流中数据 一个字节一个字节的处理;
● 字节输入流是 InputStream ;字节输出流是OutputStream
● 都是抽象类。封装了所有的字节流里面最基本的读写操作
2.4.1 字节流体系
2.4.2 InputStream
1.层级
public abstract class InputStream extends Object implements Closeable{
}
注意 :类实现Closeable 证明他就是一个资源。使用完毕之后 需要释放物理资源
不管程序是否出现了异常 都需要释放物理资源,释放方式有
1.finally
2.try…with…resoucres
3.@CleanUp
2.常用方法
方法 | 描述 |
---|---|
public int read() | 从输入流读取一个字节。返回读取的字节值。虽然读取了一个字节,但是会自动提升为int类型。如果已经到达流末尾,没有数据可读,则返回-1。 |
public int read(byte[] b) | 从输入流中读取一些字节数,并将它们存储到字节数组 b中 。每次最多读取b.length个字节。返回实际读取的字节个数。如果已经到达流末尾,没有数据可读,则返回-1。 |
public int read(byte[] b,int off,int len) | 从输入流中读取一些字节数,并将它们存储到字节数组 b中,从b[off]开始存储,每次最多读取len个字节 。返回实际读取的字节个数。如果已经到达流末尾,没有数据可读,则返回-1。 |
public void close() | 关闭此输入流并释放与此流相关联的任何系统资源。 |
public byte[] readAllBytes() | JDK9中新增的方法;从输入流读取所有字节放到一个字节数组中,返回此字节数组;如果数据过大有可能会发生OOM错误,使用时要注意; |
public byte[] readNBytes(int len) | JDK11中新增的方法;从输入流总读取指定长度的字节放入字节数组中进行放回; |
int available(); | 获得流中有效字节数量 |
long transferTo(OutputStream out); | JDK9新增的方法。将字节流输入内容转换到字节输出流对象中 |
3.常用子类构造
FileInputStream(String filePath);//name: 文件路径-----> 文件存在
FileInputStream(File file);
BufferedInputStream(InputStream in);
4.使用方法
4.1 read()
private static void testRead1() {
//目的: 读取指定文件里面的数据。
//1.指定文件
String filePath = "src/a.txt";
InputStream inputStream = null;
try {
//2.创建字节输入流对象
inputStream = new FileInputStream(filePath);//文件的数据都在inputStream
System.out.println(inputStream.available());
/*
//3.读取文件数据
int data = inputStream.read();//一次读取一个字节 -1
System.out.println("读取到的1个字节数据:"+(char)data);
System.out.println(inputStream.available());*/
//3.读取文件所有的内容
/*int available = inputStream.available();
for (int i = 0; i < available; i++) {
System.out.print((char) inputStream.read());
}*/
/*int data = inputStream.read();
while (data!=-1){
System.out.print((char)data);
data = inputStream.read();
}*/
int len;
while ((len = inputStream.read()) != -1) {
System.out.print((char) len);
}
//不管使用哪一个字节流 只要操作中文的数据 肯定会乱码的问题
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (inputStream != null) inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
4.2 try…with…resources
● 在上面的read功能里面,使用流结束之后,我们需要释放资源,调用close方法
● 代码里面嵌套了很多try…catch语句,很臃肿。
● 在JDK1.7之后,建议我们使用try…with…resources替换finally代码块
● 一种更加优雅的方式释放流资源对象。
● 想使用try…with…resources 一定要保证资源类型必须实现Closeable接口,否则程序会报错。
public class MyClass implements Closeable {
@Override
public void close() throws IOException {
System.out.println("我被关闭了.....");
}
}
private static void testRead2() throws FileNotFoundException {
//使用更加优雅的方式释放流对象 try...with...resources
//无需再编写finally 自动调用close
InputStream inputStream = new FileInputStream("src/a.txt");
MyClass myClass = new MyClass();
try (inputStream; myClass) {
//循环读取文件数据
int len;
while ((len = inputStream.read()) != -1) {
System.out.print((char) len);
}
} catch (IOException e) {
e.printStackTrace();
}
}
4.3 read(byte[] bytes)
private static void testRead2() throws FileNotFoundException {
//1.指定文件路径
String path = "src/com/java/io/InputStreamDemo.java";
//2.创建字节输入流对象
InputStream inputStream = new FileInputStream(path);
//3.操作流---->读取文件数据
try (inputStream) {
/*System.out.println("字节数量:" + inputStream.available());
byte[] bytes = new byte[1024];//1024的整数倍 理论上length越大 读取的性能高
int len = inputStream.read(bytes);//读取到的数据都在bytes
System.out.println("len:" + len);//读到的有效的字节数量 11
//字节数组转String
System.out.println(Arrays.toString(bytes));
String str = new String(bytes,0,len);
System.out.println(str);*/
//读取src/com/java/io/InputStreamDemo.java
//在控制台打印输出
byte[] bytes = new byte[500];
int len;
while ((len = inputStream.read(bytes)) != -1) {
System.out.println(new String(bytes, 0, len));
}
} catch (IOException e) {
e.printStackTrace();
}
}
4.4 read(byte[] b, int index, int len)
- 这个方法咱们一般不用, 了解即可 。
private static void testRead3() throws FileNotFoundException {
InputStream inputStream = new FileInputStream("src/a1.txt");
try (inputStream) {
byte[] bytes = new byte[10];
// public int read(byte b[], int off, int len)
//off: index 数组的索引位置
//len: 等价于read方法的返回值的结果数据 存储数组里面的真实的字节数量
int read = inputStream.read(bytes, 0, bytes.length);
System.out.println(Arrays.toString(bytes));
System.out.println(read);
} catch (IOException e) {
e.printStackTrace();
}
}
2.4.3 OutputStream
1.层级
public abstract class OutputStream extends Object implements Closeable, Flushable{
//实现Closeable 也是资源 需要释放物理资源
//实现Flushable 底层有缓冲区 在某些情况下需要调用flush()方法
}
2.常用方法
方法 | 描述 |
---|---|
public void write(int b) | 将指定的字节输出流 |
public void write(byte[] b) | 将 b.length字节从指定的字节数组写入此输出流。 |
public void write(byte[] b, int off, int len) | 从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流。 |
public void flush() | 刷新此输出流并强制任何缓冲的输出字节被写出。 |
public void close() | 关闭此输出流并释放与此流相关联的任何系统资源。 |
3.常用子类构造
public FileOutputStream(String filePath) ;
public FileOutputStream(File file)
public FileOutputStream(String name, boolean append) ;// append 追加 默认值false
public FileOutputStream(File file, boolean append);
public BufferedOutputStream(OutputStream out) {
this(out, 8192);
}
4.使用方法
public static void main(String[] args) throws FileNotFoundException {
//将指定的数据写入指定文件中
//对于字节输入流而言 执行read 保证文件必须存在
//对于字节输出流而言 执行write 文件不必存在(会自动的创建新的文件----> File.createNewFile())
//前提: 文件的父级目录必须存在
String path = "src/b.txt";
//1.创建字节输出流对象
OutputStream outputStream = new FileOutputStream(path,true);
try(outputStream){
//2.将数据写入文件中
outputStream.write('a');
outputStream.write(97);
outputStream.write('我');
outputStream.write(65);
outputStream.write('\n');
byte[] bytes = {98,99,100,102};
outputStream.write(bytes);
outputStream.write('\n');
outputStream.write("你太美".getBytes());
outputStream.write('\n');
outputStream.write("你太美".getBytes(),0,2);
} catch (IOException e) {
e.printStackTrace();
}
}
2.3.4 案例: 用户上传头像
- 思考
在开发中 哪些功能需要使用IO实现?
//文件上传与下载、文件导入导出等
//1.选择本地一张图片资源
//2.将本地图片的资源复制到另外一台计算中的另外一个文件中 ---> 读取本地资源 写入到另外一个文件中
//3.图片是回显成功的(图片服务器里面的资源) ----> 获得资源存在服务器的路径
● 一切文件数据(文本、图片、视频等)在存储时,都是以二进制数字的形式保存,都一个一个的字节,那么传输时一样如此。
● 所以,字节流可以传输任意文件数据。在操作流的时候,我们要时刻明确,无论使用什么样的流对象,底层传输的始终为二进制数据。
- 1.基础版
/**
* 上传文件
*
* @param sourceFilePath 原文件路径
* @return 上传成功之后的服务器路径
*/
public static String uploadImage(String sourceFilePath) throws FileNotFoundException {
if (sourceFilePath == null || sourceFilePath.isBlank()) {
throw new RuntimeException("文件路径不合法");
}
//读取原文件数据 写入到服务器中目标文件
InputStream inputStream = new FileInputStream(sourceFilePath);
//获得原文件的名称
String fileName = sourceFilePath.substring(sourceFilePath.lastIndexOf(File.separator) + 1);
String targetFilePath = PARENT_DIRECTORY + fileName;
OutputStream outputStream = new FileOutputStream(targetFilePath);
//读写
try (inputStream; outputStream) {
int len;
while ((len = inputStream.read()) != -1) {
outputStream.write(len);
}
} catch (IOException e) {
e.printStackTrace();
}
return targetFilePath;
}
2.问题
//以上的上传可以成功。
public static void main(String[] args) throws FileNotFoundException {
// A用户上传
//System.out.println(uploadImage("D:\\demo\\a.jpg"));
//B用户
System.out.println(uploadImage("E:\\demo\\a.jpg"));
//存在一些问题
//1. 用户上传重名的文件 会出现文件被覆盖的问题
//2. 遇见大文件的读写 性能低
//3. 用户变多 资源变多 查询一个文件 性能低
}
3.升级版
1.解决文件覆盖/重名
public static String uploadImage(String sourceFilePath) throws FileNotFoundException {
if (sourceFilePath == null || sourceFilePath.isBlank()) {
throw new RuntimeException("文件路径不合法");
}
//读取原文件数据 写入到服务器中目标文件
InputStream inputStream = new FileInputStream(sourceFilePath);
//获得原文件的名称----> 目前就不使用原文件名称 可能出现重名问题----> 文件就会被覆盖
//String fileName = sourceFilePath.substring(sourceFilePath.lastIndexOf(File.separator) + 1);
//使用UUID
String name = UUID.randomUUID().toString().replaceAll("-", "");
//获得原文件的后缀(扩展名)
String extension = sourceFilePath.substring(sourceFilePath.lastIndexOf("."));
String targetFilePath = PARENT_DIRECTORY + name + extension;//建议使用StringBuilder
OutputStream outputStream = new FileOutputStream(targetFilePath);
//读写
try (inputStream; outputStream) {
int len;
while ((len = inputStream.read()) != -1) {
outputStream.write(len);
}
} catch (IOException e) {
e.printStackTrace();
}
return targetFilePath;
}
2.解决读写慢(自定义缓冲区)
public static String uploadImage(String sourceFilePath) throws FileNotFoundException {
if (sourceFilePath == null || sourceFilePath.isBlank()) {
throw new RuntimeException("文件路径不合法");
}
//读取原文件数据 写入到服务器中目标文件
InputStream inputStream = new FileInputStream(sourceFilePath);
//获得原文件的名称----> 目前就不使用原文件名称 可能出现重名问题----> 文件就会被覆盖
//String fileName = sourceFilePath.substring(sourceFilePath.lastIndexOf(File.separator) + 1);
//使用UUID
String name = UUID.randomUUID().toString().replaceAll("-", "");
//获得原文件的后缀(扩展名)
String extension = sourceFilePath.substring(sourceFilePath.lastIndexOf("."));
String targetFilePath = PARENT_DIRECTORY + name + extension;//建议使用StringBuilder
OutputStream outputStream = new FileOutputStream(targetFilePath);
//读写
//大文件读写----> 自定义缓冲
try (inputStream; outputStream) {
byte[] bytes = new byte[1024 * 20];
int len;
while ((len = inputStream.read(bytes)) != -1) {
outputStream.write(bytes, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
}
return targetFilePath;
}
● 缓冲流,也叫高效流,按照数据类型分类:
○ 字节缓冲流:BufferedInputStream,BufferedOutputStream
○ 字符缓冲流:BufferedReader,BufferedWriter
● 缓冲流的基本原理,是在创建流对象时,会创建一个内置的默认大小的缓冲区数组,通过缓冲区读写,减少系统IO次数,从而提高读写的效率。
● 缓冲字节输入输出流:BufferedInputStream/BufferedOutputStream;
public static String upload1(String sourceFilePath) throws FileNotFoundException {
String name = UUID.randomUUID().toString().replaceAll("-", "");
String extension = sourceFilePath.substring(sourceFilePath.lastIndexOf("."));
String targetFilePath = PARENT_DIRECTORY + name + extension;
// 可以使用高效字节流解决读写慢----> 底层自带缓冲----> 8192 byte[]
BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(sourceFilePath));
BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(targetFilePath));
try (inputStream; outputStream) {
int len;
byte[] bytes = new byte[1024 * 20];//自定义的缓冲数组>8192 这是时候 底层的缓冲无效了
while ((len = inputStream.read(bytes)) != -1) {
outputStream.write(bytes, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
}
return targetFilePath;
}
3.多目录存储
//用户变多 资源变多 查询一个文件 性能低
// 解决: 多目录存储
// 1. 可以以当前日期为目录 维护用户在今日上传的图片资源
// 2. 目录打散---> 获得文件名称16进制的hash数据 获得前几个16进制的字符的数据 作为新的目录的名称
// String name = "a.jpg";
// String hexString = Integer.toHexString(name.hashCode());
// System.out.println(hexString);
// String childPath = hexString.charAt(0) + "/" + hexString.charAt(1);// 5/6
//System.out.println(new Object().toString());
/**
* 上传文件
*
* @param sourceFilePath 原文件路径
* @return 上传成功之后的服务器路径
*/
public static String uploadImage(String sourceFilePath) throws FileNotFoundException {
if (sourceFilePath == null || sourceFilePath.isBlank()) {
throw new RuntimeException("文件路径不合法");
}
//读取原文件数据 写入到服务器中目标文件
InputStream inputStream = new FileInputStream(sourceFilePath);
//获得原文件的名称----> 目前就不使用原文件名称 可能出现重名问题----> 文件就会被覆盖
//String fileName = sourceFilePath.substring(sourceFilePath.lastIndexOf(File.separator) + 1);
//使用UUID
String name = UUID.randomUUID().toString().replaceAll("-", "");
//获得原文件的后缀(扩展名)
String extension = sourceFilePath.substring(sourceFilePath.lastIndexOf("."));
//upload/user/2022-12-01/a.jpg
/*String curDateStr = LocalDate.now().toString();
//目录不存在 自动创建----> File.mkdirs()
File childDir = new File(PARENT_DIRECTORY, curDateStr);
if (!childDir.exists()) {
childDir.mkdirs();
}*/
String fileName = sourceFilePath.substring(sourceFilePath.lastIndexOf(File.separator) + 1);
String hexString = Integer.toHexString(fileName.hashCode());
String childPath = hexString.charAt(0)+"/"+hexString.charAt(1);
File childDir = new File(PARENT_DIRECTORY, childPath);
if (!childDir.exists()) {
childDir.mkdirs();
}
String targetFilePath = PARENT_DIRECTORY + childPath + "/" + name + extension;//建议使用StringBuilder
OutputStream outputStream = new FileOutputStream(targetFilePath);
//大文件读写----> 自定义缓冲
try (inputStream; outputStream) {
byte[] bytes = new byte[1024 * 20];
int len;
while ((len = inputStream.read(bytes)) != -1) {
outputStream.write(bytes, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
}
//小文件读写
/* try (inputStream; outputStream) {
int len;
while ((len = inputStream.read()) != -1) {
outputStream.write(len);
}
} catch (IOException e) {
e.printStackTrace();
}*/
return targetFilePath;
}