详细领略Java的输入流和输出流

狼北前言

说到java的输入流和输出流,IO流分为两种的(字节流和字符流),那么我们首先就得了解什么是java的字节流和字符流,然后我才来了解怎么操作这些流来写入文件中和写出文件。所以我这篇文章分为两大部分,第一部分是“IO流”,第二部分是“操作io流”。废话不多说让猿猴带你一起来领略这里面的一二。(对博主感兴趣可以加博主的抖音号langbei57048

在这里插入图片描述

Java的IO流

什么是流

Java中的流是对字节序列的抽象,我们可以想象有一个水管,只不过现在流动在水管中的不再是水,而是字节序列。和水流一样,Java中的流也具有一个“流动的方向”,通常可以从中读入一个字节序列的对象被称为输入流;能够向其写入一个字节序列的对象被称为输出流。

字节流

Java中的字节流处理的最基本单位为单个字节(字节规定一个字节由八个二进制位构成)它通常用来处理二进制数据。Java中最基本的两个字节流类是InputStream和OutputStream,它们分别代表了最基本的输入字节流和输出字节流。

字符流

Java中的字符流处理的最基本的单元是Unicode码元(大小2字节),它通常用来处理文本数据。所谓Unicode码元,也就是一个Unicode代码单元,范围是0x0000~0xFFFF。在以上范围内的每个数字都与一个字符相对应,Java中的String类型默认就把字符以Unicode规则编码而后存储在内存中。然而与存储在内存中不同,存储在磁盘上的数据通常有着各种各样的编码方式。使用不同的编码方式,相同的字符会有不同的二进制表示。实际上字符流是这样工作的:

输出字符流:把要写入文件的字符序列(实际上是Unicode码元序列)转为指定编码方式下的字节序列,然后再写入到文件中;

输入字符流:把要读取的字节序列按指定编码方式解码为相应字符序列(实际上是Unicode码元序列)从而可以存在内存中。
字符流与字节流的区别

经过以上的描述,我们可以知道字节流与字符流之间主要的区别体现在以下几个方面:

字节流操作(读写)的基本单元为字节;字符流操作的基本单元为Unicode码元。

字节流默认不使用缓冲区;字符流使用缓冲区。

字节流通常用于处理二进制数据,实际上它可以处理任意类型的数据,但它不支持直接写入或读取Unicode码元;字符流通常处理文本数据,它支持写入及读取Unicode码元。

只是读写文件,和文件内容无关的,一般选择字节流。

字节流使用场景

字节流适合所有类型文件的数据传输,因为计算机字节(Byte)是电脑中表示信息含义的最小单位,因为在通常情况下一个ACSII码就是一个字节的空间来存放。

字符流使用场景

字符流只能够处理纯文本数据(文本文件),其他类型数据不行,但是字符流处理文本要比字节流处理文本要方便。

字符流按字符读数据:一次读两个字节,返回了这两个字节所对应的字符的int型数值(编码)。写入文件时把这两个字节的内容解码成这个字符在Unicode码下对应的二进制数据写入。即把原始文件中的二进制数据以字符形式读出,再将字符以二进制形式写入,所以得到的文件以字符方式存储。而图片的数据是按字节存储的,所以打开图片时解码出错了!字节流按字节读数据:而字节不需要编码、解码,只有字节与字符之间转换时才需要编码、解码!所以可以正常读取图片数据。


操作IO流

我们在进行Android java 开发的时候,经常会遇到各种IO流操作。IO流操作一般分为两类:字符流和字节流。以“Reader”结尾都是字符流,操作的都是字符型的数据;以“Stream”结尾的都是字节流,操作的都是byte数据。现将各种常见IO流总结如下:

在这里插入图片描述

一、字节流

1.InputStream 和 OutputStream

InputStream 和 OutputStream为各种输入输出字节流的基类,所有字节流都继承这两个基类。

2.FileInputStream 和 FileOutputStream

这两个从字面意思很容易理解,是对文件的字节流操作,也会最常见的IO操作流。

都是有两个构造函数:

输入流 FileinputStream(File file或者String filePath)

输出流FileoutputStream(File file或者String filePath)

方法:

①read() :从输入流中一次读取一个字节,读完一个字节自动读取数据的下一个字节,返回0255范围内的int字节值。如果因为已经到达流末尾而没有可用的字节,则返回*-1*。在输入数据可用、检测到流末尾或者抛出异常前,此方法一直阻塞。

理解read()读取的字节为什么返回int型变量

 1、方法解释中的-1相当于是数据字典告诉调用者文件已到底,可以结束读取了,这里的-1是Int型
那么当文件未到底时,我们读取的是字节,若返回byte类型,那么势必造成同一方法返回类型不同的情况这是不允许的

 2、我们读取的字节实际是由8位二进制组成,二进制文件不利于直观查看,可以转成常用的十进制进行展示,因此需要把读取的字节从二进制转成十进制整数,故返回int型

3、 因此结合以上3点,保证返回类型一致以及直观查看的情况,因此该方法虽然读取的是字节但返回int型

②read(byte[] b) :从输入流中读取一定数量的字节,并将其读取存储在缓冲区数组 b 中。以整数形式返回实际读取的字节数。在输入数据可用、检测到文件末尾或者抛出异常前,此方法一直阻塞。如果因为流位于文件末尾而没有可用的字节,则返回值 -1每次读取的字节数是缓冲区数组的最大储存字节数(new byte[2] 数组最多储存2个字节,那么read(byte)每一次只能读到2字节,并且数组是满的状态),下一次调用读取的字节存入这个数组会替代掉上一次数组的内容(返回的是读了多少个字节而读到的字节内容储存到了b缓冲区中)

eg:我们文本文件储存的是123456

//注意下面我是简单写法,抛出异常那些都没写
       File file=new File("D://text.txt");
        FileInputStream fileInputStream=new FileInputStream(file); //获取文件输入流
            byte[] b=new byte[2]; //new一个字节数数据来当缓冲区,数组大小为2个字节  
            int i=0;
            while (n!=-1)  //当n不等于-1,则代表没到末尾
            {       
             n=fileInputStream.read(b);//读取到缓存区中返回每一次实际读取到字节数组中的字节数 
                System.out.println(n); //在文件内容字节足够时读到的就是2个字节,但是结尾不够2                                                                  个字节了,那么读到的就是剩下的字节数
               System.out.println(Arrays.toString(b)); //读取后的字节数组内容 
               i++;
               System.out.println("执行次数:"+i); 
            }
            System.out.println(new String(b));
        }

