Java---IO复习

1、IO流分类

  1. 按流的方向分为:输入流和输出流
  2. 按流的数据单位不同分为:字节流和字符流
  3. 按流的功能不同分为:节点流和处理流

字节流和字符流的区别

  • 读写单位的不同:字节流以字节(8bit)为单位。字符流以字符(16bit)为单位,根据码表映射字符,一次可能读多个字节。

  • · 处理对象不同:字节流可以处理任何类型的数据,如图片、avi等,而字符流只能处理字符类型的数据。PS:用字节流处理字符时,要注意字符的编码

节点流和处理流

节点流是流最基本的实现,处理流是用来处理节点流,使它具有某种特性,比如加快读取速度的缓存流。
形象的来说,黑色的是节点流,米色的是处理流
在这里插入图片描述

2、抽象基类及IO框架的结构

输入流输出流
字符流ReaderWriter
字节流InputStreamOutputStream

所有其他流都基于这四个基类
在这里插入图片描述

3、IO框架部分了解

首先来了解一下节点流—文件流,用来读取文件

  • FileOutputStream
  • FileInputStream
  • FileReader
  • FileWriter

1、节点流

1、文件流

1、FileReader

构造方法
    public FileReader(String fileName) throws FileNotFoundException 
    public FileReader(File file) throws FileNotFoundException 
    public FileReader(FileDescriptor fd)
    public FileReader(String fileName, Charset charset) throws IOException
    public FileReader(File file, Charset charset) throws IOException 

构造方法比较简单,这里不讲解

读取方法
    public int read() throws IOException 
    public int read(char[] cbuf) throws IOException 
    public int read(char cbuf[], int offset, int length) throws IOException 
  • read():读取一个字符,并以int值返回。如果流读取完毕,就返回-1
  • read(char[] cbuf):将字符读入一个char数组,返回读取字符的数量。如果流读取完毕,就返回-1
  • read(char cbuf[], int offset, int length):将字符读入char数组,offset表示字符存入的起始位置,length表示一次读取多少个字符。返回读取字符的数量。如果流读取完毕,就返回-1。

注意:
1、read(char cbuf[], int offset, int length): length长度不对,会抛出异常
2、Reader读取char[ ] 时,会覆盖上一次读取的字符,而不是将数组重置,再读取,这会可能导致输出时,多输出了上一次读取的字符

File: abcde
FileReader fr = new FileReader(file);
char[] c = new char[3];
fr.read(c);//c = {a,b,c}
fr.read(c);//c = {d,e,c}

接下来写一个文件输入:

 public static void main(String[] args) throws IOException {
        FileReader reader = null;
        try {
             File file = new File("test.txt");
             reader = new FileReader(file);
            int data;
            char[] chars = new char[3];
            while ((data = reader.read(chars,0,4)) != -1){
                for (int i = 0; i < data; i++) {
                    System.out.println(chars[i]);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (reader !=null){
                try{
                    reader.close();
                }catch (IOException e){
                    e.printStackTrace();
                }
            }
        }
    }

分析:
1、reader = new FileReader(file);如果文件不存在,会抛出异常
data = reader.read(chars,0,4)读取文件时,可能会抛出异常
2、为了保证流一定会被关闭,将reader.close();放入finally块中


2、FileWriter

构造函数
    public FileWriter(String fileName) throws IOException 
    public FileWriter(File file) throws IOException 
    public FileWriter(File file, boolean append) throws IOException 
    public FileWriter(String fileName, Charset charset) throws IOException 
    public FileWriter(String fileName, Charset charset, boolean append) throws IOException 
    public FileWriter(File file, Charset charset) throws IOException 
    public FileWriter(File file, Charset charset, boolean append) throws IOException 

注意:

  1. 当文件不存在时, FileWriter(File file)会自动创建一个文件
  2. FileWriter执行写入时,默认覆盖文件内容,重新写入(append = false)。构造FileWriter时,使append = true,则不覆盖文件的内容,而是追加
读取方法
    public void write(int c) throws IOException  
    public void write(char cbuf[]) throws IOException
    public void write(char cbuf[], int off, int len) throws IOException;
    public void write(String str, int off, int len) throws IOException

public void write(int c) throws IOException :c指定字符的字符值(eg:a 97)


介绍了上面两种流,剩下的流就简单了,就不一一阐述了。

3、 FileInputStream

    public int read() throws IOException 
    public int read(byte b[]) throws IOException 
    public int read(byte b[], int off, int len) throws IOException 

byte[] 存储的是文件中的字符对应的编码值,比如在utf-16编码格式下,‘A’为0X0041(16位长) ,在utf-8下为0X41(8位长)

4、FileOutputStream

    public void write(int b) throws IOException 
    public void write(byte b[]) throws IOException
    public void write(byte b[], int off, int len) throws IOException

2、处理流

1、缓冲流,加快读写操作

缓冲流多了一个缓冲区,将字节或字符先写入缓冲区,当到达缓冲区最大大小时,输出缓冲区的内容。

        BufferedWriter writer = new BufferedWriter(new FileWriter(file));
        BufferedReader reader = new BufferedReader(new FileReader(file));
        BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(file));
        BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(file));

