了解IO流

IO流

I:input O:output 流:像水流一样传输数据

用于读写数据的(可以读写文件,或网络中的数据…),存储和读写数据的解决方案。

比如,对游戏历史最高分进行记录,那么就需要用到IO流在硬盘中进行读写。

IO流的分类

按流的方向分为:输入流(读取),输出流(写出)。

按操作文件的类型:字节流(所有类型的文件),字符流(纯文本文件)。

纯文本文件:Windows自带的记事本打开能读懂的,是指只包含字符信息(如字母、数字、标点符号等),而不包含格式化信息(如字体、字号、颜色、段落格式等)的文件。

比如:.md文件、.txt记事本文件。

IO流的体系结构

了解IO流_第1张图片
但因为底下的都是抽象类,没办法直接创建他们的对象,所以要看它们的子类,子类该怎么取名,以InputStream为例,先把父类名拿出来,我想从本地文件里读取数据,文件名为File,两则一结合,FileInputStream操作本地文件的字节输入流,第一个单词File表示它的作用,第二个单词InputStream表示它的继承结构。

FileOutputStream

操作本地文件的字节输出流,可以把程序中的数据写到本地文件中,是字节流的基本流。

书写步骤:

  1. 创建字节输出流对象(创建通道)

细节1:参数是字符串表示的路径或者是File对象都是可以的
细节2:如果文件不存在会创建一个新的文件,但是要保证父级路径是存在的。
细节3:如果文件已经存在,则会清空文件

//1.创建对象
FileOutputStream fos = new FileOutputStream("D:\\java\\basic-code\\myio\\src\\a.txt");
  1. 写数据(输入数据)

细节:write方法的参数是整数,但是实际上写到本地文件中的是整数在ASCII上对应的字符。

//2.输入数据
fos.write(97);
  1. 释放资源(砸掉通道)

每次使用完流之后都要释放资源,释放资源是为了解决资源的占用。

//3,释放资源
fos.close();

FileOutputStream写数据的3种方式

void write(int b)						一次写一个字节数据
void write(byte[] b)					一次写一个字节数组数据
void write(byte[] b, int off, int len)	一次写一个字节数组的部分数据

第一个方法:

fos.write(97);

第二个方法:

byte[] bytes = {97,98,99,100,101};
fos.write(bytes);//abcde

第三个方法:

参数一:数组

参数二:起始索引

参数三:长度

fos.write(bytes,1,2)//b c

FileOutputStream写数据的两个小问题

换行写

示例:

//2.输入数据
String str = "hahaxixi";
byte[] byte1 = str.getBytes();
fos.write(byte1);
//再次写出一个换行符就可以了
String wrap = "\r\n";
byte[] byte3 = wrap.getBytes();
fos.write(byte3);
//输入数据
String str2 = "111";
byte[] byte2 = str2.getBytes();
fos.write(byte2);
//3,释放资源
fos.close();
//hahaxixi
//111

换行符:

Windows:\r\n

Linux:\n

Mac:\r

细节:在windows操作系统当中,java对回车换行进行了优化。虽然完整的是\backslash r \backslash n ,但是我们写其中一个\r或者\n.java也可以实现换行,因为java在底层会补全。建议:不要省略,还是写全了。

续写

如果想要续写,打开续写开关即可
开关位置:创建对象的第二个参数
默认false:表示关闭续写,此时创建对象会清空文件
手动传递true:表示打开续写,此时创建对象不会清空文件

FileInputStream

操作本地文件的字节输入流,可以把本地文件中的数据读取到程序中来。

书写步骤:

  1. 创建字节流输入对象

细节:如果文件不存在,则直接报错。

FileInputStream fis = new FileInputStream("D:\\java\\basic-code\\myio\\src\\a.txt");
  1. 读取数据

输出的结果为字母a的ASCll码值,如果想输出为字符,则强转类型。