结果是:read(b)因为我们设置b数组大小是2个字节,所以每次读出都是俩2字节,但最后不够2个字节(只剩下一个字节),所以read(b)返回读到的字节数是1,但是缓存区的数组是【最后一个字节,前一次留下来的】,因为只剩一个字节只能替代b数据的第一项

在这里插入图片描述

③read(byte[] bytes,int off ,int len) 参数的byte照样是缓存区数组,off是读取字节的开始位置,len是每次读取的的字节数

那么这个和上面read(b)的区别就是他指定了每一次读出多少个字节,而不再用每一次读满一个缓存数组的形式,并且这一个可以指定读取字节开始的位置。返回的也是实际每次读取到的字节数而不是字节,字节都储存在缓存数组里。(比如,new bety[10], read(bety,0,9),那么每次读进数组的是9个字节,那么每次数据就会剩下一个字节项,那么下一次用read读,读到的第一个字节就会放在数组的最后一个位置,而剩下的8个字节替换掉上次的前8个字节,想想就复杂 )。

我们常用的是read(b)接下来我们来完整的演示使用过程吧!

 /*    
     * 以字节为单位读取文件,常用于读二进制文件,如图片、声音、影像等文件。
     */
    public static void readFileByBytes(String inFile, String outFile) {
        File file = new File(fileName);         //①写好文件路径
      
        try {
            byte[] tempbytes = new byte[100];  //②创建数组当缓存区
            int byteread = 0;
            InputStream in  = new FileInputStream(inFile);  //③获取文件输入流
           OutputStream out = new FileOutputStream(outFile);  //④获取打开文件
            while ((byteread = in.read(tempbytes)) != -1) {//⑤每次读取100个字节进入缓存数组
        out.write(tempbytes, 0, byteread);      //⑥立即从缓冲区中写入文件,每次写入的字节数必须和实际读到的字节数相同
            }
        } catch (Exception e1) {
            e1.printStackTrace();
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e1) {
                }
                try {
                    out.close();
                } catch (IOException e1) {
                }
            }
        }
    }
 

