前言

今天带来的是JAVA的IO流中的字节流,InputStream和OutputStram子类流的用法。

基础概念

流是一种抽象概念,它代表了数据的无结构化传递。按照流的方式进行输入输出,数据被当成无结构的字节序或字符序列。从流中取得数据的操作称为提取操作,而向流中添加数据的操作称为插入操作。用来进行输入输出操作的流就称为IO流。换句话说,IO流就是以流的方式进行输入输出。

IO流分类导向图

IO分类导向图

何为输入流和输出流?

其实输入(InputStream,Reader)和输出(OutputStream,Writer)是相对于程序来讲,例如一个文件的数据要想在程序中被操作,那么就得输入到程序,这就是输入,操作完成之后又想保存到文件里面,从程序输出数据到文件的过程就是输出。

各种字节流的用法

①节点流

概念:可以从或向一个特定的地方(节点)读写数据。


  1. 文 件 FileInputStream,FileOutputStrean 文件进行处理的节点流。
  2. 数 组 ByteArrayInputStream,ByteArrayOutputStream 将数据与字节数组的互转的节点流。

②处理流

概念:是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写。如BufferedReader.处理流的构造方法总是要带一个其他的流对象做参数。一个流对象经过其他流的多次包装,称为流的链接


  1. 缓冲流(装饰流,装饰模式的其中之一):BufferedInputStream,BufferedOutputStream ---增加缓冲功能,避免频繁读写硬盘。
  2. 转换流:InputStreamReader, OutputStreamReader实现字节流和字符流之间的转换。
  3. 数据流:DataInputStream, DataOutputStream 提供将基础数据类型写入到文件中,或者读取出来.
  4. 对象流:ObjectInputStream, ObjectOutputStream对象流可以将一个对象写出,或者读取一个对象到程序中,也就是执行了序列化和反序列化的操作。

序列化的概念: 将一个对象存放到某种类型的永久存储器上称为保持。如果一个对象可以被存放到磁盘或磁带上,或者可以发送到另外一台机器并存放到存储器或磁盘上,那么这个对象就被称为可保持的。(在Java中,序列化、持久化、串行化是一个概念。)

③流的使用一般步骤

  1. 选择源,即是选择要操作的文件或者数据。
  2. 选择流,想要实现何种流的操作。
  3. 流的操作。
  4. 释放资源。

④流的关闭

遵循先开后闭的原则,有多种流的使用时,最先创建的流对象最后关闭。(字节数组流可以不用关闭)

流的对象的创建
try{
    流的操作
  }catch (IOException e) {
    异常处理
}finally{
    流的释放
}

Java7提供了try-with-resources机制,其类似Python中的with语句,将实现了 java.lang.AutoCloseable 接口的资源定义在 try 后面的小括号中,不管 try 块是正常结束还是异常结束,这个资源都会被自动关闭。 try 小括号里面的部分称为 try-with-resources 块。

try(流对象的创建){
    流的操作
  }catch (IOException e) {
    异常处理
}

具体用法

Ⅰ文件流

将文件abc1.txt的copy到abc2.txt

