Java IO流

*IO流

内存不能永久化存储,程序停止时数据丢失

将文件存储到硬盘的文件中可以保存数据

io流是存储和读取数据的解决方案

分类

按流传输方向

以程序/内存为参照物,读入/写出

  • 输入流input

    本地文件-->程序文件

  • 输出流output

    程序文件-->本地文件

按文件类型

  • 字节流

    所有类型文件🖼🎵🎥

  • 字符流

    纯文本文件📑

*基本流

抽象类具体实现作用
InputStreamFileInputStream操作本地文件的字节输入流, 本地-->程序
OutputStreamFileOutputStream操作本地文件的字节输出流, 程序-->本地
ReaderFileReader操作本地文件的字符输入流, 本地-->程序
WriterFileWriter操作本地文件的字符输出流, 程序-->本地

FileOutputStream

文件输出流: 程序-->本地

步骤

  1. 创建字节输出流对象

  2. 调用write方法写数据

  3. 关闭流/释放资源

//1. 创建抽象类OutStream实现类FileOutStream
FileOutputStream fos=new FileOutputStream("src\\IO\\a.txt");
//2. 调用write方法写数据
fos.write(104);//ascll码
fos.write(105);
//释放资源
fos.close();
  • FileOutputStream("输出路径"/File对象,[追加写?])

    创建程序-->本地的"通道"

    • 如果文件不存在会创建一个新文件,但是要保证父路径存在

    • 如果文件存在,覆盖写

  • fos.write(写入数据ascll码)

    通车

    方法说明
    void write(int b)一次写出一个字节数据
    void write(byte[] b)一次写出一个字节数组数据
    void write(byte[], int sta, int len)一次写出一个字节数组从sta索引开始,长度为len的数据
    //1. 创建字节输出流对象
    FileOutputStream fos = new FileOutputStream("src\\IO\\b.txt");
    //2. 写数据
    byte[] bytes = {104 ,101 ,108 ,108 ,111};
    fos.write(bytes);       //hello
    fos.write(bytes,2,2);   //ll
    //3. 关闭流
    fos.close();

  • fos.close()

    关闭通道

getBytes

字符串-->字节数组

fos.write("hello\r\n".getBytes());

换行

  • windows:/r/n

    java对其做了优化,

    无论写/r或/n java都会帮我们补齐

  • Linux:/n

FileInputStream

文件输入流: 本地-->程序

步骤

  1. 创建文件输入流

  2. 读文件

  3. 释放资源

//1.  创建字节输入流对象FileInputStream("读入目标文件路径")
FileInputStream fis=new FileInputStream("src\\IO\\ByteOutStream\\c.txt");
//2.1 读一个字节
System.out.print(fis.read());           //h-->104
System.out.print((char)fis.read());     //e
​
//每次读一个字节,指针后移一位,最后一位为-1
//2.2 连续读
int b=fis.read();
while(b !=-1){
    System.out.print((char)b);
    b=fis.read();
}
//3 关闭流
fis.close();
  • FileInputStream("读入目标文件路径"/File对象)

  • 如果文件不存在,报错

  • read

    方法说明
    public int read()每次读一个字节, 返回数据的ascll码(读不到数据返回-1), 指针后移一位
    public int read(byte[] buffer)每次读一个字节数组, 返回本次读的字节数量(读不到数据返回-1), 指针到数组下一位

    public int read(byte[] buffer)

    buffer是一个缓存区,当最后读入的数据没有装满时,会留下上一次的残存数据

    解决方法:

    fos.write(bytes,0,len)

拷贝

FileInputStream fis=new FileInputStream("src\\IO\\ByteOutStream\\c.txt");
FileOutputStream fos=new FileOutputStream("src\\IO\\ByteInStream\\a.txt");
int b;
while ((b=fis.read())!=-1){
    fos.write((char)b);
}
fis.close();
fos.close();

FileInputStream一次读一个字节(暂存在b中),速度非常慢

可以考虑用 public int read(byte[] buffer) 每次读一个字节数组

buffer太小会很慢,buffer太大占内存.

buffer大小一般为1024的倍数( 推荐 5M )

FileInputStream fis=new FileInputStream("src\\IO\\ByteOutStream\\c.txt");
FileOutputStream fos=new FileOutputStream("src\\IO\\ByteInStream\\a.txt");
byte [] bytes=new byte[3];
int len;
while ((len=fis.read(bytes))!=-1){  //-1表示文件结束
    fos.write(bytes,0,len);         //解决缓存区残存数据问题
}
fis.close();
fos.close();

