IO流

IO流

IO流概述

IO流就是读取跟写入文件

分为输入(Input)和输出(Output)

输入:从硬盘传入到内存称为输入,输入中发生的数据的流动叫做输入流(InputStream),也叫做读(Read)

输出:从内存传出到硬盘称为输出,输出中发生的数据的流动叫做输出流(OnputStream),也叫做写(Write)

IO:

I就是Input

O就是Output

通过IO流可以完成硬盘文件的读和写

IO流的分类:

​ 有多种分类方式:

​ 一种方式是按照流的方向进行分类:

​ 以内存为参照物:

​ 往内存中去,叫做输入(Input)。或叫做读(Read)。

​ 从内存中出来,叫做输出(Output)。或叫做写(Write)。

​ 另一种方式是按照读取数据方式的不同进行分类:

​ 按照字节方式读取数据,一次读取1个字节byte,等同于一次读取8个二进制位。

​ 这种流被称为万能流,什么都可以读取,包括(文本,图片,音视频等)

​ 假设文件filel.txt,采用字节流的话是:

​ a中国

​ 第一次读:’a‘字符(windows系统中占一个字节),正好读完

​ 第二次读:’中‘字符(windows系统中占两个字节),只能读取一半

​ 第三次读:’中‘字符(windows系统中占两个字节),加上第二次读的正好读完

​ 按照字符的方式读取数据,一次读取一个字符,这种流是为了方便读取普通文本文件而存在的。这种流只能读取纯文本文档,不能读取视频,声音,图片等,连word都无法读取

​ 假设文件filel.txt,采用字符流的话是:

​ a中国

​ 第一次读:’a‘字符(windows系统中占一个字节)

​ 第二次读:’中‘字符(windows系统中占两个字节)

综上所述:流的分类

​ 输入流、输出流

​ 字节流,字符流

Java中的IO流都已经写好了,java中所有的流都是在:java.io.*;中

IO流下的四大家族:

四大家族首领都是抽象类(abstract class)

java.io.InputStream; 字节输入流

java.io.OutputStream; 字节输出流

java.io.Reader; 字符输入流

java.io.Writer;字符输出流

所有的流都实现了:

​ java.io.Closeable接口,都是可关闭的,都有close()方法,流毕竟是一个管道,是内存与硬盘之间的通道,用完即关,不然会浪费很多资源

所有的输出流都实现了:

​ java.io.Flushable接口,都是可刷新的,都有flush()方法

​ 用完输出流后要调用flush()方法清空管道,flush方法会将未输出完的强行输出完(清空管道)

如果没有flush()可能会导致数据丢失

以Stream结尾都是字节流

以Reader/Writer结尾都是字符流

常用流:

文件专属:

java.io.FileInputStream

java.io.FileOutputStream

java.io.FileReader

java.io.FileWirter

转换流:(将字节流转换成字符流)

java.io.InputStreamReader

java.io.OutputStreamWriter

缓冲流专属:

java.io.BufferedReader

java.io.BufferedWriter

java.io.BufferedInputStream

java.io.BufferedOutputStream

数据流专属:

java.io.DataInputStream

java.io.DataOutputStream

对象专属流:

java.io.ObjectInputStream

java.io.ObjectOutputStream

标准输出流:

java.io.PrintStream

java.io.PrintWriter

流的使用和概念

