07、IO流(二) -- 字节流

1.2、字节流

字节流:一次读入或读出是8位二进制

8f95e300-9c0a-4ce1-82b1-3ccb0317be72

总结:字节流不需要刷新,和字节流缓冲区的区别在于读取流没有readLine()方法,写入流没有newLine()方法。

InputStream 类似于Reader,但是它是操作字节流数据的。OutputStream 类似于 Writer,但是它是操作字节流数据的。

1、FileInputStream 和 FileOutputStream

a) FileInputStream
int available()://返回写入内容的个数,包含'/r'和'/n'。老师用此方法定义一个刚刚好的字节数组 
FileInputStream fis = new FileInputStream("buf.txt");
byte[] by = new byte[fis.available()]//定义一个大小刚好的数组,这个方法对内存需求过大,慎用。 
//这样就可以避免通过while循环来判断文件内容是否被取完。

注意:字符串转成字节才能被字节流操作,字符串.getBytes();

b) FileOutputStream
FileOutputStream fos = new FileOutputStream("stream.txt");
fos.write("abcde".getBytes());

2、 BuferedInputStream 和 BufferedOutputStream

a) BufferedInputStream

概念:和 BufferedReader 是一样的。都是装饰模式,对功能增强的缓冲区。 
//它的内部其实就是提供了一个数组。对数据进行临时存储。 
区别:和 BufferedReader 的区别在于没有readLine()方法。

b) BufferedOutputStream

概念:和 BufferedWriter 是一样的。都是装饰模式,对功能增强的缓冲区。 
区别:和BufferWriter的区别在于没有newLine()方法。

c) InputStreamReader 和 OutputStreamWriter(转换流)

InputStreamReader 和 OutputStreamWriter :他们都属于字符流体系中。

它们都是将字节流数据转换成字符流数据进行操作。

注意:由于字节流的缓冲区没有readLine和newLine方法,我们通常会将字节流通过转换流转换成字符流,再通过字符流的缓冲区进行增强操作。

//读取流程
InputStream in = System.in;
InputStreamReader isr = new InputStreamReader(in);
BufferedReader bufr = new BufferedReader(isr);
//这个时候就可以使用字符流的readLine方法了, 这个方法效率高。
//简写格式
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));

//输出流程
OutputStream out = System.out;
OutputStreamWriter osw = new OutputStreamWriter(out);
BufferedWriter bufw = new BufferedWriter(osw);
//这个时候就可以使用字符流的newLine方法了,这个方法跨平台。
//简写格式
BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));

3、IO流操作规律

a) 体系:明确数据的来源和数据将要到达的目的地。
  • 字节输入流:(InputStream)
  • 字节输出流:(OutputStream)
  • 字符输入流:(Reader)
  • 字符输出流:(Writer)
b) 明确数据源和目的地
  • 数据源:
纯文本:Reader(字符流)
非纯文本:InputStream(字节流)  
  • 数据目的地
纯文本:Writer(字符流)
非纯文本:OutputStream(字节流) 
c) 明确具体设备:
  • 数据源是从哪个设备来的(源设备)
(1)是硬盘就加FileXXX。 
(2)是键盘用System.in(是一个InputStream对象)。
(3)是内存就用数组流。 ByteArrayInputStream
(4)是网络用Socket流。
  • 目的是哪个设备:(目的设备)
(1)是硬盘就加FileXXX。 

(2)是控制台用System.out(是一个OutoutStream对象) 

(3)是内存就用数组流。 ByteArrayOutputStream

(4)是网络用Socket流。

 详情参见笔记:IO数据源和数据汇.jpg

d) 是否需要额外功能:
  • 需要高效,既是否需要使用缓冲区。是就加上Buffred。
  • 需要转换,即是否需要转换流。InputStreamReade和OutputStreamWriter。
e) 拓展
  • 当涉及到编码表的转换时,需要用到转换流。

1.3、其他常用流

a) 打印流

凡是和文件相关的流对象,都是重要的。(打印流这个对象非常重要!)

PrintWriter 与 PrintStream:可以直接操作输出流和文件。该流提供了打印方法,可以将各种数据类型的数据都原样打印。 
字节打印流:PrintStream 
构造函数可以接收的参数类型。

  • file对象(File file)
  • 字符串路径(String path)
  • 字节输出流(OutputStream)


