黑马程序员_Java基础_IO流_打印流,合并流,分割文件,对象持久化,管道流...其他常用流

  一,IO的打印流: PrintStream PrintWriter

        public PrintWriter(OutputStream outboolean autoFlush)

    该打印流的强大之处在于可以提供自动刷新功能,可以直接使用println方法将数据打印到控制台或某个指定的文件,之前都是使用Write()方法,然后刷新流,将数据刷新到内存中,现在不用手动刷新了。

 

需求:通过键盘录入数据,打印到控制台,要求使用打印流(注意与之前的需求的区别)


import java.io.*;
public class PrintStreamDemo
{
    public static void main(String[] args) throws IOException {
        BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
    //  PrintWriter pw = new PrintWriter(System.out,true);
    //将键盘输入的数据存储到文件中;
        //PrintWriter pw = new PrintWriter("haha.txt");//如果参数是文件名,则不能实现自动刷新功能,所以可以:
        PrintWriter pw = new PrintWriter(new FileWriter("haha.txt"),true);
        String line = null;
        while ((line=bufr.readLine())!=null)
        {
            if("over".equals(line))
                break;
            //pw.write(line.toUpperCase());//不能换行,而newLine是BufferedWriter的方法,可以用println
            pw.println(line.toUpperCase());
            //pw.flush();//PrintWriter类中提供了自动刷新的办法PrintWriter(OutputStream out, boolean autoFlush) 
                    //只需要在构造函数中在加入一个参数即可
        }
        bufr.close();
        pw.close();
    }
}

二,IO流——合并流:SequenceInputStream

    SequenceInputStream:合并流,功能是将多个流合并到一个流中对数据进行操作,而传统的单个流中只能对单个流中的数据操作;传统单个流中开始是0,结束是-1.而该类建立的对象可以将第一个流的开始0做为开始标志,将最后一个流的结束-1作为结束标志。

 

需求:将3个文件合并成一个文件,并且将三个文件中的数据合并写到一个文件中。


import java.io.*;
import java.util.*;
public class SequenDemo
{
    public static void main(String[] args) throws IOException {
        //Enumeration只有在Vector中才有;
        Vector<FileInputStream> vc = new Vector<FileInputStream>();
        vc.add(new FileInputStream("E:\\java\\day20\\Demo\\1.txt"));
        vc.add(new FileInputStream("E:\\java\\day20\\Demo\\2.txt"));
        vc.add(new FileInputStream("E:\\java\\day20\\Demo\\3.txt"));
        Enumeration<FileInputStream> en = vc.elements();
        SequenceInputStream sis = new SequenceInputStream(en);
        FileOutputStream fos = new FileOutputStream("E:\\java\\day20\\Demo\\4.txt");
        int len = 0;
        byte[] by = new byte[1024];
        while ((len = sis.read(by)) != -1)
        {
            fos.write(by,0,len);
        }
        sis.close();
        fos.close();
    }
}

三,分割文件

既然可以合并,就可以将流文件切割。

 

需求:讲一个图片文件进行切割,切割后重新合并;

分析:一个完整的图片进行切割,可以选择每次切割成一兆,自定义大小,随便什么都可以。切割时可以用字节流进行切割,将切割后的字节流数据存储到文件中,为了不让用户点击,所以自己可以起一个后缀名。每次循环读取存储到指定文件中后要关闭次流,第二次重新新建一个新的流,完成第二次读取存储操作,这样才能得到几个切割后的文件。


