.1 IO流介绍
IO流式读储和读取数据的解决方法
I:input O:output
流:像水流一样传输数据
作用:
用读写数据(本地文件、网络)
分类1 ---- 按照流向
输出流:程序--->文件
输入流:文件--->程序
分类2 ---- 按照操作文件的类型
字节流:可以操作所以类型的文件
字符流:只能操作纯文本文件
纯文本文件
用windows系统自带的记事本打开并且能读懂的文件
txt文件、md文件、xml文件、lrc文件
IO流随用随创建,什么时候不用什么时候关闭
字符流
底层就是字节流 字符流=字节流+字符集
输入流:一次读取一个字节,遇到中文时按照编码方式读取
输出流:底层会把数据按照指定的编码方式进行编码,变成字节再写到文件中
对于纯文本文件进行读写操作
注:
字节流读取中文会乱码,但是拷贝不会乱码是因为一个字节一个字节的拷贝,不会造成二进制数据的丢失,目的地的解码方式和数据源保持一样就可以了
.2 基本流
.2.1基本字节流
.2.1.1 FileInputStream
FileInputStream 操作本地文件的字节输入流 将文件中的数据读取出来 ---写入
是字节流的基本流
读取数据
一次一个字节
一次多个字节,每次读取会尽可能把数组装满 本人一般字节数组设为1024*1024*5
读到末尾返回-1
书写步骤
创建对象、写入数据、释放资源
public class ByteStreamDemo2 {
public static void main(String[] args) throws IOException {
//字节输入流 FileInputStream
//创建对象
// 如果文件不存在,就直接报错 因为文件不存在,创建了也是空 没意义
FileInputStream fis = new FileInputStream("D:\\JavaFile\\_8_7\\a.txt");
//一次读取一个
//一次读一个文件 读出来的是数据在ASCII上对应的数组
//读到文件末尾了,read 会返回-1
int b1 = fis.read();
//读出来的是对应的ASCII码值 a->97 可以使用char强转
System.out.println((char)b1);
int b2 = fis.read();
System.out.println(b2);
System.out.println(fis.read());
//循环读取 读取到空白字返回-1 会自动分行
int b;
while(( b= fis.read()) !=-1){
System.out.print((char)b);
}
//一次读取一个字节数组的数据 一般是1024的整数倍 1024*1024*5 5MB
//创建一个字节数组
byte[] bytes = new byte[2];
FileInputStream fileInputStream = new FileInputStream("D:\\JavaFile\\_8_7\\c.txt");
//返回读取到的字节长度
//第一回读取 a b 返回长度二
/* int len = fileInputStream.read(bytes);
System.out.println(len);
System.out.println(new String(bytes));
//第二回读取 c d 返回长度二 因为数组长度为二 覆盖了 a,b
int len1 = fileInputStream.read(bytes);
System.out.println(len1);
System.out.println(new String(bytes));
//第三回读取 e 返回长度一 因为只读到一个 所以覆盖率数组中第一个元素 c 变成了e d
int len2 = fileInputStream.read(bytes);
System.out.println(len2);
System.out.println(new String(bytes));
//第四回读取 未读取到元素返回 -1 也就没有覆盖数组中元素 还是 e d
int len3 = fileInputStream.read(bytes);
System.out.println(len3);
System.out.println(new String(bytes));*/
//优化 转成字符串是 输入长度
//第一回读取
int len = fileInputStream.read(bytes);
System.out.println(len);
System.out.println(new String(bytes,0,len));
//第二回读取
int len1 = fileInputStream.read(bytes);
System.out.println(len1);
System.out.println(new String(bytes,0,len1));
//第三回读取
int len2 = fileInputStream.read(bytes);
System.out.println(len2);
System.out.println(new String(bytes,0,len2));
//第四回读取
int len3 = fileInputStream.read(bytes);
System.out.println(len3);
if(len3 !=-1){
System.out.println(new String(bytes,0,len3));
}
//释放资源
fileInputStream.close();
fis.close();
}
}
.2.1.2 FileOutputStream
FileOutputStream 操作本地文件的字节输出流 将数据写进文件当中 ---写出
书写步骤
创建对象,写出数据,释放资源
三步操作的细节
创建对象:文件存在、文件不存在、追加写入
写出数据:写出整数、写出字节数组、换行写
释放资源:关闭通道
public class ByteStreamDemo1 {
public static void main(String[] args) throws IOException {
//写一段文字到本地文件中
//创建对象 字节输出流对象
// 参数是字符串表示的路径或者是File对象都可以
// 如果文件不存在会创建一个新的文件,但是要保证父级路径是存在的
// 如果文件已经存在,则会清空文件然后才写出数据
//创建对象的第二个参数 false(默认) 关闭续写 true 打开续写
FileOutputStream fos = new FileOutputStream("D:\\JavaFile\\_8_7\\a.txt",true);
//写出数据
// write 方法的参数是整数,但是写在文件中的是对应的ASCII码值
//一次写一个数据
fos.write(97);//a的ASCII的值是97
//一次写一组数据
byte[] bytes = {97,98,99,100,101,102,103};
fos.write(bytes);
//一次写入一组数据的一部分
// 数组 起始索引 数据个数
fos.write(bytes,2,4);
//换行写 再次写出一个换行符 windows:\r\n Java可以只写一个但是建议不要省略
// Linux: \n Mac:\r
String wrp = "\r\n";
byte[] bytes1 = wrp.getBytes();
fos.write(bytes1);
//遇到一行字符串时 转化为字节数组再添加
String str = "kankelaoyezuishuai";
byte[] bytes2 = str.getBytes();
fos.write(bytes2);
//续写 创建对象时的第二个参数 把默认的false 改成true
//释放资源
//每次使用完流之后都要释放资源
fos.close();
}
}
.2.2 IO流异常处理
JDK9以后
创建流对象1;
创建流对象2;
try(流1;流2){
肯出现异常的代码;
}catch(异常类名 变量名){
异常的处理代码;
}
资源用完最终自动释放
public class ByteStreamDemo4 {
public static void main(String[] args) throws IOException {
//JDK9 的IO流捕获异常处理
//创建写出对象
FileOutputStream fos = new FileOutputStream("D:\\JavaFile\\_8_7\\b.txt");
//创建写入对象
FileInputStream fis = new FileInputStream("D:\\JavaFile\\_8_7\\a.txt");
//会自动释放资源
try (fos;fis){
byte[] bytes = new byte[1024*1024*5];
int len;
while ((len = fis.read(bytes)) != -1){
fos.write(bytes,0,len);
}
}catch (IOException e){
e.printStackTrace();
}
}
}
.2.3基本字符流
.2.3.1 FileReader
FileReader是操作本地文件的字符输入流 将数据从文件中读取出来
操作步骤:
创建字符输入流对象
读取数据 按字节读取,遇到中文一次读多个字节,读取后解码,返回一个整数。读到末尾返回-1
释放资源
public class CharStreamDemo1 {
public static void main(String[] args) throws IOException {
//创建字符流对象并关联本地文件 File对象 和字符串都可以
FileReader f1 = new FileReader("D:\\JavaFile\\_8_7\\src\\CharStreamDemo\\a.txt");
//空参read()
//read() 字符流底层也是字节流 默认一个字节一个字节读取
//如果遇到中文就是多个字节一读,UTF-8是一次3个字节 GBK是一次两个字节
//在读取之后 方法的底层会进行解码然后转成十进制 将十进制作为返回值
//想看的这些数据可以对得到的十进制数据进行char强转
int ch;
while ((ch = f1.read()) != -1){
System.out.print((char)ch);
}
//有参read()
char[] chars = new char[2];
int len;
while ((len = f1.read(chars)) != -1){
System.out.print(new String(chars,0,len));
}
//释放资源
f1.close();
}
}
.2.3.2 FileWriter
FileWriter是操作本地文件的字符输出流 将数据写进文件当中
书写细节
创建字符输出流对象
参数是字符串表示的路径或者File对象都可以
如果文件不存在会创建一个新文件,但要保证父级路径是存在的
如果文件已经存在,则会清空文件,不想清空可以打开续写
写数据
参数是整数,写在本地文件中的是整数再字符集上对应的字符
释放资源
每次使用完流之后都要释放资源
public class CharStreamDemo2 {
public static void main(String[] args) throws IOException {
//可以是file对象 可以是字符串 append : true 开启续写 (默认)false 不开续写
FileWriter fw = new FileWriter("D:\\JavaFile\\_8_7\\src\\CharStreamDemo\\b.txt");
//一次写一个字符
fw.write("你");
//中文的标点符号在UTF-8也是三个字节 英文就是一个
String str = "9527,滚出去!";
//一次写一个字符串
fw.write(str);
//写入字符串的一部分
fw.write(str,0,5);
char[] chars = {'1','我','旧','3'};
//写入一个字符数组
fw.write(chars);
//写入数组的一部分
fw.write(chars,0,3);
//释放资源
fw.close();
}
}
.3 缓冲流
缓冲流对于字符流的提示不明显,对于字符缓冲流的特点是两个特有的方法
BufferedReader 字符缓冲输入流:readLine()
BufferedWriter 字符缓冲输出流:newLine()
.3.1 字节缓冲流
BufferedInputStream 字节缓冲输入流
BufferedOutputStream 字节缓冲输出流
把基本流包装成高级流,提高读取和写出数据的性能
底层自带了长度为8192的缓冲区提高性能,默认是8kb
和基础流相比字节缓冲流拷贝文件一次一个字节数组性能最好
//因为缓冲流的底层还是字符/字节流在和文件交流 所以想要实现文件续写需要在字节/字符流里面
//字节缓冲流
//拷贝的时候会覆盖原文件内容
//BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("D:\\JavaFile\\_8_7\\src\\BufferedStreamDemo\\b.txt",true));
public class BufferedStreamDemo1 {
public static void main(String[] args) throws IOException {
//默认创建了长度为8192的缓冲区 可以自己设置 ,length
// 缓存输入流和缓存输出流不是一个缓冲区 中间有介质载体传递 这个过程是在内存中发生的
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:\\JavaFile\\_8_7\\src\\BufferedStreamDemo\\a.txt"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("D:\\JavaFile\\_8_7\\src\\BufferedStreamDemo\\b.txt"));
/* //还是一次操作一个字节
int len;
while ((len = bis.read()) != -1){
bos.write(len);
}*/
//创建字节数字 一次操作多个字节
byte[] bytes = new byte[1024*1024*5];
int len;
while ((len = bis.read(bytes)) != -1){
bos.write(bytes,0,len);
}
bos.close();
bis.close();
}
}
.3.2 字符缓冲流
BufferedReader 字符缓冲输入流
BufferedWriter 字符缓冲输出流
把基本流包装成高级流,提高读取和写出数据的性能
底层自带了长度为8192的缓冲区提高性能,默认是16kb,因为Java中一个字符是两个字节
//字符缓冲流
//字符基本流本身也带有一点缓冲区
//JAVA中底层是一个字符两个字节 字符缓冲流是创建的8192的字符缓冲区
// 也是默认覆盖的 想要实现续写也是在FileWriter对象里面加true new FileWriter("D:\\JavaFile\\_8_7\\src\\BufferedStreamDemo\\b.txt,true
// ")
public class BufferedStreamDemo2 {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new FileReader("D:\\JavaFile\\_8_7\\src\\BufferedStreamDemo\\a.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\JavaFile\\_8_7\\src\\BufferedStreamDemo\\b.txt"));
//BufferedReader读取特有的操作
//一次读取一行 不会读到换行符需要自己println 换行
/* String s = br.readLine();
System.out.println(s);
String s1 = br.readLine();
System.out.println(s1);*/
//循环读取 终止条件是读到null了
String len;
while ((len = br.readLine()) != null){
//拷贝 BufferedWriter和BufferedReader均不会都到换行符需要手动换行
bw.write(len);
//根据平台去写出对应的换行符
bw.newLine();
}
//释放资源
bw.close();
br.close();
}
}
.4 转换流
是字符流和字节流之间的桥梁
字节流可以封装转换流变成字符流的性质
字符转换输入流:InputStreamReader
字符转换输出流:OutputStreamWriter
数据源-字节流-InputStreamReader内存-OutputStreamWriter字节流-目的地
作用:
指定字符集读写(JDK11之后已淘汰)
字节流想要使用字符流当中的方法
.4.1 指定字符集读写
JDK11之后使用FileReader和FileWriter,底层还是转换流
public class ConvertStreamDemo2 {
public static void main(String[] args) throws IOException {
//底层还是转换流
//读取用GBK 写入用UTF-8
FileReader fr = new FileReader("D:\\JavaFile\\_8_7\\src\\ConvertStreamDemo\\123.txt", Charset.forName("GBK"));
//IDEA默认是UTF-8 后面就不用添加了
FileWriter fw = new FileWriter("D:\\JavaFile\\_8_7\\src\\ConvertStreamDemo\\123-UTF-8.txt");
int len;
while ((len = fr.read()) != -1){
fw.write(len);
}
fw.close();
fr.close();
}
.4.2 字节流使用字符流方法
public class ConvertStreamDemo3 {
public static void main(String[] args) throws IOException {
//使用字节流区读取带有中文的文件 且不能出现乱码
//字节流->转换流->字符缓冲流
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("D:\\JavaFile\\_8_7\\src\\BufferedStreamDemo\\b.txt")));
String line;
while ((line = br.readLine()) != null){
System.out.println(line);
}
br.close();
}
}
.5 序列化与反序列化流
序列化输出流/对象操作输出流,把一个对象写到本地文件:ObjectOutputStream
反序列化流/对象操作输入流,读取数据(写入数据):ObjectInputStream
序列化之后是一串乱码
细节:
使用序列化流将对象写到文件时,需要让Javabean类实现Serializable接口,否则会出现NotSeroalizableException异常
序列化流写到文件中的数据是不能修改的,一旦修改就无法再次读回来了
序列化对象后,修改了Javabean类,再次反序列化会出问题:抛出InvalidClassException异常 解决方案:给javabean类添加serialVersionUID(序列号,版本号)
如果一个对象中某个成员变量的值不想被序列化,则给该成员变量添加transient关键字修饰,该关键字标记的成员变量表示不参与序列化过程
public class ObjectStreamDemo2 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//同时序列化多个对象时 将对象存入集合当中
//创建对象
Student stu1 = new Student("张三",18,"山东");
Student stu2 = new Student("李四",18,"天津");
Student stu3 = new Student("王五",18,"河北");
//创建集合
ArrayList<Student> list = new ArrayList<>();
Collections.addAll(list,stu1,stu2,stu3);
//写出数据 写入文件中
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\JavaFile\\_8_7\\src\\ObjectStreamDemo\\a.txt"));
oos.writeObject(list);
//写入数据 读取文件
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\JavaFile\\_8_7\\src\\ObjectStreamDemo\\a.txt"));
ArrayList<Student> list1 = (ArrayList<Student>)ois.readObject();
for (Student student : list1) {
System.out.println(student);
}
}
}
//javabean类
/*
* Serializable 标记接口 没有方法可以重写 表示可以实现序列化
* */
public class Student implements Serializable {
//固定版本号 防止属性修改时版本号改变 私有 共享 始终 类型
private static final long serialVersionUID = 2009558412742412647L;
private String name;
private Integer age;
//如果不想某个属性进行序列号和反序列化 也就是对该属性进行保密 添加transient
//transient 瞬态关键字,该关键字标记的成员变量不参与序列化进程 读出来的是默认初始化值
//private transient String address;
private String address;
public Student() {
}
public Student(String name, Integer age, String address) {
this.name = name;
this.age = age;
this.address = address;
}
//省略get和set
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", address='" + address + '\'' +
'}';
}
}
.6 打印流
将数据打印到文件中
分类:☞ PrintStream,PrintStream两个类
字节打印流和字符打印流
构造方法创建对象时都可以指定是否自动刷新和字符编码
成员方法:
write 和之前一样
println 打印任意数据,自动刷新,自动换行
print 打印任意数据,不换行
printf 带有占位符的打印语句,不换行
字节打印流默认开启自动刷新,字符打印流需要手动开启自动刷新
特点:
打印流只操作文件目的地,不操作数据源
特有的方法可以实现数据原样写出
打印:97 文件中:97
特有的写出方法,可以实现自动刷新,自动换行
打印一次数据 = 写出+换行+刷新
注:
System.out.println();就是一个打印流
System类中的静态out方法
它是一个特殊的打印流 叫做系统中的标准输出流 它是唯一的不能关闭,关闭之后后面的打印不到控制台上
只能重新运行程序/就是重新运行JVM虚拟机
PrintStream ps = System.out;
ps.println("1238");
ps.close();
//以字符打印流为例
public class PrintStreamDemo2 {
public static void main(String[] args) throws FileNotFoundException {
//创建字符打印流对象
//PrintWriter pw1 = new PrintWriter(new FileWriter(),true)
PrintWriter pw = new PrintWriter(new FileOutputStream("D:\\JavaFile\\_8_7\\src\\PrintStreamDemo\\a.txt"),true);
//原样写出
//换行打印
pw.println(66);
//不换行
pw.print("你好");
//带有占位符的
pw.printf("%s说%s","小王","你好");
pw.close();
//注 :System.out.println();就是一个打印流
//System类中的静态out方法
//它是一个特殊的打印流 叫做系统中的标准输出流 它是唯一的不能关闭,关闭之后后面的打印不到控制台上
//只能重新运行程序/就是重新运行JVM虚拟机
PrintStream ps = System.out;
ps.println("1238");
ps.close();
System.out.println("你好你好");
ps.println("能不能打印出来");
}
}
.7 字符集
计算机存储规则
1. GB2312字符集
2. BIG5字符集
3. GBK字符集 windows系统默认使用的就是GBK。系统显示:ANSI
4. Unicode字符集:国际标准字符集,IDEA默认使用的就是UTF-8
计算机中最小的存储单元是一个字节
.7.1 ASCII
ASCII编码规则:前面补0补齐8位
要存储的英文 a
查询ASCII对应数字97 二进制为 110 0001
编码 0110 0001
ASCII解码规则:直接转成十进制
01110 0001 解码
97
查询ASCII读到的英文为a
注ASCII字符集里面没有中文
.7.2 GBK
GBK英文编码规则:不足8位前面补0
要存储的英文:a
查询GBK对应数字97转换成二进制为110 0001
编码 0110 0001
GBK英文用一个字节存储,完全兼容ASCII
GBK汉字编码规则:不需要变动
要存储的汉字:汉 -70 -70
查询GBK对应数字47802转换成二进制为10111010 10111010 编码10111010 10111010
汉字两个字节存储
高位字节二进制一定以1开头,转成十进制之后是一个负数
GBK汉字解码
10111010 10111010 解码
转成10进制 47802
查询GBK 读取到的汉字为汉
.7.3 Unicode(万国码)
UTF-8编码规则:用1~4个字节保存
UTF-8 存储英文
要存储的英文:a
查询Unicode得到97转换成UTF-8编码01100001
UTF-9 存储汉字
要存储的汉字:汉
查询Unicode对应数字27721转换成十进制01101100 01001001转换成UTF-8编码为11100110 10110001 10001001
.7.4 乱码
如何不产生乱码
不要用字节流读取文本文件
编码解码时要使用同一个码表,同一个编码方式
为什么会有乱码
读取数据时未读完整个汉字
编码和解码时的方式不统一
.7.5 编码与解码
getBytes(),填写参数就是指定方式编码,不填参数就是默认方式编码
String(byte[] bytes, String charsetName),第二个参数可以指定编码方式,不写就是默认的
public class CharSetDemo1 {
public static void main(String[] args) throws UnsupportedEncodingException {
String str = "ai你哟";
//编码
//默认方式编码 根据编译软件的默认编码方式
byte[] byte1 = str.getBytes();
System.out.println(Arrays.toString(byte1));
//指定方式编码
byte[] byte2 = str.getBytes("GBK");//大小写都可以 尽量大写
System.out.println(Arrays.toString(byte2));
//解码
//默认方式解码根据编译软件的默认解码方式
System.out.println(new String(byte1));
//指定方式解码
System.out.println(new String(byte2,"GBK"));
//编码和解码方式不同会出现乱码
System.out.println(new String(byte2));
}
}