Java IO流之字节流【一】

1.IO 概述

生活中,你肯定经历过这样的场景。当你编辑一个文本文件,忘记了 ctrl+s ,可能文件就白白编辑了。

当你电脑上插入一个U盘,可以把一个视频,拷贝到你的电脑硬盘里。那么数据都是存储在哪些设备呢?内存、硬盘、外接设备(移动硬盘,U盘)等等。

数据的传输可以看做是数据的流动,按照流动的方向,以内存为基准,分为输入流input 和输出流output ,即流向内存是输入流流出内存是输出流

Java的IO模型设计采用了Decorator(装饰者模式),按功能划分流,并且可以动态装配。

举例:应当组合使用 FileInputStream 和 BufferedInputStream,以实现具有缓冲功能的文件输入流。

2.IO流的分类

  • 根据数据的流向分为:输入流输出流
  1. 输入流 :把数据从其他设备上读取到内存中的流。
  2. 输出流 :把数据从内存中写出到其他设备上的流。
  • 根据数据的类型分为:字节流字符流
  1. 字节流 :以字节为单位,读写数据的流。
  2. 字符流 :以字符为单位,读写数据的流。Java中的字符是Unicode编码,一个字符占用两个字节
  • 根据功能分为:节点流处理流
  1. 节点流:向一个特定的地方(节点)读写数据。如 FileInputStream
  2. 处理流:是对一个已存在的流进行封装。如BufferedInputStream。处理流的构造方法需要一个流对象做参数。一个流对象可以被其他流多次封装。
流的分类 输入流 输出流
字节流 InputStream(字节输入流) OutputStream(字节输出流)
字符流 Reader(字符输入流) Writer(字符输出流)

2.2 IO流类图结构

Java IO流之字节流【一】_第1张图片

3.字节流

在计算机中,一切文件数据(文本、图片、视频等)在存储时,都是以二进制数字的形式保存的,都是一个个字节。

所以,字节流可以传输任意文件数据。

在操作流的时候,我们要时刻明确,无论使用什么样的流对象,底层传输的始终为二进制数据。

字节流类图结构:
Java IO流之字节流【一】_第2张图片

3.1 字节

  • 字节(Byte)是计算机中表述存储容量的计量单位,也表示编程语言中的数据类型和语言字符。
  • 一个字节存储8位无符号数,储存的数值范围为0-255。

字节与比特

  1. 数据存储是以“字节”(Byte)为单位,数据传输大多是以“位”(bit,又名“比特”)为单位,一个位就代表一个0或1(即二进制),每8个位(bit,简写为b)组成一个字节(Byte,简写为B),是最小一级的信息单位 [4] 。

Java基本数据类型

类型 字节数 位数 范围
byte 1 8位 最小值是 -128(-2^7); 最大值是 127(2^7-1)
short 2 16位 最小值是 -32768(-2^15); 最大值是 32767(2^15 - 1)
int 4 32位 最小值是 -2,147,483,648(-2^31); 最大值是 2,147,483,647(2^31 - 1)
long 8 64位 最小值是 -2^63; 最大值是 2^63 -1
float 4 32位 单精度、-2^128 ~ +2^128,也即-3.40E+38 ~ +3.40E+38
double 8 64位 双精度、-2^1024 ~ +2^1024,也即-1.79E+308 ~ +1.79E+308。
char 2 16 位 最小值是 \u0000(即为0);最大值是 \uffff(即为65,535)
boolean 1 8 只有两个取值:true(00000001) 和 false(00000000)

3.2 【OutputStream】字节输出流

java.io.OutputStream 是一个抽象类,是表示字节输出流的所有类的超类,能够将指定的字节信息写出到目的地。它定义了字节输出流的基本共性功能方法。

java.io.OutputStream的方法如下:

public void close() :关闭此输出流并释放与此流有关的所有系统资源
public void flush() :刷新此输出流并强制写出所有缓冲的输出字节。
public void write(byte[] b):将 b.length字节从指定的字节数组写入此输出流。该方法等同于write(byte[], 0, b.length);
public void write(byte[] b, int off, int len) :从指定的字节数组写入 len字节,从索引 off开始的len 个字节写入此输出流。该方法底层调用的是write(int b)public abstract void write(int b) :将指定的字节写入此输出流。(要写入的字节是参数 b 的8个低位。b 的 24 个高位将被忽略。)

3.2.1 FileOutputStream 【文件字节输出流】

作用: 将数据以字节的形式写入到文件中

构造方法:

public FileOutputStream(File file) : 相当于FileOutputStream(File file,false)public FileOutputStream(File file,boolean append) : 创建一个向File对象表示的文件中写入数据的文件输出流.如果第二个参数为true,则表示追加写入到文件末尾,若为false,则是覆盖写入。

public FileOutputStream(String name) :相当于FileOutputStream(String name,false)public FileOutputStream(String name,boolean append) :创建一个向具有指定名称的文件中写入数据的输出文件流。如果第二个参数为true,则表示追加写入到文件末尾,若为false,则是覆盖写入。