字符打印流:PrintWriter(不管字符还是字节流,它都能打印) 
构造函数可以接收的参数类型。

  • file对象(File file)
  • 字符串路径(String path)
  • 字节输出流(OutputStream)
  • 字符输出流(Writer)
  • PrintWriter(OutputStream out, boolean Flush)://如果为 true,则 println、printf 或 format 方法将刷新输出缓冲区。
  • PrintWriter(File file, String csn)://还能操作文件对象的同时指定字符集。不带自动刷新!

总结:既能操作字符流,也能操作字节流,其中println()能自动换行,构造函数的布尔值可以选择是否自动刷新。 
注意:PrintWriter中自动刷新只是针对流而言,如果是文件则没有这个功能。但是,我们可以将文件封装到流里面,这样也能自动刷新了! 
PrintWriter out = new PrintWriter(new FileWriter("a.txt"),true);

public static void printTest(){
    BufferedReader bufr;
    PrintWriter out;
    try {
        //键盘录入
        bufr = new BufferedReader(new InputStreamReader(System.in));
        out = new PrintWriter(System.out,true);
        String line = null;
        while((line = bufr.readLine())!=null){
            if(line.equals("over"))
                break;
            out.println(line.toUpperCase());
        }
        bufr.close();
        out.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

b) 合并流

SequenceInputStream对多个流进行合并,没有对应输出流的操作,也就是说只有字节输入流。多个源对应一个目的。也就是多个流合并成一个流。 
构造函数:

SequenceInputStream(Enumeration<? extends InputStream> e)
//通过记住参数来初始化新创建的 SequenceInputStream,该参数必须是生成运行时类型为 InputStream 对象的 Enumeration 型参数。
SequenceInputStream(InputStream s1, InputStream s2)
//通过记住这两个参数来初始化新创建的 SequenceInputStream(将按顺序读取这两个参数,先读取 s1,然后读取 s2),以提供从此 SequenceInputStream 读取的字节。

Vector 有特有的枚举迭代器,通过elements()方法返回此向量的组件的枚举。让Enumeration和SequenceInputStream建立关联。

Vector<FileInputStream> v = new Vector<FileInputStream>();
v.add(new FileInputStream("d:\\1.txt"));
v.add(new FileInputStream("d:\\2.txt"));
v.add(new FileInputStream("d:\\3.txt"));
Enumeration<FileInputStream> en = v.elements();
SequenceInputStream sis = new SequenceInputStream(en);
FileOutputStream fos = new FileOutputStream("d:\\4.txt");
byte[] buf = new byte[1024];
int len = 0;
while ((len=sis.read(buf))!=-1) {
    fos.write(buf,0,len);
}
fos.close();
sis.close();

c) 分割流

//分割文件
public static void splitFile() throws IOException{
    FileInputStream fis = new FileInputStream("d:\\1.jpg");
    FileOutputStream fos = null;
    byte[] buf = new byte[1024*1024];
    int len = 0;
    //定义一个变量来让文件名变化
    int count = 1;
    //每次创建一个新流,所以每次都要关闭。
    while((len=fis.read(buf))!=-1){
        fos = new FileOutputStream("d:\\split\\"+(count++)+".part");
        fos.write(buf,0,len);
        fos.close();
    }
    fis.close();
}
//合并文件
public static void merge() throws IOException{
    ArrayList<FileInputStream> al = new ArrayList<FileInputStream>();
    for(int x=1;x<=3;x++){
        al.add(new FileInputStream("d:\\split\\"+x+".part"));
    }
        //由于ArrayList没有枚举,但是迭代器有枚举,匿名内部类访问局部变量必须final修饰。
    final Iterator<FileInputStream> it = al.iterator();
    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("d:\\split\\0.jpg");
    byte[] buf = new byte[1024];
    int len = 0;
    while((len=sis.read(buf))!=-1){
        fos.write(buf,0,len);
    }
    fos.close();
    sis.close();                                        
}

d) 对象序列化

ObjectInputStream和ObjectOutputStream可以将对象进行持久化存储和读取。

  • 被操作的对象需要实现 Serializable以启用其序列化功能。
  • Serializable是没有方法的接口(标记接口)。
  • ObjectInputStream和ObjectOutputStream必须成对使用。
  • 被序列化的类中静态成员是不能被序列化的,只有堆内存中的成员才能被序列化。
  • 不想对非静态变量进行序列化,可以使用transient修饰符修饰。(保证在值在堆内存存在,而不存在文件中)