3.BufferedInputStream 和 BufferedOutputStream

BufferedInputStream是带缓冲区的输入流,它继承于FilterInputStream。默认缓冲区大小是8M,能够减少访问磁盘的次数,提高文件读取性能。

BufferedOutputStream是带缓冲区的输出流,它继承于FilterOutputStream,能够提高文件的写入效率。

它们提供的“缓冲功能”本质上是通过一个内部缓冲区数组实现的。例如,在新建某输入流对应的BufferedInputStream后,当我们通过read()读取输入流的数据时,BufferedInputStream会将该输入流的数据分批的填入到缓冲区中。每当缓冲区中的数据被读完之后,输入流会再次填充数据缓冲区;如此反复,直到我们读完输入流数据。(其实书写的步骤是一样的,内在区别)

public static void readAndWrite(String[] args) {    
    try {
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("f:/a.mp3"));
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("f:/b.mp3"));
        byte[] b=new byte[1024];
        int len=0;
        while(-1!= (len = bis.read(b, 0, b.length))) {
            bos.write(b, 0, len);
        }
 
    } catch (FileNotFoundException e) {
        System.out.println("文件找不到");
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }finally{
        if (null! = bos){
            bos.close();
        }
        if (null! = bis){
            bis.close();
        }
    }
}
4.DataInputStream和DataOutputStream

DataInputStream 是数据输入流,它继承于FilterInputStream。
DataOutputStream 是数据输出流,它继承于FilterOutputStream。
二者配合使用,“允许应用程序以与机器无关方式从底层输入流中读写基本 Java 数据类型”。

/**
 * DataOutputStream的API测试函数
 */
private static void testDataOutputStream() {
    DataOutputStream out = null;
    try {
        File file = new File("file.txt");
        out = new DataOutputStream(new FileOutputStream(file));
        
        out.writeBoolean(true);
        out.writeByte((byte)0x41);
        out.writeChar((char)0x4243);
        out.writeShort((short)0x4445);
        out.writeInt(0x12345678);
        out.writeLong(0x0FEDCBA987654321L);
        out.writeUTF("abcdefg");
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (SecurityException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            out.close();
        } catch(IOException e) {
        }
    }
}

/**
 * DataInputStream的API测试函数
 */