import java.io.*;
import java.util.*;
public class SplitFile
{
    public static void main(String[] args) throws IOException {
        split();
        Sequen();
    }
    //将刚刚切割的图片文件进行合并方法;
    public static void Sequen() throws IOException {
        ArrayList<FileInputStream> list = new ArrayList<>();//将切割的字节流作为元素存储到集合中
        for (int i=0;i<6 ;i++ )//6是切割后的文件的数目,就是要合并的数目;
        {
            list.add(new FileInputStream("e:\\java\\day20\\jpg\\" + (i+1) + ".part"));
        }
        final Iterator<FileInputStream> it = list.iterator();
        //由于SequenceInputStream类只能用Enumeration进行迭代操作,而Enumeration只能由Vector对象引用
        //它的效率比较低,所以采用集合迭代的方式;所以重写Enumeration的方法;
        Enumeration<FileInputStream> en = new Enumeration<FileInputStream>() {
            public boolean hasMoreElements() {
                return it.hasNext();
            }
            public FileInputStream nextElement() {
                return it.next();
            }
        };
        SequenceInputStream sis = new SequenceInputStream(en);//合并成一个文件后要将其存储到目的文件中,所以新建
                                //输出流存储到目标文件中,看是否可见;
        FileOutputStream fos = new FileOutputStream("E:\\java\\day20\\jpg\\haha.jpg");
        int len = 0;
        byte[] b = new byte[1024];
        while ((len = sis.read(b))!=-1)
        {
            fos.write(b,0,len);
        }
        sis.close();
        fos.close();
    }
    //切割方法:
    public static void split() throws IOException {
        //创建一个流于文件关联;
        FileInputStream fis = new FileInputStream("C:\\1.jpg");
        FileOutputStream fos = null;
        //创建一个目的文件,将读取到的流文件分别存储到指定目的文件中;
        int count = 1;
        int len = 0;
        byte[] b = new byte[1024*100];//每次存取一兆,即分割出来的单个文件时一兆;
        while ((len = fis.read(b))!=-1)
        {
            fos = new FileOutputStream("e:\\java\\day20\\jpg\\" + (count++) + ".part");
            fos.write(b,0,len);
            fos.close();//关闭本次输出流,以确保第二次以后重新创建输出流,新建不同的单个文件;
        }
        fis.close();
    }
}

四,对象的持久化存储

ObjectOutputStream(OutputStream out)

将内存中创建的对象存储到硬盘中的文件中,以便于文件传输之后还能访问到该对象。

对应的读取流是ObjectInputStream

注意:这两个方法必须成对使用,意思是使用ObjectOutputStream存储的文件必须使用ObjectInputStream读取要存储的对象必须实现Serializable接口,目的就是获得一个标记,以便每次读取到的是上一次存储的。也可以自己定义标识号。

 

需求:将一个Person对象存储到当前目录下的一个文件中,实现持久化存储,然后在从该文件中读取出里面的对象,打印在控制台,对比打印出来的对象是否是自己new的那个对象。


import java.io.*;
public class ObjectStreamDemo
{
    public static void main(String[] args) throws Exception {
        //writeObj();
        readObj();
    }
    
    //读取存取的持久化对象
    public static void readObj() throws Exception {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ttt.obj"));
        Person p = (Person)ois.readObject();
        sop(p);
        ois.close();
    }
    
    //写入持久化对象
    public static void writeObj() throws Exception {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ttt.obj"));
        oos.writeObject(new Person("zhangsan",15,"zhongguo"));//将person这个对象写入到ttt.obj中;
        oos.close();
    }
    public static void sop(Object obj) {
        System.out.println(obj);
    }
}
class Person implements Serializable//要启用序列化功能必须实现该接口;
{
    //在实现Serializable接口的时候,每次编译后会自动生成一个序列号serialVersionUID,都不一样
    //可能jvm的不同都会导致这个序列号的不同,所以为了能在不同平台运行该类,则可以自己定义序列号
    //static final long serialVersionUID = 42L;
    static final long serialVersionUID = 42L;//给该类设置一个固定标示号,就是修改类中数据,编译后还是能读取
    String name;
    static String country = "USA";//静态变量创建类之前就存在,所以初始化的时候,不会被改变;
    transient int age; //变量被transient修饰以后,气其值也不会再被序列化,保证该值在堆内存中存在,而不再文件中存在;
    Person(String name,int age,String country) {
        this.name = name;
        this.age = age;
        this.country = country;
    }
    public String toString() {
        return name + ":::" + age + "::" + country;
    }
}

五,管道流

管道流:pipedInputStreamPipedOutputStream,管道输入流和管道输出流;可以将管道输出流连接到管道输入流来创建通信管道。

(注意:集合中涉及到IO流的就是Properties类,在IO流中涉及到多线程的就是管道流)

1public class PipedInputStreamextends InputStream

管道输入流应该连接到管道输出流;管道输入流提供要写入管道输出流的所有数据字节。通常,数据由某个线程从 PipedInputStream 对象读取,并由其他线程将其写入到相应的 PipedOutputStream。不建议对这两个对象尝试使用单个线程,因为这样可能死锁线程。

2public class PipedOutputStreamextends OutputStream

可以将管道输出流连接到管道输入流来创建通信管道。管道输出流是管道的发送端。通常,数据由某个线程写入 PipedOutputStream 对象,并由其他线程从连接的 PipedInputStream 读取。不建议对这两个对象尝试使用单个线程,因为这样可能会造成该线程死锁。

 