需要注意的方法:

  1. 缓冲输出流的public synchronized void flush() throws IOException,手动刷新缓冲区
  2. BufferedReader的 public String readLine() throws IOException,一次读取一行,以换行符为一行的结束标志,当达到EOF(end of file)时,返回null。注意:这个方法不会读取换行符
  3. BufferedWriter 的public void newLine() throws IOException ,输出一个换行符
PS
  1. 不同系统下的换行符不同,在 UNIX 中, 换行符为 \n,在Windows中,为 \r\n
  2. 只需关闭缓冲流,就能同时关闭节点流。

2、转换流----提供字节流与字符流的转换

  • InputStreamReader:将字节的输入流转换为字符的输入流 (解码)
  • OutputStreamWriter:将字符的输出流转换为字节的输出流 (编码)

InputStreamReader:

    public InputStreamReader(InputStream in)
    指明读取文件的编码集,建立一个使用此编码的流
    public InputStreamReader(InputStream in, String charsetName)throws UnsupportedEncodingException
    public InputStreamReader(InputStream in, Charset cs) 
    返回此编码的历史名称,如果流已关闭,则为 null
    public String getEncoding()

OutputStreamWriter:

    public OutputStreamWriter(OutputStream in)
    指明读取文件的编码集,建立一个使用此编码的流
    public OutputStreamWriter(OutputStream in, String charsetName)throws UnsupportedEncodingException
    public OutputStreamWriter(OutputStream in, Charset cs) 
    返回此编码的历史名称,如果流已关闭,则为 null
    public String getEncoding()
  1. 不管是那种转换流,如果创建流时没有指定字符集编码,就使用系统默认的字符集编码
  2. 转换流属于字符流

3、标准输入输出流

  • System.out:PrintStream,标准输出流,默认从屏幕输出
  • System.in:InputStream,标准输入流,默认从键盘输入
  • System.err:PrintStream

可以用System的静态方法,设置标准输出流

public static void setIn(InputStream in)
public static void setOut(PrintStream out)
public static void setErr(PrintStream err)

4、打印流

将基本数据类型转换为字符串输出

  1. PrintStream
  2. PrintWriter

PS

  1. 打印流有自动flush功能
  2. 存在一系列重载的printprintln方法
  3. PrintStream打印的字符使用默认的字符编码,将其转换为字节

5、数据流

用于读写基本数据类型变量或字符串

  • DataOutputStream
  • DataInputStream

使用数据流时,读取不同类型的数据要与当初写入文件的顺序一致

6、对象流

用于读取与存储基本数据类型与对象的处理流

  • ObjectInputStream
  • ObjectOutputStream

对象流的实现基于Java的序列化机制

  • 序列化:用ObjectOutputStream把基本数据类型或对象保存进数据源的机制;
  • 反序列化:用ObjectInputStream从数据源中读取基本数据类型或对象的机制;

