观前提示:本文涉及的代码演示部分,为了文章的观赏性,许多代码演示中有意忽略了导包、异常处理…
所谓 IO,I(input) O(output),即输入输出。
文件是电脑的一种存储形式,文件有不同的格式(.txt .doc .ppt .mp4 .jpg .rar …),文件夹即目录路径
File
是一个类,在java.io
包中,File
是文件或目录路径名的抽象表示形式,与真实硬盘中的文件或文件夹,不是一个东西File
在内存中的一个对象 --> 硬盘上的文件或文件夹,与电脑上的文件或文件夹(目录)产生一一对应的映射关系import java.io.File;
File f = new File("文件或文件夹路径");
路径
是看创建的对象能否与硬盘中的真实文件产生映射关系
File
可以操控一个文件或文件夹的属性
一
boolean x = f.canExecute();
boolean x = f.canRead();
boolean x = f.canWrite();
二
boolean x = f.isHidden();
boolean x = f.isFile();
boolean x = f.isDirectory();
boolean x = f.isAbsolute();
完整的描述文件位置的路径就是绝对路径,通常是从盘符开始到目标位置的路径。
三
long value = f.getFreeSpace();//分区未分配字节数
long value = f.getUsableSpace();//分区可用字节数
long value = f.getTotalSpace();//分区总字节数
long value = f.length();
long value = f.lastModified();//返回的是毫秒值
boolean x = f.setLastModified(long time);
补充:返回的是毫秒值,需要一定的转化,输出为年月日时分秒的形式
File f= new File("文件");
long time = f.lastModified();
Date date = new Date(time);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd kk:mm:ss");
System.out.println(sdf.format(date));//2020-07-19 14:55:28
四
String value = f.getAbsolutePath();
String name = f.getName();
boolean x = f.createNewFile();
boolean x = f.mkdir();
boolean value = f.mkdirs();
boolean x = f.delete();
父
String value = f.getParent();
获取当前 f 的父亲对象的名字File file = f.getParentFile();
获取当前 f 的父亲对象File file = new File("路径");
File pfile = file.getParentFile();
while(pfile != null){
System.out.println(pfile.getAbsolutePath());//输出父文件夹的绝对路径名
pfile = pfile.getParentFile();
}
子
String[] values = f.list();
获取当前文件夹对像 f 的所有子对象名字File[] files = f.listFiles();
获取当前文件夹对像 f 的所有子对象调用上面两个方法后的结果,体现的意义:
null
,则证明当前的 f 是一个文件;null
,证明当前的 f 是一个文件夹;0
,证明当前的 f 是一个不为空的文件夹(目录中有元素)遍历文件夹
public void showFile(File file){
File[] files = file.listFiles();
if(files != null && files.length != 0){//判断file是文件夹 且 文件夹内有元素
for(File f:files){
this.showFile(f);
}
}
//file是文件或者是空文件夹
System.out.println(file.getAbsolutePath());
}
删除非空文件夹
public void deleteFile(File file){
File[] files = file.listFiles();
if(files != null && files.length != 0){
for(File f:files){
this.deleteFile(f);
}
}
file.delete();
}
一般使用较多的是高级流,虽然字节型文件流和字符型文件流是低级流,但是高级流还是通过低级流操作文件的,大部分方法的使用是一致的
File
对象
但是File
对象不可以操作文件中的内容-----这个要通过I/O流的方式来完成
流按照方向(功能)来区分:in(读取)、out(写入)
顾名思义,读取文件中的信息in,将信息写入文件中out
流按照操作的目标来区分:文件流、数组流、字符串流、数据流、对象流、网络流
文件流按照读取或写入的单位大小来区分:
FileInputStream
/ FileOutputStream
FileReader
/ FileWriter
我们为什么要把信息存在文件中?
容器:
变量 (只能存一个)
数组 (存储好多个 数据类型统一)
集合 (存储好多个 存储后个数还能改变 泛型—数据类型统一)
================================================
如上三个都是Java中的类型(对象存储在内存中,而这个内存是Java虚拟机开辟的,当程序执行完毕,虚拟机停止的时候,内存空间就回收了)
如上三种存储方式,存储的信息都是临时性的
文件 (存储好多信息 )
文件是存储在硬盘上的----永久性保存
文件毕竟不在内存中,需要通过IO操作文件
数据库 JDBC
java.io
包,继承自InputStream
类(字节型输入流的父类)fis
为FileInputStream
对象):
int code = fis.read();
每次从流管道中读取一个字节(返回这个字节的Unicode码)int count = fis.read(byte[] b);
每次从流管道中读取若干个字节,存入数组中,返回有效元素个数int count = fis.avaliable();
返回流管道中还有多少缓存的字节数long v = fis.skip(long n);
跳过n-1个字节再读取第n个字节,返回参数fis.close();
将流通道关闭—必须要做,最好放在finally中,注意代码的健壮性,判断严谨常用方法一:read()
File file = new File("文件路径");
FileInputStream fis = new FileInputStream(file);
int i = fis.read();
while(i != -1){//没有可读取的,就返回-1
System.out.print(i + " ");//输出Unicode码
System.out.println((char)code);//强制转换为字符
i = fis.read();
//读取的字节对应的Unicode码(范围 0---65535)
//读取一个之后,就像迭代器一样,会自动往后移一位,在读取时就读取的是下一位
}
补充:
\r
\n
常用方法二:read(byte[] b)
FileInputStream fis = new FileInputStream("文件路径");
byte[] b = new byte[5];//每次读取5个字节
int count = fis.read(b);//返回的是有效读取的字节个数,读取内容放入数组b中
while(count != -1){
//String value = new String(b);//错误写法,因为数组是覆盖写入,读取到最后一次很可能会出问题!
String value = new String(b,0,count);//正确写法
System.out.print(value);//输出
count = fis.read(b);
}
【举个例子】假设是读取下面这个文件,若是使用错误写法 String value = new String(b);
这个文件中有21个字节(第一行为7个+/r+/n 为9个;第二行为7个+/r+/n 为9个;第三行就3个)
但是实际上,我们只创建了一个数组b用来当作小推车
第一次数组b存储a b c d e
第二次数组b把原来的都覆盖了,再存储f g \r \n h
第三次数组b把原来的都覆盖了,再存储i j k l m
第四次数组b把原来的都覆盖了,再存储n \r \n o p
第五次数组b只覆盖了第一个(后四个内容不变),数组b实际上变为q \r \n o p
所以输出时,会多了一些不需要的内容,解决办法一般有两种:
String value = new String(b,0,count);
常用方法三:available()
int count = fis.avaliable();
这个方法和流的概念有关,流是一个管道:
这个图不太严谨(管其实连着的是File对象,而不是真实的硬盘中的文件,硬盘中的文件和File对象有映射关系;而上图将File给抽象掉了)
补充:这个方法读取本地的数据不会有什么问题,但是当去读取网络中的数据时,可能会有问题
常用方法四:skip(long n)
long v = fis.skip(long n);
这个方法一般没什么用,不过在多线程中利用几个线程同时读取文件,让他们分工读取字节,skip就有用了
常用方法五: close()
流是一个管道,通信完成后,管道是必须要关闭的
关闭的是流通道,不是File对象
为了程序设计的健壮性,close()的放置位置是有讲究的:一般放在finally中
File file = new File("文件路径");
FileOutputStream fos = new FileOutputStream(file);//默认为文件覆盖写入模式
File file = new File("文件路径");
FileOutputStream fos = new FileOutputStream(file, true);//文件追加写入模式
fos.write(int code);
将给定的code对应的字符向流管道中写入fos.write(byte[] b);
将数组中的全部字节向流管道写入fos.flush();
将流管道中的数据写到文件中,这个方法默认调用fos.close();
关闭流管道常用方法六:write()
向流管道中写东西,会通过File对象映射到硬盘中的文件中,最后会写到文件中
【1】虽然write(int x)
中的参数要求是int型,那么直接把char类型的传到参数中行吗?
答案当然是肯定的(char可以自动类型转换为int型)
【2】要把"1+1=2"
写入文件中有什么办法?
write()
方法fos.write(49); fos.write('+'); fos.write(49);
fos.write('=');
fos.write(50);
byte[] b = new byte[]{'1','+','1','=','2'};
fos.write(b);
String str = "1+1=2";
byte[] b = str.getBytes();
fos.write(b);
//参数
// - file:被复制的文件对象
// - path:文件复制目标路径
public void copyFile(File file,String path){
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream(file);
File newFile = new File(path + "//" + file.getName());//无则自动创建该文件
fos = new FileOutputStream(newFile);
byte[] b = new byte[1024];//通常创建的数组 1kb--8kb之间
int count = fis.read(b);
while(count != -1) {
fos.write(b, 0, count);//将读取到的有效字节 写入
fos.flush();
count = fis.read(b);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(fis!=null) {
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if(fos!=null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
文件的加密其实也不难,就是在文件的复制的基础进行一定的改动即可,其余代码与上述代码相同!
//...
while(count != -1) {
//很简单的加密方法:每一次数组的前两个元素位置互换 1024
byte temp = b[0];
b[0] = b[1];
b[1] = temp;
fos.write(b,0,count);//将读取到的有效字节 写入
fos.flush();
count = fis.read(b);
}
//...
由于加密代码使用的为对称加密,只需要改一下参数再运行一遍即可解密了
可以复制文件,空文件夹,非空文件夹
//参数
// - file:被复制的文件/文件夹对象
// - path:文件/文件夹复制目标路径
public void superCopyFile(File file,String newPath){
//获取file的绝对路径, 拼串的方式获取新文件或者新文件夹的路径
String oldFilePath = file.getAbsolutePath();
int idx1 = oldFilePath.lastIndexOf('\\');
int idx2 = oldFilePath.lastIndexOf('/');
int idx = idx1 > idx2 ? idx1 :idx2;
String newFilePath = newPath + oldFilePath.substring(idx, oldFilePath.length());
File newFile = new File(newFilePath);
//判断当前传递进来的file是个文件还是文件夹 isFile isDirectory listFiles
File[] files = file.listFiles();
if(files != null){//文件夹
newFile.mkdir();
System.out.println(newFile.getName() + " 文件夹复制完毕");
if(files.length != 0){//非空文件夹
for(File f:files){
this.superCopyFile(f,newFilePath);
}
}
}else{//文件
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream(file);
fos = new FileOutputStream(newFile);
byte[] b = new byte[1024];
int count = fis.read(b);
while(count!=-1){
fos.write(b,0,count);
fos.flush();
count = fis.read(b);
}
System.out.println(newFile.getName() + " 文件复制完毕");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(fis!=null) {
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if(fos!=null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
文件夹的剪切 = 文件夹的复制 + 文件夹的遍历删除
字节型文件流很强大,基本上什么文件都能操作;但是有一点不好,处理纯文本文件可能会产生乱码的问题
而字符型文件流,只能操作纯文本文件(我们可以这么理解:文件右键打开,记事本打开,出现的内容可以看的懂的)
其实字符型文件流的玩法和字节型文件流是一样的,除了读取的单位不同
还有在使用字符型文件流的时候,很容易出现字符编码的问题
不同国家的数字和符号是一样的,但不同国家有不同的文字(中文、英文、法文、韩文、日文。。。)
与此相似,在计算机最早产生时,它是按照英文字母,单个字符设计的
字母 数字 符号 ----- 1字节( 8bit,范围256 )
如果计算机想要处理除了上述字母符号以外的其他字符----比如中文(2字节),就需要将中文进行字符编码-------拆分和组合
字符编码:拆分组合的规则
常见的字符编码:
平台(操作系统)默认字符集:
集成开发环境(IDE)的默认字符集:
HTML---->浏览器解析文字使用的字符集(用charset来设置,一般都是UTF-8或者GBK)
注意在用记事本存储文字,流操作纯文本形式的时候,字符的形式采用统一的编码方式(比如:UTF-8)
byte数组组合成字符串,用String的这个构造方法
new String(byte[],"UTF-8")
字符串拆分成byte数组:
String s = "陌生人,祝愿你";
byte[] b = s.getBytes("UTF-8");
缓冲流是通过字节型文件流或者字符型文件流构造的,方法基本一致,但是效率更棒,所以这个流用的多,而且缓冲流中的BufferedReader和BufferedWriter多了两个很有趣而且实用的方法,可以对文件一行一行的进行操作
文件流中的低级流
缓冲流:
在管道内增加缓存的数据,让我们使用流读取文字更加的流畅
缓冲流(高级流)的创建是通过低级流(上面四个)来完成的,真实进行操作的还是低级流(缓冲流构建的时候没有boolean类型的参数)
用起来和文件流完全一样
BufferedInputStream 对象的创建过程
File file = new File("文件路径");
FileInputStream fis = new FileInputStream(file);
BufferedInputStream bis = new BufferedInputStream(fis);
方法
BufferedOutputStream 对象的创建过程
File file = new File("文件路径");
FileOutputStream fos = new FileOutputStream(file, true);
BufferedOutputStream bos = new BufferedOutputStream(fos);
BufferedReader的创建过程也是通过低级流FileReader:
File file = new File("文件路径");
FileReader fr = new FileReader(file);
BufferedReader br = new BufferedReader(fr);
BufferReader的方法也和Filereader基本差不多,但是多了一个很有意思的方法readLine()
,可以读取一行
BufferedWriter的创建过程也是通过低级流FileWriter:
File file = new File("文件路径");
FileWriter fr = new FileWriter(file, true);
BufferedWriter br = new BufferedWriter(fr);
BufferedWriter 的方法也和FileWriter 基本差不多,但是多了一个很有意思的方法newLine()
,可以写入行分隔符(姑且理解为\n
)
缓冲流中,write方法之后,必须要写flush方法
自此,文件流写完了
FileInputStream
/ FileOutputStream
FileReader
/ FileWriter
BufferedInputStream
/ BufferedOutputStream
(最有用)BufferedReader
/ BufferedWriter
(最有用)还有数组流(不太常用)
ByteArrayInputStream
/ ByteArrayOutputStream
CharArrayReader
/ CharArrayWriter
还有数据流(没啥用)
DataInputStream
读取一个字节,可以通过调用不同的读取方法,返回这个字节的不同的基本类型DataOutputStream
还有字符串流(没啥用)
StringReader
StringWriter
对象流(挺有用的)
ObjectInputStream
ObjectOutputStream
一般对文件的操作,缓冲流的使用就足够了,而且可以保存对象的属性;但是如果想把一个对象的属性,甚至是方法都保存到文件中,缓冲流就爱莫能助了
ObjectInputStream
ObjectOutputStream
将对象直接记录在文件中 永久保存 这就是对象的持久化(对象的序列化)
Serializable
接口Person p = new Person();//这个对象 implements Serializable 即可
FileOutputStream fos = new FileOutputStream("文件路径");
ObjectOutputStream oos = new ObjectOutputStream(fos);
//将对象拆分成字节碎片 序列化到文件里
oos.writeObject(p);
oos.flush();
private long serialVersionUID = 任意L;
,才可以进行反序列化ObjectInputStream ois = new ObjectInputStream(new FileInputStream("对象序列化时写入的文件路径"));
Person p = (Person)ois.readObject();
System.out.println(p);
p.eat();//执行该对象的一个方法
在Idea中,可以设置自动添加序列化版本ID:idea -> File -> Settings -> Editor -> Inspections -> Java -> Serialization issues