需求:管道流的使用方法.


import java.io.*;
public class PipedStreamDemo
{
    public static void main(String[] args) throws IOException {
        PipedInputStream pis = new PipedInputStream();
        PipedOutputStream pos = new PipedOutputStream();
        pos.connect(pis);//管道流只能管道输出流关联管道读取流;在PipedInputStream类中无次方法;
        Write w = new Write(pos);
        Read r = new Read(pis);
        new Thread(w).start();
        new Thread(r).start();
    }
}
class Write implements Runnable
{
    private PipedOutputStream out;
    Write(PipedOutputStream out) {
        this.out = out;
    }
    public void run() {
        try
        {
            System.out.println("开始写入数据,等待6秒");
            Thread.sleep(6000);
            out.write("kaishixieru".getBytes());
            out.close();
        }
        catch (Exception e)
        {
            throw new RuntimeException("管道输出流创建失败!");
        }
    }
}
class Read implements Runnable
{
    private PipedInputStream in;
    Read(PipedInputStream in) {
        this.in = in;
    }
    public void run() {
        try
        {
            System.out.println("读取流开始运行");
            byte[] buf = new byte[1024];
            int len = 0;
            len = in.read(buf);
            String s = new String(buf,0,len);
            System.out.println(s);
            in.close();
        }
        catch (IOException e)
        {
            throw new RuntimeException("管道读取流创建失败!");
        }
    }
}

六,随机访问文件的读写类:RandomAccessFile

1RandomAccessFile

该类不算是IO体系的子类。而是直接继承自Object

但是他是IO包中的成员,因为他具备读和写的功能。内部封装了一个数组,而且通过指针对数组元素进行操作。可以通过getFilePointer获取指针的位置,同时可以通过seek改变指针的位置。

2,原理:其实完成读写的原理就是内部封装了字节输入流和输出流。

3,特点:

1)通过构造函数可以看出该类只能操作文件。而且操作文件还有模式:只读:r ;读写:rw  ...

2)如果模式为只读,不会创建文件,回去读取一个已存在的文件,如果该文件不存在则会出现异常,如果模式为可读可写,那么操作文件不存在会创建,存在也不会覆盖;

3)而且该对象的构造函数要操作的文件如果不存在,则会自动创建,如果存在不会覆盖;

4RandomAccessFile是可以实现数据的分段写入。可以先把数据分段以后,一个线程负责一段,效率会提高,这也就是下载软件的多线程下载原理,多线程下载时同时向硬盘写数据,可同时写入硬盘。存储完后是一个完整文件。

 

4seek方法可以按照指定的位置进行读取和写入,是重点之处;我们可以把数据都按照一定的字节存储,就可以变成有规律的数据段了,就可以随意指定位置进行操作;

需求:向一个文件中随机写入数据,然后随机读取数据。

分析:使用RandomAccessFile对象可以通过指针的移动,将数据写入指定位置,也可以通过指针的移动读取指定位置的数据。


import java.io.*;
public class RandomAccessFileDemo
{
    public static void main(String[] args) throws Exception {
        //writeR();
        //readR();
        readR2();
    }
    public static void readR2() throws Exception {
        RandomAccessFile raf = new RandomAccessFile("demo.txt","rw");
        raf.seek(8*3);//第一个位置存储的是张三97岁,第二个位置存储的是李四99岁,这里是指定跳过第三位,
                    //存储到第四位;
                    //如果seek(8*0)是跳转到第一位将周红103写到第一位,因为有第一位,所以第一位的张三97会往后移
                    //不会被覆盖;这就是它的强大之处。
        raf.write("周红".getBytes());
        raf.writeInt(103);
        raf.close();
    }
    public static void readR() throws Exception {
        RandomAccessFile raf = new RandomAccessFile("demo.txt","r");
        //调整对象中的指针-->特点:随机访问的文件可以通过指针的偏移取到任意一段数据,
        //前提是数据存储的必须要有规律,比如存储的姓名年龄都是八个字节一存储,所以是
        //有规律的,可以以八个字节一位取。
        //raf.seek(8);
        //从第八个字节开始取,也就是从王五开始取;
        //跳过指定的字节数取数据段;只可往下跳,不能往回走;
        raf.skipBytes(8);//和raf.seek(8)结果一样,但是seek可以随便取,skipBytes只能往前跳不能往后,所以skipBytes用的少,一般都用seek()方法
        byte[] b = new byte[4];
        raf.read(b);
        String s = new String(b);
        System.out.println(s);
        int age = raf.readInt();
        System.out.println(age);
        raf.close();
    }
    public static void writeR() throws Exception {
        RandomAccessFile raf = new RandomAccessFile("demo.txt","rw");//如果模式为"r",当没有文件时不会创建,会报异常
        raf.write("李四".getBytes());//只写出去了int型的低八位,一个字节
        //raf.write(287);//这里打印的是97对应的ASCII码值;
        raf.writeInt(97);//按四个字节将 int 写入该文件,先写高字节,所以文件中a前面空了三个字节
        raf.write("王五".getBytes());
        raf.writeInt(99);
        raf.close();
    }
}

