JavaIO流
前言
1. 基本知识
File类的对象可以针对文件或者文件夹
File类只能对文件本身进行操作,比如删除文件,获取文件大小,文件名信息等,但不能读写里面的数据
IO流就是用来读写文件的,还可以读写网络中的数据
右键,属性,安全,就可以看到一个文件的位置信息了
public class test {
public static void main(String[] args) {
File file=new File("D:\\a.txt");//指定路径
System.out.println(file.length());//文件大小、、字节
}
}
File file=new File("D:/a.txt");
System.out.println(file.length());
或者这样写也可以
File file=new File("D:"+File.separator+"a.txt");
System.out.println(file.length());
或者这样
separator就是分隔符的意思
文件大小,就是里面装的内容的大小
如果是文件夹,就是里面装的文件的大小和,不是所有内容和
如果指定的是一个不存在的路径,那么求出来的大小就是0
File file=new File("D:"+File.separator+"a.txt");
System.out.println(file.length());
System.out.println(file.exists());//true
exists就是看这个路径的文件是否存在
File file=new File("test1021_1\\src\\a.txt");
System.out.println(file.length());
System.out.println(file.exists());//true
对于同一个项目里面的文件,可以使用相对路径,默认是在项目里面找的
所以相对路径的第一个就是模块名,而不是盘符
2.文件方法
File file=new File("test1021_1\\src\\a.txt");
System.out.println(file.length());
System.out.println(file.exists());
System.out.println(file.isFile());//看是不是文件
System.out.println(file.isDirectory());//看是不是文件夹
System.out.println(file.getName());//获取文件名称
long time=file.lastModified();//获取文件的最后修改时间,时间戳
SimpleDateFormat sdf=new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
System.out.println(sdf.format(time));
File file1=new File("test1021_1\\src\\a.txt");
File file2=new File("D:\\\\a.txt");
System.out.println(file1.getPath());//获取创建文件对象时,使用的路径
System.out.println(file2.getPath());
System.out.println(file1.getAbsoluteFile());
System.out.println(file2.getAbsoluteFile());//获取绝对路径
File f1=new File("D:/b.txt");//不存在这个路径
System.out.println(f1.createNewFile());//创建一个新文件(文件内容为空),创建成功返回true
File f2=new File("D:/bbb");//不存在这个路径
System.out.println(f2.mkdir());//创建文件夹,只能创建一级文件夹
File f3=new File("D:/bbb/ccc/ddd");//不存在这个路径
System.out.println(f3.mkdir());//创建文件夹,只能创建一级文件夹
File f3=new File("D:/bbb/ccc/ddd");//不存在这个路径,存在这个路径也不能创建
System.out.println(f3.mkdirs());//创建文件夹,创建多级文件夹
System.out.println(f1.delete());//删除文件或者空文件夹,非空文件夹不能删除,然后就是删除后不会进入回收站
File f3=new File("D:/aaa");
String[] names=f3.list();//会把下一级的文件名,或者文件,弄到一个字符串数组中
for(String name:names)//范围for
{
System.out.println(name);
}
File f3=new File("D:/aaa");
File[] files=f3.listFiles();//会把下一级的文件夹,或者文件,弄到一个文件对象数组中
for(File file:files)//范围for
{
System.out.println(file.getAbsoluteFile());
}
当aaa是文件的时候,返回null
当aaa是空文件夹的时候,返回长度为0的数组
返回的数组也会有隐藏文件
如果没有权限访问aaa文件夹,返回null
3. 方法递归
public static void test1()
{
test1();
}
这个是直接递归
public static void test1()
{
test2();
}
public static void test2()
{
test1();
}
这个是间接递归
3.1文件搜索
在D盘中,找到QQ.exe这个文件,然后输出其位置
1.先找出D盘下所有一级文件对象
2.遍历所有一级文件对象,判断是否为文件
3.如果是文件,判断是否是自己想要的
4.如果是文件夹,需要继续进入到该文件夹,重复上面这个过程
/**
*
* @param dir /**右键就可以得到 目录
* @param fileName 要搜索的文件名称
*/
public static void searchFile(File dir,String fileName)
{
//先看存不存在
if(!dir.exists()||dir==null)
{
return;
}
//首先看是不是文件夹
if(dir.isFile())
{
if(dir.getName().contains(fileName)){
System.out.println("找到了"+dir.getPath());
return;
}
return;
}
//是文件夹
File[] files=dir.listFiles();
if(files!=null&&files.length>0)
{
for(File f:files)
{
searchFile(f,fileName);
}
}
}
那么删除非空文件夹也是类似的思想
也是递归的思想
4. 编码
4.1 基本知识
ASCII编码
就是一个字节存储一个字符,最高位为0
对于美国人完全够用了
但是我们汉字多
我们用的是GBK编码,就是一个中文字符编码成两个字节形式储存
还要注意的是GBK是包含了ASCII的
但是汉字的最高位必须是1,不然怎么区分哪个是英语字符,哪个是汉字字符
比如"我a你"
就这样
1xxxxxxx xxxxxxxx 0xxxxxxx 1xxxxxxx xxxxxxxx
然后又有Unicode字符集,是万国码,针对所有国家的
UTF-32 表示4个字节为一个字符,但是这样太浪费了
UTF-8编码,可变长编码
英文,数字,用ASCII
汉字三个字符
a 97 01100001
我 25105 110 001000 01001
m 109 01101101
0xxxxxxx ASCII
110xxxxx 10xxxxxx
1110xxxx 10xxxxxx 10xxxxxx
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
填充时,就填充xxxxxxx,其余的都是固定的
比如“a我m”
01100001 1110xxxx 10xxxxxx 10xxxxxx 01101101
将110 001000 01001填上去就可以了
开发人员用的都是UTF-8,编码不一样,就会出现乱码
英文和数字不会乱码,因为都包含了ASCII
这里可以更改编码方式
比如我们用GBK写a我m
转化为UTF-8,就变成这个样子了
分析一下
GBK中a我m为
0xxxxxxx 1xxxxxx xxxxxxx 0xxxxxxx
UTF-8中
第一个字节认识
第二个不认识所以为?
4.2 编码与解码
String data="a我b";
byte[] bytes=data.getBytes();//默认以平台的字符集编码 将a我b编码成二进制,然后一个一个字节存在里面,存放在数组中
System.out.println(Arrays.toString(bytes));
String data="a我b";
byte[] bytes=data.getBytes("GBK");
System.out.println(Arrays.toString(bytes));
这样就是指定的
//解码
String s1=new String(bytes);//将二进制的字节的数组解码成字符串,也是默认的平台解码
System.out.println(s1);
//解码
String s1=new String(bytes,"GBK");//这样就可以指定
System.out.println(s1);
5.IO字节流
对于内存来说,数据到了磁盘或网络,叫输出
数据到了自己,叫输入
所以总共有四类
字节输入流,字节输出流,字符输入流,字符输出流
5.1文件字节输入流
//1.创建文件字节输入流,与源文件接通
InputStream is=new FileInputStream(new File("E:\\java\\test1021_1\\src\\a.txt"));
FileInputStream is=new FileInputStream("E:\\java\\test1021_1\\src\\a.txt");
InputStream is=new FileInputStream("test1021_1\\src\\a.txt");
这三种构造方法都可以,但我们一般选择最后一种就可以了
int b1=is.read();//读一个字节到int中,读的是字节大小
System.out.println(b1);
System.out.println((char)b1);
int b1=is.read();//读一个字节到int中,读的是字节大小
System.out.println((char)b1);
int b2=is.read();//读一个字节到int中,读的是字节大小
System.out.println((char)b2);
int b3=is.read();//读一个字节到int中,读的是字节大小
System.out.println((char)b3);
int b4=is.read();//读一个字节到int中,读的是字节大小
System.out.println(b4);
多来几次就会发现当没有数据读的时候,那么就会返回-1
所以可以优化一下
int b1=0;
while((b1=is.read())!=-1)
{
System.out.print((char)b1);
}
但是这有缺点,就是读取慢,一个一个读,读一次,就是一次系统调用,所以很麻烦
然后就是读到汉字的时候,就会出现乱码,因为只读一个字节
is.close();
使用完毕以后要关闭,释放系统资源
byte[] buffer=new byte[3];//读取的时候还可以一个一个数组的读 这个就表示一次读取三个字节
int len=is.read(buffer);//返回的是读取的字节个数
String rs=new String(buffer);
System.out.println(rs);
System.out.println(len);
byte[] buffer=new byte[3];//读取的时候还可以一个一个数组的读 这个就表示一次读取三个字节
int len=is.read(buffer);//返回的是读取的字节个数
String rs=new String(buffer);
System.out.println(rs);
System.out.println(len);
len=is.read(buffer);//返回的是读取的字节个数
rs=new String(buffer);
System.out.println(rs);
System.out.println(len);
len=is.read(buffer);//返回的是读取的字节个数
rs=new String(buffer);
System.out.println(rs);
System.out.println(len);
可以看出,没有数据读的时候,返回的读取个数就是-1
还有就是,对于同一个buffer来说,每次都是去覆盖的,所以会把上次的都打印了
优化一下
len=is.read(buffer);//返回的是读取的字节个数
rs=new String(buffer,0,len);
System.out.println(rs);
System.out.println(len);
rs这样构造就不会打印错误了
注意len为-1,不能去构造,我没有构造的
byte[] buffer=new byte[3];
int len;
while((len=is.read(buffer))!=-1){
String rs=new String(buffer,0,len);
System.out.println(rs);
}
这个就是循环读取的方法
但是这个也不能避免汉字乱码问题,因为是读一次打印一次
避免乱码的话,可以定义一个和文件一样大的数组,一次性读完
InputStream is=new FileInputStream("test1021_1\\src\\a.txt");
File f=new File("test1021_1\\src\\a.txt");
long size=f.length();//length返回的是long,而字节数组里面的大小为int,所以改一下,,这个length()返回的就是文件里面的字符个数
byte[] buffer=new byte[(int)size];
int len=is.read(buffer);
System.out.println(new String(buffer));
System.out.println(len);
System.out.println(size);
InputStream is=new FileInputStream("test1021_1\\src\\a.txt");
byte[] bytes=is.readAllBytes();//这个函数可以把文件中的所有字节都读到这个数组中,更方便
System.out.println(new String(bytes));
其实读写文件更适合用字符流
5.2文件字节输出流
OutputStream os=new FileOutputStream("test1021_1\\src\\b.txt");//没有文件会自己创建
os.write(97);//写字节数据
os.write('b');
// os.write('磊');//这样就不行了,因为这是三个字节
byte[] bytes="我爱你中国abc".getBytes();
os.write(bytes);
os.write(bytes,0,15);
OutputStream os=new FileOutputStream("test1021_1\\src\\b.txt",true);//没有文件会自己创建
构造器后面加个true,就可以拼接了,原来默认是false,每次运行都会清空原来的
os.write("\r\n".getBytes());
换行符是占两个字节的,它是由\r\n组成的
文件复制,如果要复制图片的话,就要用字节流,不能用字符流
5.3 处理资源释放
我们上面写的有一个问题,就是万一在close之前就异常抛出的话,就无法关闭了
所以我们有两种处理方式
第一种就是close放在finally中
还有就是千万不要再finally中返回数据
OutputStream os= null;
try {
os = new FileOutputStream("test1021_1\\src\\b.txt",true);
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}finally {
os.close();
}
还有就是close也可能抛异常,os为null的时候不能调用,所以还要继续套娃
OutputStream os= null;
try {
os = new FileOutputStream("test1021_1\\src\\b.txt",true);
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}finally {
try {
if(os!=null)os.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
第二种方法就是在try的括号里面定义,就会自动close,前提在try括号里面的必须是资源
try (
OutputStream os = new FileOutputStream("test1021_1\\src\\b.txt",true);
){
//代码
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
怎么验证呢,资源通常都会实现AutoCloseable接口,我们可以自己定义资源,自己重写close,打印一下就知道了
6.字符流
和字节流差不多,只不过这个传输的是字符
try(
Reader fr=new FileReader("test1021_1\\src\\b.txt");
) {
int c;
while ((c=fr.read())!=-1)
{
System.out.print((char)c);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
try(
Writer fw=new FileWriter("test1021_1\\src\\b.txt",true);//追加数据
) {
fw.write('a');
fw.write(97);
fw.write('磊');
fw.write("\r\n");
fw.write("aaaaaaa");
fw.write("\r\n");
fw.write("测内存",0,1);
char[] buffer={'黑','a','d'};
fw.write("\r\n");
fw.write(buffer);
fw.write(buffer,0,1);//从下标0开始写入,写入一个字符
} catch (FileNotFoundException e) {
e.printStackTrace();
}
Writer fw=new FileWriter("test1021_1\\src\\c.txt");
fw.write('a');
fw.write(97);
字符流写出数据的时候,必须刷新流或者关闭流,写出去的才生效,不然一直存在缓冲区中,或者缓冲区满了,才会输出过去
Writer fw=new FileWriter("test1021_1\\src\\c.txt");//追加数据
fw.write('a');
fw.write(97);
fw.flush();
而关闭流是会自动刷新的
7. 缓冲流
后面我们讲的流都是高级流了
缓冲流的作用就是在内存中开辟一个类似于缓冲区的东西,比如8kb,如果没有这个的话,就要进行16次系统调用,才可以把数据源弄过来
现在有了这个缓冲流的话,我们就可以先把数据源的8kb放在缓冲流中,相当于只用两次系统调用,因为系统调用很耗时间,所以能节约就节约,这样效率就提高了
字节缓冲输入流自带了8kb缓冲池
try(
InputStream is=new FileInputStream("test1021_1\\src\\b.txt");
InputStream bis=new BufferedInputStream(is);//这个就是字节缓冲输入流
OutputStream os=new FileOutputStream("test1021_1\\src\\a.txt");
OutputStream bos=new BufferedOutputStream(os);
) {
byte[] buffer=new byte[1024];
int len;
while((len=bis.read(buffer))!=-1){
bos.write(buffer,0,len);
}
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
这个就算你遇到一个中文截断了也没事,因为字节与字节之间可以拼接,而不像原来的打印,如果截断了,打印出来肯定也是乱码
这个就是缓冲流,相比原来,速度快多了,因为增加了缓冲流
try(
Reader fr=new FileReader("test1021_1\\src\\b.txt");
Reader br=new BufferedReader(fr);//这个就是字符缓冲输入流
) {
char[] buffer=new char[3];
int len;
while((len=br.read(buffer))!=-1){
System.out.print(new String(buffer,0,len));
}
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
使用特有方法
try(
Reader fr=new FileReader("test1021_1\\src\\b.txt");
// Reader br=new BufferedReader(fr);//这个就是字符缓冲输入流
//因为我们要使用特有的功能,所以还是不要多态了
BufferedReader br=new BufferedReader(fr);
) {
char[] buffer=new char[3];
String line;
while((line=br.readLine())!=null){
System.out.print(line);
}
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
但这个不会读换行符
try (
Writer fw = new FileWriter("test1021_1\\src\\b.txt");
// Reader br=new BufferedReader(fr);//这个就是字符缓冲输入流
//因为我们要使用特有的功能,所以还是不要多态了
BufferedWriter bw = new BufferedWriter(fw);
) {
bw.write('a');
bw.write("\r\n");
bw.write('a');
bw.newLine();
bw.write('a');
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
明显缓冲流要比前面的流要快点
不同编码问题
如果代码编码和文本文件的编码不一致,使用字符流读取文本文件的时候就会出现乱码
8. 转换流
比如文本文件GBK,你写代码的地方UTF-8,字符流读取的时候就会出现乱码,转换流就可以解决这个问题
多一个参数,就是文本文件的编码形式
try(
InputStream is=new FileInputStream("E:\\java\\test1021_1\\src\\b.txt");
Reader isr=new InputStreamReader(is,"GBK");
//这个就是转换流
BufferedReader br=new BufferedReader(isr);//转换流包装成缓冲流
) {
String line;
while((line =br.readLine())!=null) {
System.out.println(line);
}
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
}
这样就可以了
如果要控制写出去的字符为GBK则这样
String data="adhiu带我";
byte[] bytes=data.getBytes("GBK");
或者用我们这里的转化流,就是字符输出转换流
try(
OutputStream is=new FileOutputStream("E:\\java\\test1021_1\\src\\b.txt");
Writer isr=new OutputStreamWriter(is,"GBK");
//这个就是转换流
BufferedWriter br=new BufferedWriter(isr);//转换流包装成缓冲流
) {
br.write("神奇的");
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
9. 打印流
try(
PrintStream ps=new PrintStream("E:\\java\\test1021_1\\src\\a.txt");
) {
ps.println(97);
ps.println('a');
ps.println("AdIdasS马克");
ps.println(true);
ps.println(99.5);
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
try(
PrintStream ps=new PrintStream("E:\\java\\test1021_1\\src\\a.txt", Charset.forName("GBK"));
) {
ps.println(97);
ps.println('a');
ps.println("AdIdasS马克");
ps.println(true);
ps.println(99.5);
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
还可以这样指定GBK打印过去
然后就是这个打印流,和上面的没什么区别,区别就是write方法,一个写字节,一个写字符
一般系统默认的System.out.println();是打印在控制台的,但我们可以修改
try(
PrintStream ps=new PrintStream("E:\\java\\test1021_1\\src\\a.txt", Charset.forName("GBK"));
) {
System.setOut(ps);//将System.out.println();的打印位置修改
System.out.println("aaaaaaaaaaaaaaa");
System.out.println("bbbbbbbbbbbbb");
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
10.数据流
这个写进去的就是任意数据了,这个的好处就是读出来也是对应的数据
try(
DataOutputStream ps=new DataOutputStream(new FileOutputStream("E:\\java\\test1021_1\\src\\a.txt"));
) {
ps.writeInt(97);
ps.writeDouble(97.1);
ps.writeBoolean(true);
ps.writeUTF("adsaf猝死");
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
这个写进去的我们不用管,因为是按照类型来写的,所以看不懂
只有写进去为字符我们才看得懂
try(
DataInputStream ps=new DataInputStream(new FileInputStream("E:\\java\\test1021_1\\src\\a.txt"));
) {
int i=ps.readInt();
System.out.println(i);
double d=ps.readDouble();
System.out.println(d);
boolean b=ps.readBoolean();
System.out.println(b);
String rs=ps.readUTF();
System.out.println(rs);
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
这个读取也要对应的读取,不然不对
11. 序列化流
这个流就是把java对象写入文件中–》对象序列化
把文件中的对象读出来—》反序列化
先创建一个类和对象
public class User implements Serializable {
private String name;
public User(String name) {
this.name = name;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}
注意对象如果需要序列化的话,必须要继承序列化接口,就是Serializable
try(
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("E:\\java\\test1021_1\\src\\a.txt"));
) {
User u=new User("aaaaa");
oos.writeObject(u);//序列化对象到文件中
} catch (IOException e) {
throw new RuntimeException(e);
}
因为是以对象存储的,所以也看不懂
try(
ObjectInputStream ois=new ObjectInputStream(new FileInputStream("E:\\java\\test1021_1\\src\\a.txt"));
) {
User u=(User) ois.readObject();//这个会返回一个Object类,所以强转
System.out.println(u);
} catch (Exception e) {
throw new RuntimeException(e);
}
这个就是从文件中读取对象
private transient String name;
给name添加一个transient ,这样的话这个参数就不会参与序列化,但是原来的对象里面有name,文件中没有name,反序列化后的也没有name
这个可以对密码使用,这样就不会被看到密码了
如果要序列化多个对象,那么把这多个对象存在ArrayList集合中就可以了,因为ArrayList已经实现了序列化接口
12. 框架
框架就是jar包的格式
我们讲的是IO框架就是Commons-io
我们可以去官网下载
IO框架
下载这个然后解压就可以了
选择这个
在模块下创建文件夹,复制粘贴jar包
然后添加为库
这样就可以使用了
FileUtils.copyFile(new File(),new File());//拷贝文件
FileUtils.copyDirectory(new File(),new File());//拷贝文件夹
FileUtils.deleteDirectory(new File());//删除文件夹
总结
下一节对应特殊文件