自定义序列号UID: 
UID其实是根据类中的成员算出来的,当我们修改了被序列化的类的源代码的时候,会产生一个新的class文件, 
这样我们就不能再通过操作对象的流进行读取了,为了保证UID的一致性,我们可以自定义序列化UID号。

static final long serialVersionUID = 42L;//自定义序列化UID号。返回任意类型。

常用方法:

(1)Object readObject():从 ObjectInputStream 读取对象。
(2)void writeObject(Object obj):将指定的对象写入 ObjectOutputStream。

了解:当存储多个对象时,我们可以多次用readObject按存储先后顺序将其读出来。

class Person implements Serializable{
    String name;
    int age;
    Person(String name,int age){
        this.name = name;
        this.age = age;
    }
    public String toString(){
        return name + ":" + age;
    }
}
class ObjectStreamDemo{
    public static void main(String[] args) throws Exception{
        //writeObj();
        readObj();
    }
    //读取文件
    public static void readObj() throws Exception{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("obj.txt"));
        Person p = (Person)ois.readObject();
        System.out.println(p);
    }
    //持久化存储,写文件
    public static void writeObj() throws IOException{
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("obj.txt"));
        oos.writeObject(new Person("lisi",39));
        oos.close();
    }
}

e) 管道流

PipedInputStream 和 PipedOutputStream输入和输出可以直接进行连接,通过结合线程使用。

管道流,用于线程间的通信。一个线程的PipedInputStream对象从另外一个线程的PipedOutputStream对象读取输入。要使管道流有用,必须同时构造管道输入流和管道输出流。
管道流和读写流的区别

  • 普通的读写流,是通过读取流,将数据存进一个数组,再对数组进行操作。
  • 管道流则不同,它是让读写操作进行对接,类似于对接成一个管道。
  • 由于管道流不能分别是读还是写的操作,所以不能用单线程。
  • 根据以上特点,管道流是通过两个线程对读写进行分开操作。

小知识:集合中涉及到IO流的是Properties,IO流涉及到多线程的是管道流。 
两种连接方式:

  • 创建构造函数的时候,将对应的流传进去即可.
  • 如果是空构造函数,则通过connect(流)方法连接。

模拟流水线生产产品的同时,进行消费:

a) 生产产品的类

// 生产产品
public class Producer extends Thread {
    private PipedOutputStream pos;
    public Producer(PipedOutputStream pos) {
        this.pos = pos;
    }