中文乱码问题

  • ascll码用来存欧美人的字符

  • GB 2312-80是简体汉字编码编码表

  • GBK是GB 2312-80扩展,包括简繁中文,英文,日文,韩文

    操作系统默认GBK编码

    英文字符一个字节(2^9-1 bit/个)表示就够了,但是汉字比较多(2^9-1)个不够放,

    所以用两个字节表示一个汉字(可表示2^17-1个)

  • Unicode万国码,包含世界上大多数国家字符

    UTF-8:( unicode transfer format)用1~4个字符存储( 根据不同用于改变 )

    • 中文用3个字节表示: 1110xxxx 10xxxxxx 10xxxxxx

    • 英文用1个字节表示: 0xxxxxxx

乱码原因

  • 读取数据时未读完整个汉字编码

    一个汉字占3个字节,当只读取其中部分字节时对应10进制为负数,找不到对应字符

  • 编码和解码方式不统一

    比如编码用UTF-8时3B表示一个汉字,

    若解码时用GBK,前两个字节被解成一个汉字剩下1B 乱码?

解决方法

  • 不要用字节流读取文本文件

    因为字节流每次读1B,会把中文拆开

  • 编码译码用同一个编码表

同编码表代码实现

  • 编码方法

    String类中方法说明
    public byte[] getBytes()使用默认方法进行编码,idea默认UTF-8
    public byte[] getBytes(String charsetName)使用指定方法进行编码
  • 解码方法

    String类中方法说明
    String(bytes[] bytes)使用默认方法进行编码
    String(bytes[] bytes,String charsetName)使用指定方式进行编码

getBytes: 数据-->字节数组

//编码:getBytes
String str = "diva在学习";
byte[] bytes = str.getBytes("UTF-8");   //String-->字节数组
System.out.println(Arrays.toString(bytes));
//解码:new String(byte[] bytes,int offset,int length)
String str2 = new String(bytes,"UTF-8");//字节数组-->String
System.out.println(str2);

结果

[100, 105, 118, 97, -27, -100, -88, -27, -83, -90, -28, -71, -96]

diva在学习

一个英文一个字节,一个汉字3个字节

对于字节流会未读完整个汉字编码的情况,我们可以使用字符流

字符流一次读一个字符,但是只能处理纯文本文件

FileReader

文件输入流: 本地-->程序

步骤

  • 创建字符流输入对象

  • 读取数据

  • 释放资源

//1. 创建对象
        FileReader fr = new FileReader("src\\IO\\CharInStream\\a.txt");
        //2.1 调用read()方法读取数据
//        int ch = 0;
//        while ((ch = fr.read()) != -1) {
//            System.out.print((char) ch);
//        }
        //2.2 调用read(char[])方法读取数据
        char[] chars = new char[7];
        int len = 0;
        while ((len = fr.read(chars)) != -1) {
            System.out.print(new String(chars,0,len));//解决缓存区覆盖问题
        }
        //3. 释放资源
        fr.close();

  • FileReader("输出路径"/File对象)

    从本地(外存)读取文件时,程序先关联文件,并自动在内存建立缓冲区(8192B大小数组)

  • read

    成员方法说明
    public int read()读入数据,返回解码后10进制值,文件末尾返回-1
    public int read(char[] buffer)读入多个数据,返回一次读的数据个数,文件末尾返回-1。数据解码10进制再强转字符后放到buffer里

底层原理

int ch = 0;
System.out.print((char) ch);

变量ch从缓冲区读,以便减少内存对外存的访问次数

  • 缓冲区有数据

    ch从缓冲区读

  • 缓冲区没有数据

    外存文件-->缓冲区(尽可能装满)-->ch

    字节流:外存文件-->程序

    中间无缓冲区

FileWriter

文件输出流: 程序-->本地

步骤

  • 创建字符流输出对象

  • 写数据

  • 释放资源