private static void testDataInputStream() {
    DataInputStream in = null;
    try {
        File file = new File("file.txt");
        in = new DataInputStream(new FileInputStream(file));
 
        System.out.printf("byteToHexString(0x8F):0x%s\n", byteToHexString((byte)0x8F));
        System.out.printf("charToHexString(0x8FCF):0x%s\n", charToHexString((char)0x8FCF));
        System.out.printf("readBoolean():%s\n", in.readBoolean());
        System.out.printf("readByte():0x%s\n", byteToHexString(in.readByte()));
        System.out.printf("readChar():0x%s\n", charToHexString(in.readChar()));
        System.out.printf("readShort():0x%s\n", shortToHexString(in.readShort()));
        System.out.printf("readInt():0x%s\n", Integer.toHexString(in.readInt()));
        System.out.printf("readLong():0x%s\n", Long.toHexString(in.readLong()));
        System.out.printf("readUTF():%s\n", in.readUTF());
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (SecurityException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            in.close();
        } catch(IOException e) {
        }
    }



二.字符流


二、字符流

1.InputStreamReader 和 OutputStreamWriter

InputStreamReader 和 OutputStreamWriter为各种输入输出字符流的基类,所有字符流都继承这两个基类。实际上,这两个类内部各自持有一个inputStream 和 outputStream对象,相当于是对inputStream 和 outputStream进行了包装,将输入字节流转换成字符流,便于读写操作。

/**
 * 以字符为单位读取文件,常用于读文本,数字等类型的文件
 */
 //用reader的read()一次只读一个字符
 
public static void readFileByChars(String fileName) {
    File file = new File(fileName);
    Reader reader = null;
    try {
        System.out.println("以字符为单位读取文件内容,一次读一个字节:");
        //1. 一次读一个字符
reader = new InputStreamReader(new FileInputStream(file));//可以是任意的InputStream类,不一定必须FileInputStream
        int tempchar;
        while ((tempchar = reader.read()) != -1) {
            if (((char) tempchar) != '\r') {
                System.out.print((char) tempchar);
            }
        }
        reader.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
 ----------------------------------------------------------------------------------------
 //用用reader的read(b)一次读数组大小个字符
    try {
        System.out.println("以字符为单位读取文件内容,一次读多个字节:");
        //2. 一次读多个字符
        char[] tempchars = new char[30];
        int charread = 0;
        reader = new InputStreamReader(new FileInputStream(fileName));
        while ((charread = reader.read(tempchars)) != -1) {
            for (int i = 0; i < charread; i++) {
                if (tempchars[i] != '\r') {
                    System.out.print(tempchars[i]);
                }
            }
        }
 
    } catch (Exception e1) {
        e1.printStackTrace();
    } finally {
        if (reader != null) {
            try {
                reader.close();
            } catch (IOException e1) {
            }
        }
    }
}
2.FileReader 和 FileWriter

FileReader 和 FileWriter分别继承自 inputStreamReader 和 outputStreamWriter。它是对读取文件操作系统的封装,所有的读写都是直接操作文件系统。因此如果是频繁读写操作,不建议使用FileReader 和 FileWriter,性能将会非常低,这时你需要使用BufferedReader。

(1)FileWriter类

构造方法:

FileWriter fw = new FileWriter(String fileName); //创建字符输出流类对象和已存在的文件相关联。文件不存在的话,并创建。
FileWriter fw = new FileWriter(String fileName,boolean append); //创建字符输出流类对象和已存在的文件相关联,并设置该该流对文件的操作是否为续写。

主要方法:

write(char[] buffer, int offset, int count) //将字符数组写入,offset为数组的起始地址,count为需要写入的字符数
void write(String str) //写入字符串。当执行完此方法后,字符数据还并没有写入到目的文件中去。此时字符数据会保存在缓冲区中。
viod flush() //刷新该流中的缓冲。将缓冲区中的字符数据保存到目的文件中去。
viod close() //关闭此流。在关闭前会先刷新此流的缓冲区。在关闭后,再写入或者刷新的话,会抛IOException异常。

(2)FileReader类

构造方法:

FileReader fr = new FileReader(String fileName); //使用带有指定文件的String参数的构造方法。创建该输入流对象。并关联源文件。

主要方法:

int read(); // 读取单个字符。返回作为整数读取的字符,如果已达到流末尾,则返回 -1。
int read(char []cbuf); //将字符读入数组。返回读取的字符数。如果已经到达尾部,则返回-1。
void close(); //关闭此流对象。释放与之关联的所有资源。

public static void readAndWrite() {
    FileReader fr = null;
    FileWriter fw = null;
    try {
        fr = new FileReader("C:\\my.txt");
        fw = new FileWriter("D:\\you.txt");
        //1.读一个字符,写一个字符方法
        int ch = 0;  
        while ((ch = fr.read()) != -1) {  
            fw.write(ch);  
        } 
        
        //2.读一个数组大小,写一个数组大小方法。
        char []buf = new char[1024];
        int len = 0;
        while((len = fr.read(buf)) != -1){
            fw.write(buf, 0, len);              
        }
        
        //3.直接写一个字符串
        fw.write("hello world!");
    } catch (Exception e) {
        System.out.println(e.toString());
    } finally {
        if (fr != null)
            try {
                fr.close();
            } catch (Exception e2) {
                throw new RuntimeException("关闭失败!");
            }
        if (fw != null){
            try {
                fw.close();
            } catch (IOException e) {
                throw new RuntimeException("关闭失败!");
            }
        }
    }
}
4.BufferedReader 和 BufferedWriter

(1)BufferedReader和BufferedWriter类各拥有8192字符的缓冲区。当BufferedReader在读取文本文件时,会先尽量从文件中读入字符数据并置入缓冲区,而之后若使用read()方法,会先从缓冲区中进行读取。如果缓冲区数据不足,才会再从文件中读取,使用BufferedWriter时,写入的数据并不会先输出到目的地,而是先存储至缓冲区中。如果缓冲区中的数据满了,才会一次对目的地进行写出。
(2)从标准输入流System.in中直接读取使用者输入时,使用者每输入一个字符,System.in就读取一个字符。为了能一次读取一行使用者的输入,使用了BufferedReader来对使用者输入的字符进行缓冲。readLine()方法会在读取到使用者的换行字符时,再一次将整行字符串传入

/**
 * 以行为单位读取文件,常用于读面向行的格式化文件
 */
public static void readWithBufferedReader(String fileName) {
    File file = new File(fileName);
    BufferedReader reader = null;
    try {
        reader = new BufferedReader(new FileReader(file));
        String tempString = null;
        int line = 1;
        // 一次读入一行,直到读入null为文件结束
        while ((tempString = reader.readLine()) != null) {
            System.out.println("line " + line + ": " + tempString);
            line++;
        }
        reader.close();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (reader != null) {
            try {
                reader.close();
            } catch (IOException e1) {
            }
        }
    }
}

System.in是一个位流,为了转换为字符流,可使用InputStreamReader为其进行字符转换,然后再使用BufferedReader为其增加缓冲功能。例如:

public static void readAndWrite() {    try {        //缓冲System.in输入流        //System.in是位流,可以通过InputStreamReader将其转换为字符流        BufferedReader bufReader = new BufferedReader(new InputStreamReader(System.in));        //缓冲FileWriter        BufferedWriter bufWriter = new BufferedWriter(new FileWriter("/sdcard/log/test.txt"));         String input = null;        //每读一行进行一次写入动作        while(!(input = bufReader.readLine())) {            bufWriter.write(input);            //newLine()方法写入与操作系统相依的换行字符,依执行环境当时的OS来决定该输出那种换行字符            bufWriter.newLine();        }        bufReader.close();        bufWriter.close();    } catch(ArrayIndexOutOfBoundsException e) {        System.out.println("没有指定文件");    } catch(IOException e) {        e.printStackTrace();    }}

三、 RandomAccessFile

RandomAccessFile不属于InputStream和OutputStream类系的。实际上,它和这两个类系毫不相干,甚至不使用InputStream和OutputStream类中已经存在的任何功能;它是一个完全独立的类。这可能是因为RandomAccessFile能在文件里面前后移动,所以它的行为与其它的I/O类有些根本性的不同。
RandomAccessFile的基本功能有:定位用的getFilePointer( ),在文件里移动用的seek( ),以及判断文件大小的length( )、skipBytes()跳过多少字节数。此外,它的构造函数还要一个表示以只读方式(“r”),还是以读写方式(“rw”)打开文件的参数。实际它和C的fopen()一模一样,都是直接对文件句柄操作。

/** * 随机读取文件内容 */public static void readFileByRandomAccess(String fileName) {    RandomAccessFile randomFile = null;    try {        // 打开一个随机访问文件流,按只读方式        randomFile = new RandomAccessFile(fileName, "rw");        long fileLength = randomFile.length();         // 设置读写文件的起始位置        randomFile.seek(0);         // 一次读取多个数据        byte[] bytes = new byte[10];        int byteread = 0;        while ((byteread = randomFile.read(bytes)) != -1) {            System.out.write(bytes, 0, byteread);        }        //一次写入多个数据        randomFile.write(bytes);                // 一次读取单个数据        randomFile()        // 一次写入单个数据        randomFile.writeDouble(47.0001);       } catch (IOException e) {        e.printStackTrace();    } finally {        if (randomFile != null) {            try {                randomFile.close();            } catch (IOException e1) {            }        }    }}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值