1.2、字节流
字节流:一次读入或读出是8位二进制
总结:字节流不需要刷新,和字节流缓冲区的区别在于读取流没有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