IO流学习

参考链接:
https://www.cnblogs.com/QQ846300233/p/6046388.html
https://www.jianshu.com/p/e1416f026c3d

IO流

流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流,流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。

字符流和字节流

根据处理数据类型的不同分为:字符流和字节流

根据数据流向不同分为:输入流和输出流

字符流的由来: 因为数据编码的不同,而有了对字符进行高效操作的流对象。本质其实就是基于字节流读取时,去查了指定的码表。字节流和字符流的区别:

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

(2)处理对象不同:字节流能处理所有类型的数据(如图片、avi等),而字符流只能处理字符类型的数据。

(3)字节流在操作的时候本身是不会用到缓冲区的,是文件本身的直接操作的;而字符流在操作的时候下后是会用到缓冲区的,是通过缓冲区来操作文件,我们将在下面验证这一点。

结论:优先选用字节流。首先因为硬盘上的所有文件都是以字节的形式进行传输或者保存的,包括图片等内容。但是字符只是在内存中才会形成的,所以在开发中,字节流使用广泛。

输入字节流InputStream

InputStream 是所有的输入字节流的父类,它是一个抽象类。

过程例如:

  • 写入数据–>内存中字符数据–>字符流–>OutputStreamWriter–>字节流–>文件保存(或网络传输)
  • 读取数据<–内存中的字符数据<–字符流<–InputStreamReader<–字节流<–文件保存(或网络传输)
    不管如何操作,最终都是以字节的形式保存在文件中或进行网络传输。

ByteArrayInputStream、StringBufferInputStream、FileInputStream 是三种基本的实体流,它们分别从Byte 数组、StringBuffer、和本地文件中读取数据。PipedInputStream 是从与其它线程共用的管道中读取数据。

ObjectInputStream 和所有FilterInputStream的子类都是装饰流(装饰器模式的主角)。意思是FileInputStream类可以通过一个String路径名创建一个对象,FileInputStream(String name)。而DataInputStream必须装饰一个类才能返回一个对象,DataInputStream(InputStream in)。

FilterInputStream栗子

public class FileipsDemo {
    public static void main(String[] args) throws IOException {
     String filepath="C:\\Users\\songmingcheng\\Desktop\\排查交叉口.txt";
        File f=new File(filepath);
        InputStream ips= new FileInputStream(f);
        byte[] getdata = new byte[1024];
        //ips.read(getdata);
        //ips.close();
        //System.out.println(new String(getdata));
        //该示例中由于getdata字节数组长度为1024,如果文件较小,则会有大量填充空格。
       byte[] getdata2=new byte[(int) f.length()];
      //  ips.read(getdata2);
      //  ips.close();
     //   System.out.println(f.length()+"--------"+new String(getdata2));
        //注意:上面的几个例子都是在知道文件的内容多大,然后才展开的,有时候我们不知道文件有多大,这种情况下,我们需要判断是否独到文件的末尾。
         int count=0;
         int tmp;
        while((tmp=ips.read())!=-1){
            getdata2[count++]= (byte) tmp;
        }
        System.out.println(f.length()+"--------"+new String(getdata2));
    }
}

DataInputStream栗子:

DataInputStream 继承于FilterInputStream

public class DataOutputStreamDemo{
   public static void main(String[] args) throws IOException{
       File file = new File("d:" + File.separator +"hello.txt");
       //以其它流对象(实体流对象或装饰流对象,此处以FileInput)为基础建立的流类
       DataInputStream input = new DataInputStream(new FileInputStream(file));
       
       char[] ch = new char[10];
       int count = 0;
       char temp;
       while((temp = input.readChar()) != 'C'){
           ch[count++] = temp;
       }
       System.out.println(ch);
    }
}

Java装饰流:

按照流是否直接连接实际数据源,例如文件,网络,字节数组等,可以将流分为实体流和装饰流两大类。

  • 实体流指直接连接数据源的流类,如FileInputStream/FileOutputStream和FileReader/FileWriter,该类流直接实现数据源转换为流对象,实体流类均可单独使用。
  • 装饰流不直接连接数据源,而是以其它流对象(实体流对象或装饰流对象)为基础建立的流类,该类流实现了将实体流中的数据进行转换,增强了流的读写能力,常用的有DataInputStream/DataOutputStream和BufferedReader/BufferedWriter等,装饰流不可以单独使用,必须配合实体流或装饰流使用。
  • 由于装饰流都是在已有流对象基础上进行创建的,所以这种创建流的方式被称作“流的嵌套”。装饰流不改变原来实体流对象中的数据内容,只是进行了一些功能的增强。

回退流

在JAVA IO中所有的数据都是采用顺序的读取方式,即对于一个输入流来讲都是采用从头到尾的顺序读取的,如果在输入流中某个不需要的内容被读取进来,则只能通过程序将这些不需要的内容处理掉,为了解决这样的处理问题,在JAVA中提供了一种回退输入流(PushbackInputStream、PushbackReader),可以把读取进来的某些数据重新回退到输入流的缓冲区之中。

PushbackInputStream类的常用方法
1、public PushbackInputStream(InputStream in) 构造方法 将输入流放入到回退流之中。
2、public int read() throws IOException 普通 读取数据。
3、public int read(byte[] b,int off,int len) throws IOException 普通方法 读取指定范围的数据。
4、public void unread(int b) throws IOException 普通方法 回退一个数据到缓冲区前面。
5、public void unread(byte[] b) throws IOException 普通方法 回退一组数据到缓冲区前面。
6、public void unread(byte[] b,int off,int len) throws IOException 普通方法 回退指定范围的一组数据到缓冲区前面。

一个栗子:

public class pushbackDemo {
    public static void main(String[] args) throws IOException {
        String pushstr="www.baidu.com";
        //定义内存输入流
        ByteArrayInputStream bai=new ByteArrayInputStream(pushstr.getBytes());
        //定义回退流对象
        PushbackInputStream pushbackInputStream= new PushbackInputStream(bai);
        int tmp=0;
        while((tmp=pushbackInputStream.read())!=-1){ //一个个读取内容
            if(tmp=='.'){
                pushbackInputStream.unread(tmp);//放回到缓冲区
                tmp=pushbackInputStream.read();
                System.out.print("(退回:"+(char)tmp+")");
            }else {
                System.out.print((char) tmp);
            }
        }
    }
}

管道流

管道流是用来在多个线程之间进行信息传递的Java流
管道流分为字节流管道流和字符管道流
字节管道流:PipedOutputStream 和 PipedInputStream。
字符管道流:PipedWriter 和 PipedReader。
PipedOutputStream、PipedWriter 是写入者/生产者/发送者;
PipedInputStream、PipedReader 是读取者/消费者/接收者。

如果要想进行管道输出,则必须把输出流连在输入流之上,在PipeOutputStream上有如下方法用于连接管道。

//PipeOutputStream输出方法:
void write(byte[] b, int off, int len) 
          将 len 字节从初始偏移量为 off 的指定 byte 数组写入该管道输出流
//PipeInputStream输入方法:读取文件的方法,将连接的PipeOutputStream对象实例的输入流的数据,通过read方法,把内容读取到数组中。
 int read(byte[] b, int off, int len) 
          将最多 len 个数据字节从此管道输入流读入 byte 数组。 

字符管道流原理跟字节管道流一样,只不过底层一个是 byte 数组存储 一个是 char 数组存储的。

java的管道输入与输出实际上使用的是一个循环缓冲数来实现的

  • 输入流PipedInputStream从这个循环缓冲数组中读数据,
  • 输出流PipedOutputStream往这个循环缓冲数组中写入数据。
  • 当这个缓冲数组已满的时候,输出流PipedOutputStream所在的线程将阻塞;当这个缓冲数组为空的时候,输入流PipedInputStream所在的线程将阻塞。

