IO流概述
IO流:用于读写文件中的数据。其中I表示input,O表示output。
IO流的分类
- 字节流:操作所有类型的文件
- 字符流:只能操作纯文本文件
IO流体系
具体实现步骤
- 创建对象
- 写出数据
- 释放资源
字节流
字节输入流(InputStream)基本
具体实现
- 创建对象(使用FileOutputStream类)
FileOutputStream fop = new FileOutputStream("io\\a.txt");
- 写出数据
fop.write(97);
- 释放资源
fop.close();
字节输出流的细节
- 创建对象
- 参数字符串或者File对象均可
- 如果文件不存在会重新创建一个新的文件,但是只要保证父类文件存在即可
- 如果文件已经存在,则会清空原本文件
- 写数据
- write方法参数是整数,但是写到对应路径上的文件是ASCII上的字符
- 释放资源
- 每次使用完之后都要进行资源的释放。
FileOutputStream的三种写数据方法
方法名称 | 说明 |
---|---|
void write ( int b ) | 一次写一字节数据 |
void write ( byte[] b ) | 一次写一字节数组数据 |
void write ( byte[] b ,int off ,int len) | 一次写一部分字节数组数据 |
具体实现
FileOutputStream fop = new FileOutputStream("io\\a.txt");
String str = "lalalalawoshimaibaodexiaohangjia";
//将字符串转换为byte类型的数组
byte[] bytes = str.getBytes(str);
fop.write(bytes);
fop.close();
关于换行
不同的系统换行操作是不同的
- Windows系统:\r\n
- Linux系统:\n
- 苹果系统:\r
我们在实现换行操作时直接读入换行符即可
String str1 = "\r\n";
byte[] bytes1 = str1.getBytes(str1)
fop.write(bytes1);
字节输出流(OutputStream)基本
具体实现
- 创建对象(FileInputStream类)
FileInputStream fis = new FileInputStream("io\\a.txt");
- 读取数据
int b1 = fis.read();
System.out.println((char)b1);
- 释放资源
fis.close();
字节输入流的细节
- 创建对象
- 如果文件不存在,会报错。
- 写数据
- 一次读入一个字节,读出来是对应ASCII的数字.。
- 读到文件末了,read方法返回-1。
- 释放资源
- 每次使用完之后都要进行资源的释放。
字节输入流的循环格式
通过read方法返回-1来循环
FileInputStream fis = new FileInputStream("io\\a.txt");
int b1;
//while循环进行判断
while((b1 = fis.read())!=-1){
System.out.print((char)b1);
}
fis.close();
还可以用byte []来接收
FileInputStream fis = new FileInputStream("io\\a.txt");
int b1;
byte[] bytes = new byte[5];
while((b1 = fis.read(bytes))!=-1){
System.out.print(new String(bytes,0,b1));
}
fis.close();
关于字节流的拷贝问题
对于小文件
我们可以采取边读边录入的方式进行
//创建对象
FileInputStream inputStream = new FileInputStream("io\\a.txt");
FileOutputStream outputStream = new FileOutputStream("io\\b.txt");
//边读入边录入
int b;
while((b = inputStream.read())!=-1){
outputStream.write(b);
}
//先开后关 后开先关
outputStream.close();
inputStream.close();
对于大文件
我们采用一个字节一个字节的读入会使得速度缓慢,这是我们需要一个字符数组一个字符数组的读入
FileInputStream inputStream = new FileInputStream("io\\a.txt");
FileOutputStream outputStream = new FileOutputStream("io\\b.txt");
int b;
//创建你想要传输的字节数组大小
byte[] bytes = new byte[1024*1024*5];
//传输
while((b = inputStream.read(bytes))!=-1){
outputStream.write(bytes,0,b);
}
释放
outputStream.close();
inputStream.close();
细节
此时我们使用write(byte[],int off,int len)方法是防止数据可能最后读入时无法取整的情况
对于字节流的异常处理
基本做法
手搓异常处理
try(){
//可能出现的异常
}catch(异常类名 变量名){
//异常的处理代码
}finally{
//执行所以资源的释放
}
对于之前的数据拷贝进行异常处理
//将实例创建放在外面,是的finally中的变量能够使用
//对于成员变量要实现 空 的初始化,不然可能会警示成员变量未初始化。
FileInputStream inputStream = null;
FileOutputStream outputStream = null;
try{
inputStream = new FileInputStream("io\\a.txt");
outputStream = new FileOutputStream("io\\b.txt");
int b;
byte[] bytes = new byte[1024*1024*5];
while((b = inputStream.read(bytes))!=-1){
outputStream.write(bytes,0,b);
}
}catch(IOException e){
System.out.println(e.getMessage());
}finally{
//如果为空,则没有关闭流的必要
if(outputStream!=null){
//对于close还要进行异常处理
try{
outputStream.close();
}catch (IOException e){
e.getMessage();
}
}
if(inputStream!=null){
try{
inputStream.close();
}catch (IOException e){
System.out.println(e.getMessage());
}
}
}
JDK8
将对象的创建直接放入try中,之间用分号隔开
try(FileInputStream inputStream = new FileInputStream("io\\a.txt");
FileOutputStream outputStream = new FileOutputStream("io\\b.txt");){
int b;
byte[] bytes = new byte[1024*1024*5];
while((b = inputStream.read(bytes))!=-1){
outputStream.write(bytes,0,b);
}
}catch (IOException e){
e.getMessage();
}
JDK9
将对象创建放在外面,直接由函数抛出对象创建的异常。try中直接放入变量名。
public static void main(String[] args) throws FileNotFoundExpection{
FileInputStream inputStream = new FileInputStream("io\\a.txt");
FileOutputStream outputStream = new FileOutputStream("io\\b.txt");
try(inputStream;outputStream){
int b;
byte[] bytes = new byte[1024*1024*5];
while((b = inputStream.read(bytes))!=-1){
outputStream.write(bytes,0,b);
}
}catch (IOException e){
e.getMessage();
}
}
字符集
关于字符集
了解字符集之前,我们要知道以下两点
- 计算机中,任意数据都是二进制存储
- 计算机中,最小的存储单元是一个字节(8位二进制)
ASCII字符集
对于欧美国家已经受用且一个英文占用一个字节。
GDK字符集
是我国所定义的含义中文日文韩文等的字符集
- 简体中文版的Windows默认GDK字符集
- GDK字符集兼容ASCII字符集
- 一个英文占一个字节,二进制首位为0
- 一个中文站两个字节,高进制首位为1
Unicode字符集
国际上定义的字符集,几乎含有所有语言,最常用UTF-8的编码形式
- 一个英文占用一个字节,二进制首位为0,转为十进制为正数
- 一个中文占用三个字节,二进制首位为1,第一个二进制转为十进制为负数
关于乱码
为什么会产生乱码
- 读取数据时没有读完完整的汉字
- 编码解码时没有使用同一种方式
如何规避
- 不用字节流读取文本文件
- 编码解码时使用同一种方式
Java中关于编码与解码
编码
String类里面的方法 | 说明 |
---|---|
public byte[] getBytes() | 使用默认方式编码 |
public byte[] getBytes(String charsetName) | 使用指定方式编码 |
解码
String类里面的方法 | 说明 |
---|---|
String (byte[] bytes) | 默认方式解码 |
String (byte[] bytes ,String charsetName) | 指定方式解码 |
具体实现
String str0 = "喜欢你!";
byte[] byte1 = str0.getBytes();
byte[] byte2 = str0.getBytes("GBK");
//解码
String str1 = new String(byte1);
String str2 = new String(byte2,"GBK");
System.out.println(str1);
System.out.println(str2);
字符流
字符流的底层就是字节流:
字符流 = 字节流 + 字符集
特点
- 输入流:一次读入一个字节,遇到中文时候读入多个字节
- 输出流:底层会把指定的编码方式进行编译,变成字节在写到文件之中
字符输入流(Reader)的基本
具体实现
- 创建对象
FileReader fr = new FileReader("io\\reader.txt");
- 读取数据
//单个数据
int cha1 = fr.read();
//多个数据
int cha2 = fre.read(char[] buffer);
- 释放资源
fr.close();
空参read
FileReader fr = new FileReader("io\\reader.txt");
//读取数据
int ch;
while((ch = fr.read()) != -1){
System.out.print((char)ch);
}
//资源释放
fr.close();
有参read
有参数的read方法相当于将数据读取,解码,强制三部并和了
FileReader fr = new FileReader("io\\reader.txt");
int ch;
//用字符串数组去接收
char[] chars = new char[25];
while((ch = fr.read(chars)) != -1){
System.out.print(new String(chars,0,ch));
}
fr.close();
字符输出流(Writer)基本
具体实现
- 创建对象
FileWriter fw = new FileWriter("io\\writers");
- 读取数据
fw.write("我爱你");
//也可读入一个字符数组write(char[] cha);
- 释放资源
fw.close();
原理解析
- 创建对象
- 关联文件,创建缓冲区(长度为8192字节的数组)
- 读取数据
- 判断缓冲区是否有数据可以读取
- 缓冲区没有数据时:从文件中获取数据,尽可能填满缓冲区;如果文件中没有数据,则返回-1
- 缓冲区有数据:则从缓冲区读取数据
扩展
方法名称 | 说明 |
---|---|
public void flush() | 将缓冲区的数据刷新到本地文件中 |
public void close | 释放资源/关流 |
File类
- File表示什么?
- File对象表示路径,可以是文件,可以是文件夹,可存在也可不存在
- 绝对路径与相对路径:
- 绝对路径带有盘符
- 相对路径不带盘符,默认当前项目下寻找
File类的构造方法
方法名称 | 说明 |
---|---|
public File (String pathname) | 用字符串表示路径 |
public File (String parent , String child) | 把父级路径和子级路径拼接到一起 |
public File (File parent , String child) | 同上 |
常用成员方法
方法名称 | 说明 |
---|---|
public boolean createNewFile() | 创建一个新的文件 |
public boolean mkdir() / mkdirs() | 创建单极/多级文件 |
public boolean delete() | 删除文件,空的文件夹 |
public boolean delete() | 获取当前路径下的所有内容 |
public boolean createNewFile() :创建一个新的文件
- 如果当前路径表示的文件不存在,则创建成功并且返回true,反之则返回false
- 如果父级路径不存在,则会抛出IOException异常
- createNewFile方法创建的一定是文件,如果没有后缀名,则会创建没有后缀的文件
public boolean mkdir() / mkdirs() :创建单极/多级文件
- Windows系统中文件名是唯一的,如果存在,则创建失败返回false
- **mkdirs()**方法既可创建单极也可创建多级,经常使用
public boolean delete() : 删除文件,空的文件夹
- 如果删除的是文件或空的文件夹,则直接删除且不走回收站
- 如果删除的是有内容的文件夹,则删除失败,返回false
public File[] listFiles() :获取当前路径下的所有内容
- 当调用者File表示的路径不存在或者为文件时,返回null
- 当为空文件时,返回长度为0的数组
- 当文件夹有内容时,返回所有内容的路径
- 当有隐藏文件时,照样会返回路径
- 当有权限修饰时,会返回null
一些具体的实现
文件夹的拷贝
基本原理:
- 写一个文件拷贝的函数,传递数据源路径和目标路径
- 遇到文件则进行拷贝,遇到文件夹则进行递归
先写copy函数
public static void copy(File parent,File child) throws IOException {
//防止为空
child.mkdirs();
//阅览目标文件下的内容
File[] files = parent.listFiles();
//增强for语句遍历
for(File file : files){
//如果是文件,直接拷贝
if(file.isFile()){
FileInputStream fis = new FileInputStream(file);
//拷贝地址为父类文件加上子类文件名
FileOutputStream fos = new FileOutputStream(new File(child,file.getName()));
byte[] bytes = new byte[1024*1024];
int len;
while((len = fis.read(bytes)) != -1){
fos.write(bytes,0,len);
}
fos.close();
fis.close();
//如果为文件,则进行递归操作
}else{
copy(file,new File(child,file.getName()));
}
}
再写上主函数
File str = new File("F:\\kaobei\\Java");
File md = new File("F:\\kaobei\\Javadekaobei");
try{
copy(str,md);
}catch (IOException e){
System.out.println(e.getMessage());
}catch(Exception e){
System.out.println(e.getMessage());
}
缓冲流
作用:
- 对于字符流:在内存中创建一个最大长度为8192的缓冲区来接受,传递数据,提高传输效率。
- 对于字节流
- 由于自带缓冲区,所以对于传输速度影响不大
- 有许多好用的方法比如readLine(),newLine等。
缓冲字节流
- 创建对象
方法名称 | 说明 |
---|---|
public BufferedInputStream (InputStream is) | 输入流创建 |
public BufferedOutputStream (OutputStream is) | 输出流创建 |
具体实现:
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("io\\a.txt"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("io\\b.txt"));
- 读取数据
与之前的字节输入输出流用法一致 - 释放资源
bis.close();
bos.close();
注解:这里的close()方法只需要关闭缓冲流的即可,底层代码会自动关闭字节流。
缓冲字符流
创建对象,读取数据,释放资源与上类似,来讲讲他的好用的方法
public String readLine():缓冲字符输入流 特有 方法
public String readLine()方法会读取一整行,遇到回车换行会返回null,所以我们在用readLine()方法时判断条件应改为 :
String len;
while((len = br.readLine()) != null){.....}
public void newLine():缓冲字符输出流 特有 方法
因为windows,Linux,苹果系统之间的换行符不一样,所以使用newLine()方法能统一换行,更加严谨。
转换流
作用:
指定字符集的读写(已经被淘汰!)- 字节流想要使用字符流中的方法。
具体实现:
- 创建对象
InputStreamReader isr = new InputStreamReader(new FileInputStream());
//若想使用BufferedInputStream的方法,则如下创立对象
InputStreamReader isr = new InputStreamReader(new BufferedInputStream(new FileInputStream()));
- 读取数据
- 释放资源
序列化流
作用:将成员变量存储到文件当中
具体实现
- 构造:
构造方法 | 说明 |
---|---|
public ObjectOutputStream (OutputStream is) | 把基本输出流包装为高级流 |
public ObjectInputStream (InputStream is) | 把基本输入流包装为高级流 |
ObjectOutputStream oos = new ObjectOutputStream( new OutputStream());
- 读取数据
方法 | 说明 |
---|---|
public final void writeObject (Object obj) | 把对象序列化,并且读入到文件中 |
public final void readObject (Objetct obj) | 把文件中的对象反序列化并且读出 |
oos.writeObject(new Student());
- 释放资源
细节
- 使用序列化流时,要让javabean实现Serializable接口
public class Student implements Serializable{...}
- 序列化流写道文件中无法被改变了,改变会出现异常
- 序列化对象后,如果我们改变javabean,在进行反序列化
- 会出现异常,因为每一次改变都会更变javabean的“版本号”
- 要规避这个异常,我们就要自己固定版本号serialVersionUID,并且使用private static final修饰。
private static final long serialVersionUID = -7160264594827633452L;
- 如果一个成员变量不想被序列化,则用transient进行修饰
private transient int age;
多对象读取
思路:
- 多对象的读取到文件末尾不会返回-1或者null
- 我们想要定义一个**ArrayList< E >**容器来装取多个对象,只需要读取一次即可
对于对象的录入
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("io\\xulie1"));
ArrayList<Student> list = new ArrayList<>();
//多个对象的定义
Student stu1 = new Student("zhangsan",17);
Student stu2 = new Student("lisi",18);
Student stu3 = new Student("wangwu",19);
//将对象装入容器中
list.add(stu1);
list.add(stu2);
list.add(stu3);
//写入文件当中
oos.writeObject(list);
//释放资源
oos.close();;
对于对象的录出 :
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("io\\xulie1"));
//读取数据
ArrayList<Student> o = (ArrayList<Student>)ois.readObject();
//增强for来阅览容器
for (Student student : o) {
System.out.println(student);
}
资源释放
ois.close();
打印流
特点
- 打印流只有输出流,无输入流
- 特有的方法可以实现,可以保证数据的原始输出
- 特有的写出方法,可以实现自动刷新,自动换行
字节打印流
构造方法
构造方法 | 说明 |
---|---|
public PrintStream(OutputStream/File/String) | 关联字节输出流 |
public PrintStream(String fileName, Charest charset) | 指定字符编码 |
public PrintStream(OutputStream out, boolean autoFlush) | 自动刷新 |
public PrintStream((OutputStream out, boolean autoFlush, String encoding) | 指定字符编码并且自动刷新 |
成员方法
方法名称 | 说明 |
---|---|
public void write() | 常规方法:与之前的写出规则一样 |
public void println() | 特点方法:打印任意数据,自动换行,自动刷新 |
public void print() | 特点方法:打印任意数据,不换行 |
public void printf() | 特定方法“带有占位符的打印语句,不换行 |
说明:
- 由于字节流的底部没有缓冲区,所以自动刷新功能没有用处。
PrintStream ps = new PrintStream(new FileOutputStream("io\\reader"),true, StandardCharsets.UTF_8);
//写出数据
ps.println(97);
ps.print(true);
ps.printf("%dis%d",1,2);
//释放资源
ps.close();
字符打印流
与字节打印流差不多,记得自动刷新要手动开启。
关于System.out.println()
在System类中有一个静态的out是这么定义的
public static final PrintfStream out = null;
- 此时我们获取打印流对象,在虚拟机启动时默认创建,指向控制台
- 是特殊的打印流,是唯一的,不能进行关闭操作
针对以上,我们可以如下写出代码
PrintStream ps = System.out;
ps.println("你好你好");