细节1: 一次读一个字节,读出来的是数据在ASCII上对应的数字
细节2:读到文件末尾了,read方法返回-1。

int b1 = fis.read();
System.out.println(b1);//97
  1. 释放数据

细节:每次使用完流之后都要释放资源

fis.close();

文件拷贝

核心思想:边拷边写

while((b = fis.read()) != -1){
	fos.write(b);
}

但是FileInputStream一次读写一个字节(速度慢)

怎样读取多个字节方法

public int read(byte[] buffer);

注:一次读一个字节数组的数据,每次读取会尽可能把数组装满

1024的整数倍

try…catch…finally异常处理->捕获异常

没有这个文件,读取不到数据就是空指针异常

接口:AutoCloseable->特定的情况下,可以自动释放资源

jdk9方案

创建流对象1;
创建流对象2;
try(1;2){
	可能出现的异常代码;
}catch(异常类名 变量名){
	异常的处理代码;
}

字符流->会出现乱码

ASCll字符集

计算机的存储规则(英文)

a -> 110 0001(97) -> (编码)0110 0001二进制

ASCll编码规则,前面补0,补齐8位。

Windows系统默认使用的就是GBK

字符集

Unicode:万国码(包含大多数国家)

UTF-8编码规则:用1-4个字节保存

0xxxxxxx(英文)

110xxxxx 10xxxxxx

1110xxxx 10xxxxxx 10xxxxxx(汉字)

那么UTF-8是一个字符集吗?

UTF-8是Unicode字符集的一种编码方式

为什么会乱码?

字符流:一次只能读取一个字节

如何不产生乱码?

  1. 不要用字符流来读取文本文件
  2. 编码解码时使用同一个码表,同一个编码方式

为什么拷贝不会出现乱码?

拷贝 字符源没有丢失,记事本在读取的时候,要用到的数据集跟编码方式跟数据源保持一致,这样就不会报错。

Java中编码的构造方法

public byte[] getBytes();//默认
public byte[] getBytes(String charsetName);//指定

Java中编码的成员方法

String(byte[] bytes);
String(byte[] bytes,String charsetName);

字符流

字符流的底层就是字节流

字符流=字节流+字符集

特点:

输入流:一次读一个字节,遇到中文时,一次读多个字节

输出流:底层会把数据指定的编码方式进行编码,变成字节再写到文件中。

字符流原理解析

  1. 创建字符输入流对象

底层:关联文件,并创建缓冲区(长度为8192的字节数组)

  1. 读取数据

底层:

  1. 判断缓冲区中是否有数据可以读取

  2. 缓冲区没有数据,就从文件中获取数据装到缓冲区中,每次尽可能装满缓冲区,如果文件中也没有数据,返回-1.

  3. 缓冲区有数据,就从缓冲区中读取

    空参的read方法:一次读取一个字节,遇到中文一次读取多个字节,把字节解码转成二进制返回。

    有参的read方法:把读码,解码,强转合并,强转之后的字符放到数组中

bb:缓冲区

若超出8192个内容,剩余的内容会再次进入缓冲区,(没有理解清楚,继续补充)

什么情况下会自动保存到目的地

情况一:装满了

情况二:手动刷新(flash)

情况三:close

flush和close方法

public void flush();//将缓冲区中的数据,刷新到本地
public void close();释放资源/关流

若要修改文件中的顺序

解法一:

BufferedReader逐行读取文件内容,提供readLinst()方法,用collections.sort排序

解法二:

用TreeMap集合存储,他有自动排序功能,写出就用Entry遍历就可以。

拷贝方式(较快的方式)

字节流的基本流:一次读取一个字节数组

//创建对象
FileInputStream fis = new FileInputStream("D:\\图集\\OIP-C (1).jpg");
FileOutputStream fos = new FileOutputStream("D:\\java\\basic-code\\myio\\src\\视频.txt");
//边读边写
int len;
byte[] bytes = new byte[1024*1024*5];
while((len = fis.read(bytes)) != -1){
    fos.write(bytes,0,len);
}
//释放资源
fos.close();
fis.close();