文件专属:
java.io.FileInputStream
  1. 万能文件输入流,任何文件都可以采用这个流来读

  2. 字节的方式,完成输入的操作(硬盘–>内存)

    FileInputStream fis = null;
    try{
    	//创建文件字节输入流
    	fis = new FileInputStream("路径");
    	int readData = fis.read();//开始读取文件,每次调用读取一个字节,这个方法会返回读到的”字节“本身
    //读取的位置如果什么都没有返回-1,表示读到末尾了
    	System.out.println(readData);
    }catch(FileNotFoundException e){
        e.printStackTrace();
    }catch(IOException e){
        e.printStackTrace();
    }finally{
        if(fis != null){
        	try{
        		fis.close();//关闭流,流不是空没必要关
            }catch(IOexception e){
                e.printStackTrace();
            }
        }
    }
    
    //程序改进,上方程序虽然也能读取,但是我们不可能无数次的去调用,因此我们需要设定一个循环让他自动
    FileInputStream fis = null;
    try{
    	//创建文件字节输入流
    	fis = new FileInputStream("路径");
        while(true){
    		int readData = fis.read();//开始读取文件,每次调用读取一个字节,这个方法会返回读到的”字节“本身,也就是ASCII
    	//读取的位置如果什么都没有返回-1,表示读到末尾了
            if(readDate == -1){
                break;
            }
            System.out.println(readData);
        }
    }catch(FileNotFoundException e){
        e.printStackTrace();
    }catch(IOException e){
        e.printStackTrace();
    }finally{
        if(fis != null){
        	try{
        		fis.close();//关闭流,流不是空没必要关
            }catch(IOexception e){
                e.printStackTrace();
            }
        }
    }
    

    对while再次改进

    int readData;
    while((readData = file.read()) != -1){
        System.out.println(readData);
    }
    

    这种一个一个字节读取方式太慢,和文件交互太频繁,浪费太多时间和资源

    byte[] b = new byte[4];
    //int read(byte[] b),一次最多读取数组.length的数量
    int readCount = fis.read(b);//读取的不是字符本身,而是数量
    //若你的文件中有abcdef六个字符
    //第一次调用会读取4个字符也就是abcd放进数组中,所以返回4
    System.out.println(readCount);
    System.out.println(new String(b),0,readCount);
    readCount = fis.read(b);
    //第二次调用会读取后面的,已读的不会再读,读取出来的会覆盖数组前两个,任何返回读取数量
    System.out.println(readCount);
    System.out.println(new String(b),0,readCount);
    readCount = fis.read(b);
    //第三次读取会发现里面以及没有可读取的内容了,那么会返回-1
    System.out.println(readCount);
    System.out.println(new String(b),0,readCount);
    

    这时我们可以调用String将读取的字符数组转换输出

    System.out.println(new String(b),0,readCount);

    返回剩下没获取到的字节数量:fis.available();

    结合available使用

    byte[] b = new byte[fis.available()];

    这样可以省下循环,一次直接读取完,但不适合大文件,因为byte数据不能太大

    skip():跳过几个字节不读取

    fis.skip(3);跳过3个字节不读取

    文件的路径,idea的默认路径是Project的根

    若文件位于根下则路径直接写文件名.文件类型

    若在根中的某个文件夹中则文件夹名/文件名.文件类型

    在根外面则写全路径

java.io.FileOutputStream
byte[] b = new byte[]{2,5,6,7,9,3,8,1};
FileOutputStream fos = null;
try{
    fos = new FileOutputStream("路径",true);//文件不存在则自动新建,true表示追加,不会清空原内容
    fos.write(b);//将byte数组中的数据写出文件中,会将文件本身内容清空再写入
    fos.write(b,2,4);//跳过某一部分数据写出
    fos.flush();//用完一定要刷新
}catch(FileNotFoundException e){
    e.printStackTrace();
}catch(IOException e){
    e.printStackTrace();
}finally{
    if(fos != null){
    	try{
    		fos.close();//关闭流,流不是空没必要关
        }catch(IOexception e){
            e.printStackTrace();
        }
    }

文件的复制:先读取文件放入数组中,在通过数组转移到新文件中

FileInputStream fis = null;
FileOutputStream fos = null;
int count = 0;
try {
	fis = new FileInputStream("file");
    fos = new FileOutputStream("D:/file",true);
    byte[] b = new byte[4];
    while((count = fis.read(b)) != -1){}
    	fos.write(b,0,count);
    }
    fos.flush();
} catch (FileNotFoundException e) {
	e.printStackTrace();
} catch (IOException e) {
	e.printStackTrace();
}finally {
	if (fis != null){
    	try {
        	fis.close();
		} catch (IOException e) {
        	e.printStackTrace();
        }
    }
	if (fos != null){
    	try {
        	fos.close();
		} catch (IOException e) {
        	e.printStackTrace();
        }
	}
}
java.io.FileReader

​ 文件字符输入流,只能读取普通文本

​ 读取文本内容时,比较方便,快捷

根FileInputStream一样的作法

FileReader fr = null;
int count = 0;
char[] c = new char[5];
try {
	fr = new FileReader("file");
    //while ((count = fr.read(c)) != -1){
    //	System.out.println(new String(c,0,count));
    //}
    fr.read(c);//按字符读取,任何foreach输出
    for(char c2 : c){
        System.out.println(c2);
    }
} catch (FileNotFoundException e) {
	e.printStackTrace();
} catch (IOException e) {
	e.printStackTrace();
} finally {
	if (fr != null){
    	try {
        	fr.close();
        } catch (IOException e) {
        	e.printStackTrace();
        }
    }
}

