在前面几章博客中介绍的流是需要我们理解和掌握的,其实在Java IO包下面还有很多不同种类的流,但是用的不是非常多,所以这里就汇总简单举个例子。
这些流包括:数组流、字符串流、打印流、数据流、合并流、管道流、回退流、随机存储文件流。
1、数组(内存)流
数组流:就是将数据都以数组的方式存入该类中的字节或字符数组中,然后取数据也是从这个数组中取。
数组流分为:字节数组流和字符数组流。
- 字节数组流:ByteArrayInutStream 和 ByteArrayOutputStream
- 字符数组流:CharArrayReader 和 CharArrayWriter
字节数组流:ByteArrayOutputStream 、ByteArrayInputStream。
package com.thr.other; import java.io.*; /** * @author Administrator * @date 2020-02-29 * @desc 字节数组流 */ public class ByteArrayStreamTest { public static void main(String[] args) throws IOException { //程序——>字节数组(内存) ByteArrayOutputStream bos = new ByteArrayOutputStream(); byte[] bytes = "中国牛逼".getBytes(); System.out.println(bytes.length);//因为UTF-8下的中文占3个字节,所以长度为12 bos.write(bytes); //将数据放在temp数组中 byte[] temp = bos.toByteArray(); System.out.println(new String(temp,0,temp.length)); //字节数组(内存)——>程序 ByteArrayInputStream bis = new ByteArrayInputStream(temp);//将上面temp数组中字节拿到程序中来 byte[] buffer = new byte[20]; int len=-1; while ((len=bis.read(buffer))!=-1){ System.out.println(new String(buffer,0,len)); } //释放资源 bis.close(); bos.close(); //bos.write(1); } }
其实字节数组流中的流可以不调用close()方法,因为它内部定义的是一个空方法,没有做任何操作。即使关闭流后仍可调用其他方法,而不会产生任何IOException。
字符数组流:CharArrayReader、CharArrayWriter。
package com.thr.other; import java.io.*; /** * @author Administrator * @date 2020-02-29 * @desc 字符数组流 */ public class CharArrayStreamTest { public static void main(String[] args) throws IOException { //程序——>字符数组(内存) CharArrayWriter caw = new CharArrayWriter(); caw.write("中国牛逼"); //将数据放在temp缓冲区数组中 char[] temp = caw.toCharArray(); System.out.println(temp); //字符数组(内存)——>程序 CharArrayReader car = new CharArrayReader(temp);//将上面temp数组中字节拿到程序中来 char[] buffer = new char[20]; int len=-1; while ((len=car.read(buffer))!=-1){ System.out.println(new String(buffer,0,buffer.length)); } //释放资源 car.close(); caw.close(); } }
2、字符串流
字符串流:以一个字符串为数据源,来构造一个字符流。StringWriter内部其实调用了StringBuffer来拼接字符串。
字符串流有两个类:StringReader和StringWriter。
package com.thr.other; import java.io.*; /** * @author Administrator * @date 2020-02-29 * @desc 字符串流 */ public class StringStreamTest { public static void main(String[] args) throws IOException { StringWriter sw = new StringWriter(); sw.write("中国牛逼!"); sw.write("中国加油!"); String string = sw.toString(); System.out.println(string);//中国牛逼!中国加油! StringReader sr = new StringReader(string); char[] buffer = new char[20]; int len; while ((len=sr.read(buffer))!=-1){ System.out.println(new String(buffer,0,len)); } //释放资源 sr.close(); sw.close(); } }
关闭字符串流无效。此类中的方法在关闭该流后仍可调用方法,而不会产生任何IOException。
3、打印流
打印流:实现将任何数据类型的数据格式转为字符串输出。它是输出信息最为方便的类。
打印流分为两种:字节打印流PrintStream、字符打印流PrintWriter。它们的内部提供了提供了一系列重载的print()和println()方法,用于多种数据类型的输出。我们最先接触的system.out中的print()和println()方法都是PringStream中的。注意:System.out.println();其实等价于 PrintStream ps = System.out; ps.println()。
打印流的一些特点:
- 不负责数据源,只负责数据目的
- 不会抛出IOException,但是可能抛出其他异常
- 有自动flush功能
字节打印流举例:
package com.thr.other; import java.io.*; /** * @author Administrator * @date 2020-02-29 * @desc 字节打印流 */ public class PrintStreamTest { public static void main(String[] args) throws IOException { //打印流输出基本数据类型、字符串 PrintStream ps1 = new PrintStream("D:\IO\print.txt"); ps1.println(666); ps1.println(123.456); ps1.println(123456789L); ps1.println("中国牛逼"); ps1.close(); //打印流输出目的是流对象 FileOutputStream fos = new FileOutputStream("D:\IO\print1.txt"); PrintStream ps2 = new PrintStream(fos,true);//加个true表示开启自动flush功能 ps2.print("China Niubility"); ps2.close(); //打印流复制文本 BufferedReader br=new BufferedReader(new FileReader("D:\IO\print.txt")); PrintWriter pw=new PrintWriter(new FileWriter("D:\IO\printofcopy.txt"),true); String line =null; while((line=br.readLine())!=null){ pw.println(line); } pw.close(); br.close(); } }
由于字符打印流和字节打印流的用法基本一致,这里就不多举例了。
4、数据流
数据流:可以很方便的将Java基本数据类型(八大基本数据类型)和String类型的数据写入到文件中。作用就是:保留数据的同时也保留了数据类型,方便后期获取数据类型而不需要强转。
数据流有两个类:DataInputStream和DataOutStream,这两个类中分别定义了writeXXX()和readXXX()方法用来写出或写入特定的数据类型。
数据流写出并读取数据,其中注意的两点:
- 先写出再读取:因为用DataOutputStream写出的数据,必须用DataInputStream来读写。
- 写出的数据和读取的数据顺序必须保持一致,否则会抛出异常EOFException。
package com.thr.other; import java.io.*; /** * @author Administrator * @date 2020-02-29 * @desc 数据流写入写出数据 */ public class DataStreamTest { public static void main(String[] args) { wirteData();//写出数据 readData();//读取数据 } public static void wirteData() { //定义数据流 DataOutputStream dos = null; try { //创建数据输出流对象 dos = new DataOutputStream(new FileOutputStream("D:\IO\data.txt")); //写出数据 dos.writeUTF("张三"); dos.writeInt(20); dos.writeBoolean(true); dos.writeDouble(99999999.99); } catch (IOException e) { e.printStackTrace(); } finally { if (dos!=null){ try { dos.close(); } catch (IOException e) { e.printStackTrace(); } } } } public static void readData() { //定义数据流 DataInputStream dis = null; try { //创建数据输入流对象 dis = new DataInputStream(new FileInputStream("D:\IO\data.txt")); //写入数据 String name = dis.readUTF(); int age = dis.readInt(); boolean isMale = dis.readBoolean(); double money = dis.readDouble(); //打印 System.out.println("name:"+name); System.out.println("age:"+age); System.out.println("isMale:"+isMale); System.out.println("money:"+money); } catch (IOException e) { e.printStackTrace(); } } }
5、合并流
合并流(SequenceInputStream):就是将两个输入流合并成一个输入流。读取的时候是先读第一个,读完了再读下面一个流。
package com.thr.other; import java.io.*; /** * @author Administrator * @date 2020-02-29 * @desc 合并流 */ public class SequenceInputStreamTest { public static void main(String[] args) { //定义流 SequenceInputStream sis = null; FileOutputStream fos = null; try { //创建合并流对象 sis = new SequenceInputStream(new FileInputStream("D:\IO\1.txt"), new FileInputStream("D:\IO\2.txt")); //创建输出流对象 fos = new FileOutputStream("D:\IO\3.txt"); //声明byte数组用于存储从输入流读取到的数据 byte[] by = new byte[1024]; //该变量纪录每次读取到的字符个数 int len = 0; //读取输入流中的数据 while((len = sis.read(by))!=-1){ //输出从输入流中读取到的数据 fos.write(by, 0, len); } } catch (IOException e) { e.printStackTrace(); }finally { //释放资源 if (sis!=null){ try { sis.close(); } catch (IOException e) { e.printStackTrace(); } } if (fos!=null){ try { fos.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
6、回退流
回退流:在JAVA IO中所有的数据都是采用顺序的读取方式,即对于一个输入流来讲都是采用从头到尾的顺序读取的,如果在输入流中某个不需要的内容被读取进来,则只能通过程序将这些不需要的内容处理掉,为了解决这样的处理问题,在JAVA中提供了一种回退输入流可以把读取进来的某些数据重新回退到输入流的缓冲区之中。
回退流有两个类:PushbackInputStream、PushbackReader
使用InputStream 要使用read() 方法不断读取,是采用顺序的读取方式。
回退操作同样分为字节流和字符流,本教程还是以字节流为准。
PushbackInputStream类的常用方法
- public PushbackInputStream(InputStream in) 构造方法 将输入流放入到回退流之中。
- public int read() throws IOException 普通 读取数据。
- public int read(byte[] b,int off,int len) throws IOException 普通方法 读取指定范围的数据。
- public void unread(int b) throws IOException 普通方法 回退一个数据到缓冲区前面。
- public void unread(byte[] b) throws IOException 普通方法 回退一组数据到缓冲区前面。
- public void unread(byte[] b,int off,int len) throws IOException 普通方法 回退指定范围的一组数据到缓冲区前面。
对于回退操作来说,提供了三个unread()的操作方法,这三个操作方法与InputStream类中的read()方法是一一对应的。
内存中使用ByteArrayInputStream,把内容设置到内存之中。
程序代码如下:
package com.thr.other; import java.io.*; /** * @author Administrator * @date 2020-02-29 * @desc 回退流 */ public class PushInputStreamTest{ public static void main(String args[]) throws Exception { // 所有异常抛出 String str = "www.pornhub.com;www.xvideos.com" ; // 定义"老司机"字符串 PushbackInputStream push = null ; // 定义回退流对象 ByteArrayInputStream bai = null ; // 定义内存输入流 bai = new ByteArrayInputStream(str.getBytes()) ;// 实例化内存输入流 push = new PushbackInputStream(bai) ; // 从内存中读取数据 System.out.print("读取之后的数据为:") ; int temp = 0 ; while((temp=push.read())!=-1){ // 读取内容 if(temp=='.'){ // 判断是否读取到了“.” push.unread(temp) ; //放回到缓冲区之中 temp = push.read() ; //再读一遍 System.out.print("(退回"+(char)temp+")") ; }else{ System.out.print((char)temp) ; //输出内容 } } } }
运行结果: 读取之后的数据为:www(退回.)pornhub(退回.)com;www(退回.)xvideos(退回.)com。
此处参考博客:https://blog.csdn.net/hanshileiai/article/details/6719647
7、管道流
管道流:是用来在多个线程之间进行信息传递的流。
管道流分为:字节流管道流和字符管道流。(它们必须成双成对的使用)
- 字节管道流:PipedOutputStream 和 PipedInputStream。
- 字符管道流:PipedWriter 和 PipedReader。
其中 PipedOutputStream、PipedWriter可以理解为写入者、生产者、发送者,PipedInputStream、PipedReader可以理解为读取者、消费者、接收者。
一个PipedInputStream对象必须和一个PipedOutputStream对象进行连接而产生一个通信管道, PipedOutputStream可以向管道中写入数据,PipedInputStream可以从管道中读取PipedOutputStream写入的数据。
字节管道流:PipedOutputStream、PipedInputStream
package com.thr.other; import java.io.*; /** * @author Administrator * @date 2020-02-29 * @desc 字节管道流 */ public class PipeStreamTest { public static void main(String[] args) { try { Sender sender = new Sender(); // 创建线程对象Sender Receiver receiver = new Receiver(); // 创建线程对象Receiver PipedOutputStream out = sender.getOutputStream(); // 写入 PipedInputStream in = receiver.getInputStream(); // 读出 out.connect(in); // 将输出发送到输入 sender.start();// 启动线程 receiver.start(); } catch(IOException e) { e.printStackTrace(); } } } //写入数据的线程,发送端 class Sender extends Thread { private PipedOutputStream out = new PipedOutputStream(); public PipedOutputStream getOutputStream() { return out; } public void run() { try { String s = new String("Receiver,你好!"); out.write(s.getBytes()); out.close(); } catch(IOException e) { e.printStackTrace(); } } } //读取数据的线程,接收端 class Receiver extends Thread { private PipedInputStream in = new PipedInputStream(); public PipedInputStream getInputStream() { return in; } public void run() { try { String s = null; byte[] b = new byte[1024]; int len = in.read(b); s = new String(b, 0, len); System.out.println("收到了以下信息:" + s); in.close(); } catch(IOException e) { e.printStackTrace(); } } }
注意:当点进去PipedInputStream的源码可以发现,其内部包含一个缓冲区,其默认值为DEFAULT_PIPE_SIZE = 1024个字节,这就意味着 通过PipedOutputStream写入的数据会保存到对应的 PipedInputStream的内部缓冲区。如果对应的 PipedInputStream输入缓冲区已满,再次企图写入PipedOutputStream的线程都将被阻塞,直至出现读取PipedInputStream的操作从缓冲区删除数据。
对于字符管道流,这里就不分析了,因为字符管道流原理跟字节管道流一样,只不过底层一个是 byte 数组存储 一个是 char 数组存储的。
8、随机存储文件流
随机存储文件流(RandomAccessFile):RandomAccessFile是一个非常特殊的流,它虽然声明在java.io包下,但是它是直接继承自java.lang.Object类的。并且它实现了DataInput和DataOutput这两个接口,表明这个类既可以读也可以写数据。但是和其他输入/输入流不同的是,程序可以直接跳到文件的任意位置来读写数据。 因为RandomAccessFile可以自由访问文件的任意位置,所以如果我们希望只访问文件的部分内容,那就可以使用RandomAccessFile类。
RandomAccessFile类的构造方法如下所示:
- RandomAccessFile(File file , String mode):创建随机存储文件流,文件属性由参数File对象指定
- RandomAccessFile(String name , String mode):创建随机存储文件流,文件属性由指定名称的文件读取。
这两个构造方法均涉及到一个String类型的参数mode,它决定随机存储文件流的操作模式,其中mode值及对应的含义如下:
- “r”:以只读的方式打开,调用该对象的任何write(写)方法都会导致IOException异常。
- “rw”:以读、写方式打开,支持文件的读取或写入。若文件不存在,则创建之。
- “rws”:以读、写方式打开,与“rw”不同的是,还要求对文件内容或元数据的每个更新都同步写入到底层设备。这里的“s”表示synchronous(同步)的意思
- “rwd”:以读、写方式打开,与“rw”不同的是,还要对文件内容的每次更新都同步更新到潜在的存储设备中去。使用“rwd”模式仅要求将文件的内容更新到存储设备中,而使用“rws”模式除了更新文件的内容,还要更新文件的元数据(metadata),因此至少要求1次低级别的I/O操作
接下来简单举例:
package com.thr.other; import java.io.*; /** * @author Administrator * @date 2020-02-29 * @desc 随机存储流 */ public class RandomAccessFileTest { public static void main(String[] args) throws IOException { //随机流读数据--r RandomAccessFile raf1 = new RandomAccessFile("D:\IO\RAF.txt", "r"); byte[] buffer1 = new byte[1024]; int len1; while ((len1=raf1.read(buffer1))!=-1){ System.out.println(new String(buffer1,0,len1)); }; //随机流写数据--rw RandomAccessFile raf2 = new RandomAccessFile("D:\IO\RAF1.txt", "rw"); byte[] buffer2 = new byte[1024]; int len2; while ((len2=raf1.read(buffer2))!=-1){//注意这里是raf1,因为从RAF.txt读取数据,输出到RAF1.txt中 raf2.write(buffer2,0,len2); }; raf1.close(); raf2.close(); } }
RandomAccessFile还有一个特殊的地方是它包含了一可以从超大的文本中快速定位我们的游标,用于标记当前读写处的位置。当前读写n个字节后,文件指示器将指向这n个字节后面的下一个字节处。除此之外,RandomAccessFile是可以自由的移动记录指针,即可以向前移动,也可以向后移动。RandomAccessFile包含了以下两个方法来操作文件的记录指针:
- long getFilePointer():返回文件记录指针的当前位置 。
- void seek(long pos):将文件记录指针定位到pos位置。
①、使用RandomAccessFile实现从指定位置读取文件的功能。
package com.thr.other; import java.io.*; /** * @author Administrator * @date 2020-02-29 * @desc 随机存储流中的游标方法使用示例 */ public class RandomAccessFileMethodTest1 { public static void main(String[] args) { RandomAccessFile raf=null; try { raf=new RandomAccessFile("D:\IO\test.txt","r"); // 获取 RandomAccessFile对象文件指针的位置,初始位置为0 System.out.print("输入内容:"+raf.getFilePointer()); //移动文件记录指针的位置 raf.seek(2); byte[] b=new byte[1024]; int len=0; //循环读取文件 while((len=raf.read(b))>0){ //输出文件读取的内容 System.out.print(new String(b,0,len)); } }catch (IOException e){ e.printStackTrace(); }finally { try { raf.close(); } catch (IOException e) { e.printStackTrace(); } } } }
②、使用RandomAccessFile实现向文件中追加内容的功能
package com.thr.other; import java.io.*; /** * @author Administrator * @date 2020-02-29 * @desc 随机存储流中的游标方法使用示例 */ public class RandomAccessFileMethodTest2 { public static void main(String[] args)throws IOException { RandomAccessFile raf=null; File file=null; try { // 以读写的方式打开一个RandomAccessFile对象 raf=new RandomAccessFile("D:\IO\test.txt","rw"); //将记录指针移动到该文件的最后 raf.seek(raf.length()); //向文件末尾追加内容 raf.writeUTF("【这是追加内容】"); }catch (IOException e){ e.printStackTrace(); }finally { raf.close(); } } }
③、使用RandomAccessFile实现向文件指定位置插入内容的功能
注:RandomAccessFile不能向文件的指定位置插入内容,如果直接将文件记录指针移动到中间某位置后开始输出,则新输出的内容会覆盖文件原有的内容,如果需要向指定位置插入内容,程序需要先把插入点后面的内容写入缓存区,等把需要插入的数据写入到文件后,再将缓存区的内容追加到文件后面。
package com.thr.other; import java.io.*; /** * @author Administrator * @date 2020-02-29 * @desc 随机存储流中的游标方法使永示例 */ public class RandomAccessFileMethodTest3 { public static void main(String[] args)throws IOException { insert("D:\IO\test.txt",10,"【插入指定位置指定内容】"); } /** * 插入文件指定位置的指定内容 * @param filePath 文件路径 * @param pos 插入文件的指定位置 * @param insertContent 插入文件中的内容 * @throws IOException */ public static void insert(String filePath,long pos,String insertContent)throws IOException{ RandomAccessFile raf=null; File tmp= File.createTempFile("temp",null); tmp.deleteOnExit(); try { // 以读写的方式打开一个RandomAccessFile对象 raf = new RandomAccessFile(new File(filePath), "rw"); //创建一个临时文件来保存插入点后的数据 FileOutputStream fileOutputStream = new FileOutputStream(tmp); FileInputStream fileInputStream = new FileInputStream(tmp); //把文件记录指针定位到pos位置 raf.seek(pos); raf.seek(pos); //------下面代码将插入点后的内容读入临时文件中保存----- byte[] buffer = new byte[64]; //用于保存实际读取的字节数据 int len = 0; //使用循环读取插入点后的数据 while ((len = raf.read(buffer)) != -1) { //将读取的内容写入临时文件 fileOutputStream.write(buffer, 0, len); } //-----下面代码用于插入内容 ----- //把文件记录指针重新定位到pos位置 raf.seek(pos); //追加需要插入的内容 raf.write(insertContent.getBytes()); //追加临时文件中的内容 while ((len = fileInputStream.read(buffer)) != -1) { //将读取的内容写入临时文件 raf.write(buffer, 0, len); } }catch (Exception e){ throw e; } } }
上面的程序使用File类的createTempFile方法创建了一个临时文件(该文件将在JVM退出后被删除),用于保存被插入点后面的内容。程序先将文件中插入点后的内容读入临时文件中,然后重新定位到插入点,将需要插入的内容添加到文件后面,最后将临时文件的内容添加到文件后面,通过这个过程就可以向指定文件,指定位置插入内容。每次运行上面的程序,都会看到test.txt文件中多了一行内容。