    @Override
    public void run() {
        super.run();
        try {
            pos.write("Hello".getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

b) 生产同时进行消费

// 消费者
public class Consumer extends Thread {
    private PipedInputStream pis;

    public Consumer(PipedInputStream pis) {
        this.pis = pis;
    }

    @Override
    public void run() {
        super.run();
        byte[] b = new byte[100]; // 将数据保存在byte数组中
        try {
            int len = pis.read(b); // 从数组中得到实际大小。
            System.out.println(new String(b, 0, len));
            pis.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

c) 测试,此时生产的产品会在第一时间消费掉,所以管道流可以用来进程通信。

public class PipedStreamTest {
    public static void main(String[] args) {
        PipedOutputStream pos = new PipedOutputStream();
        PipedInputStream pis = new PipedInputStream();
        try {
            pos.connect(pis);// 连接管道
            new Producer(pos).start();// 启动线程
            new Consumer(pis).start();// 启动线程
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

f) 随机流

RandomAccessFile 
|--DataInput 
|--DataOutput

  • 随机访问文件,自身具备读写方法。
  • 通过skipBytes(int x),seek(int x)来达到随机访问。

概述:

  • 该类不算是IO体系的子类。(内部封装了流)而是直接继承Object类。
  • 但是它是IO包中的成员,因为它具备读和写的功能。
  • 内部封装了一个数组,而且通过指针对数组中的元素进行操作。
  • 可以通过getFilePointer获取指针位置。同时可以通过seek改变指针的位置。
  • 其实完成读写的原理就是内部封装了字节输入流和字节输出流。

构造方法:

  • andomAccessFile(File file, String mode)//参数是文件和模式。
  • RandomAccessFile(String name, String mode)//参数是指定文件名和模式。

常用模式: 
"r" 不会创建文件,会去读取一个已经存在的文件,如果文件不存在则会出现异常。 
"rw" 操作的文件不存在,会自动创建,如果存在则不会覆盖。 
注:而且该对象的构造函数要操作的文件不存在,会自动创建,如果存在则不会覆盖。

public class TestRandomAccessFile {
    public static void main(String[] args) throws IOException {
        // 写入基本类型double数据
        RandomAccessFile rf = new RandomAccessFile("rtest.dat", "rw");
        for (int i = 0; i < 10; i++) {
            rf.writeDouble(i * 1.414);
        }
        rf.close();
        
        // 直接将文件指针移到第5个double数据后面,然后覆盖第6个数据
        rf = new RandomAccessFile("rtest.dat", "rw");
        rf.seek(5 * 8);    
        rf.writeDouble(47.0001);    
        rf.close();
        rf = new RandomAccessFile("rtest.dat", "r");
        for (int i = 0; i < 10; i++) {
            System.out.println("Value " + i + ": " + rf.readDouble());
        }
        rf.close();
    }
}

具体参考:http://blog.csdn.net/akon_vm/article/details/7429245

g) 操作基本数据类型

DataInputStream 与 DataOutputStream,凡是流操作基本数据类型就用这个。 
构造函数:

  • DataInputStream(InputStream in):传进去一个输入流进行操作。
  • DataOutputStream(OutputStream out):传进去一个输出流进行操作。

方法:常用的操作基本类型方法的规律[write基本类型]和[read基本类型]例:readInt(),writeDouble()。

其中readUTF()方法必须要对应的writeUTF()方法。使用别的方法会出现异常。(字节数不同)

a) 写数据的方法:

public void write() throws Exception {
    String path = this.getClass().getClassLoader().getResource("test.txt").toURI().getPath();
    OutputStream os = new FileOutputStream(path);
    DataOutputStream dos = new DataOutputStream(os);
    dos.writeDouble(Math.random());
    dos.writeBoolean(true);
    dos.writeInt(1000);
    dos.writeInt(2000);
    dos.flush();
    os.close();
    dos.close();
}

b) 读取数据的方法:

public void write() throws Exception {
    String path = this.getClass().getClassLoader().getResource("test.txt").toURI().getPath();
    OutputStream os = new FileOutputStream(path);
    DataOutputStream dos = new DataOutputStream(os);
    dos.writeDouble(Math.random());
    dos.writeBoolean(true);
    dos.writeInt(1000);
    dos.writeInt(2000);
    dos.flush();
    os.close();
    dos.close();
}

输出结果:

1.3042321165175584E-76
true
943011632
842479160

h) 操作字节数组

ByteArrayInputStream 与 ByteArrayOutputStream

  • 由于该流并未调用底层资源,所以关闭该流是无效的。也不会报IOException异常。
  • 缓冲区会随着数据的不断写入而自动增长。
  • 可使用toByteArray()和toString()获取数据,不用flush()。

构造函数: 
ByteArrayInputStream :在构造的时候,需要接受数据源,而数据源是一个字节数组。

  • ByteArrayInputStream(byte[] buf)://创建一个 ByteArrayInputStream,使用 buf 作为其缓冲区数组。
  • ByteArrayInputStream(byte[] buf, int offset, int length)://创建 ByteArrayInputStream,使用 buf 作为其缓冲区数组。

ByteArrayOutputStream:在构造的时候,不用定义数据目的,因为该对象中已经内部封装可变长度的数组(目的)

  • ByteArrayOutputStream()://创建一个新的 byte 数组输出流。
  • ByteArrayOutputStream(int size)://创建一个新的 byte 数组输出流,它具有指定大小的缓冲区容量(以字节为单位)。

void writeTo(OutputStream out)://将此 byte 数组输出流的全部内容写入到指定的输出流参数中,这与使用 out.write(buf, 0, count)调用该输出流的write方法效果一样。 
注:这个方法很特殊,是数组流中唯一一个会报异常的方法。

public class ByteArrayTester {  
    public static void main(String[] args) throws IOException {  
        byte[] buff = new byte[] { 2, 15, 67, -1, -9, 9 };  
        ByteArrayInputStream in = new ByteArrayInputStream(buff, 1, 4);  
        int data = in.read();  
        while (data != -1) {  
            System.out.println(data + " ");  
            data = in.read();  
        }  
        // ByteArrayInputSystem 的close()方法实际上不执行任何操作 
        in.close(); 
    }  
}  

以上字节数组输入流从字节数组buff的下标为1的元素开始读,一共读取4个元素。对于读到的每一个字节类型的元素,都会转换为int类型。

i) 操作字符数据

CharArrayReader 与 CharArrayWriter

j) 操作字符串

StringReader 与 StringWriter

 

转载于:https://www.cnblogs.com/pengjingya/p/5657778.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值