InputStream is = null;
OutputStream os = null;
try {
    InputStream is = new FileInputStream("abc1.txt");//参数可以为File对象
    OutputStream os = new FileOutputStream("abc2.txt");//参数可以为File对象
    byte[] flush = new byte[1024]; //缓冲容器
    int len = -1; //接收长度
    while((len=is.read(flush))!=-1) {
        os.write(flush,0,len); //分段写出
        }
    } catch (IOException e) {
        e.printStackTrace();
    }finally{ //先开后闭
        try {
            if (null != os) {
                os.close();
            } 
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            if(null!=is) {
                is.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
}

Ⅱ字节数组流

简单demo将图片用字节数组流和文件流进行复制。

public class Test {

    public static void main(String[] args) {
        byte[] datas = fileToByteArray("p.png");
        System.out.println(datas.length);
        byteArrayToFile(datas,"p-byte.png");
    }
    /**
     * 1、图片读取到字节数组
     * 1)、图片到程序  FileInputStream
     * 2)、程序到字节数组   ByteArrayOutputStream
     */
    public static byte[] fileToByteArray(String filePath) {
        //1、创建源
        File src = new File(filePath);
        byte[] dest =null;
        //2、选择流
        InputStream  is =null;
        ByteArrayOutputStream baos =null;
        try {
            is =new FileInputStream(src);
            baos = new ByteArrayOutputStream();
            //3、操作 (分段读取)
            byte[] flush = new byte[1024*10]; //缓冲容器
            int len = -1; //接收长度
            while((len=is.read(flush))!=-1) {
                baos.write(flush,0,len);         //写出到字节数组中
            }
            baos.flush();
            return baos.toByteArray();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            //4、释放资源
            try {
                if(null!=is) {
                    is.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }
    /**
     * 2、字节数组写出到图片
     * 1)、字节数组到程序 ByteArrayInputStream
     * 2)、程序到文件 FileOutputStream
     */
    public static void byteArrayToFile(byte[] src,String filePath) {
        //1、创建源
        File dest = new File(filePath);
        //2、选择流
        InputStream  is =null;
        OutputStream os =null;
        try {
            is =new ByteArrayInputStream(src);
            os = new FileOutputStream(dest);
            //3、操作 (分段读取)
            byte[] flush = new byte[1024]; //缓冲容器
            int len = -1; //接收长度
            while((len=is.read(flush))!=-1) {
                os.write(flush,0,len);          //写出到文件 
            }
            os.flush(); //防止文件太小,都存在缓冲容器里面,刷新一下
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            //4、释放资源
            try {
                if (null != os) {
                    os.close();
                } 
            } catch (Exception e) {
             e.printStackTrace();
            }
        }
    }
}

Ⅲ缓冲流

装饰流指不直接连接数据源,而是以其它流对象(实体流对象或装饰流对象)为基础建立的流类,该类流实现了将实体流中的数据进行转换,增强流对象的读写能力,比较常用的有BufferedInputStream/BufferedOutputStream和BufferedReader/BufferedWriter等,装饰流类不可以单独使用,必须配合实体流或装饰流进行使用。作用:可以一定限度加快读写速度。

输入流例子:

InputStream  is = new FileInputStream(File对象或者文件Path);//当然实例化可以是别的流的对象。
BufferedInputStream bis = new BufferedInputStream(is);

-----------------------------------------------------------------
//装饰模式的写法 ~~俄罗斯套娃?~~ 
BufferedInputStream bis = new BufferedInputStream(FileInputStream(File对象或者文件Path));
输出流例子:

OutputStream os = new FileOutputStream(File对象或者文件Path);//当然实例化可以是别的流的对象。
BufferedOutputStream bos = new BufferedOutputStream(os);
-----------------------------------------------------------------
//装饰模式的写法 ~~俄罗斯套娃?~~ 
BufferedOutputStream bos = new BufferedOutputStream(FileOutputStream(File对象或者文件Path));

Ⅳ转换流

作用:实现字节流和字符流之间的转换。


1、OutputStreamWriter,将字节输出流转换为字符输出流。

API方法:

1、flush():刷新该流的缓冲

2、close():关闭此流,关闭前需要刷新

3、getEncoding():获取此流使用的字符编码的名称。

4、write():write(char[] ,int offset,int length),写入字符数组的某一部分

write(String ,int offset ,int length),写入字符串的某一部分 write(String ),写入单个字符。

2、InputStreamReader,将字节输入流转换为字符输入流。

API方法:

1、close():关闭此流

2、getEncoding():获取此流使用的字符编码的名称

3、ready():判断此流是否已经准备好用于读取

4、read():read(),读取单个字符。

5、read(char[],int offset ,int length),将字符读入数组的某一部分。

public class ConvertTest {
    public static void main(String[] args) {
        //操作System.in 和System.out
        try(BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter writer =new BufferedWriter(new OutputStreamWriter(System.out));){
            //循环获取键盘的输入(exit退出),输出此内容
            String msg ="";
            while(!msg.equals("exit")) {
                msg = reader.readLine(); //循环读取
                writer.write(msg); //循环写出
                writer.newLine();
                writer.flush(); //强制刷新
            }
        }catch(IOException e) {
            System.out.println("操作异常");
        }       
    }
}

Ⅴ数据流

DataOutputStream的API方法:

1、void writeByte(int v) 将一个 byte 值以 1-byte 值形式写出到基础输出流中。

2、void writeShort(int v) 将一个 short 值以 2-byte 值形式写入基础输出流中,先写入高字节。

3、void writeInt(int v)将一个 int 值以 4-byte 值形式写入基础输出流中,先写入高字节。

4、void writeLong(long v) 将一个 long 值以 8-byte 值形式写入基础输出流中,先写入高字节。

5、void writeFloat(float v) 使用 Float 类中的 floatToIntBits 方法将 float 参数转换为一个 int 值, 然后将该 int 值以 4-byte 值形式写入基础输出流中,先写入高字节。

6、void writeDouble(double v) 使用 Double 类中的 doubleToLongBits 方法将 double 参数转换为一个 long 值, 然后将该 long 值以 8-byte 值形式写入基础输出流中,先写入高字节。

7、void writeChar(int v) 将一个 char 值以 2-byte 值形式写入基础输出流中,先写入高字节。

8、void writeBoolean(boolean v) 将一个 boolean 值以 1-byte 值形式写入基础输出流。

9、void flush() //清空此数据输出流。写入文件

10、int size() //返回计数器 written 的当前值,即到目前为止写入此数据输出流的字节数。

1、 byte readByte() ;//读取并返回一个输入字节。该字节被看作是 -128 到 127(包含)范围内的一个有符号值。

2、 int readInt() ;//读取四个输入字节并返回一个 int 值。

3、 short readShort() ;//参见 DataInput 的 readShort 方法的常规协定。

4、 long readLong() ;//读取八个输入字节并返回一个 long 值。

5、 float readFloat() ;//读取四个输入字节并返回一个 float 值。

6、 double readDouble() ;//读取八个输入字节并返回一个 double 值。实现这一点的方法是:先使用与 readlong 方法完全相同的方式构造一个 long 值, 然后使用与 Double.longBitsToDouble 方法完全相同的方式将此 long 值转换成一个 double 值。

7、 boolean readBoolean() ;//读取一个输入字节,如果该字节不是零,则返回 true,如果是零,则返回 false。

8、 char readChar() ;//读取两个输入字节并返回一个 char 值.

9、 String readUTF();//读入一个已使用 UTF-8 修改版格式编码的字符串.

public class DataTest {

    public static void main(String[] args) throws IOException {
        //写出
        ByteArrayOutputStream baos =new ByteArrayOutputStream();
        DataOutputStream dos =new DataOutputStream(new BufferedOutputStream(baos));
        //操作数据类型 +数据
        dos.writeUTF("Himit_ZH");
        dos.writeInt(520);
        dos.writeBoolean(false);
        dos.writeChar('q');
        dos.flush();
        byte[] datas =baos.toByteArray();
        System.out.println(datas.length);
        //读取
        DataInputStream dis =new DataInputStream(new BufferedInputStream(new ByteArrayInputStream(datas)));
        //顺序与写出一致
        String msg = dis.readUTF(); 
        int age = dis.readInt();
        boolean flag = dis.readBoolean();
        char ch = dis.readChar();
        System.out.println(flag);
    }

}

Ⅵ对象流(序列化和反序列化)

对象流将一个序列化的对象保存到硬盘中,或者硬盘中读取一个对象。对象流的存储和读取包含以下几点内容:

1、所保存的对象必须实现Serializable接口。

2、 所保存的对象的属性也必须实现Serializable接口。

3、 最好要给该对象提供一个版本号,private static final long serialVersionId。

public class ObjectTest {

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        //写出 -->序列化
        ByteArrayOutputStream baos =new ByteArrayOutputStream();
        ObjectOutputStream oos =new ObjectOutputStream(new BufferedOutputStream(baos));
        //操作数据类型 +数据
        oos.writeUTF("Himit_ZH真帅");
        oos.writeInt(520);
        oos.writeBoolean(false);
        oos.writeChar('a');
        //对象
        oos.writeObject("Python是最好用的");
        oos.writeObject(new Date());
        Employee emp =new Employee("马云",400);
        oos.writeObject(emp);
        oos.flush();
        oos.close();
        byte[] datas =baos.toByteArray();
        System.out.println(datas.length);
        //读取 -->反序列化
        ObjectInputStream ois =new ObjectInputStream(new BufferedInputStream(new ByteArrayInputStream(datas)));
        //顺序与写出一致
        String msg = ois.readUTF(); 
        int age = ois.readInt();
        boolean flag = ois.readBoolean();
        char ch = ois.readChar();
        System.out.println(flag);
        //对象的数据还原  
        Object str = ois.readObject();
        Object date = ois.readObject();
        Object employee = ois.readObject();
        //对应类型进行转型
        if(str instanceof String) {
            String strObj = (String) str;
            System.out.println(strObj);
        }
        if(date instanceof Date) {
            Date dateObj = (Date) date;
            System.out.println(dateObj);
        }
        if(employee instanceof Employee) {
            Employee empObj = (Employee) employee;
            System.out.println(empObj.getName()+"-->"+empObj.getSalary());
        }
        ois.close();

    }

}
//javabean 封装数据
class Employee implements java.io.Serializable{ //必须实现Serializable接口才能序列化
    private transient String name; //transient 该数据不需要序列化
    private double salary;
    public Employee() {
    }
    public Employee(String name, double salary) {
        this.name = name;
        this.salary = salary;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public double getSalary() {
        return salary;
    }
    public void setSalary(double salary) {
        this.salary = salary;
    }

}

Ⅶ打印流(PrintStream)

类的继承:

          java.lang.Object
                   |-java.io.OutputStream
                            |-java.io.FilterOutputStream
                                   |-java.io.PrintStream

PrintStream 是用来装饰其它输出流。它能为其他输出流添加了功能,使它们能够方便地打印各种数据值表示形式。 与其他输出流不同,PrintStream 永远不会抛出 IOException;它产生的IOException会被自身的函数所捕获并设置错误标记, 用户可以通过 checkError() 返回错误标记,从而查看PrintStream内部是否产生了IOException。 另外,PrintStream 提供了自动flush 和 字符集设置功能。所谓自动flush,就是往PrintStream写入的数据会立刻调用flush()函数。

API方法: PrintStream​(File file) 使用指定的文件创建没有自动行刷新的新打印流。

PrintStream​(File file, String csn) 使用指定的文件和字符集创建一个没有自动行刷新的新打印流。

PrintStream​(File file, Charset charset) 使用指定的文件和字符集创建一个没有自动行刷新的新打印流。

PrintStream​(OutputStream out) 创建新的打印流。

PrintStream​(OutputStream out, boolean autoFlush) 创建新的打印流。

PrintStream​(OutputStream out, boolean autoFlush, String encoding) 创建新的打印流。

PrintStream​(OutputStream out, boolean autoFlush, Charset charset) 创建一个新的打印流,具有指定的OutputStream,自动行刷新和字符集。

PrintStream​(String fileName) 使用指定的文件名创建没有自动行刷新的新打印流。

PrintStream​(String fileName, String csn) 使用指定的文件名和字符集创建一个没有自动行刷新的新打印流。

PrintStream​(String fileName, Charset charset) 使用指定的文件名和字符集创建一个没有自动行刷新的新打印流。

PrintStream append​(char c) 将指定的字符追加到此输出流。

void close() 关闭流.

void flush() 刷新流.

public class Demo {
    public static void main(String[] args) throws Exception {// 此处直接抛出错误
        PrintStream ps = new PrintStream(new FileOutputStream(文件path或者File对象));
        //写出到对应文件,基本数据类型,String..
        ps.println("Himit_ZH");
        ps.print('c');
        ps.println(11.6 + 11.89); //多了换行
        ps.print(10 + 30);
        ps.write(byte[] buffer, int offset, int length);
        ps.flush();
        ps.close();
    }

}

Ⅷ附加:随机访问流

RandomAccessFile类:是Object的子类,此类的实例支持对随机访问文件的读取和写入。随机访问文件的行为类似存储在文件系统中的一个大型 byte 数组。存在指向该隐含数组的光标或索引,称为文件指针;输入操作从文件指针开始读取字节,并随着对字节的读取而前移此文件指针。如果随机访问文件以读取/写入模式创建,则输出操作也可用;输出操作从文件指针开始写入字节,并随着对字节的写入而前移此文件指针。写入隐含数组的当前末尾之后的输出操作导致该数组扩展。该文件指针可以通过 getFilePointer 方法读取,并通过 seek 方法设置。

用途:例如迅雷的下载资源是随机下载资源的各个部分,而不是从头到尾。有些视频下载也是随机资源块下载。

public class RandTest {

    public static void main(String[] args) throws IOException {
        //分多少块
        File src = new File("p.png");
        //总长度
        long len = src.length();
        //每块大小
        int blockSize =1024;
        //块数: 多少块
        int size =(int) Math.ceil(len*1.0/blockSize);
        System.out.println(size);

        //起始位置和实际大小
        int beginPos = 0;
        int actualSize = (int)(blockSize>len?len:blockSize); 
        for(int i=0;i<size;i++) {
            beginPos = i*blockSize;
            if(i==size-1) { //最后一块
                actualSize = (int)len;
            }else {
                actualSize = blockSize;
                len -=actualSize; //剩余量
            }
            System.out.println(i+"-->"+beginPos +"-->"+actualSize);
            split(i,beginPos,actualSize);
        }

    }
    /**
     * 指定第i块的起始位置 和实际长度
     * @param i
     * @param beginPos
     * @param actualSize
     * @throws IOException
     */
    public static void split(int i,int beginPos,int actualSize ) throws IOException {
        RandomAccessFile raf =new RandomAccessFile(new File("p.png"),"r");
        RandomAccessFile raf2 =new RandomAccessFile(new File("dest/"+i+"p.png"),"rw");
        //随机读取 
        raf.seek(beginPos);
        //读取
        //3、操作 (分段读取)
        byte[] flush = new byte[1024]; //缓冲容器
        int len = -1; //接收长度
        while((len=raf.read(flush))!=-1) {
            if(actualSize>len) { //获取本次读取的所有内容
                raf2.write(flush, 0, len);
                actualSize -=len;
            }else { 
                raf2.write(flush, 0, actualSize);
                break;
            }
        }
        raf2.close();
        raf.close();
    }

}

最后

CSDN的博文链接: 1. Java中的IO流(二)字符流的常用操作 2. Java中的IO流(三)Apache Commons IO组件的常用操作

评论