//1. 创建字符输出流对象FileWriter
FileWriter fw = new FileWriter("src\\IO\\CharOutStream\\b.txt",true);//追加写
//2.  调用write()方法写数据
fw.write(97);       //a
fw.write("hello"); //hello
fw.write(new char[]{'a','b','c'});//abc只是在缓冲区
//3. 释放资源
fw.close();//abc被写到本地文件
  • FileWriter("输出路径"/File对象,[追加写?])

  • write

    成员方法说明
    void write(int c)写出整数c在字符集上对应字符
    void write(String str)写出字符串str
    void write(String str, int sta, int len)写出字符串str从sta开始长度为len字符串
    void write(char[] cbuf)写出一字符数组
    void write(char[] cbuf, int sta, int len)写出字符数组cbuf从sta开始长度为len字符串

底层原理

内存程序-->内存缓冲区(8192字节数组)-->外存文件

缓冲区-->外存条件

  • 缓冲区装满了(8192字节)

  • 手动刷新(flush)

  • 关流(close)

flush和close区别是:

flush把缓冲区数据刷新到本地文件,通道还在,仍可以继续写出数据

close之后,通道断开,无法继续写出数据

方法说明
public void flush()将缓冲区中数据刷新到本地文件
public void close()释放资源

案例

文件夹拷贝

public static void main(String[] args) throws IOException {
    File from=new File("src\\IO\\example\\aaa");
    File to=new File("src\\IO\\example\\bbb");
    copyDir(from,to);
}
//copyDir(源文件,目标文件)
public static void copyDir(File from, File to) throws IOException {
​
    //如果是文件夹,递归创建文件夹
    if(from.isDirectory()) {
        //创建建目标文件夹
        to.mkdir();
        //获取源文件夹下的所有文件或文件夹并遍历
        File[] files = from.listFiles();
        for (File file : files) {
            copyDir(file, new File(to, file.getName()));
        }
    }
    //如果是文件,直接复制文件
    else{
        //创建输入输出流
        FileInputStream fis=new FileInputStream(from);
        FileOutputStream fos=new FileOutputStream(to);
        byte [] bytes=new byte[3];
        int len;  //解决缓冲区覆盖
        //调用read()方法读取数据,写入文件中
        while((len=fis.read(bytes))!=-1){
            fos.write(bytes,0,len);
        }
        //释放资源
        fos.close();
        fis.close();
    }
​
}

结果

注意

  • 文件夹拷贝前要用to.mkdir()创建对应目标文件夹

  • 若文件全是文本文件,可以用FileReaderFileWriter

  • 若有音频/视频等格式文件,可以用二进制传输FileInputStreamFileOutputStream

文件加密

加密原理

^:异或符.

例如2^10

0010^1010对应位上同假(0)异真(1). 结果为1000.在转10进制为8

接着8^10

1000^1010.结果又变回2(0010)

public static void main(String[] args) throws IOException {
    File f=new File("src\\IO\\example\\aaa\\a2.txt");
    File ef=new File("src\\IO\\example\\aaa\\a3.txt");
    File df=new File("src\\IO\\example\\aaa\\a4.txt");
    //加密
    EDcryption(f,ef);
    //解密
    EDcryption(ef,df);
}
​
//文件加解密(源文件,加解密文件)
private static void EDcryption(File f,File ef) throws IOException {
    //创建二进制对象输入流,输出流
    FileInputStream fis=new FileInputStream(f);
    FileOutputStream fos=new FileOutputStream(ef);
    //循环读取文件内容,并加密写入文件
    int b;
    while((b=fis.read())!=-1){
        fos.write(b^2);
    }
    //释放资源
    fis.close();
    fos.close();
}

你好 我是a2 你好 我是a2 <===> 濢秿"䊓䚭c0濢秿"䊓䚭c0濢秿"䊓䚭c0 你好 我是a2

修改文件数据

把文件内容(6-8-2-9-4)按顺序排序

public static void main(String[] args) throws IOException {
    FileInputStream fis=new FileInputStream("src\\IO\\example\\aaa\\a1\\a11");
    FileOutputStream fos=new FileOutputStream("src\\IO\\example\\aaa\\a1\\a12");
    int b;
    StringBuffer sb = new StringBuffer();   //stringbuffer是一个可变的字符序列,用于存储字符串。
    while ((b=fis.read())!=-1){
        sb.append((char)b);
    }
    ArrayList<Integer> list=new ArrayList<>();    //arraylist是一个可变的数组,用于存储对象。
    String[] s=sb.toString().split("-");
    for(String t:s){
        list.add(Integer.parseInt(t));    //Integer.parseInt:字符串-->int
    }
    //Collections.sort对list排序
    Collections.sort(list);
    for(Integer i:list){
        fos.write(i.toString().getBytes());    //getBytes():字符串-->字节数组
        fos.write("-".getBytes());
    }
    fis.close();
    fos.close();
}
​