public FileOutputStream(FileDescriptor fdObj) : 创建一个向指定文件描述符处写入数据的输出文件流,该文件描述符表示一个到文件系统中的某个实际文件的现有连接。 

构造方法源码注释:


	public FileOutputStream(File file) throws FileNotFoundException {
     
        this(file, false);
    }
	public FileOutputStream(File file, boolean append)
        throws FileNotFoundException
    {
     
    	// 获取构造方法中传入的路径名
        String name = (file != null ? file.getPath() : null);
        // 获取系统安全管理器
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
     
        	// 验证是否有写权限
            security.checkWrite(name);
        }
        if (name == null) {
     
            throw new NullPointerException();
        }
        // 验证文件路径是否无效
        if (file.isInvalid()) {
     
            throw new FileNotFoundException("Invalid file path");
        }
        // 创建一个文件描述符类
        this.fd = new FileDescriptor();
        // 增加一个可关闭的标签,以便跟踪
        fd.attach(this);
        this.append = append;
        this.path = name;
		/** 打开指定名称的文件以进行覆盖写入或追加写入。
         *  append :false 覆盖写入
         *  append :true 追加写入
         */
        open(name, append);
    }
    // 打开指定名称的文件以进行覆盖写入或追加写入。
	private void open(String name, boolean append) throws FileNotFoundException {
     
        open0(name, append);
    }
	// native方法,调用系统底层。
	private native void open0(String name, boolean append) throws FileNotFoundException;
        
	public FileOutputStream(String name) throws FileNotFoundException {
     
        this(name != null ? new File(name) : null, false);
    }
    
	public FileOutputStream(String name, boolean append)
        throws FileNotFoundException
    {
     
        this(name != null ? new File(name) : null, append);
    }

	public FileOutputStream(FileDescriptor fdObj) {
     
		// 获取系统安全管理器
        SecurityManager security = System.getSecurityManager();
        if (fdObj == null) {
     
            throw new NullPointerException();
        }
        if (security != null) {
     
        	// 验证是否有写入权限
            security.checkWrite(fdObj);
        }
        this.fd = fdObj;
        this.append = false;
        this.path = null;

        fd.attach(this);
    }

通过源码,可以看出,这几个构造方法真正调用的其实都是 public FileOutputStream(File file, boolean append) 这个构造方法。

写入数据的步骤:

 java程序-->jvm(java虚拟机)-->OS(操作系统)-->OS调用写数据的方法-->把数据写入到文件中

简单示例:

package com.hanyxx.io;

import java.io.FileOutputStream;
import java.io.IOException;

/**
 * @author layman
 */
public class Demo01 {
     
    public static void main(String[] args) throws IOException {
     
        FileOutputStream fos = new FileOutputStream("layman.txt");
        // 写入单个字节
        fos.write(65);

		/**
         * 一次性写入多个字节
         *   如果第一个字节是正数(0~127),那么显示的时候会查询ASCII表
         *   如果第一个字节是负数,那么第一个字节会和第二个字节,组成一个中文显示。使用系统默认码表(GBK)。
         */
        
        byte[] bytes = {
     -65,-66,67,-68,69};
        //fos.write(bytes);

		/**
         * write(byte b[], int off, int len):写入字节数组的一部分
         *      off :开始写入的数组索引
         *      len :写入的字节个数
         */
        //fos.write(bytes,1,2);
        fos.close();
    }
}

注意:

  • 写数据的时候,会把整数65转为二进制,也就是01000001。
  • 任意的文本编辑器(记事本,nodepad++…,Sublime Text在打开文件的时候,都会去查询编码表,把字节转化为字符进行显示。)
  • 0~127 ,查询ASCII码表,如果是其他值,则查询系统默认码表,如果是中文,则查询GBK。
  • 所以虽然写入了65,实际上是个A (要不起!)

换行符:

windows系统: \r\n    linux系统:    \r    mac系统:    \n

3.3 InputStream【字节输入流】

java.io.InputStream 是一个抽象类,是表示字节输入流的所有类的超类,能够将指定的字节信息写入内存。它定义了字节输入流的基本共性功能方法。

java.io.InputStream的方法如下:

public int available():返回此输入流下一个方法调用可以不受阻塞地从此输入流读取(或跳过)的估计字节数。此方法应该由子类重写
public void close() :关闭此输入流并释放与其关联的所有系统资源。 
public void mark(int readlimit):在此输入流中标记当前位置。
public boolean markSupported():测试此输入流是否支持 mark 和 reset 方法。
public abstract int read():从输入流中读取数据的下一个字节。返回 0255 范围内的 int 字节值。如果读到流末尾,返回 -1public int read(byte[] b):从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b 中。。该方法等同于 read(b, 0, b.length) 
public int read(byte[] b, int off, int len):将输入流中最多 len 个数据字节读入 byte 数组。尝试读取 len 个字节,但读取的字节也可能小于该值。
public void reset():将此流重新定位到最后一次对此输入流调用 mark 方法时的位置。 
public long skip(long n):跳过和丢弃此输入流中的 n 个字节。

3.3.1 FileInputStream【文件字节输入流】

作用: 将数据以字节的形式读取到内存中

构造方法:

public FileInputStream(File file):通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由File对象 指定。创建一个新 FileDescriptor 对象来表示此文件连接。 
public FileInputStream(FileDescriptor fdObj):过使用文件描述符 fdObj 创建一个 FileInputStream,该文件描述符表示到文件系统中某个实际文件的现有连接。 
public FileInputStream(String name):通过打开与实际文件的连接来创建一个 FileInputStream,该文件通过文件路径名 name 指定。创建一个新 FileDescriptor 对象来表示此文件连接。 

构造方法源码:

	public FileInputStream(String name) throws FileNotFoundException {
     
        this(name != null ? new File(name) : null);
    }
	public FileInputStream(File file) throws FileNotFoundException {
     
        String name = (file != null ? file.getPath() : null);
        // 获取系统安全管理器
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
     
        	// 验证是否有读权限
            security.checkRead(name);
        }
        if (name == null) {
     
            throw new NullPointerException();
        }
        // 验证文件路径是否无效
        if (file.isInvalid()) {
     
            throw new FileNotFoundException("Invalid file path");
        }
        // 跟踪
        fd = new FileDescriptor();
        fd.attach(this);
        path = name;
        open(name);
    }
    public FileInputStream(FileDescriptor fdObj) {
     
        SecurityManager security = System.getSecurityManager();
        if (fdObj == null) {
     
            throw new NullPointerException();
        }
        if (security != null) {
     
            security.checkRead(fdObj);
        }
        fd = fdObj;
        path = null;
        fd.attach(this);
    }

通过源码,可以看出,构造方法底层真正调用的其实都是 public FileInputStream(File file) 这个构造方法。

读取字节的源码:

	// 从流中读取一个字节
	public int read() throws IOException {
     
	        return read0();
	    }
	// 调用系统底层native方法
    private native int read0() throws IOException;
    // 读取字节数组
    public int read(byte b[]) throws IOException {
     
        return readBytes(b, 0, b.length);
    }
    // 读取一部分字节数组
    public int read(byte b[], int off, int len) throws IOException {
     
        return readBytes(b, off, len);
    }
    // 调用系统底层native方法
    private native int readBytes(byte b[], int off, int len) throws IOException;

简单演示:

package com.hanyxx.io;

import java.io.FileInputStream;
import java.io.IOException;

/**
 * @author layman
 */
public class Demo02 {
     

    static FileInputStream fis;

    public static void main(String[] args) throws IOException {
     
        read01();
        read02();
        read03();
    }

    // 循环读取单个字节
    private static void read01() throws IOException {
     
        fis = new FileInputStream("layman.txt");
        int len;
        while((len = fis.read()) != -1){
     
            // 将读取到的字节转为字符,并打印输出
            System.out.print((char)len);
        }
        System.out.println();
        fis.close();
    }
    /**
     * int read(byte[] b):从输入流中将最多 b.length 个字节的数据读入一个 byte 数组中。
     */
    private static void read02() throws IOException {
     
        fis = new FileInputStream("layman.txt");
        // fis.available():获取流中能够读取到的有效字节个数
        byte[] bytes = new byte[fis.available()];
        int length = fis.read(bytes);
        fis.close();

		System.out.println("------------------");
        System.out.println("读取到的字节个数: " + length);
        System.out.println(new String(bytes));
    }
    /**
     * int read(byte[] b, int off, int len) :从此输入流中将最多 len 个字节的数据读入一个 byte 数组中。off:偏移量,不是数组索引
     */
    private static void read03() throws IOException {
     
        fis = new FileInputStream("layman.txt");

        byte[] bytes = new byte[fis.available()];
        int len = fis.read(bytes,2,3);
        fis.close();

		System.out.println("------------------");
        System.out.println("读取到的字节个数: " + len);
        System.out.println(new String(bytes));
    }
}
运行结果:
BIGHUGE
------------------
读取到的字节个数: 7
BIGHUGE
------------------
读取到的字节个数: 3
  BIGBIGHUGE
读取到的字节个数: 7
BIGHUGE
读取到的字节个数: 3
  BIG 

简单案例(图片复制):

将D:\food.jpg复制为D:\food_copy.jpg

package com.hanyxx.io;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * 图片复制
 * @author layman
 * @date 2021/3/7
 */
public class Demo03 {
     
    public static void main(String[] args) throws IOException {
     
        long start = System.currentTimeMillis();
        FileInputStream fis = new FileInputStream("D:\\food.jpg");
        FileOutputStream fos = new FileOutputStream("D:\\food_copy.jpg");
        int len;
        //单字节写入
        /*while((len = fis.read()) != -1){
            fos.write(len);
        }*/

        // 使用数组缓冲流读取字节(1KB)
        byte[] bytes = new byte[1024];
        while ((len = fis.read(bytes))!=-1) {
     
            fos.write(bytes, 0 , len);
        }
        //流的关闭原则:先开后关,后开先关。
        fos.close();
        fis.close();

        long end = System.currentTimeMillis();
        System.out.println("复制耗时:" + (end-start) + "毫秒");
    }
}

你可能感兴趣的:(#,IO,java,IO流)