软件运行次序

实现一个验证程序运行次数的程序

当程序运行超过3次时给出提示”本软件只能免费使用3次,欢迎您注册会员后使用“,一开始会想到在程序中添加计数器,但它只存在内存中,程序重启后回归0,所以必须把它存在文件中。

用BufferedReader读取一行

  1. 把文件的数字读取内存中
  2. 判断
  3. 把当前自增之后的count写到文件当中

转换流(属于字符流)

字符转换输入流:InputStreamReader

将字节流转换为字符流存入内存

字符转换输出流:OutputStreamWriter

将字符流转换为字节流存入目的地

字符流和字节流之间的桥梁

作用1:指定字符集读写数据(jdk11之后淘汰了)

原来的方式:利用转换流按照指定字符编码读取(了解)

//1.创建对象并指定字符编码
InputStreamReader isr = new InputStreamReader(new FileInputStream("myio\\gbkfile.txt"),"GBK");
//2.读写数据
int ch;
while((ch = isr.read()) != -1){
	System.out.println((char)ch);
}

jdk11之后FileReader添加了一个新的构造,可以指定字符编码

FileReader fr = new FileReader("myio\\b.txt", Charset.forName("gbk"));
int ch;
while((ch = fr.read()) != -1){
    System.out.print((char)ch);
}
fr.close();

利用转换流按照指定字符编码写出(了解)

OutputStreamWriter osw = new OutputStreamWriter(new fileoutputStream("myio\\b.txt"),"GBk");
osw.write("你好你好");
osw.close();

替代方案

FileWriter fw = new FileWriter("myio\\b.txt",Charset.forName("GBK"));
fw.writer("你好你好");
fw.close();

练习:将本地文件中的GBK文件,转成UTF-8

jdk11以前的版本

InputStreamReader isr = new InputStreamReader(new FileInputStream("myio\\b.txt"),"GBK");
OutputStreamWriter osw = new OutputStreamWriter(new fileoutputStream("myio\\b.txt"),"UTF-8");
int b;
while((b = isr.read()) != -1){
	osw.write(b);
}
osw.close();
isr.close();

替代版本

FileReader fr = new FileReader("myio\\b.txt", Charset.forName("gbk"));
FileWriter fw = new
FileWriter("myio\\b.txt",Charset.forName("UTF-8"));
int b;
while((b = fr.read()) != -1){
	fw.write(b);
}
fw.close();
fr.close();

作用2:字节流想要使用字符流中的方法(如果字节流里面想要使用字符流的方法,就可以转一下)

练习:利用字符串读取文件中的数据,每次读一整行,而且不会出现乱码

BufferedReader br = new BufferedReader(new InputStreeamReader(new FileInputStream("myio\\b.txt")));
String line;
while((line = br.readLine()) != -1){
	System.out.println(line);
}
br.close;

序列化流(属于字节流)

序列化流:ObjectOutputStream

序列化流

可以把Java中的对象写到本地文件中,所以我们要先创建对象,再将对象通过序列化流写到文件中,写到文件中的对象,我马上看不懂的,所以需要通过反序列化流读取。

应用场景:

在游戏中,每个角色都有各自的属性值,如果只是单纯的将它们写到文件中,很容易被其他人修改,为了避免这一操作,我们就要用到序列化流将其变成看不懂的文本,这样别人就无法修改角色的属性。

序列化流又称对象操作输出流

构造方法:

关联基本流,将基本流包装成高级流

public ObjectOutputStream(OutputStream out);

成员方法:

把对象序列化写到文件当中去

public final void writeObject(object obj);

代码展示

public static void main(String[] args) throws IOException {
        //序列化流
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("myio\\src\\c.txt"));
        oos.writeObject(new Student("张三",23,"男"));
        oos.close();
    }

