一.IO流的介绍
1.什么是IO流?
I:input
o:output
流:像流水一样传输数据
2.IO流的分类
输入和输出是相对于程序来说的
简单来理解就是:输入是从文本读到程序,用read
输出是从程序到文本,用write
字节流:可以操作所有类型的文件
字符流:只能操作纯文本文件
这里的InputStream,OutputStream,Reader,Writer都是抽象类,不能直接创建他们的对象,但是可以通过他们的子类来创建对象
如:FileInputStream操作本地文件的字节输入流
FileOutputStream操作本地文件的字节输出流
3.IO流的作用
用于读、写数据
二.IO流的基本流
1.字节输出流 FileOutputStream
演示:字节输出流FileOutputStream
* 实现需求:写出一段文字到本地文件中(暂时不写中文)
* 实现步骤:
* 1.创建对象
* 细节1:参数是字符串表示的路径和File对象都可以
* 细节2:如果文件不存在会创建一个新的文件,但是要保证父级路径是存在的
* 细节3:如果文件已经存在,则会清空文件
* 2.写出数据
* 细节:write方法的参数是整数,但实际上写到本地文件中的是该整数在ASCII上对应的字符
写出数据的三种方式:
void write(int b) 一次写一个字节数据
void write(byte[] b) 一次写一个字节数组数据
void write(byte[] b,int off,int len) 一次写一个字节数组的部分数据
* 参数一:数组
* 参数二:起始索引
* 参数三:个数
* 3.释放资源
* 细节:每次使用完流之后都要释放资源
代码实现:
//1.创建对象
FileOutputStream fos = new FileOutputStream("D:\\aaa\\bbb.doc");
//2.写出数据
//一次写一个字节数据
fos.write(97);
byte[] bytes = {97,98,99,100};
//一次写一个字节数组数据
fos.write(bytes);
//一次写一个字节数组的部分数据
fos.write(bytes,1,2);
//3.释放资源
fos.close();
总结:字节输出流的实现就是把数据写到本地当中,分为三个步骤,首先创建对象,其次再写出数据,最后再释放资源,这里的创建对象和写出数据都有其注意点。
1.创建对象中的参数可以是两种形式,可以直接传递字符串路径,也可以传递创建的File对象,还有一个续写模式,就是在参数后面加上
true ,创建时也要注意此路径是否存在,如果存在就有是否情况原来的数据两种情况,如果不存在,就会根据父级路径是否存在来决定是否创建一个新的路径。
2.写出数据注意有三种写出方式,可以只写出一个数据,也可以写出整个字节数组的数据,也可以写出字节数组的一部分数据,用的都是write方法,就是里面的参数不同
字符串String中有一个方法,可以把字符串储存到字符数组当中,代码实现如下:
String str1 = "imfmsncioanfweaonfcb";
byte[] bytes1 = str1.getBytes();
用此方法也可以实现换行,
就是把换行符当成字符串写进去,如:
String wrap = "\r\n";
byte[] bytes2 = wrap.getBytes();
2.字节输入流FileInputStream
/*实现步骤:
* 1.创建对象
* 细节1:如果文件不存在,就直接报错
* 输出流:不存在,创建
* 把数据写到文件中
*
* 输入流:不存在,报错
* 因为创建出来的文件是没有数据的,没有任何意义
* 所以java就没有设计这种无意义的逻辑,文件不存在则直接报错
*
* 程序中最重要的是数据
* 2.读取数据
* 细节1:一次读一个字节,读出来的是数据在ASCII上对应的数字
* 细节2:读到文件末尾了,read方法返回-1
*
* 3.释放资源
* 每次使用问流之后都要释放资源
*/
代码实现
//1.创建对象
FileInputStream fis = new FileInputStream("D:\\aaa\\bbb");
//2.读取数据
//单个字节读取
int b1 = fis.read();
System.out.println(b1);
//循环读取
int b;
while((b=fis.read()) != -1) {
System.out.println((char)b);
}
//3.释放资源
fis.close();
总结:
1.创建对象:这里的创建对象跟字节输出流的不同就是如果文件不存在时是不会自动创建一个的,因为根本没有意义
2.读取数据:注意这里的循环读取的方法,但是这里的循环读取也是一次循环只读取一个数据,需要的时间比较长,可以改写成一次读取一个数组的,后面会讲到。这里的循环注意定义变量是写在循环外的,而且必须得定义变量来记录,不然。。。(会跳读),注意条件是!=-1,因为没有数据时,读出来的就是-1
3.文件的拷贝(单级文件夹)
小文件的拷贝的代码实现 :
//1.创建对象
FileInputStream fis = new FileInputStream("D:\\aaa\\bbb");
FileOutputStream fos = new FileOutputStream("D:\\aaa\\rrr");
//2.拷贝
//核心思想:边读边写
int b;
while((b=fis.read()) != -1) {
fos.write(b);
}
//3.释放资源
//规则:先开的最后关闭
fos.close();
fis.close();
总结:
创建对象:拷贝就是边读边写,这里的话需要两个文件,一个是源文件,是读的对象,是输入,一个数写出的对象,是输出,所以一开始就创建两个对象。
拷贝:就是边读边写,融合了两个内容,通过读出数据的循环,循环体是写出数据
释放资源:规则是先开的流后关
大文件的拷贝和代码实现
//1.创建对象
FileInputStream fis = new FileInputStream("D:\\aaa\\bbb");
FileOutputStream fos = new FileOutputStream("D:\\aaa\\rrr");
//2.拷贝
//核心思想:边读边写
int len;
byte[] bytes = new byte[1024*1024*5];
while((len=fis.read(bytes)) != -1) {
fos.write(bytes,0,len);
}
//3.释放资源
//规则:先开的最后关闭
fos.close();
fis.close();
总结:跟小文件的拷贝不同的是拷贝部分,创建对象和释放资源都是一样的
拷贝是利用数组,把拷贝的内容放到数组中储存起来,然后再进行边读点写,读的时候是把数组放到read方法的参数中,写是只写读到的那一部分内容,注意不能是整个数组,因为到最后不一定能读满整个数组,那么写进去的就会有重复的内容,len就是表示读到了多少个。
4.字符集的相关内容
1.字符集详解
ASCII字符集: 一个英文占一个字节,二进制第一位是0
GBK:一个中文占两个字节,二进制最高位字节的第一位是1
Unicode字符集: 一个英文占一个字节,一个中文占三个字节,二进制第一位是0,转成十进制是正数(UTF-8编码格式)
2.为什么会有乱码?
字节流读取中文会乱码,但是拷贝却不会乱码
如何不产生乱码?
/*1.不要用字节流读取文本文件
* 2.编码、解码时使用同一个码表,用同一个编码方式
*/
3.编码和解码的代码实现
方法:
/*
* java中编码的方法:
* public byte[] getBytes() 使用默认方式进行编码
* public byte[] getBytes(String charsetName) 使用指定方式进行编码
*
*
* java中解码方式:
* String(byte[] bytes) 使用默认方式进行解码
* String(byte[] bytes,String charsetName) 使用指定方式进行解码
*/
//这里的默认方式是UTF-8
//1.编码
String str1 = "hi你好";
//使用默认方式进行编码
byte[] bytes1 = str1.getBytes();
System.out.println(Arrays.toString(bytes1));
//使用指定方式进行编码
byte[] bytes2 = str1.getBytes("GBK");
System.out.println(Arrays.toString(bytes2));
//2.解码
//使用默认方式进行解码
String str2 = new String(bytes1);//如果括号里的是bytes2就会乱码,因为此时编码的解码用的方式不同
System.out.println(str2);
//使用指定方式进行解码
String str3 = new String(bytes2,"GBK");//如果括号里的是bytes1就会乱码,因为此时编码的解码用的方式不同
System.out.println(str3);
结果:
5.字符输入流FileReader
/*
* 第一步:创建对象
* public FileReader(File file) 创建字符输入流关联本地文件
* public FileReader(String pathname) 创建字符输入流关联本地文件
* 细节:如果文件不存在,就直接报错
*
*
* 第二步:读取数据
* public int read() 读取数据,读到末尾返回-1
* public int read(char[] buffer) 读取数据,读到末尾返回-1
* 细节:按字节进行读取,遇到中文,一次读多个字节,读取后解码,返回一个整数
*
*
* 第三步:释放资源
* public void close() 释放资源/关流
*/
字符输入流跟字节输入流很像
重点在read有无参数的学习上
1.无参的read方法
//1.创建对象并关联本地文件
FileReader fr = new FileReader("D:\\aaa\\yyy");
/*2.读取数据
* 字符流的底层也是字节流,默认也是一个字节一个字节读取的
* 如果遇到中文就会一次读取多个,GBK一次读两个字节,UTF-8一次读三个字节
*
*
* read()细节:
* 在读取之后,方法的底层还会将解码转换成十进制的
* 最终把这个十进制作为返回值
* 这个十进制的数据也表示字符集上的数字
* 英文:文件里面的二进制数据:0110 0001
* read方法进行读取,解码并转成十进制:97
* 中文:文件里的二进制数据:11100110 10110001 10001001
* read方法进行读取,解码并转成十进制:27721
*/
//最后想看到这些中文汉字,就是把这些十进制数据进行强转就行
int ch;
while((ch=fr.read())!=-1) {
System.out.print((char)ch);
}
2.有参的read方法
//1.创建对象
FileReader fr = new FileReader("D:\\aaa\\yyy");
//2.读取数据
char[] chars = new char[2];
int len;
//read(chars):读取数据、解码、强转 三步合并了,把强转之后的字符放到数组当中
//空参的read方法要自己进行强转
while((len=fr.read(chars))!=-1) {
//把数组中的数据变成字符串再进行打印,用字符创String中的构造方法,其中可以控制要变成字符串的长度范围
System.out.print(new String(chars,0,len));
}
//3.释放资源
fr.close();
总结:把有参的read和无参的可以对比来学,注意有参的最后不需要强转
注意这里的是字符数组,不是字节数组!最后打印的时候要把数组变成字符串再打印,并且也要指定打印的数组长度
6.字符输出流FileWriter
/*
* 第一步:创建对象
* public FileWriter(File file) 创建字符输出流关联本地文件
* public FileWriter(String pathname) 创建字符输出流关联本地文件
* public FileWriter(File file,boolean append) 创建字符输出流关联本地文件,续写
* public FileWriter(String pathname,boolean append) 创建字符输出流关联本地文件,续写
*
*
* 第二步:读取数据
* void write
*(int c) 写出一个字符
* void write(String str) 写出一个字符串
* void write(String str,int off,int len) 写出一个字符串的一部分
* void write(char[] cbuf) 写出一个字符数组
* void write(char[] cbuf,int off,int len) 写出一个字符数组的一部分
*
*
* 第三步:释放资源
* public void close() 释放资源/关流
FileWriter fw = new FileWriter("D:\\aaa\\bbb");
char[] chars = {'a','b','c'};
fw.write(chars);
fw.write("你好啊!");
fw.write(97);
fw.close();
//字节流:拷贝任意类型的数据
//字符流:读取纯文本中的数据 往纯文本文件中写出数据
注意这里有个write方法里面可以直接传字符串,应该比较好用
三.IO流的基本流的综合练习
1.拷贝文件夹
//需求:拷贝一个文件夹,考虑子文件夹
public static void main(String[] args) throws IOException {
//需求:拷贝一个文件夹,考虑子文件夹
//1.创建对象表示数据源
File src = new File("D:\\aaa");
//2.创建对象表示目的地
File dest = new File("D:ppp");
//3.调用方法开始拷贝
copydir(src,dest);
}
private static void copydir(File src, File dest) throws IOException {
//递归
//1.进入数据源
File[] files = src.listFiles();
//2.遍历数组
if(files!=null) {
for(File file:files) {
if(file.isFile()) {
//3.判断 文件 拷贝
FileInputStream fis = new FileInputStream(file);
FileOutputStream fos = new FileOutputStream(new File(dest,file.getName()));
byte[] bytes = new byte[1024];
int len;
while((len=fis.read(bytes)) != -1) {
fos.write(bytes,0,len);
}
fos.close();
fis.close();
}else {
//4.判断 文件夹 递归
copydir(file,new File(dest,file.getName()));
}
}
}
}
总结:1.考虑子文件夹,就考虑用递归的方法
2.main方法中创建对象要把数据源和目的地的对象都创建出来,只用File创建即可。
3.最后方法中返回来的应该是一个文件夹
4.用方法的递归,里面要传递两个参数,最后返回来拷贝好的文件夹
5.递归方法的步骤:
①进入数据源
用File中遍历文件夹的方法,最后储存到一个数组中
②遍历数组
③判断是文件
直接拷贝,直接用之前二.3中的文件拷贝的方法
但是这里注意传出文件时的创建对象不能只是路径,要跟数据源的路径对应上!
④判断是文件夹
继续递归(递归的路径也要对应上)
(这里的递归可以与File里面单纯的查找多级文件夹的案例相对比)
2.加密和解密文件
1.题目需求:
/*为了保证文件的安全性,就需要对原始文件进行加密储存,再使用的时候再对其进行解密处理
* 加密原理:
* 对原始文件的每一个字节数据进行更改,然后将更改以后的数据储存到新的文件中
*
* 解密原理:
* 读取加密之后的文件,按照加密的规则反向操作,变成原始文件
*/
2.先了解异或的原理
/*异或:
* 两边相同:false
两边不同:true
0:false
1:true
100:1100100
10:1010
1100100
^ 0001010
--------------
1101110 十进制:110
0001010
--------------
1100100 十进制:100
System.out.println(100^10)//110
System.out.println(110^10)//100
可以把100比喻成加密前的文件,把110比喻成加密后的文件,通过10
*/
3.代码实现
//1.创建对象关联原始文件
FileInputStream fis = new FileInputStream("D:\\aaa\\bbb");
//2.创建对象关联加密文件
FileOutputStream fos = new FileOutputStream("D:\\ooo");
//3.加密处理(类似拷贝)
int b;
while((b=fis.read())!= -1) {
fos.write(b^2);
}
//4.释放资源
fos.close();
fis.close();
总结:我的理解本质就是在拷贝的过程进行加密
重点就是要知道加密的方式可以用异或的原理
3.修改文件中的数据
1.题目需求:
/*文本中有以下数据:
* 2-1-9-4-7-8
* 将文件中的数据进行排序,变成以下的数据
* 1-2-4-7-8-9
*/
2.代码实现---方法一
//1.读取数据
FileReader fr = new FileReader("D:\\aaa\\zzz");
StringBuilder sb = new StringBuilder();//用一个容器来装读取到的数据
int ch;
while((ch=fr.read()) !=-1) {
sb.append((char)ch);
}
fr.close();
System.out.println(sb);
//2.排序
String str = sb.toString();//把读取到的数据变成一个字符串
String[] arrStr = str.split("-");//变成一个字符串后才能切割,切割后才能排序
ArrayList<Integer> list = new ArrayList<>();//定义一个集合来储存整数类型的数据
for(String s : arrStr) {
int i= Integer.parseInt(s);//把数据的字符串类型变成int类型
list.add(i);
}
Collections.sort(list); //用集合中的方法来直接排序,默认从小到大排序
System.out.println(list);
//3.写出
FileWriter fw = new FileWriter("D:\\aaa\\xxx");
for(int i=0; i<list.size(); i++) {
if(i==list.size()-1) {
fw.write(list.get(i)+"");//是吧数字变成字符串就能直接输出了
}else {
fw.write(list.get(i)+"-");
}
}
fw.close();
fr.close();
总结:
1.首先要理解题意,分步骤进行。这里是要修改数据,那么首先就要读取数据,再修改数据,最后再写出修改后的数据。
2.读取数据:读取到的数据要装起来,这里用的是StringBuilder,不低是方便后面的修改数据
3.修改数据(排序):注意这里要把-去才可以进行排序,那么就想到split,但是只有字符串才能用这个方法,因此在容器里的东西要把它先变成字符串,再进行切割
4.这里的排序用的是Collections的sort方法,那么就先把切割后的数据进行排序再放到集合中
5.写出数据:就遍历集合把里面的每一个数据都用write方法输出即可,还要恢复之前有-的结构
最后再一起关流
3.代码实现---方法二
//1.读取数据
FileReader fr = new FileReader("D:\\aaa\\zzz");
StringBuilder sb = new StringBuilder();//用一个容器来装读取到的数据
int ch;
while((ch=fr.read()) !=-1) {
sb.append((char)ch);
}
fr.close();
System.out.println(sb);
//2.排序
Integer[] arr = Arrays.stream(sb.toString()
.split("-"))
.map(Integer::parseInt)
.sorted()
.toArray(Integer[]::new);
//3.写出
FileWriter fw = new FileWriter("D:\\aaa\\mmm");
String s = Arrays.toString(arr).replace(", ", "-");
String result = s.substring(1,s.length()-1);
fw.write(result);
fw.close();
fr.close();
重点分析与方法一不同的地方:
1.读取数据一样
2.排序:获取一条流水线:注意方法有种,但是是针对不同情况使用的,这里是把数据都变成数组,再用Arrays去获得流水线,然后这里把容器里的数据变成字符串再切割后就是储存到数组里了,载用map转换数据类型,这里直接引用方法,再用sorted方法排序,最后再用数组储存起来
3写出数据:用了replace和result方法
四.IO流的高级流
1.缓冲流
(1)字节缓冲流
/*需求:
* 利用字节缓冲流拷贝文件
*
* 字节缓冲输入流的构造方法:
* public BufferedInputStream(InputStream is)
*
* 字节缓冲输出流的构造方法:
* public BufferedOutputStream(OutputStream os)
*
*
* 原理:底层自带了长度为8192的缓冲区提高性能
*/
//字节缓冲流拷贝文件 一次读写一个字节
//1.创建缓冲流对象
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:\\aaa\\bbb"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("D:\\aaa\\kkk"));
//2.循环读取并达到目的地
int b;
while((b=bis.read()) !=-1) {
bos.write(b);
}
//3.释放资源
bos.close();
bis.close();
//字节缓冲流拷贝文件 一次读写一个字节数组
//1.创建缓冲流对象
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:\\aaa\\bbb"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("D:\\aaa\\kkk"));
//2.循环读取并达到目的地
byte[] bytes = new byte[1024];
int len;
while((len=bis.read(bytes)) !=-1) {
bos.write(bytes,0,len);
}
//3.释放资源
bos.close();
bis.close();
字节缓冲流注意点:
1.创建对象的参数里面创建对应的基本流的对象
2.释放资源时只需要释放高级流的就行,因为高级流底层会释放低级流
(2)字符缓冲流
字节缓冲输入流
/*
* 字符缓冲输入流:
* 构造方法:
* public BufferedReader(Reader r)
*
* 特有方法:
* public String readLine()读取一整行数据,如果没有数据可读了,会返回null
*/
//1.创建字符缓冲输入流
BufferedReader br = new BufferedReader(new FileReader("D:\\aaa\\bbb"));
//2.读取数据
/*细节:
* readLine方法在读取的时候,一次读一整行,遇到回车换行结束
* 但是他不会把换行回车读到内存中(就是第二次读取时如果没有换行,就不会自己自动换行)
*/
String line = br.readLine();
System.out.println(line);
//3.释放资源
br.close();
注意点:字符缓冲流最特别的就是有一个方法可以读取一整行的数据
创建对象时也要注意参数里面也要创建基本流的对象
字节缓冲输出流
/*
* 字符缓冲输出流:
* 构造方法:
* public BufferedWriter(Writer r)
*
* 特有方法:
* public void newLine() 跨平台的换行
*/
//1.创建字符缓冲输入流
BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\aaa\\bbb",true));
//2.写出数据
bw.write("123");
bw.newLine();
bw.write("345");
bw.newLine();
//3.释放资源
bw.close();
注意点:1.创建对象里面的参数
2.写出数据时有一个特有的方法可以换行
(3)练习一.四种拷贝方式的对比
/*
* 1.字节流的基本流:一次读写一个字节 (最慢)
* 2.字节流的基本流:一次读写一个字节数组 (16秒)
* 3.字节缓冲流:一次读写一个字节 (95秒)
* 4.字节缓冲流:一次读写一个字节数组 (18秒)
*/
(4)练习二.恢复出师表的顺序
方法一
//1.读取数据
BufferedReader br = new BufferedReader(new FileReader("D:\\aaa\\出师表"));
String line;
ArrayList<String> list = new ArrayList<>();//用一个东西把获取出来等会要排序的东西装起来
while((line=br.readLine())!=null) {//注意这里是不等于null//考虑到获取数据一整行一整行获取再比较,所以用字符缓冲流
list.add(line);
}
br.close();
//2.排序
//排序规则:按照每一行前面的序号进行排列
Collections.sort(list,new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
//获取o1和o2的序号
int i1 = Integer.parseInt(o1.split("\\.")[0]);
int i2 = Integer.parseInt(o2.split("\\.")[0]);
return i1-i2;
}
});
System.out.println(list);
//3.写出
BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\aaa\\bbb"));
for(String str:list) {
bw.write(str);
bw.newLine();
}
bw.close();
方法二
//1.读取数据
BufferedReader br = new BufferedReader(new FileReader("D:\\aaa\\出师表"));
String line;
TreeMap<Integer,String> tm = new TreeMap<>();
while((line=br.readLine())!=null) {//注意这里是不等于null//考虑到获取数据一整行一整行获取再比较,所以用字符缓冲流
String[] arr = line.split("\\.");
tm.put(Integer.parseInt(arr[0]), line);
}
br.close();
//2.读出数据
BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\aaa\\bbb"));
Set<Entry<Integer,String>> entries = tm.entrySet();
for(Map.Entry<Integer, String> entry:entries) {
String value = entry.getValue();
bw.write(value);
bw.newLine();
}
bw.close();
方法二用了Map集合
(5)练习三.控制软件运行次数
/*
* 实现一个验证程序运行次数的小程序,要求如下:
* 1.当程序运行超过3次时给出提示:本软件只能免费使用3次,欢迎您注册会员后继续使用~
* 2.程序运行演示如下:
* 第一次运行控制台输出:欢迎使用本软件,第1次免费使用~
* 第二次运行控制台输出:欢迎使用本软件,第2次免费使用~
* 第三次运行控制台输出:欢迎使用本软件,第3次免费使用~
* 第四次及之后控制台输出:本软件只能免费使用3次,欢迎您注册会员后继续使用~
*/
//1.把文件的数字读取到内存中
BufferedReader br = new BufferedReader(new FileReader("D:\\aaa\\bbb"));
String line = br.readLine();
int count = Integer.parseInt(line);
//表示当前软件又运行了一次
count++;
//2.判断
if(count <=3) {
System.out.println("欢迎使用本软件,第"+count+"次使用免费~");
}else {
System.out.println("本软件只能免费使用3次,欢迎您注册会员后继续使用~");
}
//3.把当前自增之后的count写出到文件当中
BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\\\aaa\\\\bbb"));
bw.write(count+"");
bw.close();
br.close();
这里指的是运行次数,而不能用之前简单的计数器
2.转换流
//转换流是字符流,是字符流和字节流之间的桥梁
//需求1:利用转换流按照指定的字符编码读取
/* InputStreamReader isr = new InputStreamReader(new FileInputStream("D:\\aaa\\bbb"),"GBK");
int ch ;
while((ch=isr.read()) !=-1) {
System.out.println((char)ch);
}
isr.close();*/
FileReader fr = new FileReader("D:\\aaa\\bbb",Charset.forName("GBK"));
int ch ;
while((ch=fr.read()) !=-1) {
System.out.println((char)ch);
}
fr.close();
//需求2:利用转换流按照指定字符编码写出
//创建转换流对象
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("D:\\aaa\\bbb"),"GBK");
//写出数据
osw.write("你好你好");
//释放资源
osw.close();*/
FileWriter fw = new FileWriter("D:\\aaa\\bbb",Charset.forName("GBK"));
fw.write("你好你好");
fw.close();
//将本地文件中的GBK文件,转成UTF-8
FileReader fr = new FileReader("D:\\aaa\\bbb",Charset.forName("GBK"));
FileWriter fw = new FileWriter("D:\\aaa\\ttt",Charset.forName("UTF-8"));
int b;
while((b=fr.read())!=-1) {
fw.write(b);
}
fw.close();
fr.close();
//需求:利用字节流读取文件中的数据,每次读一整行,而且不能出现乱码
/*分析:
* 1.用字节流读取文字时是会乱码的,但是字符流可以搞定
* 2.字节流里面是没有一次读一整行的方法的,只有字符缓冲流才能搞定
*/
FileInputStream fis = new FileInputStream("D:\\aaa\\ttt");//创建一个字节流对象
InputStreamReader isr = new InputStreamReader(fis);//把字节流转换成字符流
BufferedReader br = new BufferedReader(isr);//将字符流包装成字符缓冲流
String line = br.readLine();//一次读取一行
System.out.println(line);
br.close();//只关一个流即可
/* BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("D:\\aaa\\ttt")));
String line;
while((line=br.readLine())!=null) {
System.out.println(line);
}
br.close();*/
3.序列流和反序列流
(1)序列化流
把java对象写到本地文件中,是ObjectOutputStream
/*
* 需求:利用序列化流、对象操作输出流,把一个对象写到本地文件中
*
* 构造方法:
* public ObjectOutStream(OutputStream out) 把基本流变成高级流
*
* 成员方法:
* public final void writeObject(Object obj) 把对象序列化(写出)到文件中
*
*/
//1.创建对象
Student stu = new Student("张三",23);
//2.创建序列化流对象/对象操作输出流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\aaa\\ttt"));
//写出数据
oos.writeObject(stu);
//释放资源
oos.close();
注意这里的创建对象要实现一个接口: implements Serializable
/*Serializable接口里面是没有抽象方法的,标记型接口
* 一旦实现了这个接口,那么就表示当前的Student类可以被序列化
* 理解:一个物品的合格证
*
*/
(2)反序列流
把序列化到本地的对象,读取到程序中来,是ObjectInputStream
/*
* 构造方法:
* public ObjectInputStream(InputStream in) 把基本流变成高级流
*
* 成员方法:
* public Object readObject() 把序列化到本地文件的对象,读取到程序中来
*/
//创建反序列流的对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\aaa\\ttt"));
//读取数据
Object o = ois.readObject();
System.out.println(o);
//释放资源
ois.close();
(3)序列流和反序列流的细节总汇
/*
* 1.使用序列化流将对象写到文件时,需要让javabean类实现Serializable接口
* 否则,会出现NotSerializableException异常
*
* 2.序列化流写到文件中的数据是不能更改的,一旦修改就无法再读回来了
*
* 3.序列化对象后,修改了javabean类,再次反序列化,会不会有问题?
* 会出现问题,会抛出InvalidClassException异常
* 解决方案:给javabean类添加serialVersionUID(序列号、版本号)
* 格式:private static final long serialVersionUID = 1L;
*
* 4.如果一个对象中的某个成员变量的值不想被序列化,又该如何实现呢?
* 解决方案:给该成员变量加transient关键字修饰,该关键字标记的成员变量不参与序列化过程
*/
(4)综合练习(读写多个对象)
//需求:将多个自定义对象序列化到文件中,但是由于对象的个数不确定,反序列流该如何读取呢?
//思路:把多个对象创建出来之后放到一个集合中,之后序列化的时候就可以直接把整个集合写出,反序列化的时候就直接把整个集合读出来
Student s1 = new Student("zhangsan",23);
Student s2 = new Student("lisi",24);
Student s3 = new Student("wangwu",25);
ArrayList<Student> list = new ArrayList<>();
list.add(s1);
list.add(s2);
list.add(s3);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\aaa\\ttt"));
oos.writeObject(list);
oos.close();
}
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\aaa\\ttt"));
ois.readObject();
ois.close();
//思路:把多个对象创建出来之后放到一个集合中,之后序列化的时候就可以直接把整个集合写出,反序列化的时候就直接把整个集合读出来
4.打印流
/*
* 1.分类:打印流一般是指:PrintStream、PrintWriter两个类
*
* 特点1:打印流只操作文件目的地,不操作数据源
*
* 特点2:特有的写出方法可以实现,数据原样写出
* 例如:打印:97 文件中:97
*
* 特点3:特有的写出发,可以实现自动刷新,自动换行
* 打印一次数据=写出+换行+刷新
*/
(1)字节打印流
字节流底层没有缓冲区,开不开自动刷新都一样
/*
* 字节打印流:
* 构造方法:
* public PrintStream(OutputStream/File/String) 关联字节输出流/文件/文件路径
* public PrintStream(String filename,Charset charset) 指定字符编码
* public PrintStream(OutputStream out,boolean autoFlush) 自动刷新
* public PrintStream(OutputStream out,booolean autoFlush,String ensoding) 指定字符编码且自动更新
*
* 成员方法:
* public void write(int b) 常规方法:规则跟之前的一样,将指定的字节写出
* public void println(Xx xx) 特有方法:打印任意数据,自动刷新,自动换行
* public void print(Xx xx) 特有方法:打印任意数据,不换行
* public void printf(String format,Object...args) 特有方法:带有占位符的打印语句,不换行
*/
//创建字节打印流的对象
PrintStream ps = new PrintStream(new FileOutputStream("D:\\aaa\\ttt"),true,Charset.forName("GBK"));
//写出数据
ps.write(97);
ps.println();
ps.println(90);
ps.println("今天天气真好");
ps.print(90);
ps.println();
ps.printf("%s天气真%s","今天","好");
//释放资源
ps.close();
(2)字符打印流
字符流底层有缓冲区,想要自动刷新需要开启
字符打印流的构造方法和成员方法都跟字节打印流的一样,就是把PrintStream改成PrintWriter即可
(3)打印流的应用场景
//获取打印流的对象,此打印流在虚拟机启动的时候由虚拟机创建,默认指向控制台
//特殊的打印流,系统中的标准输出流,是不能关闭的,在系统中是唯一的
PrintStream ps = System.out;
//调用打印流中的println方法
//写出数据,自动换行,自动刷新
ps.println(123);
// ps.close();//关闭之后,后面无法再打印
ps.println(234);
System.out.println(456);