skip():

​ 跳过指定数量字符

java.io.FileWirter
FileWriter fw = null;
int count = 0;
char[] c = new char[]{'人','国','中','是','我'};
try {
	fw = new FileWriter("file",true);//表示在末尾添加
	//fw.write(c);//会清空原内容
	fw.write(c,1,4);
	//fw.write("ssaasfsf");可以直接写出字符串
	fw.write("\n");//表示换行
	fw.flush();
} catch (FileNotFoundException e) {
	e.printStackTrace();
} catch (IOException e) {
	e.printStackTrace();
} finally {
	if (fw != null){
		try {
			fw.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

FileOutputStream和Write只能复制普通文件

FileReader fr = null;
FileWriter fw = null;
try {
	fr = new FileReader("file");
    fw = new FileWriter("D:/file",true);
    char[] b = new char[4];
    int count = 0;
    while((count = fr.read(b)) != -1) {
    	fw.write(b,0,count);
    }
    fw.flush();
} catch (FileNotFoundException e) {
	e.printStackTrace();
} catch (IOException e) {
	e.printStackTrace();
}finally {
	if (fr != null){
    	try {
        	fr.close();
        } catch (IOException e) {
        	e.printStackTrace();
        }
    }
    if (fw != null){
    	try {
        	fw.close();
        } catch (IOException e) {
        	e.printStackTrace();
        }
	}
}
缓冲流专属:
java.io.BufferedReader

带有缓冲区的字符输入流

使用这个流的时候不需要自定义数组,自带缓冲

//当一个流的构造方法中需要一个流时,这个被传进来的流叫做节点流
//外部负责包装的这个流,叫做:包装流,还有一个名字叫做:处理流
//像下面的例子:FireReader就是节点流,BufferedReader就是包装流(处理流)
FileReader fr = null;
BufferedReader br = null;
String str = null;
String s = null;
try {
	fr = new FileReader("file");
	br = new BufferedReader(fr);
    //对于包装流来说,只需要关闭包装流,节点流会自动关闭
    str = br.readLine();//读取一行
	System.out.println(str);
    while((s = br.readLine()) != null){//循环获取每一行数据,要是获取到的是null表示获取结束
    	System.out.println(s);//不会自动换行,是println换的行
    }
} catch (FileNotFoundException e) {
	e.printStackTrace();
} catch (IOException e) {
	e.printStackTrace();
}finally {
	br.close();
}

BufferedReader只能传如字符流,无法传入字节流,需要将字节流转换成字符流

java.io.BufferedWriter

带有缓冲区的字符输出流

FileWriter fw = null;
BufferedWriter bw = null;
String str = null;
String s = null;
try {
	fw = new FileWriter("file");
	bw = new BufferedWriter(fr);
    //对于包装流来说,只需要关闭包装流,节点流会自动关闭
    str = br.writer("dfdffsfas");//写入数据,不会清空,会追加在后方
    bw.writer();
    bw.flush();
} catch (FileNotFoundException e) {
	e.printStackTrace();
} catch (IOException e) {
	e.printStackTrace();
}finally {
	bw.close();
}

new BufferedWriter(new OutputStreamWriter(new FileOutputStream));将字节流转换成字符流传入BufferedWriter中,简化

java.io.BufferedInputStream(自己了解,名字已经说明一切)
java.io.BufferedOutputStream(自己了解,名字已经说明一切)
转换流:(将字节流转换成字符流)
java.io.InputStreamReader

创建一个字节流

FileInputStream fis = new FileInputStream("file");

将字符类转换成字符流

InputStreamReader isr = new InputStreamReader(fis);

将转换好的字符流传入缓存流读取

BufferedReader br = new BufferedReader(isr);

java.io.OutputStreamWriter

FileOutputStream fos = new FileOutStream("file");

OutputStreamWriter osw = new OutputStreamWriter(fos);

BufferedWriter bw = new BufferedWriter(osw);

数据流专属:
java.io.DataInputStream

这个流可以将数据连同数据的类型一并读取文件,是用来读取DataOutputStream写入的内容的,并且必须按照写进去的顺序取出,不然会报错。

FileInputStream fis = new FileInputStream("file");
DataInputStream dis = new DataInputStream(fis);
byte b3 = dis.readByte();
short s1 = dis.readShort();
int i1 = dis.readInt();
long l1 = dis.readLong();
float f1 = dis.readFloat();
double d1 = dis.readDouble();
char c1 = dis.readChar();
boolean b2 = dis.readBoolean();
dis.close();
System.out.println(b3);
System.out.println(s1);
System.out.println(i1);
System.out.println(l1);
System.out.println(f1);
System.out.println(d1);
System.out.println(c1);
System.out.println(b2);
java.io.DataOutputStream

这个流可以将数据连同数据的类型一并写入文件

这个文件不是普通文本文档(使用记事本无法打开)

并且只有DataInputStream数据流才能读取

FileOutputStream fos = new FileOutputStream("file");
DataOutputStream dos = new DataOutputStream(fos);
byte b = 'a';
short s = 100;
int i = 500;
long l = 1000;
float f = 13.5f;
double d = 45.4;
char c = '你';
boolean b1 = true;
dos.writeByte(b);
dos.writeShort(s);
dos.writeInt(i);
dos.writeLong(l);
dos.writeFloat(f);
dos.writeDouble(d);
dos.writeBoolean(b1);
dos.flush();
if(dos!=null) {
	dos.close();
}
标准流:
java.io.PrintStream

标准字节输出流,默认输出到控制台

联合写:

System.out.println("HelloWorld!");

分开写:

PrintStream ps = System.out;

ps.println("HelloWorld!");

标准输出流不需要手动close关闭

PrintStream printStream = new PrintStream(new FileOutputStream("log"));//标准输出流不再指向控制台,而是指向log文件
System.Out(printStream);
java.io.PrintWriter
日志文件
try {
            PrintStream ps = new PrintStream(new FileOutputStream("log",true));
            System.setOut(ps);
            Date date = new Date();
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:ss:mm sss");
            String str = sdf.format(date);
            System.out.println(str+": "+amg);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
java.io.File类

File不是一个流,所以不能完成文件的读写。

File是一个文件或路径名的一个抽象表示形式

如:D:\LOL或者D:\LOL\log 都是File对象

一个File对象有可能对应的是目录,也可能是文件

File只是一个路径名和的抽象表现形式

FIle类的常用方法:
File f1 = new File("路径");
f1.exists();//判断路径中的文件是否存在
f1.createNewFile();//以文件形式创建
//判断文件是否存在,不存在则创建
if(f1.exists())
    //只能创建文件
    f1.createNewFile();
f1.mkdir();//创建目录
if(f1.exists())
    f1.mkdirs();//创建多重目录,创建文件夹

File f1 = new File("路径");
//获取文件父路径,就是文件上一层目录的位置
System.out.println(f1.getParent());
File parenetFile = f1.getParentFile();
System.out.println("获取绝对路径"+parentFile.getAbsolutePath());
File file = new File("log");
System.out.println("获取绝对路径"+file.getAbsolutePath());

delete()删除文件或目录

File f1 = new File("路径");
//获取文件名
System.out.println(f1.getName());
f1.isDirectory();//判断是否是一个目录
f1.isFile();//判断是否是一个文件
f1.isHidden();//是否是隐藏文件
f1.lastModified();//返回最后一次修改的时间,是一个毫秒,从1970年1月1日开始计算的
f1.length();//获取文件长度,字节
File[] file = f1.listFiles();//获取当前目录下的所有子目录
for(File file2 : file){
    System.out.println(file2.getAbsolutePath());
}

序列号和反序列化

java.io.ObjectOutputStream

Java序列化就是指把Java对象转换为字节序列的过程

就是将java对象从内存存储到文件夹中,将java对象的状态保存下来的过程

Serializable 序列化 : 将对象转化为便于传输的格式, 常见的序列化格式:二进制格式,字节数组,json字符串,xml字符串。

参与序列号的对象必须实现Serializable接口

通过源代码发现,Serailizable接口只是一个标志接口:

接口中什么代码都没有,主要起的是一个标识,标志作用,java虚拟机看见这个类,可能会对这个类进行特殊待遇

Serializable这个标志是给java虚拟机参考的,java虚拟机看到这个接口后,会为该对象类自动生成一个序列号版本号

序列号版本号,对以前的类的源代码进行改变后JVM会重新分配的序列号,用来区分类

java语言中采用什么机制区分类:

  • 首先通过类名进行对比,如果类名不一样,看到不是同一个类

  • 如果类名一样,则靠版本号区分

    只要实现了序列化接口Serializable即便你类名相同,JVM同样可以分辨出这是不同的两个类,因为实现了序列化接口后会产生序列化版本号来进行分辨

    只要修改了,必然会重新分配,此时会生成一个新的序列化版本号,这时候Java虚拟机会认为这时一个全新的类

    因此,继承了Serializable的类一旦代码确定后,将不可修改

//先创建一个Student类继承(implements)Serializable,给上几个属性
//接着定义一个测试类
public static void main(String[] args){
    //创建一个java对象
    Student s = new Student(111,"zhangsan");
    //序列化,将java对象Student序列化存储进student文件中
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("student"));
    oos.writeObject(s);//序列化对象
    oos.flush();
    oos.close();
}
java.io.ObjectInputStream

Java反序列化就是指把字节序列恢复为Java对象的过程。

deseriallization 反序列化:将序列化的数据恢复为对象的过程。

//将需要反序列化的文件传入
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("student"));
Object obj = ois.readObject();
System.out.println(obj);
ois.close();

多对象序列号,可以将对象添加到ArrayList集合中,在反序列化中,readObject()获取到的会是一个ArrayList只需要强转后遍历即可

ArrayList<StudyClass> asc = new ArrayList<>();
        asc.add(new StudyClass(1,"语文"));
        asc.add(new StudyClass(2,"数学"));
        asc.add(new StudyClass(3,"English"));
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("study"));
        oos.writeObject(asc);
        oos.flush();
        oos.close();
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("study"));
        ArrayList<StudyClass> asc2 = (ArrayList<StudyClass>) ois.readObject();
        for (StudyClass sc : asc2) {
            System.out.println(sc.toString());
        }