直接写出,会出现NotSerializableException异常

解决方案:需要让JavaBean类实现Serializable接口,实现完了之后不需要重写它的抽象类,查看源码,发现它里面没有抽象方法,所以我们可以认为它是标记型接口,一旦实现了这个接口,那么就表示当前的Student类可以被序列化。

反序列化流

反序列化流:ObjectInputStream

可以把序列化到本地文件中的对象,读取到程序中来

构造方法

public ObjectInputStream(InputStream out);//把基本流转换为高级流

成员方法

public Object  readObject();//把序列化到文件中的对象,读取到程序中

代码展示

public static void main(String[] args) throws IOException, ClassNotFoundException {
        //反序列化流
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("myio\\src\\c.txt"));
        Student o = (Student)ois.readObject();
        System.out.println(o);
        ois.close();
    }

若修改javaBean类里面的属性,代码会报错,是因为java会根据类里面的内容计算出一个long类型的序列号,就相当于版本号,它会与对象一起写到本地文件中,如果对JavaBean类对象进行修改,版本号也会发生变化,文件中的版本号就与它不匹配,就会报错。所以为了不让版本号发生变化,我们可以定义一个版本号。

private static final long serialVersionUID = -6357601841666449654L;

细节汇总:

  1. 使用序列化流将对象写到文件时,需要让Javabean类实现Serializable接口。否则,会出现NotSerializableException异常
  2. 序列化流写到文件中的数据是不能修改的,一旦修改就无法再次读回来了
  3. 序列化对象后,修改了Javabean类,再次反序列化,会不会有问题?会出问题,会抛出InvalidClassException异常解决方案:给Javabean类添加serialVersionUID(序列号、版本号)
  4. 如果一个对象中的某个成员变量的值不想被序列化,又该如何实现呢?
    解决方案:给该成员变量加transient关键字修饰,该关键字标记的成员变量不参与序列化过程

打印流

分类:打印流一般指PrintStream,PrintWrite两个类。

特点一:打印流只操作文件目的地,不操作数据源

特点二:特有的写出方法可以实现,数据原样写出

特点三:特有的写出方法,可以实现自动刷新,自动换行

PrintStream(字节打印流)

无缓冲区,开不开自动刷新都一样。

构造方法:

public PrintStream(OutputStream/File/String);//关联字节输出流
public PrintStream(String fileName, Charset charset);//指定字符编码

成员方法

public void write(int b);
public void println(xxx,xxx);
public void print(xxx,xxx);

PrintWriter(字符打印流)

字符流底层有缓冲区,想要就可以自动打开。

构造方法

public PrintWriter(write/File/String);//关联字节输出流/文件/文件路径
public PrintWriter(String fileName,Charset charset);//指定字符编码
public PrintWriter(Writer w,boolean autoFlush);//自动刷新(字符流底层有缓冲区,想要自动刷新就必须打开)
public PrintWriter(OutputStream out, boolean autoFlush, Charset charset);// 指定字符编码且自动刷新

特有方法

public void println(Xxx xx);//打印任意数据,自动刷新,自动换行
public void print(Xxx xx);//打印任意数据,不换行
public void printf(String format, Object…args);//带有占位符的打印语句,不换行

应用场景

获取打印流的对象,此打印流在虚拟机启动的时候,由虚拟机创建,默认指向控制台。

特殊的打印流

系统的标准输出流,是不能关闭的,在系统中是唯一的。

PrintStream ps = System.out;
//调用打印流中的方法println
ps.println("123");

ZipInputStream(解压缩流)

是专门用于读取ZIP格式压缩文件的输入流。它允许逐个读取ZIP文件中的条目(ZipEntry),并解压每个条目到指定位置。

构造方法:

ZipInputStream(InputStream in);//创建一个新的ZIP输入流
ZipInputStream(InputStream in,Charset charset);//创建一个新的ZIP输入流
  • 构造函数需要一个InputStream,通常是FileInputStream
  • 使用getNextEntry()方法逐个读取ZIP文件中的条目(ZipEntry)。
  • 每个ZipEntry可以是文件或目录。
  • 如果是文件,使用read()方法读取内容并写入到目标文件。
  • 如果是目录,直接创建目录结构。
  • 使用closeEntry()关闭当前条目,继续读取下一个条目。

ZipEntry

  • 表示ZIP文件中的一个条目(文件或目录)。
  • 可以通过getName()获取条目的名称(路径)。
  • 通过isDirectory()判断是否为目录。

代码演示:

import java.io.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

public class ZipStreamDemo1 {
    public static void main(String[] args) throws IOException {

        //1.创建一个File表示要解压的的压缩包
        File src = new File("D:\\aaa.zip");
        //2.创建一个File表示解压的目的地
        File dest = new File("D:\\");

        //调用方法
        unzip(src,dest);
    }

    private static void unzip(File src, File dest) throws IOException {
        //解压的本职:把压缩包里面的每一个文件或者文件夹读取出来,按照层级拷贝到目的地当中

        //创建一个解压缩流用来读取压缩包中的数据
        ZipInputStream zip = new ZipInputStream(new FileInputStream(src));

        //表示当前在压缩包中获取到的文件或者文件夹
        ZipEntry entry;
        while((entry = zip.getNextEntry()) != null){
            System.out.println(entry);
            if(entry.isDirectory()){
                //文件夹:需要目的地dest处创建一个同样的文件夹
                File file = new File(dest, entry.toString());
                file.mkdirs();
            }else{
                //文件:需要读取压缩包中的文件,并把他存放在目的地dest文件夹中
                FileOutputStream fos = new FileOutputStream(new File(dest, entry.toString()));
                int b;
                while((b = zip.read()) != -1){
                    fos.write(b);
                }
                fos.close();
                //表示在压缩包中的一个文件处理完毕了
                zip.closeEntry();
            }
        }
        zip.close();
    }
}

ZipOutputStream(压缩流)

是 Java 中用于创建 ZIP 文件的输出流。它允许你将多个文件或目录打包成一个 ZIP 文件,并支持压缩和设置文件属性等功能。

构造方法:

ZipOutputStream(OutputStream out);//创建一个新的ZIP输出流
ZipOutputStream(OutputStream out, Charset charset);//创建一个新的ZIP输出流
  • 构造函数需要一个 OutputStream,通常是 FileOutputStream
  • 使用 putNextEntry(ZipEntry entry) 方法添加一个新的条目(文件或目录)。
  • 使用 write(byte[] b, int off, int len) 方法写入条目的内容。
  • 使用 closeEntry() 方法完成当前条目的写入。
  • 使用 close() 方法关闭 ZipOutputStream,完成 ZIP 文件的创建。

代码演示(将单个文件打包成一个压缩包):

import java.io.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

public class ZipStreamDemo2 {
    public static void main(String[] args) throws IOException {
        //将aaa.txt文件打包成一个压缩包
        //1.创建File对象表示要压缩的文件
        File src = new File("D:\\b.txt");
        //2.创建File对象表示压缩包的位置
        File dest = new File("D:\\");
        //3调用方法
        toZip(src,dest);
    }

    private static void toZip(File src, File dest) throws IOException {
        //1.创建压缩流关联压缩包
        ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(new File(dest,"a.zip")));
        //2.创建ZipEntry对象,表示压缩包里面的每一个文件和文件夹
        ZipEntry entry = new ZipEntry("b.txt");
        //3.把ZipEntry对象放在压缩包中
        zos.putNextEntry(entry);
        //4.把src文件中的数据写到压缩包中
        FileInputStream fis = new FileInputStream(src);
        int b;
        while ((b = fis.read()) != -1) {
            zos.write(b);
        }
        zos.closeEntry();
        zos.close();
    }
}

你可能感兴趣的:(了解IO流)