Java IO简介
Java IO:即 Java 输入 / 输出系统。
区分 Java 的输入和输出:把自己当成程序, 当你从外边读数据到自己这里就用输入(InputStream/Reader), 向外边写数据就用输出(OutputStream/Writer)。
Stream:Java 中将数据的输入输出抽象为流,流是一组有顺序的,单向的,有起点和终点的数据集合,就像水流。按照流中的最小数据单元又分为字节流和字符流。
- 字节流:以 8 位(即 1 byte,8 bit)作为一个数据单元,数据流中最小的数据单元是字节。
- 字符流:以 16 位(即 1 char,2 byte,16 bit)作为一个数据单元,数据流中最小的数据单元是字符, Java 中的字符是 Unicode 编码,一个字符占用两个字节。
JDK提供的流继承了四大类:InputStream(字节输入流),OutputStream(字节输出流),Reader(字符输入流),Writer(字符输出流)。
- 对文件进行操作:FileInputStream(字节输入流),FileOutputStream(字节输出流),FileReader(字符输入流),FileWriter(字符输出流)
- 对管道进行操作:PipedInputStream(字节输入流),PipedOutStream(字节输出流),PipedReader(字符输入流),PipedWriter(字符输出流)。PipedInputStream的一个实例要和PipedOutputStream的一个实例共同使用,共同完成管道的读取写入操作。主要用于线程操作。
- 字符、字节数组:ByteArrayInputStream,ByteArrayOutputStream,CharArrayReader,CharArrayWriter是在内存中开辟了一个字节或字符数组。
- Buffered缓冲流:BufferedInputStream,BufferedOutputStream,BufferedReader,BufferedWriter,是带缓冲区的处理流,缓冲区的作用的主要目的是:避免每次和硬盘打交道,提高数据访问的效率。
- 转化流:InputStreamReader/OutputStreamWriter,把字节转化成字符。
- 数据流:DataInputStream,DataOutputStream。因为平时若是我们输出一个8个字节的long类型或4个字节的float类型,那怎么办呢?可以一个字节一个字节输出,也可以把转换成字符串输出,但是这样转换费时间,若是直接输出该多好啊,因此这个数据流就解决了我们输出数据类型的困难。数据流可以直接输出float类型或long类型,提高了数据读写的效率(写入和读取的顺序是一致的)。
- 打印流:PrintStream,PrintWriter,一般是打印到控制台,可以进行控制打印的地方。
- 对象流:ObjectInputStream,ObjectOutputStream,把封装的对象直接输出,而不是一个个在转换成字符串再输出。
- 序列化流:SequenceInputStream/SequenceOutputStream。
- 对象序列化:把对象直接转换成二进制,写入介质中。使用对象流需要实现Serializable接口,否则会报错。而若用transient关键字修饰成员变量,不写入该成员变量,若是引用类型的成员变量为null,值类型的成员变量为0。
非流式主要类
File
用于文件或者目录的描述信息,例如生成新目录,修改文件名,删除文件,判断文件所在路径等。
构造函数
File共提供了四个不同的构造函数,以不同的参数形式灵活地接收文件和目录名信息。
- File (String pathname):通过将给定路径名字符串转换成抽象路径名来创建一个新 File 实例。
File f1=new File("FileTest1.txt"); //创建文件对象f1,f1所指的文件是在当前目录下创建的FileTest1.txt
- File(URI uri):通过将给定的 file: URI 转换成一个抽象路径名来创建一个新的 File 实例。
- File (String parent , String child):根据 parent 路径名字符串和 child 路径名字符串创建一个新 File 实例。
// 注意:D:\\dir1目录事先必须存在,否则异常 File f2=new File(“D:\\dir1","FileTest2.txt");
- File (File parent , String child):通过给定的父文件和子路径名字符串创建一个新的File实例。
File f4=new File("E:\\dir3"); File f5=new File(f4,"FileTest5.txt"); //在如果 E:\\dir3目录不存在则需要先使用f4.mkdir()先创建
常用方法
- 创建
createNewFile()在指定位置创建一个空文件,成功就返回true,如果已存在就不创建,然后返回false。
mkdir() 在指定位置创建一个单级文件夹。
mkdirs() 在指定位置创建一个多级文件夹。
renameTo(File dest)如果目标文件与源文件是在同一个路径下,那么renameTo的作用是重命名, 如果目标文件与源文件不是在同一个路径下,那么renameTo的作用就是剪切,而且还不能操作文件夹。
- 删除
delete() 删除文件或者一个空文件夹,不能删除非空文件夹,马上删除文件,返回一个布尔值。
deleteOnExit()jvm退出时删除文件或者文件夹,用于删除临时文件,无返回值。
- 判断
exists() 文件或文件夹是否存在。
isFile() 是否是一个文件,如果不存在,则始终为false。
isDirectory() 是否是一个目录,如果不存在,则始终为false。
isHidden() 是否是一个隐藏的文件或是否是隐藏的目录。
isAbsolute() 测试此抽象路径名是否为绝对路径名。
- 获取
getName() 获取文件或文件夹的名称,不包含上级路径。
getAbsolutePath()获取文件的绝对路径,与文件是否存在没关系
length() 获取文件的大小(字节数),如果文件不存在则返回0L,如果是文件夹也返回0L。
getParent() 返回此抽象路径名父目录的路径名字符串;如果此路径名没有指定父目录,则返回null。
lastModified()获取最后一次被修改的时间。
- 文件夹相关
static File[] listRoots()列出所有的根目录(Window中就是所有系统的盘符)
list() 返回目录下的文件或者目录名,包含隐藏文件。对于文件这样操作会返回null。
listFiles() 返回目录下的文件或者目录对象(File类实例),包含隐藏文件。对于文件这样操作会返回null。
list(FilenameFilter filter)返回指定当前目录中符合过滤条件的子文件或子目录。对于文件这样操作会返回null。
listFiles(FilenameFilter filter)返回指定当前目录中符合过滤条件的子文件或子目录。对于文件这样操作会返回null。
示例
/** * file相关的方法 * * @author linhongwei */ public class FileMethodTest { public static void main(String[] args) throws IOException { File fileCreate = new File("D:\\linhongwei\\study\\files\\file.txt"); // 创建单级文件夹 System.out.println("单级文件夹创建:" + fileCreate.mkdir()); // 创建多级文件夹 System.out.println("多级文件夹创建:" + fileCreate.mkdirs()); //创建文件 System.out.println("创建文件:" + fileCreate.createNewFile()); //文件重命名 File toFile = new File("D:\\coco_xu\\study\\files\\toFile.txt"); System.out.println("文件重命名:" + fileCreate.renameTo(toFile)); // 删除方法 File file = new File("D:\\linhongwei\\study\\files\\toFile.tx"); System.out.println("删除文件:" + file.delete()); file.deleteOnExit(); // 判断方法 /* * File file = new File("F:\\a.txt"); * System.out.println("文件或者文件夹存在吗?"+file.exists()); * System.out.println("是一个文件吗?"+file.isFile()); * System.out.println("是一个文件夹吗?"+file.isDirectory()); * System.out.println("是隐藏文件吗?"+file.isHidden()); * System.out.println("此路径是绝对路径名?"+file.isAbsolute()); */ // 获取方法 /* * File file = new File("f:\\a.txt"); * System.out.println("文件或者文件夹得名称是:"+file.getName()); * System.out.println("绝对路径是:"+file.getPath()); * System.out.println("绝对路径是:"+file.getAbsolutePath()); * System.out.println("文件大小是(以字节为单位):"+file.length()); * System.out.println("父路径是"+file.getParent()); * //使用日期类与日期格式化类进行获取规定的时间 * long lastmodified= file.lastModified(); * Date data = new Date(lastmodified); * SimpleDateFormat simpledataformat = new SimpleDateFormat("YY年MM月DD日 HH:mm:ss"); * System.out.println("最后一次修改的时间是:"+simpledataformat.format(data)); */ // 文件或者文件夹的方法 /* * File[] file = File.listRoots(); * System.out.println("所有的盘符是:"); * for (File item : file) { * System.out.println("\t" + item); * } * File filename = new File("D:\\coco_xu"); * String[] name = filename.list(); * System.out.println("指定文件夹下的文件或者文件夹有:"); * for (String item : name) { * System.out.println("\t" + item); * } * File[] f = filename.listFiles(); * System.out.println("获得该路径下的文件或文件夹是:"); * for (File item : f) { * System.out.println("\t" + item.getName()); * } */ } }
RandomAccessFile
它的功能丰富,可以从文件的任意位置进行存取(输入输出)操作。
RandomAccessFile既可以读取文件内容,也可以向文件输出数据。同时,RandomAccessFile支持“随机访问”的方式,程序块可以直接跳转到文件的任意地方来读写数据。
由于RandomAccessFile可以自由访问文件的任意位置,所以如果需要访问文件的部分内容,而不是把文件从头读到尾,使用RandomAccessFile将是更好的选择。
与OutputStream、Writer等输出流不同的是,RandomAccessFile允许自由定义文件记录指针,RandomAccessFile可以不从开始的地方开始输出,因此RandomAccessFile可以向已存在的文件后追加内容。如果程序需要向已存在的文件后追加内容,则应该使用RandomAccessFile。
RandomAccessFile的方法虽然多,但它有一个最大的局限,就是只能读写文件,不能读写其他IO节点。
RandomAccessFile的一个重要使用场景就是网络请求中的多线程下载及断点续传。
构造函数
"r":以只读方式打开。调用结果对象的任何 write 方法都将导致抛出 IOException。
"rw":打开以便读取和写入。
"rws":打开以便读取和写入。相对于 "rw","rws" 还要求对“文件的内容”或“元数据”的每个更新都同步写入到基础存储设备。
"rwd" :打开以便读取和写入,相对于 "rw","rwd" 还要求对“文件的内容”的每个更新都同步写入到基础存储设备。
重要方法
- seek:指定文件的光标位置,通俗点说就是指定你的光标位置,然后下次读文件数据的时候从该位置读取。
- getFilePointer:我们注意到这是一个long类型的返回值,字面意思就是返回当前的文件光标位置。这样方便我们后面读取插入。
- length:毫无疑问的方法,文件的长度,返回long类型。注意它并不会受光标的影响。只会反应客观的文本长度。
- read()、read(byte[] b)、read(byte[] b,int off,int len):最后一个方法是定义缓冲数组,从数组的off偏移量位置开始写,读取转换为数组数据达到len个字节。总之这是一个读文件内容的标准操作api。
- readDouble() readFloat() readBoolean() readInt()readLong() readShort() readByte() readChar():这些方法都是去read每一个字符,个人感觉就是返回他们的ASCII码。比如readLong就是要求你的文本内容必须有八个字符,不然会报错。伴随着也就是writeDouble() writeFloat() writeBoolean() writeInt() writeLong() writeShort() writeByte() writeChar()
- readFully(byte[] b):这个方法的作用就是将文本中的内容填满这个缓冲区b。如果缓冲b不能被填满,那么读取流的过程将被阻塞,如果发现是流的结尾,那么会抛出异常。这个过程就比较像“凑齐一车人在发车,不然不走”。
- getChannel:它返回的就是nio通信中的file的唯一channel。
- skipBytes(int n):跳过n字节的位置,相对于当前的point。
可以看出RandomAccessFile实现了大部分文件输入输出流的方法,但是底层实现中他实现的是DataInput和DataOutput接口,并非是FileInputStream和FileOutputStream。RandomAccessFile使用很多native方法实现了对文件的操作,并且很多native方法跟inputstream都有重叠,比如read0方法。我想这么做可能是为了让这个DataInput接口的职责更明确吧。
示例
读文件
import java.io.IOException; import java.io.RandomAccessFile; public class RandomAccessRead { public static void main(String[] args) { if (args == null || args.length == 0) { throw new RuntimeException("请输入路径"); } RandomAccessFile raf = null; try { raf = new RandomAccessFile(args[0], "r"); System.out.println("RandomAccessFile的文件指针初始位置:" + raf.getFilePointer()); raf.seek(100); byte[] bbuf = new byte[1024]; int hasRead = 0; while ((hasRead = raf.read(bbuf)) > 0) { System.out.print(new String(bbuf, 0, hasRead)); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { try { if (raf != null) { raf.close(); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
文件末尾追加内容
import java.io.IOException; import java.io.RandomAccessFile; public class RandomAccessWrite { public static void main(String[] args) { if (args == null || args.length == 0) { throw new RuntimeException("请输入路径"); } RandomAccessFile raf = null; try { String[] arrays = new String[] { "Hello Hadoop", "Hello Spark", "Hello Hive" }; raf = new RandomAccessFile(args[0], "rw"); raf.seek(raf.length()); raf.write("追加内容:\n".getBytes()); for (String arr : arrays) { raf.write((arr + "\n").getBytes()); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { try { if (raf != null) { raf.close(); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
指定位置插入内容
import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.RandomAccessFile; public class InsertContent { public static void main(String[] args) { if (args == null || args.length != 3) { throw new RuntimeException("请分别输入操作文件、插入位置和插入内容"); } FileInputStream fis = null; FileOutputStream fos = null; RandomAccessFile raf = null; try { raf = new RandomAccessFile(args[0], "rw"); File tmp = File.createTempFile("tmp", null); tmp.deleteOnExit(); fis = new FileInputStream(tmp); fos = new FileOutputStream(tmp); raf.seek(Long.parseLong(args[1])); byte[] bbuf = new byte[64]; int hasRead = 0; while ((hasRead = raf.read(bbuf)) > 0) { fos.write(bbuf, 0, hasRead); } raf.seek(Long.parseLong(args[1])); raf.write("\n插入内容:\n".getBytes()); raf.write((args[2] + "\n").getBytes()); while ((hasRead = fis.read(bbuf)) > 0) { raf.write(bbuf, 0, hasRead); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { try { if (fis != null) { fis.close(); } if (fos != null) { fos.close(); } if (raf != null) { raf.close(); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
常用流类
InputStream 抽象类
InputStream 为字节输入流,它本身为一个抽象类,必须依靠其子类实现各种功能,此抽象类是表示字节输入流的所有类的超类。
继承自InputStream 的流都是向程序中输入数据的,且数据单位为字节(8bit);
InputStream是输入字节数据用的类,所以InputStream类提供了3种重载的read方法。
Inputstream类中的常用方法:
- public abstract int read( ):读取一个byte的数据,返回值是高位补0的int类型值。若返回值=-1说明没有读取到任何字节读取工作结束。
- public int read(byte b[ ]):读取b.length个字节的数据放到b数组中。返回值是读取的字节数。
- public int read(byte b[ ], int off, int len):从输入流中最多读取len个字节的数据,存放到偏移量为off的b数组中。
- public int available( ):返回输入流中可以读取的字节数。注意:若输入阻塞,当前线程将被挂起,如果InputStream对象调用这个方法的话,它只会返回0,这个方法必须由继承InputStream类的子类对象调用才有用。
- public long skip(long n):忽略输入流中的n个字节,返回值是实际忽略的字节数, 跳过一些字节来读取 。
- public int close( ) :我们在使用完后,必须对我们打开的流进行关闭。
主要子类有:
- FileInputStream:把一个文件作为InputStream,实现对文件的读取操作
- ByteArrayInputStream:把内存中的一个缓冲区作为InputStream使用
- StringBufferInputStream:把一个String对象作为InputStream
- PipedInputStream:实现了pipe的概念,主要在线程中使用
- SequenceInputStream:把多个InputStream合并为一个InputStream
OutputStream抽象类
OutputStream提供了3个write方法来做数据的输出,这个是和InputStream是相对应的。
- public void write(byte b[ ]):将参数b中的字节写到输出流。
- public void write(byte b[ ], int off, int len) :将参数b的从偏移量off开始的len个字节写到输出流。
- public abstract void write(int b) :先将int转换为byte类型,把低字节写入到输出流中。
- public void flush( ) : 将数据缓冲区中数据全部输出,并清空缓冲区。
- public void close( ) : 关闭输出流并释放与流相关的系统资源。
主要子类:
- ByteArrayOutputStream:把信息存入内存中的一个缓冲区中
- FileOutputStream:把信息存入文件中
- PipedOutputStream:实现了pipe的概念,主要在线程中使用
- SequenceOutputStream:把多个OutStream合并为一个OutStream
注:流结束的判断,方法read()的返回值为-1时;readLine()的返回值为null时。
FileInputStream文件输入流
FileInputStream可以使用read()方法一次读入一个字节,并以int类型返回,或者是使用read()方法时读入至一个byte数组,byte数组的元素有多少个,就读入多少个字节。
在将整个文件读取完成或写入完毕的过程中,这么一个byte数组通常被当作缓冲区,因为这么一个byte数组通常扮演承接数据的中间角色。
作用:以文件作为数据输入源的数据流。或者说是打开文件,从文件读数据到内存的类。
FileOutputStream文件输出流
FileOutputStream类用来处理以文件作为数据输出目的数据流;一个表示文件名的字符串,也可以是File或FileDescriptor对象。
作用:用来处理以文件作为数据输出目的数据流;或者说是从内存区读数据到文件。
创建文件流的方式:
- FileOutputStream(File file) :创建一个向指定 File 对象表示的文件中写入数据的文件输出流。
- FileOutputStream(String name) :创建一个向具有指定名称(文件路径)的文件中写入数据的输出文件流。
- FileOutputStream(String name, boolean append) :创建一个向具有指定 name 的文件中写入数据的输出文件流。 append表示内容是否追加
注意:
- 文件中写数据时,若文件已经存在,则覆盖存在的文件;
- 在读/写操作结束时,应调用close方法关闭流。
缓冲输入输出流 BufferedInputStream/ BufferedOutputStream(包装流)
BufferedInputStream:当向缓冲流写入数据时候,数据先写到缓冲区,待缓冲区写满后,系统一次性将数据发送给输出设备。
BufferedOutputStream :当从向缓冲流读取数据时候,系统先从缓冲区读出数据,待缓冲区为空时,系统再从输入设备读取数据到缓冲区。
1、将文件读入内存
//将BufferedInputStream与FileInputStream相接 FileInputStream in=new FileInputStream( “file1.txt “); BufferedInputStream bin=new BufferedInputStream(in);
2、将内存写入文件
//将BufferedOutputStream与 FileOutputStream相接 FileOutputStreamout=new FileOutputStream(“file2.txt”); BufferedOutputStream bin=new BufferedInputStream(out);
3、键盘输入流读到内存
//将BufferedReader与标准的数据流相接 InputStreamReader sin=new InputStreamReader (System.in) ; BufferedReader bin=new BufferedReader(sin);
对象流ObjectInputStream/ObjectOutputStream
该流允许读取或写入用户自定义的类,但是要实现这种功能,被读取和写入的类必须实现Serializable接口,其实该接口并没有什么方法,可能相当于一个标记而已,但是确是不可缺少的。
public class ObjetStream { /** * @param args */ public static void main(String[] args) { ObjectOutputStream objectwriter=null; ObjectInputStream objectreader=null; try { objectwriter=new ObjectOutputStream(new FileOutputStream("D:/test/Java/files/student.txt")); objectwriter.writeObject(new Student("gg", 22)); objectwriter.writeObject(new Student("tt", 18)); objectwriter.writeObject(new Student("rr", 17)); objectreader=new ObjectInputStream(new FileInputStream("D:/test/Java/files/student.txt")); for (int i = 0; i < 3; i++) { System.out.println(objectreader.readObject()); } } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); }finally{ try { objectreader.close(); objectwriter.close(); } catch (IOException e) { e.printStackTrace(); } } } } class Student implements Serializable{ private String name; private int age; public Student(String name, int age) { super(); this.name = name; this.age = age; } @Override public String toString() { return "Student [name=" + name + ", age=" + age + "]"; } }
打印流PrintStream/PrintWriter
打印流顾名思义就是打印内容,是输出信息最方便的类,包含PrintStream(字节打印流)和 PrintWriter(字符打印流)。打印流提供了非常方便的打印功能,可以打印任何类型的数据信息,例如:小数,整数,字符串。打印流必须是输出流(两个都是)。
常用的System.out变量就是PrintStream实例。
PrintStream示例:
public class PrintStreamDemo { public static void main(String[] args) throws IOException { //System.out.println("hello"); // //PrintStream ps = System.out; //ps.println("hello"); File dir = new File("tempfile"); if (!dir.exists()) { dir.mkdir(); } //演示PrintStream的特有方法。 //1.创建PrintStream对象,目的就定位文件 PrintStream out = new PrintStream("tempfile\\print.txt"); //out.write(353);//字节流的write方法一次只写出一个字节,也就是将一个整数的最低八位写出 //out.write("353".getBytes()); 麻烦 out.print(353);//保证数值的表现形式。原理:write(String.valueOf(i));将数值转成字符串。 out.close(); } }
PrintWriter示例:
import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.FileWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; public class PrintWriterDemo { public static void main(String[] args) throws IOException { /* * 演示一个小例子 * 读取键盘录入,将数据转成大写显示在屏幕上。 */ //1.键盘录入 BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in)); //2.定义目的 //BufferedWriter bufw = new BufferedWriter(new PrintStream(System.out)); PrintWriter pw = new PrintWriter(System.out,true);//对println,printf,format方法自动刷新 //字符流内部都有缓冲区 //改变目的为文件,还想自动刷新。 pw = new PrintWriter(new BufferedWriter(new FileWriter("tempfile\\1.txt")),true); //3.读一行,写一行,键盘录入一定要定义结束标记 String line = null; while((line=bufr.readLine())!=null){//readline是一个阻塞式方法 if("over".equals(line)) break; pw.println(line.toUpperCase()); //pw.flush();//因为数据被临时缓存了 } pw.close();//系统流关不关都可以 bufr.close();//不需要关闭键盘录入这种标准输入流,一旦关闭,后面获取不到。 } }
序列化流SequenceInputStream/SequenceOutputStream
两个流合并:
import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.FileInputStream; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.SequenceInputStream; /** * SequenceInputStream 表示其他输入流的逻辑串联。 * 它从输入流的有序集合开始,并从第一个输入流开始读取,直到到达文件末尾; * 接着从第二个输入流读取,依次类推,直到到达包含的最后一个输入流的文件末尾为止。 * */ public class SequenceInputStreamDemo01 { // SequenceInputStream(InputStream s1, InputStream s2) public static void main(String[] args) throws IOException { InputStream s1 = new FileInputStream("src/stream/sequence/SequenceInputStreamDemo01.java"); InputStream s2 = new FileInputStream("src/stream/sequence/SequenceInputStreamDemo02.java"); SequenceInputStream sis = new SequenceInputStream(s1, s2); InputStreamReader isr = new InputStreamReader(sis); BufferedReader br = new BufferedReader(isr); BufferedWriter bw = new BufferedWriter(new FileWriter("CopySequence.java")); String line = null; while((line = br.readLine()) != null) { // while(br.ready()) { /* 为什么合并流之后,不能使用ready方法?结果只有S1 */ // line = br.readLine(); bw.write(line); bw.newLine(); bw.flush(); } s1.close(); s2.close(); br.close(); bw.close(); /* BufferedReader br = new BufferedReader( new InputStreamReader( new SequenceInputStream( new FileInputStream("src/stream/sequence/SequenceInputStreamDemo01.java"), new FileInputStream("src/special/RandomAccessFileDemo02.java") ) ) ); BufferedWriter bw = new BufferedWriter(new FileWriter("CopySequence.java")); br.close(); bw.close(); */ } }
多个流的时候存放到Vector中后进行合并:
import java.io.BufferedOutputStream; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.SequenceInputStream; import java.util.Vector; /** * SequenceInputStream 表示其他输入流的逻辑串联。 * 它从输入流的有序集合开始,并从第一个输入流开始读取,直到到达文件末尾; * 接着从第二个输入流读取,依次类推,直到到达包含的最后一个输入流的文件末尾为止。 * */ public class SequenceInputStreamDemo02 { // SequenceInputStream(Enumeration<? extends InputStream> e) // 通过枚举类:进行多个流的合并 public static void main(String[] args) throws IOException { InputStream s1 = new FileInputStream("src/stream/sequence/SequenceInputStreamDemo02.java"); InputStream s2 = new FileInputStream("src/stream/sequence/SequenceInputStreamDemo01.java"); InputStream s3 = new FileInputStream("src/stream/sequence/SequenceInputStreamDemo02.java"); Vector<InputStream> v = new Vector<InputStream>(); v.addElement(s1); v.addElement(s2); v.addElement(s3); SequenceInputStream sis = new SequenceInputStream(v.elements()); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("copySequence02.java")); byte[] buf = new byte[1024]; int len = buf.length; while((len = sis.read(buf, 0, len)) != -1) { bos.write(buf, 0, len); } sis.close(); bos.close(); } }
转换流InputStreamReader/OutputStreamWriter
InputStreamReader类是从字节流到字符流的桥接器:它使用指定的字符集读取字节并将它们解码为字符。 它使用的字符集可以通过名称指定,也可以明确指定,或者可以接受平台的默认字符集。每次调用一个InputStreamReader的read()方法都可能导致从底层字节输入流中读取一个或多个字节。 为了实现字节到字符的有效转换,可以从基础流中提取比满足当前读取操作所需的更多字节。为了获得最高效率,请考虑在BufferedReader中包装InputStreamReader。
字节流到字符流的桥梁怎么理解?
计算机存储的单位是字节,如尽管txt文本中有中文汉字这样的字符,但是对计算机而言,其是字节形式存在的。
字节流读取是单字节读取,但是不同字符集解码成字符需要不同的个数,因此字节流读取会报错。
那么就需要一个流把字节流读取的字节进行缓冲后再通过字符集解码成字符返回,因而形式上看是字符流。
InputStreamReader流就是起这个作用,实现从字节流到字符流的转换。
使用指定的字符集读取字节并将它们解码为字符怎么理解?
字节本质是8个二进制位,且不同的字符集对同一字节解码后的字符结果是不同的,因此在读取字符时务必要指定合适的字符集,否则读取的内容会产生乱码。
它使用的字符集可以通过名称指定,也可以明确指定,或者可以接受平台的默认字符集怎么理解?
意味着InputStreamReader类有多个方法或者多个构造方法来设置字符集。
每次调用一个InputStreamReader的read()方法都可能导致从底层字节输入流中读取一个或多个字节怎么理解?
read()方法会尝试尽量从底层字节流中读取2个字符到字符缓冲区中,注意这里是尽量,若遇到文件最后字符,则就只能读取到1个字符,因此每次read()方法读取的字节数是不定的。
Writer/Reader字符流
Reader类用于读取字符流的抽象类。子类必须实现的方法只有 read(char[], int, int) 和 close()。但是,多数子类将重写此处定义的一些方法,以提供更高的效率和/或其他功能。
Reader主要子类:
- CharArrayReader
- StringReader
- InputStreamReader :从输入流读取字节,在将它们转换成字符
- FilterReader: 允许过滤字符流
- BufferReader:接受Reader对象作为参数,并对其添加字符缓冲器,使用readline()方法可以读取一行。
Writer是写入字符流的抽象类。子类必须实现的方法仅有 write(char[], int, int)、flush() 和 close()。但是,多数子类将重写此处定义的一些方法,以提供更高的效率和/或其他功能。
文件输入输出流FileReader/FileWriter
FileReader类从InputStreamReader类继承而来。该类按字符读取流中数据。
FileWriter类从 OutputStreamWriter 类继承而来。该类按字符向流中写入数据。
缓冲字符流BufferedReader/BufferedWriter
BufferedReader类从字符输入流中读取文本并缓冲字符,以便有效地读取字符,数组和行。
可以通过构造函数指定缓冲区大小也可以使用默认大小。对于大多数用途,默认值足够大。
由Reader构成的每个读取请求都会导致相应的读取请求由基础字符或字节流构成,建议通过BufferedReader包装Reader的实例类以提高效率。
BufferedReader in = new BufferedReader(new FileReader(“foo.in”));
从字符输入流中读取文本并缓冲字符,以便有效地读取字符,数组和行怎么理解?
说明该类存在缓冲字符数组并且是该类可以高效读取字符的关键
构造函数指定缓冲区大小也可以使用默认大小怎么理解?
意味着该类存在的构造方法既可以传递数值指定缓冲区大小也可以由类中的默认大小指定
由Reader构成的每个读取请求都会导致相应的读取请求由基础字符或字节流构成,建议通过BufferedReader包装Reader的实例类以提高效率?
Reader构成的对象是字符对象,每次的读取请求都会涉及到字节读取解码字符的过程,而BufferedReader类中有设计减少这样的解码次数的方法,进而提高转换效率
BufferedReader类与InputStreamReader类比较
InputStreamReader中的文档说明提到过:为了获得最高效率,请考虑在BufferedReader中包装InputStreamReader?
从read()方法理解,若使用InputStreamReader的read()方法,可以发现存在每2次就会调用一次解码器解码,但若是使用 BufferedReader包装InputStreamReader后调用read()方法,可以发现只会调用一次解码器解码,其余时候都是直接从BufferedReader的缓冲区中取字符即可。
从read(char cbuf[], int offset, int length)方法理解,若使用InputStreamReader的方法则只会读取leng个字符,但是使用BufferedReader类则会读取读取8192个字符,会尽量提取比当前操作所需的更多字节;
例如文件中有20个字符,我们先通过read(cbuf,0,5)要读取5个字符到数组cbuf中,然后再通过read()方法读取1个字符。那么使用InputStreamReader类的话,则会调用一次解码器解码然后存储5个字符到数组中,然后又调用read()方法调用一次解码器读取2个字符,然后返回1个字符;等于是调用了2次解码器,若使用BufferedReader类的话则是先调用一次解码器读取20个字符到字符缓冲区中,然后复制5个到数组中,在调用read()方法时,则直接从缓冲区中读取字符,等于是调用了一次解码器。
因此可以看出BufferedReader类会尽量提取比当前操作所需的更多字节,以应该更多情况下的效率提升,所以在设计到文件字符输入流的时候,我们使用BufferedReader中包装InputStreamReader类即可。
Java IO 的一般使用原则
按数据来源(去向)分类
- 是文件: FileInputStream, FileOutputStream, ( 字节流 )FileReader, FileWriter( 字符 )
- 是 byte[] : ByteArrayInputStream, ByteArrayOutputStream( 字节流 )
- 是 Char[]: CharArrayReader, CharArrayWriter( 字符流 )
- 是 String: StringBufferInputStream, StringBufferOuputStream ( 字节流 )StringReader, StringWriter( 字符流 )
- 网络数据流: InputStream, OutputStream,( 字节流 ) Reader, Writer( 字符流 )
按是否格式化输出分
要格式化输出: PrintStream, PrintWriter。
按是否要缓冲分
要缓冲: BufferedInputStream, BufferedOutputStream,( 字节流 ) BufferedReader, BufferedWriter( 字符流 )
按数据格式分
- 二进制格式(只要不能确定是纯文本的,比如图片、音频、视频) : InputStream, OutputStream 及其所有带 Stream 结尾的子类
- 纯文本格式(含纯英文与汉字或其他编码方式); Reader, Writer 及其所有带 Reader, Writer 的子类
按输入输出分
- 输入: Reader, InputStream 类型的子类
- 输出: Writer, OutputStream 类型的子类
特殊需要
- 从 Stream 到 Reader,Writer 的转换类: InputStreamReader, OutputStreamWriter
- 对象输入输出: ObjectInputStream, ObjectOutputStream
- 进程间通信: PipeInputStream, PipeOutputStream, PipeReader, PipeWriter
- 合并输入: SequenceInputStream
- 更特殊的需要: PushbackInputStream, PushbackReader, LineNumberInputStream, LineNumberReader