在使用管道流之前,需要注意以下要点:

  • 管道流仅用于多个线程之间传递信息,若用在同一个线程中可能会造成死锁;
  • 管道流的输入输出是成对的,一个输出流只能对应一个输入流,使用构造函数或者connect函数进行连接;
  • 一对管道流包含一个缓冲区,其默认值为1024个字节,若要改变缓冲区大小,可以使用带有参数的构造函数;
  • 管道的读写操作是互相阻塞的,当缓冲区为空时,读操作阻塞;当缓冲区满时,写操作阻塞;
  • 管道依附于线程,因此若线程结束,则虽然管道流对象还在,仍然会报错“read dead end”;
  • 管道流的读取方法与普通流不同,只有输出流正确close时,输出流才能读到-1值。
public class PipedStreamDemo {
    public static void main(String[] args) {
        //创建一个线程池
        ExecutorService executorService = Executors.newCachedThreadPool();

        try {
            //创建输入和输出管道流
            PipedOutputStream pos = new PipedOutputStream();
            PipedInputStream pis = new PipedInputStream(pos);

            //创建发送线程和接收线程
            Sender sender = new Sender(pos);
            Reciever reciever = new Reciever(pis);

            //提交给线程池运行发送线程和接收线程
            executorService.execute(sender);
            executorService.execute(reciever);
        } catch (IOException e) {
            e.printStackTrace();
        }
        //通知线程池,不再接受新的任务,并执行完成当前正在运行的线程后关闭线程池。
        executorService.shutdown();
        try {
            //shutdown 后可能正在运行的线程很长时间都运行不完成,这里设置超过1小时,强制执行 Interruptor 结束线程。
            executorService.awaitTermination(1, TimeUnit.HOURS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    static class Sender extends Thread {
        private PipedOutputStream pos;

        public Sender(PipedOutputStream pos) {
            super();
            this.pos = pos;
        }

        @Override
        public void run() {
            try {
                String s = "hello world, amazing java !";
                System.out.println("Sender:" + s);
                byte[] buf = s.getBytes();
                pos.write(buf, 0, buf.length);
                pos.close();
                TimeUnit.SECONDS.sleep(5);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    static class Reciever extends Thread {
        private PipedInputStream pis;

        public Reciever(PipedInputStream pis) {
            super();
            this.pis = pis;
        }

        @Override
        public void run() {
            try {
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                byte[] buf = new byte[1024];
                int len = 0;
                while ((len = pis.read(buf)) != -1) {
                    baos.write(buf, 0, len);
                }
                byte[] result = baos.toByteArray();
                String s = new String(result, 0, result.length);
                System.out.println("Reciever:" + s);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

输出字节流OutputStream

OutputStream 是所有的输出字节流的父类,它是一个抽象类。

ByteArrayOutputStream、FileOutputStream是两种基本的实体流,它们分别向Byte 数组、和本地文件中写入数据。PipedOutputStream 是向与其它线程共用的管道中写入数据,

ObjectOutputStream 和所有FilterOutputStream的子类都是装饰流。具体例子跟InputStream是对应的。

FileOutputStream栗子:

public class fileopsDemo {
    public static void main(String[] args) throws IOException {
        String outFilePath="C:\\Users\\songmingcheng\\Desktop\\out1.txt";
        File f= new File(outFilePath);
        FileOutputStream ops = new FileOutputStream(f);
        String outText= "皇马买内马尔吧!!!不要博格巴";
        byte[] sendData=outText.getBytes();
        ops.write(sendData);
        ops.close();
    }
}

向文件中追加新内容:

public class fileopsDemo {
    public static void main(String[] args) throws IOException {
        String outFilePath="C:\\Users\\songmingcheng\\Desktop\\out1.txt";
        File f= new File(outFilePath);
        FileOutputStream ops = new FileOutputStream(f,true);//true表示追加模式,否则为覆盖
        String outText= "皇马买范德维尔也可以!!!不要博格巴";
        byte[] sendData=outText.getBytes();
        ops.write(sendData);
        ops.close();
    }
}

DataOutputStream类示例:

import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
public class DataOutputStreamDemo{
   public static void main(String[] args) throws IOException{
       File file = new File("d:" + File.separator +"hello.txt");
       char[] ch = { 'A', 'B', 'C' };
       DataOutputStream out = null;
       out = new DataOutputStream(new FileOutputStream(file));
       for(char temp : ch){
           out.writeChar(temp);
       }
       out.close();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值