*缓冲流

把基本流封装成高级流,提高数据性能

在内存中增加缓冲区(字节缓冲流默认8192B,字符缓冲流默认8192char),提高传输效率.

(字符流本来就有缓冲区,提高效果不明显)

方法
public BufferedInputStream(InputStream is)对字节输入流封装
public BufferedOutputStream(OutputStream os)对字节输出流封装
public BufferedReader(Reader r)对字符输入流封装
public BufferedWriter(Writer r)对字符输出流封装

BufferedInputStream

字节缓冲流

BufferedInputStream(BufferedOutputStream类似)

关闭字节输入缓冲流close方法内部自己关闭基本流

类似于FileInputStream也有public int read()public int read(byte[] buffer)

  • 案例:用缓冲流实现文件拷贝

public static void main(String[] args) throws IOException {
    //创建缓冲流
    BufferedInputStream bis=new BufferedInputStream(new FileInputStream("src\\IO\\BufferStream\\a.txt"));
    BufferedOutputStream bos=new BufferedOutputStream(new FileOutputStream("src\\IO\\BufferStream\\b.txt"));
    //读写数据
    int b;
    while ((b=bis.read())!=-1){
        bos.write(b);
    }
    //释放资源
    bos.close();
    bis.close();
}

也可以用带参read,一次读写多个字节

//一次读写多个数据
byte[] bytes=new byte[1024];
int len;
while ((len=bis.read(bytes))!=-1){
    bos.write(bytes,0,len);
}
  • 原理

BufferedReader

BufferedReader BufferedWriter,由于字符缓冲流本来就有缓冲区,所以加速效果不明显.

但是他们拥有不同于FileReader和FileWriter的特有方法

  • public String readLine()

    字符缓冲输入流特有方法

    一次读入一行数据,遇到回车换行结束.但不会把回车换行读入内存

    如果没有数据可读就返回null

    //创建缓冲区
    BufferedReader br = new BufferedReader(new FileReader("src/IO/BufferStream/a.txt"));
    String line = new String();
    while ((line=br.readLine())!=null){
        System.out.println(line);     //默认没有回车换行
    }
    //释放资源
    br.close();

  • public void newLine()

    字符缓冲输出流特有方法

    跨平台换行

    无论是Windows(\r\n), Linux(\n)还是mac(\r)都实现换行

    //创建对象
    BufferedWriter bw=new BufferedWriter(new FileWriter("src/IO/BufferStream/c.txt"));
    //写出数据
    bw.write("hello");
    bw.newLine();       //无论是什么系统都可以换行
    bw.write("world");
    //释放资源
    bw.close();

*转换流

字符流和字节流之间桥梁

InputStreamReader

继承抽象方法Reader.

对字节流封装,使数据不会乱码

  • 参数

    • InputStreamReader(FileInputStream(...))

    • InputStreamReader(FileInputStream(...),"编码方式")淘汰

    FileReader在jdk11后增加

    public FileReader(File file, Charset charset) throws IOException {
        super(new FileInputStream(file), charset);
    }

    参数"Charset"指定编码方式.Charset.forname("utf-8")

    取代InputStreamReader(FileInputStream(...),"编码方式")

  • 读入utf-8编码的数据,写出gbk编码的数据

//创建对象
InputStreamReader isr = new InputStreamReader(new FileInputStream("src/IO/ConvertStream/a.txt"),"utf-8");
OutputStreamWriter  osw = new OutputStreamWriter(new FileOutputStream("src/IO/ConvertStream/b.txt"),"gbk");
//写出出数据
int len;
while ((len=isr.read())!=-1){
    osw.write(len);
}
//释放资源
isr.close();
osw.close();

替代方法

FileReader isr = new FileReader("src/IO/ConvertStream/a.txt", Charset.forName("utf-8"));
FileWriter osw = new FileWriter("src/IO/ConvertStream/b.txt", Charset.forName("gbk"));
//写出出数据
int len;
while ((len=isr.read())!=-1){
    osw.write(len);
}
//释放资源
isr.close();
osw.close();
  • 用字节流读文件,每次读一行,不出现乱码