序列化机制
1、可以把Java对象转换成二进制流,并持久化地保存在磁盘或者通过网络传输给另一个网络节点
2、当其他Java程序获取到这种二进制流时,可以将其恢复成Java对象

PS:对象流不能序列化statictransient修饰的成员变量

上文提到了序列化idserialVersionUID,那么这个id究竟是什么?

  • 这个id用来表明类的不同版本的兼容性,简单的来说,是用来验证是否能够成功反序列化的标识
  • 如果没有指定serialVersionUID,JVM就会根据类的结构自动生成一个serialVersionUID,所以类的结构(比如变量的个数)变化会引起自动生成的serialVersionUID变化。所以最好显式指定一个serialVersionUID
  • 在反序列化时,JVM会将传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常,即是InvalidCastException。

一个类可序列化的条件

  1. 实现必须实现Serializable接口,并且提供一个全局常量private static final long serialVersionUID
  2. 这个类中的所有成员变量必须可序列化

3、RandomAccessFile

1、RandomAccessFile简介
  • 在java.io包下,但直接继承了Object。实现了DataInputDataOutput接口,所以RandomAccessFile既可以输入也可以输出
  • 支持“随机访问”的方式,程序可以跳到文件的任意地方来读与写文件
    • 支持只访问文件的部分内容
    • 可以向已存在的文件追加内容
  • RandomAccessFile对象包含了一个记录指针,用以标记当前读写处的位置。

RandomAccessFile的方法虽然多,但它有一个最大的局限,就是只能读写文件,不能读写其他IO节点。RandomAccessFile的一个重要使用场景就是网络请求中的多线程下载及断点续传

2、RandomAccessFile方法
  • 构造方法
  • 重要方法

构造方法:

 public RandomAccessFile(String name, String mode) throws FileNotFoundException
 public RandomAccessFile(File file, String mode) throws FileNotFoundException

RandomAccessFile的构造方法中的mode参数,指定RandomAccessFile是读还是写,或是读写

mode模式

r : 以只读方式打开。调用结果对象的任何 write 方法都将导致抛出 IOException。
rw: 打开以便读取和写入。
rws: 打开以便读取和写入,对“文件的内容”或“元数据”的每个更新都同步写入到基础存储设备。
rwd : 打开以便读取和写入,对“文件的内容”的每个更新都同步写入到基础存储设备。

重要方法

设置与获取指针位置
 public void seek(long pos) throws IOException
 public native long getFilePointer() throws IOException
 
 返回文件的大小(字节为单位)
 public native long length() throws IOException
 
 为文件设置一个新的大小(字节为单位)
 public native void setLength(long newLength) throws IOException;

public native void setLength(long newLength) throws IOException详解

  • 当文件的length小于newLength,把文件大小设置为newLength。这种情况下,文件扩展部分的内容为空,是未定义的。
  • 当文件的length大于newLength,文件将被截断。在这种情况下,如果 getFilePointer 返回的文件偏移量大于 newLength,则在此方法返回后,偏移量将等于 newLength。

注意事项:

  • 如果写出的文件不存在,RandomAccessFile会自动创建一个文件
  • 如果写出的文件存在,RandomAccessFile会从文件头开始覆盖,而不是把文件内容全部覆盖

4、总结

  • 对于所有输入流,如果文件不存在会抛出异常
  • 对于所有用数组读取的方法,要注意覆盖读取问题与数组溢出问题
  • 一般来说,读取字符使用字符流,其他文件使用字节流。
  • java.io包下的流实现了同步机制,如果有多线程共同持有一个流,那么同一时刻只有一个线程能读取或写出
  • 除了RandomAccessFile的其他输入流,只能 ”读取一次“ ,从开始读取,直至流被关闭。流的读取是不可逆的,要想重新读取文件的某个部分,只能重新开始。
  • RandomAccessFile由于是随机访问的,所以可以实现断点续传
    在这里插入图片描述
    字符流会把char[ ]中的字符编码,存放在自己内部的缓存区(byte[ ])

