一,简介及分类
IO流用来处理设备之间的数据传输;JAVA对数据的操作是通过流的方式;JAVA用于操作流的类都在IO包中
流按流向分为两种:输入流,输出流。
流按操作类型分为两种:
字节流 : 字节流可以操作任何数据,因为在计算机中任何数据都是以字节的形式存储的
字符流 : 字符流只能操作纯字符数据,比较方便。
区别:字节流每次读写一个字节,而字符流每次读写一个字符;
区分字符还是字节流:看类名结尾,例如:FileInputStream以Stream结尾的就是字节流,FileReader以Reader结尾的就是字符流
二,IO流常用父类
字节流的抽象父类:
InputStream
OutputStream
字符流的抽象父类:
Reader
Writer
三,字节流的使用
3.1,FileInputStream 字节输入流
private static void demo1() throws FileNotFoundException, IOException {
FileInputStream fis = new FileInputStream("abc.txt");
int b;
while ((b=fis.read())!=-1) { //从该输入流读取一个字节的数据
System.out.println(b);
}
fis.close(); //注意:流使用完之后,要关闭
}
问题:read()方法返回为啥是int类型而不是byte类型
因为一个字节的底层表示8bit,即二进制表示最大为11111111,再读取数据时有机会为1111111;而-1的byte表示也是11111111,所以如果用byte接受,在正常读取数据时候有可能当作结束标记结束了;在读取的时候用int类型接收,如果11111111会在其前面补上24个0凑足4个字节,那么byte类型的-1就变成int类型的255了这样可以保证整个数据读完,而结束标记的-1就是int类型;
3.2,FileOutputStream 字节输出流
FileOutputStream fos = new FileOutputStream("mm.txt"); //mm.txt如果没有这个文件,就创建一个;如果这个文件已存在,在创建FileOutputStream的时候,会清空文件内容;
fos.write(100);
fos.write("你好".getBytes());
fos.close();
注意:如果mm.txt已经存在,那么在执行new FileOutputStream("mm.txt")的时候就会清空mm.txt文件内容;如果想以追加的方式写入数据,就需要在创建FileOutputStream对象时这样写fos = new FileOutputStream("mm.txt",true);
3.3,FileInputStream和FileOutputStream综合使用,其实以下六行代码就是IO流的核心代码,其他都是根据核心代码进行升级;
private static void demo2() throws FileNotFoundException, IOException {
FileInputStream fis = new FileInputStream("abc.txt"); //1
FileOutputStream fos = new FileOutputStream("ss.txt"); //2
int b;
while ((b = fis.read())!=-1) { //从该输入流读取一个字节的数据 //3
fos.write(b); //4
}
fis.close(); //5
fos.close(); //6
}
这样写效率不高,字节流一次读写一个字节;读一次写一次;
通过available() 方法改进,此方法时获取读的文件所有的字节个数,如下:
FileInputStream fis = new FileInputStream("aaa.mp3");
FileOutputStream fos = new FileOutputStream("bbb.mp3");
byte[] arr = new byte[fis.available()]; //根据文件大小(fis.available() 字节个数)创建一个字节数组
fis.read(arr); //将文件上的所有字节读取到数组中
fos.write(arr); //将数组中的所有字节一次写到了文件上
fis.close();
fos.close();
弊端:有可能会内存溢出
最终改进方案:定义小数组,每次读写一个自定义数组大小的数据;
private static void demo3() throws IOException {
FileInputStream fis = new FileInputStream("abc.txt");
FileOutputStream fos = new FileOutputStream("ss.txt");
byte [] arr = new byte[1024];
int len; //有效字节数的个数
while ((len = fis.read(arr))!=-1) {
fos.write(arr,0,len); //写出有效个字节个数
}
fis.close();
fos.close();
}
3.4,BufferedInputStream和BufferOutputStream
BufferedInputStream内置了一个缓冲区(数组)
BufferedOutputStream也内置了一个缓冲区(数组)
程序向流中写出字节时, 不会直接写到文件, 先写到缓冲区中
直到缓冲区写满, BufferedOutputStream才会把缓冲区中的数据一次性写到文件里
使用:
FileInputStream fis = new FileInputStream("aaa.mp3");
BufferedInputStream bis = new BufferedInputStream(fis);
FileOutputStream fos = new FileOutputStream("bbb.mp3");
BufferedOutputStream bos = new BufferedOutputStream(fos);
int b;
while((b = bis.read()) != -1) {
bos.write(b);
}
bis.close(); //只关装饰后的对象即可
bos.close();
注意:BufferedOutputStream中的flush()和close()方法的区别
flush()方法
用来刷新缓冲区的,刷新后可以再次写出
close()方法
用来关闭流释放资源的的,如果是带缓冲区的流对象的close()方法,不但会关闭流,还会再关闭流之前刷新缓冲区,关闭后不能再写出
3.5,对图片加密,原理:一个数异或两次同一个值之后等于自己,例如 b^222^22b2=b;对于图片加密每读取一个字节异或上一个数,解密的时候再异或同一个数就好;
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("a.jpg"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("c.jpg"));
int b;
while((b = bis.read()) != -1) {
bos.write(b ^ 222);
}
bis.close();
bos.close();
3.6,案例:在控制台录入文件的路径,将文件拷贝到当前项目下
public static void main(String[] args) throws IOException {
File file = getFile();
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file.getName()));
int b;
while ((b = bis.read())!=-1) {
bos.write(b);
}
bis.close();
bos.close();
}
public static File getFile(){
Scanner sr = new Scanner(System.in); //创建键盘录入对象
while(true){
String line = sr.nextLine();
File file = new File(line);
if(!file.exists()){
System.out.println("录入的路径不存在");
}else if (file.isDirectory()) {
System.out.println("录入的是文件夹路径");
}else {
return file;
}
}
}
3.7,案例:将键盘录入的数据拷贝到当前项目下的text.txt文件中,键盘录入数据当遇到exit时就退出
public static void main(String[] args) throws IOException {
Scanner sc = new Scanner(System.in);
FileOutputStream fos = new FileOutputStream("text.txt");
System.out.println("请输入:");
while(true){
String line = sc.nextLine();
if("exit".equals(line)){
break;
}
fos.write(line.getBytes());
fos.write("\r\n".getBytes());
}
fos.close();
}
最后,注意字节流读取中文会出现乱码;写出的时候不会;解决读取乱码的问题有两个方法:1,使用字符输入流;2,使用内存输出流ByteArrayOutputStream
3.8,序列流 SequenceInputStream
序列流可以把多个字节输入流整合成一个, 从序列流中读取数据时, 将从被整合的第一个流开始读, 读完一个之后继续读第二个, 以此类推;
例如:把两个或者更多的mp3整合成一个;
private static void demo1() throws FileNotFoundException, IOException {
Vector<InputStream> streams = new Vector();
streams.add(new FileInputStream("E:\\CloudMusic\\齐秦 - 狼.mp3")); //注意反斜杠\\第一个时转义字符
streams.add(new FileInputStream("E:\\CloudMusic\\庄心妍 - 梦诛缘·忆暖冬.mp3"));
SequenceInputStream sis = new SequenceInputStream(streams.elements());
FileOutputStream fos = new FileOutputStream("E:\\CloudMusic\\齐秦和庄心妍.mp3");
int len;
byte[] b = new byte[1024*8];
while((len =sis.read(b))!=-1){
fos.write(b, 0, len);
}
sis.close();
fos.close();
}
3.9,内存输出流 ByteArrayOutputStream
该输出流可以向内存中写数据, 把内存当作一个缓冲区, 写出之后可以一次性获取出所有数据
面试题:定义一个文件输入流,调用read(byte[] b)方法,将a.txt文件中的内容打印出来(byte数组大小限制为5)
private static void demo1() throws FileNotFoundException, IOException {
FileInputStream fis = new FileInputStream("a.txt");
ByteArrayOutputStream baos = new ByteArrayOutputStream(); //没有指向任何文件,也就是没有和硬盘上建立流通道,所以不需要关流;
byte[] b = new byte[5];
int len;
while((len = fis.read(b))!=-1){
baos.write(b, 0, len);
}
fis.close();
// System.out.println(baos);
byte[] data = baos.toByteArray();
System.out.println(new String(data));
}
调用ByteArrayOutputStream的close()方法无效,就是调用之后也在是使用对象也不会抛异常;
3.10,对象输出输入流
- ObjectOutputStream
- ObjectInputStream
案例:
private static void demo2() throws IOException, FileNotFoundException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("b.txt"));
List<Film> films = new ArrayList(); //使用List是防止读取时,不知道存了多少个对象,导致超出存入的数量会出现EOFExceptions
films.add(new Film("战狼2"));//Film别忘了实现Serializable,可序列化的;
films.add(new Film("绿皮书"));
films.add(new Film("速度与激情"));
oos.writeObject(films);
oos.close();
}
private static void demo3() throws IOException, FileNotFoundException,
ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("b.txt"));
List<Film> films = (List<Film>) ois.readObject();
for (Film film : films) {
System.out.println(film);
}
ois.close();
}
public class Film implements Serializable{
private static final long serialVersionUID = 111; //不加这个参数,会有个默认参数,反序列化多次时会出现InvalidClassException;或者没写入就读取也会出现此异常前提是么有加这个ID号;
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Film(String name) {
super();
this.name = name;
}
@Override
public String toString() {
return "Film [name=" + name + "]";
}
}
四,字符流
4.1,字符流介绍
字符流是可以直接读写字符的IO流
字符流读取字符, 就要先读取到字节数据, 然后转为字符. 如果要写出字符, 需要把字符转为字节再写出.
4.2,FileReader简单使用
private static void demo1() throws FileNotFoundException, IOException {
FileReader fr = new FileReader("aaa.txt");
int ch;
while ((ch = fr.read()) != -1) { //一次读一个字符(char);
System.out.print((char) ch);
}
fr.close();
}
4.3,FileWriter简单使用
private static void demo2() throws IOException {
FileWriter fw = new FileWriter("cc.txt");
fw.write("大家好,才是真的好!!");
fw.close();
}
4.4,FileReader和FileWriter复制
private static void demo3() throws FileNotFoundException, IOException {
FileReader fr = new FileReader("aaa.txt");
FileWriter fw = new FileWriter("ddd.txt");
int ch;
while((ch= fr.read())!=-1){
fw.write(ch);
}
fr.close();
fw.close();
}
4.5,自定义字符数组,完成复制;
private static void demo4() throws FileNotFoundException, IOException {
FileReader fr = new FileReader("aaa.txt");
FileWriter fw = new FileWriter("mm.txt");
int len;
char c[] = new char[1024];
while ((len=fr.read(c))!=-1) {
fw.write(c,0,len);
}
fr.close();
fr.close();
}
注意:FileWriter的close()方法,有自带缓冲区;不关流有可能会写出不完整,也就是有一部分数据在缓冲区中没写出;
4.6,带缓冲的字符流:BufferedReader和BufferedWriter
BufferedReader的read()方法读取字符时会一次读取若干字符到缓冲区, 然后逐个返回给程序, 降低读取文件的次数, 提高效率
BufferedWriter的write()方法写出字符时会先写到缓冲区, 缓冲区写满时才会写到文件, 降低写文件的次数, 提高效率
private static void demo1() throws FileNotFoundException, IOException {
BufferedReader br = new BufferedReader(new FileReader("aaa.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("nn.txt"));
int ch;
while((ch = br.read())!=-1){
bw.write(ch);
}
br.close();
bw.close();
}
BufferedReader中的readLine()和BufferedWriter中的newLine()
readLine()方法可以读取一行字符(不包含换行符号)
newLine()可以输出一个跨平台的换行符号"\r\n"
private static void demo2() throws FileNotFoundException, IOException {
BufferedReader br = new BufferedReader(new FileReader("aaa.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("nn.txt"));
String s;
while((s = br.readLine())!=null){
bw.write(s);
bw.newLine(); //写出一个跨平台的换行符
}
br.close();
bw.close();
}
4.7,什么情况下使用字符流
字符流也可以拷贝文本文件, 但不推荐使用. 因为读取时会把字节转为字符, 写出时还要把字符转回字节.
程序需要读取一段文本, 或者需要写出一段文本的时候可以使用字符流
读取的时候是按照字符的大小读取的,不会出现半个中文
写出的时候可以直接将字符串写出,不用转换为字节数组
总结,只读或者只写一段文本的时候,使用字符流;
4.8,字符流是否可以拷贝非纯文本的文件
不可以拷贝非纯文本的文件
因为在读的时候会将字节(文件内容都是以字节的形式存储的)转换为字符(两个字节转换成一个字符),在转换过程中,可能找不到对应的字符,就会用?代替,写出的时候会将字符转换成字节写出去
如果是?,直接写出,这样写出之后的文件就乱了,看不了了
4.9,LineNumberReader
是BufferedReader的子类,比父类更强大的地方就是可以获取和设置行号:getLineNumber()和setLineNumber();
private static void demo3() throws FileNotFoundException, IOException {
LineNumberReader lnr = new LineNumberReader(new FileReader("aaa.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("ss.txt"));
String line;
lnr.setLineNumber(100); //设置行号从100起
while((line= lnr.readLine())!=null){
bw.write(lnr.getLineNumber()+": "+ line);
bw.newLine();
}
lnr.close();
bw.close();
}
4.10,转换流
InputStreamReader和OutputStreamWriter都是字符流;使用方式类似于FileReader和FileWriter;
InputStreamReader:把字节流转换字符流读入,并使用指定的编码表将其解码为字符;
OutputStreamWriter:把字符流转换字节流输出,向其写入的字符编码成使用指定的字节编码表;
问题和替换:
FileReader是使用默认码表读取文件, 如果需要使用指定码表读取, 那么可以使用InputStreamReader(字节流,编码表)
FileWriter是使用默认码表写出文件, 如果需要使用指定码表写出, 那么可以使用OutputStreamWriter(字节流,编码表)
private static void demo4() throws UnsupportedEncodingException,
FileNotFoundException, IOException {
InputStreamReader isr = new InputStreamReader(new FileInputStream("utf-8.txt"),"UTF-8"); //utf-8.txt是UTF_8编码格式
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("gbk.txt"),"GBK"); //gbk.txt文件使用的是GBK编码表
int ch;
while((ch = isr.read())!=-1){
osw.write(ch);
}
isr.close();
osw.close();
}
4.11,获取文本中字符出现的次数
private static void demo2() throws FileNotFoundException, IOException {
FileReader fr = new FileReader("aaa.txt");
FileWriter fw = new FileWriter("jj.txt");
int ch;
TreeMap<Character, Integer> map = new TreeMap<Character, Integer>();
while((ch = fr.read())!=-1){
char c = (char) ch;
map.put( c, !(map.containsKey(c)) ? 1: map.get(c)+1);
}
for(Character c: map.keySet()){ //map.keySet()相当于遍历set集合;
switch (c) {
case '\r':
fw.write("\\t" +": "+ map.get(c));
break;
case '\n':
fw.write("\\n" +": "+ map.get(c));
break;
case '\t':
fw.write("\\t" +": "+ map.get(c));
break;
default:
fw.write(c +": "+ map.get(c));
break;
}
fw.write("\r\n"); //FileWriter可以换成BufferedWriter,就可以使用跨平台的换行:newLine();
}
fr.close();
fw.close();
}
4.12,从键盘输入接收一个文件夹路径,打印出该文件夹下所有的.png文件名
通过递归完成;
public static void main(String[] args) throws IOException {
File file = getFile();
printPNG(file);
}
private static void printPNG(File dir){
File[] subFile = dir.listFiles();
for (File file : subFile) { //递归结束条件
if(file.isFile()&&file.getName().endsWith(".png")){
System.out.println(file);
} else if(file.isDirectory()){
printPNG(file);
}
}
}
private static File getFile(){
System.out.println("请输入一个文件夹路径:");
Scanner sn = new Scanner(System.in);
while(true){
String path = sn.nextLine();
File file = new File(path);
if (file.isDirectory()) {
return file;
}else if(file.isFile()){
System.out.println("输入的是文件,请重新输入:");
}else {
System.out.println("请输入正确的文件夹路径");
}
}
}
五,其他流
5.1,打印流 PrintStream 和PrintWriter
打印流只操作数据目的,可以从打印流的构造方法(有OutputStream参数)可以看出;
private static void demo2() throws FileNotFoundException {
PrintWriter pw =new PrintWriter("mm.txt");
pw.print(22); //不能自动刷出
pw.println("aabbcc"); //自动刷出
pw.close();
}
private static void demo1() throws FileNotFoundException {
// PrintStream ps = new PrintStream("mm.txt");
PrintStream ps = System.out; //其默认向控制台输出信息
ps.print(111);
ps.println("mmmmmmm");
ps.close();
}
5.2,标准输入输出流System.in 和System.out;
System.in是InputStream, 标准输入流, 默认可以从键盘输入读取字节数据
System.out是PrintStream, 标准输出流, 默认可以向Console中输出字符和字节数据
修改标准输入输出流
修改输入流: System.setIn(InputStream)
修改输出流: System.setOut(PrintStream)
System.setIn(new FileInputStream("a.txt")); //修改标准输入流
System.setOut(new PrintStream("b.txt")); //修改标准输出流
InputStream in = System.in; //获取标准输入流
PrintStream ps = System.out; //获取标准输出流
int b;
while((b = in.read()) != -1) { //从a.txt上读取数据
ps.write(b); //将数据写到b.txt上
}
in.close();
ps.close();
5.3,数据输入输出流
DataInputStream和DataOutputStream:可以按照基本数据类型大小读写数据;例如按int大小写出一个数字, 写出时该数据占8字节. 读取的时候也可以按照Long类型读取, 一次读取8个字节。
private static void demo4() throws FileNotFoundException, IOException {
DataOutputStream dos = new DataOutputStream(new FileOutputStream("cc.txt"));
dos.writeInt(1111);
dos.writeLong(2222);
dos.close();
}
private static void demo5() throws FileNotFoundException, IOException {
DataInputStream dis = new DataInputStream(new FileInputStream("cc.txt"));
System.out.println(dis.readInt());
System.out.println(dis.readLong());
dis.close();
}
5.4,随机访问流
RandomAccessFile类不属于流,是Object类的子类。但它融合了InputStream和OutputStream的功能。
支持对随机访问文件的读取和写入。
主要方法:read(),write(); seek() :设置到此文件开头测量到的文件指针偏移量,在该位置发生下一个读取或写入操作。