//创建对象
FileInputStream fis = new FileInputStream("src/IO/ConvertStream/a.txt");
InputStreamReader isr = new InputStreamReader(fis);
BufferedReader br = new BufferedReader(isr);
​
FileOutputStream fos = new FileOutputStream("src/IO/ConvertStream/c.txt");
OutputStreamWriter osw = new OutputStreamWriter(fos);
BufferedWriter bw = new BufferedWriter(osw);
​
String line;
while ((line=br.readLine())!=null){
    bw.write(line);
    bw.newLine();
}
//释放资源
bw.close();
br.close();

*序列化流

对基本流封装,把java对象写入本地文件

方法说明
public ObjectOutputStream(OutputStream out)对基本流封装
public ObjectInputStream(InputStream in)对基本流封装

ObjectOutputStream

序列化流/对象操作输出流.

将实现Serializable接口的对象写入到本地文件(直接看文件看不懂, 反序列化后才能看懂 )

成员方法

  • public final void writeObject(Object obj)

    把对象序列化到文件中

    //创建Student对象
    Student s=new Student("diva",20);
    //创建ObjectOutputStream对象
    ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("src/IO/ObjectStream/b.txt"));
    //写入对象
    oos.writeObject(s);
    //释放资源
    oos.close();

    对象要实现Serializable接口.

    Serializable接口中没有方法,是标记型接口

    表示该类可被序列化

    写入文件结果为

ObjectInputStream

反序列化流/对象操作输入流

把序列化的文件从本地读入程序

成员方法

  • public Object readObject()

    把已序列化本地的文件反序列化,读取到程序

    //创建ObjectInputStream对象
    ObjectInputStream ois=new ObjectInputStream(new FileInputStream("src/IO/ObjectStream/b.txt"));
    //读入
    System.out.println(ois.readObject());
    //释放资源
    ois.close();

版本号问题

当业务需求要更改JavaBean时,之前被序列化的文件就不能被反序列化成JavaBean对象了

  • 原因

    底层会给实现Serializable接口的JavaBean一个序列号/版本号

    使用序列化流写出对象时,也会将JavaBean版本号写入

    当JavaBean改变时,其版本号也改变

    但是文件中版本号不变. JavaBean版本号!=文件版本号

    因此文件反序列化会报错

  • 解决方法

    • 在JavaBean中固定版本号serialVersionUID

      public class Student implements Serializable {
          private static final long serialVersionUID=1L;
          String name;
          int age;
          ...
      }

    • 在setting中设置

    在JavaBean类名上alt+enter

  • 自动生成

    @Serial
    private static final long serialVersionUID = -4946731678200075446L;

保密属性问题

在序列化对象时可能会有些对象是保密的,不希望被序列化到本地文件

此时可以在保密属性前加上transient修饰

  • transient

    瞬态关键字

    作用就是不会把当前属性序列化到本地文件

*打印流

PrintStream PrintWriter

只能操作文件目的地,不能操作数据源(只能写,不能读)

特有方法可以实现数据写出和自动刷新换行

PrintStream

字节打印流

构造方法说明
public PrintStream(OutputStream/File/String)关联字节输出流/文件/文件路径
public PrintStream(String fileName,Charset charset)指定字符编码方式
public PrintStream(OutputStream out,boolean autoFlush)自动刷新否
public PrintStream(OutputStream out,boolean autoFlush,String encoding)指定字符编码方式+自动刷新否
成员方法说明
public void write(int b)规则同上
public void print(...)支持任意数据原样写出,不换行
public void println(...)支持任意数据原样写出,自动刷新换行
public void printf(String 含有占位符字符串,Object 对应位置%占位符值)带有占位符的打印语句,不换行
//创建对象
PrintStream ps=new PrintStream(new FileOutputStream("src/IO/PrintStream/a.txt"),true,"utf-8");
//写出
ps.println(97);     //写出97而不是a
ps.print("哈哈哈,");
ps.printf("%d是%s的幸运数字",10,"diva");
//释放资源
ps.close();

PrintWriter

字符打印流

底层有缓冲区,想要刷新需要开启

构造方法说明
public PrintWriter(Writer/File/String)关联字符输出流/文件/文件路径
public PrintWriter(String fileName,Charset charset)指定字符编码方式
public PrintWriter(Write w,boolean autoFlush)自动刷新否
public PrintWriter(Writer w,boolean autoFlush,Charset charset)指定字符编码方式+自动刷新否

成员方法同PrintStream

*标准输出流

System.out

虚拟机启动时自动创建,默认指向控制台

不能关闭,在系统中是唯一的

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值