5、应用

使用输入流输出流多线程拷贝一份文件

class Task implements Runnable{

    private FileWriter fileWriter;

    private FileReader fileReader;

    public Task(FileWriter fileWriter,FileReader fileReader) {
        this.fileWriter = fileWriter;
        this.fileReader = fileReader;
    }

    @Override
    public void run() {
        try {
            char[] chars = new char[4];
            int len = 0;
            while ((len = fileReader.read(chars)) != -1){
                fileWriter.write(chars,0,len);
            }
        } catch (IOException e) {
            if (!e.getMessage().equals("Stream closed"))
                e.printStackTrace();
        } finally {
            try {
                if (fileReader != null)
                    fileReader.close();
            } catch (Exception e) {
               if (!e.getMessage().equals("Stream closed"))
                    e.printStackTrace();
            }
            try {
                if (fileWriter != null) {
                    fileWriter.close();
                }
            } catch (Exception e) {
                if (!e.getMessage().equals("Stream closed"))
                    e.printStackTrace();
            }
        }
    }
}

注意点:

  1. 当流读取完毕,被关闭后如果执行write或close操作会报异常,所以对异常进行判断流是否读取完毕。

使用RandomAccessFile实现多线程下载(类似迅雷的多线程下载)

public class Test {

	//定义一个线程读写500MB的文件大小
    private static final long mem = 1024*1024*500;

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

        download(new File("E:\\新建文件夹 (2)\\新建文件夹\\花束般的恋爱 花束みたいな恋をした 2021.mp4"),
                new File("D:\\users\\desktop\\test.mp4"));
    }

    public static void download(File src,File dest) throws IOException {
        long start = System.currentTimeMillis();
        RandomAccessFile reader = null;
        try {

            reader = new RandomAccessFile(dest,"rw");
            long length = reader.length();
            //预先开辟一片空间,创建临时文件
            reader.setLength(length);

            long l = splitFile(src);
            Thread[] threads = new Thread[(int) l];

            for (int i = 0; i < threads.length; i++) {
                 if (i == threads.length - 1){
                //计算剩余需要读写的大小,进行读写
                    Thread thread = new Thread(new Task(new RandomAccessFile(src, "r"), new RandomAccessFile(dest, "rw"),
                            mem * i, length - l * mem));
                            thread.start();
                            thread.join();
                }else {
                    Thread thread = new Thread(new Task(new RandomAccessFile(src, "r"), new RandomAccessFile(dest, "rw"),
                            mem * i, mem));
                    thread.start();
                    thread.join();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        System.out.println("当前文件下载时间为:" + ((System.currentTimeMillis() - start)/1000) + "秒");
    }
    
	//对文件分块,计算需要几个进程
    private static long splitFile(File file){
        long length = file.length();
        return (length%mem == 0)? (length/mem) : (length/mem+1);
    }


}
class Task implements Runnable{

    private RandomAccessFile reader;

    private RandomAccessFile writer;

    private long startPosition; //定义每个线程从哪开始读取与写入

    private long size; //定义读取与写入的大小,用于判断何时停止读写

    private static final int l = 1024*1024*50; byte数组的大小

    public Task(RandomAccessFile reader, RandomAccessFile writer, long startPosition, long size) {
        this.reader = reader;
        this.writer = writer;
        this.startPosition = startPosition;
        this.size = size;
    }

    @Override
    public void run() {
        try {

            reader.seek(startPosition);
            writer.seek(startPosition);

            byte[] bytes = new byte[l];
            int len = 0;
            long data = 0; 
            while ((len = reader.read(bytes)) != -1){
                writer.write(bytes,0,len);
                data += len; //计算读写了多大空间
                if (data == size) break;
            }

        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                if (writer != null)
                     writer.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (reader != null)
                     reader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

6、Java编码学习

Java字符编码学习
Java编码转换详细过程

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值