七,数据输入流和数据输出流

DataInputStreamDataOutputstream

可用与操作基本数据类型的数据流对象,凡是遇到操作基本数据类型都用它。

基本使用方法:


import java.io.*;
public class DataStreamDemo
{
    public static void main(String[] args) throws IOException {
        //dataWrite();
        //dataRead();
        //writeUtf();
        //普通方式按照UTF-8将数据写入记事本;
    /*  OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("utfGbk.txt"),"UTF-8");
        osw.write("你好");
        osw.close();  */
        readUTF();
    }
    public static void readUTF() throws IOException {
        DataInputStream dis = new DataInputStream(new FileInputStream("utf.txt"));
        String s = dis.readUTF();//如果读取的不是writeUTF写入的数据文件,则会报异常EOFException;
        System.out.println(s);
    }
    public static void writeUtf() throws IOException {
        DataOutputStream dos = new DataOutputStream(new FileOutputStream("utf.txt"));
        dos.writeUTF("你好");//使用 UTF-8 编码以与机器无关的方式将一个字符串写入该文件。
                    //默认的是GBK编码,每一个中文2字节。而UTF-8每一个中文3个字节;
                    //dos.writeUTF写入的数据,用普通的UTF-8或者GBK是不能读取出来的;
        dos.close();
    }
    public static void dataRead() throws IOException {
        DataInputStream dis = new DataInputStream(new FileInputStream("data.txt"));
        int i = dis.readInt();
        boolean b = dis.readBoolean();
        double d = dis.readDouble();
        System.out.println(i);
        System.out.println(b);
        System.out.println(d);
        dis.close();
    }
    public static void dataWrite() throws IOException {
        DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.txt"));
        dos.writeInt(125);
        dos.writeBoolean(false);
        dos.writeDouble(152.356);
        /*
        打开写入的文件,可以看到该文件是乱码,那是因为记事本是记录字符的软件,
        当读取到字节数据时会去查表,显示出字符。该文件大小是13字节,那是因为:
        整数4个字节,Boolean是1个字节,double型是8个字节;
        所以可以让他显示到控制台上,读取时也要按顺序读取;
        */
        dos.close();
    }
}

八,用于操作字节数组,字符数组,字符串数组的流对象

操作字节数组的流:

ByteArrayInputStream

ByteArrayOutputStream

操作字符数组的流:

CharArrayReader

CharArrayWriter

操作字符串数组的流:

StringReader

StringWriter

 

用于操作字节数组的流对象:

ByteArrayInputStream:在构造时要接受数据源,而且数据源是一个字节数组;

ByteArrayOutputStream:在构造的时候,不用定义数据目的,因为该对象中已经在内部封装了可变长读的字节数组,这就是数据目的地。

这两个流对象的源和目的都是内存,之前在使用流操作数据时,确定使用哪个流对象的方法是通过源和目的来确定的。

源:

键盘:System.in   硬盘:FileStream  内存:ArrayStream

目的:

键盘:System.in   硬盘:FileStream  内存:ArrayStream

基本使用方法:


import java.io.*;
public class ByteArrayStream
{
    public static void main(String[] args) throws IOException {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        //一建立数组读取流,内存中必须存在字节数组
        ByteArrayInputStream bis = new ByteArrayInputStream("sahddash".getBytes());
        int by = 0;
        while ((by = bis.read())!=-1)
        {
            bos.write(by);
        }
        System.out.println(bos.size());
        System.out.println(bos.toString());
        //bos.write(new FileOutputStream("a.txt"));
    } 
}

总结:通过字节数组操作流的基本使用,就可以明白它的原理,关于字符数组操作流和字节数组操作流的使用方法和内部原理和字节数组操作流是一样的。


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值