public class StudyClass implements Serializable {
    private  int no;
    private String clasies;

    public void setNo(int no) {
        this.no = no;
    }

    public int getNo(){
        return no;
    }
    public String getClasies(){
        return clasies;
    }
    public void setClasies(String clasies){
        this.clasies = clasies;
    }
    public StudyClass(){}
    public StudyClass(int no,String clasies){
        this.clasies = clasies;
        this.no = no;
    }
    @Override
    public String toString() {
        return "StudyClass{" +
                "no=" + no +
                ", clasies='" + clasies + '\'' +
                '}';
    }
}

transient关键字

表示游离的,不参与序列号

private transient int no;

序列化版本号

序列号版本号,对以前的类的源代码进行改变后JVM会重新分配的序列号,用来区分类

java语言中采用什么机制区分类:

  • 首先通过类名进行对比,如果类名不一样,看到不是同一个类

  • 如果类名一样,则靠版本号区分

    只要实现了序列化接口Serializable即便你类名相同,JVM同样可以分辨出这是不同的两个类,因为实现了序列化接口后会产生序列化版本号来进行分辨

    只要修改了,必然会重新分配,此时会生成一个新的序列化版本号,这时候Java虚拟机会认为这时一个全新的类

    因此,继承了Serializable的类一旦代码确定后,将不可修改

凡是一个类实现了Serializable接口,建议给该类提供一个固定不变的序列化版本号

这样,以后或者各类即使代码修改了,但是版本号不变,java虚拟机会认为这时同一个类

建议在该类中写上:

private static final long serialVersionUID = 6546189194999991616161L;

固定好后,重新编译也不会出现问题

IO和Properties联合使用

将Key=Value数据的文件通过IO加载到类中(反射使用)

//写入数据在properties文件
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("properties"));
osw.write("key=value\n1=zhangsan");
osw.flush();
osw.close();
//读取properties存放在Properties集合当中
FileReader reader = new FileReader("properties");
Properties p = new Properties();
p.load(reader);//从输入流中获取数据key跟value对应Properties集合中的key-Value
System.out.println(p);//输出
System.out.println(p.getProperty("1"));//通过key获取value

我们把properties这种文件叫做配置文件

java规范中建议属性配置文件以 .properties结尾

Properties这个类是专门存放数学配置文件内容的一个类

属性配置文件

属性配置文件中#是注释

key重复的话,后面会覆盖签名的

建议key和value之间使用=方式

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值