IO模型与NIO基础二

抽象基类之二

FilterInputStream

FilterInputStream 的作用是用来"封装其它的输入流,并为它们提供额外的功能"。
它的常用的子类有BufferedInputStream和DataInputStream
(1) BufferedInputStream的作用就是为"输入流提供缓冲功能,以及mark()和reset()功能"。

  1. InputStream和Reader提供的一些移动指针的方法:
  2. void mark(int readlimit ); 在记录指针当前位置记录一个标记(mark)。
  3. boolean markSupported(); 判断此输入流是否支持mark()操作,即是否支持记录标记。
  4. void reset(); 将此流的记录指针重新定位到上一次记录标记(mark)的位置。
  5. long skip(long n); 记录指针向前移动n个字节/字符。

readlimit 参数给出当前输入流在标记位置变为非法前允许读取的字节数。

这句话的意思是说:mark就像书签一样,用于标记,以后再调用reset时就可以再回到这个mark过的地方。mark方法有个参数,通过这个整型参数,告诉系统,希望在读出多少个字符之前,这个mark保持有效。

比如说mark(10),那么在read()10个以内的字符时,reset()操作指针可以回到标记的地方,然后重新读取已经读过的数据,如果已经读取的数据超过10个,那reset()操作后,就不能正确读取以前的数据了,mark()打标记已经失效,reset()会报错。

但实际的运行情况却和JAVA文档中的描述并不完全相符。 有时候在BufferedInputStream类中调用mark(int readlimit)方法后,即使读取超过readlimit字节的数据,mark标记仍可能有效,仍然能正确调用reset方法重置。

事实上,mark在JAVA中的实现是和缓冲区相关的。只要缓冲区够大,mark后读取的数据没有超出缓冲区的大小,mark标记就不会失效。如果不够大,mark后又读取了大量的数据,导致缓冲区更新,原来标记的位置自然找不到了。

因此,mark后读取多少字节才失效,并不完全由readlimit参数确定,也和BufferedInputStream类的缓冲区大小有关。 如果BufferedInputStream类的缓冲区大小大于readlimit,在mark以后只有读取超过缓冲区大小的数据,mark标记才会失效。

java 复制代码
public class test1 {
	public static void main(String[] args) throws IOException {
		byte[] b=new byte[] {1,2,3,4,5};
		//把数组转为数组输入流
		ByteArrayInputStream bais = new ByteArrayInputStream(b);
		//进行一次封装,封装时指定缓冲区大小,先指定2个字节大小
		BufferedInputStream bis = new BufferedInputStream(bais,2);
		//先读出第一个字节数据出来,指针会指向第二个字节即2上面
		 System.out.println(bis.read());  // 1
		//现在指针在2上,打一个标记
		 bis.mark(1);   
		//按官方文档来说,读第一个数据出来后,标记会失效,reset()方法会报错,事实上不会报错,经过测试,是缓冲区bis读三个数据时,
		   //大小缓冲区大小,缓冲区装不下了,标记才有用。
		 System.out.println(bis.read());  //2
		 System.out.println(bis.read());  //3
	     bis.reset();
	     System.out.println(bis.read());  //2
		
	}

}
----------------------------------------------------------------------------
1
2
3
2

当连续读三个数据时,缓冲区(2个字节大小)装不下,标记才会失效

java 复制代码
public class test1 {
	public static void main(String[] args) throws IOException {
		byte[] b=new byte[] {1,2,3,4,5};
		//把数组转为数组输入流
		ByteArrayInputStream bais = new ByteArrayInputStream(b);
		//进行一次封装,封装时指定缓冲区大小,先指定2个字节大小
		BufferedInputStream bis = new BufferedInputStream(bais,2);
		//先读出第一个字节数据出来,指针会指向第二个字节即2上面
		 System.out.println(bis.read());  // 1
		//现在指针在2上,打一个标记
		 bis.mark(1);   
		//按官方文档来说,读第一个数据出来后,标记会失效,reset()方法会报错,事实上不会报错,经过测试,是缓冲区bis读三个数据时,
		   //大小缓冲区大小,缓冲区装不下了,标记才有用。
		 System.out.println(bis.read());  //2
		 System.out.println(bis.read());  //3
		 System.out.println(bis.read());  //4
	     bis.reset();
	     System.out.println(bis.read());  没有数据打印,并报错了
		
	}

}
----------------------------------------------------------------------------
1
2
3
4
Exception in thread "main" java.io.IOException: Resetting to invalid mark
	at java.io.BufferedInputStream.reset(Unknown Source)
	at cn.ybzy.io.filter.test1.main(test1.java:33)

不设标记,不重设定位正常读没问题

java 复制代码
public class test1 {
	public static void main(String[] args) throws IOException {
		byte[] b=new byte[] {1,2,3,4,5};
		//把数组转为数组输入流
		ByteArrayInputStream bais = new ByteArrayInputStream(b);
		//进行一次封装,封装时指定缓冲区大小,先指定2个字节大小
		BufferedInputStream bis = new BufferedInputStream(bais,2);
		 System.out.println(bis.read());  // 1
		 System.out.println(bis.read());  //2
		 System.out.println(bis.read());  //3
		 System.out.println(bis.read());  //4
	     System.out.println(bis.read());  //5
		
	}

}

(2) DataInputStream 是用来装饰其它输入流,它"允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型"。

应用程序可以使用DataOutputStream(数据输出流)写入由DataInputStream(数据输入流)读取的数据。

FilterOutputStream

FilterOutputStream 的作用是用来"封装其它的输出流,并为它们提供额外的功能"。

它主要包括BufferedOutputStream, DataOutputStream和PrintStream。

(1) BufferedOutputStream的作用就是为"输出流提供缓冲功能"。

(2) DataOutputStream 是用来装饰其它输出流,将DataOutputStream和DataInputStream输入流配合使用,

"允许应用程序以与机器无关方式从底层输入流中读写基本 Java 数据类型"。

打印流PrintStream

PrintStream是用来装饰其它输出流。它能为其他输出流添加了功能,使它们能够方便地打印各种数据值表示形式。

打印流提供了非常方便的打印功能,可以打印任何类型的数据信息,例如:小数,整数,字符串。

之前打印信息需要使用OutputStream但是这样,所有数据输出会非常麻烦,PrintStream可以把OutputStream类重新包装了一下,使之输出更方便。

java 复制代码
public class PrintStreamTest {
	public static void main(String[] args) throws FileNotFoundException {
		FileOutputStream fos =new FileOutputStream("c:\\c.txt");
		PrintStream print=new PrintStream(fos);
		print.print("xiongshaowen");
		print.print(123);
		print.print(12.3);
		print.print(new Object());
		print.close();
	}
	

}


格式化输出:

JAVA对PrintStream功能进行了扩充,增加了格式化输出功能。直接使用Print即可。但是输出的时候需要指定输出的数据类型。

java 复制代码
public class PrintStreamTest {
	public static void main(String[] args) throws FileNotFoundException {
		FileOutputStream fos =new FileOutputStream("c:\\c.txt");
		PrintStream ps=new PrintStream(fos);
		/*print.print("xiongshaowen");
		print.print(123);
		print.print(12.3);
		print.println(new Object());
		print.close();*/
		
  //格式打印
	    int i = 10;
	    String s="打印流";
	    float f = 15.5f;
	    ps.printf("整数 %d,字符串 %s,浮点数 %f",i,s,f);
	    ps.println();
	    ps.printf("整数 [%d],字符串 %s,浮点数 '%f'",i,s,f);
	    ps.close();
	    
	    
	}
	

}

推回输入流的使用

通常我们使用输入流的时候,我们读取输入流是顺序读取,当读取的不是我们想要的怎么办,又不能放回去,虽然我们可以使用程序做其他的处理来解决,但是Java提供了推回输入流来解决这个问题,

推回输入流可以做这样子的事情:将已经读取出来的字节或字符数组内容推回到推回缓冲区里面,

从而允许重复读取刚刚读取的我们不想要的东西之前内容

注意:当程序创建一个推回输入流时需要指定推回缓冲区的大小,默认的推回缓冲区长度为一,

如果程序推回到推回缓冲区的内容超出了推回缓冲区的大小,将会引发Pushback buffer overflow 异常。
程序举例:

假如C盘下有一个aa.txt文件,内容如下:我现在只想读取aaa前面的内容,后面的我不想要。

java 复制代码
public class TuiHuistream {
	
	public static void main(String[] args) throws IOException {
		//1文件流,2字符流 3输入流
		Reader reader = new FileReader("C:\\aa.txt");
		PushbackReader pr = new PushbackReader(reader,1024);
		//从输入流中读取数据
		char[] cs = new char[5];
		int hasReadCount = 0;
		String sumString="";
		int count=0;                       //计数器,看看下面读了多少次数据
		while((hasReadCount=pr.read(cs))!=-1) {
			String curString = new String(cs,0,hasReadCount);
			sumString =sumString+curString;
			count++;
			int aaaIndex = sumString.indexOf("aaa");   //这里我们以'aaa'为标记,推回aaa之前的内容到缓冲区中
			if(aaaIndex>-1) {
				pr.unread(sumString.toCharArray());    //把所有内容推到缓冲区中(cs)
				   //重新把我想要的内容(即aaa之前的内容)读出来
				if(aaaIndex >5) {
					cs = new char[aaaIndex];//扩容缓冲区
				}
				pr.read(cs,0,cs.length);
				System.out.println("我想要的内容为:"+new String(cs));
				break;
			}else {
				System.out.println(new String(cs));
			}
			
			
		}
		System.out.println("一共读了多少次:"+count+"次");
		pr.close();
	}

}
------------------------------------------------------------------------------------------------------------------------------
ccccc
ccccc
ccccc
cc
c
cccca
我想要的内容为:ccccccccccccccccc
ccccc
一共读了多少次:6次


//注释掉pr.unread(sumString.toCharArray());结果为
ccccc
ccccc
ccccc
cc
c
cccca
我想要的内容为:ttttttttttttttttttttttt
一共读了多少次:6次

数据输出输入流

数据流DataInputStream和DataOutputStream的使用

DataOutputStream数据输出流允许应用程序将基本Java数据类型写到基础输出流中,而DataInputStream数据输入流允许应用程序以机器无关的方式从底层输入流中读取基本的Java类型.
DataInputStream的内部方法:

read(byte b[])---从数据输入流读取数据存储到字节数组b中.
read(byte b[],int off,in len)---从数据输入流中读取数据存储到数组b里面,位置从off开始,长度为len个字节.
readFully(byte b[])---从数据输入流中循环读取b.length个字节到数组b中.
readFully(byte b[],int off,in len )---从数据输入流中循环读取len个字节到字节数组b中.从b的off位置开始放
skipBytes(int b)---跳过n个字节.
readBoolean()---从数据输入流读取布尔类型的值.
readByte()---从数据输入流中读取一个字节.
readUnsignedByte()---从数据输入流中读取一个无符号的字节,返回值转换成int类型.
readShort()---从数据输入流读取一个short类型数据.
readUnsignedShort()---从数据输入流读取一个无符号的short类型数据.
readChar()---从数据输入流中读取一个字符数据
readInt()---从数据输入流中读取一个int类型数据.
readLong()---从数据输入流中读取一个long类型的数据.
readFloat()---从数据输入流中读取一个float类型的数据.
readDouble()---从数据输入流中读取一个double类型的数据.
readUTF()---从数据输入流中读取用UTF-8格式编码的UniCode字符格式的字符串.

DataOutputStream的内部方法

intCount(int value)---数据输出流增加的字节数.
write(int b)---将int类型的b写到数据输出流中.
write(byte b[],int off, int len)---将字节数组b中off位置开始,len个长度写到数据输出流中.
flush()---刷新数据输出流.
writeBoolean()---将布尔类型的数据写到数据输出流中,底层是转化成一个字节写到基础输出流中.
writeByte(int v)---将一个字节写到数据输出流中(实际是基础输出流).
writeShort(int v)---将一个short类型的数据写到数据输出流中,底层将v转换2个字节写到基础输出流中.
writeChar(int v)---将一个charl类型的数据写到数据输出流中,底层是将v转换成2个字节写到基础输出流中.
writeInt(int v)---将一个int类型的数据写到数据输出流中,底层将4个字节写到基础输出流中.
writeLong(long v)---将一个long类型的数据写到数据输出流中,底层将8个字节写到基础输出流中.
writeFloat(flloat v)---将一个float类型的数据写到数据输出流中,底层会将float转换成int类型,写到基础输出流中.
writeDouble(double v)---将一个double类型的数据写到数据输出流中,底层会将double转换成long类型,写到基础输出流中.
writeBytes(String s)---将字符串按照字节顺序写到基础输出流中.
writeChars(String s)---将字符串按照字符顺序写到基础输出流中.
writeUTF(String str)---以机器无关的方式使用utf-8编码方式将字符串写到基础输出流中.
size()---写到数据输出流中的字节数.

例:c盘下 java.txt文件,我们输出内容到里面,再打印到控制台真实内容,文件中的数据为二进制,所以看不出来是什么。

java 复制代码
public class DataStream {
	public static void main(String[] args) throws IOException {
	    DataOutputStream dos = new DataOutputStream(new FileOutputStream("c:\\java.txt"));
	    dos.writeUTF("αα熊少文");
	    dos.writeInt(1234567);
	    dos.writeBoolean(true);
	    dos.writeShort((short)123);
	    dos.writeLong((long)456);
	    dos.writeDouble(99.98);
	    DataInputStream dis = new DataInputStream(new FileInputStream("c:\\java.txt"));
	    System.out.println(dis.readUTF());
	    System.out.println(dis.readInt());
	    System.out.println(dis.readBoolean());
	    System.out.println(dis.readShort());
	    System.out.println(dis.readLong());
	    System.out.println(dis.readDouble());
	    dis.close();
	    dos.close();
	}


}
--------------------------------------------------------------------------------
αα熊少文
1234567
true
123
456
99.98

重定向标准输入或输出

Java的标准输入/输出分别通过System.in和System.out来代表,默认情况下它们分别代表键盘和显示器,

当程序通过System.in来获取输入时,实际上是从键盘读取输入,当程序试图通过System.out执行输出时,程序总是输出到屏幕。

System类里提供三个重定向标准输入/输出的方法:

static void setErr(PrintStream err):重定向"标准"错误输出流

static void setIn(InputStream in):重定向"标准"输入流

static void setOut(PrintStream out):重定向"标准"输出流
例:标准输出流重定向

java 复制代码
public class System_in_out_x {
	public static void main(String[] args) {
		PrintStream ps=null;
		try {
			FileOutputStream fos = new FileOutputStream("c:\\eee.txt");
			//打印到文件中
			ps = new PrintStream(fos);
			System.setOut(ps);
			System.out.println("关联了关联文件,这里的打印信息,不会输出到控制台,而是输出到文件中了!");
			
			
		}catch(FileNotFoundException e) {
			e.printStackTrace();
		}finally {
		   if(ps!=null)
			ps.close();    //内存以外的流一定要关闭,关闭顶层流即可。
		}
	}

}


例:标准输入流重定向

java 复制代码
		InputStream in=null;
		Scanner scanner=null;
		try {
		    in = new FileInputStream("c:\\fff.txt");
			System.setIn(in);
			 scanner = new Scanner(System.in);
			scanner.useDelimiter("\n");     //分隔符为换行符
			while(scanner.hasNext()) {
				System.out.println(scanner.next());
			}
			
		}catch(FileNotFoundException e) {
			e.printStackTrace();
		}finally {
			if(scanner!=null)
				scanner.close(); //内存以外的流一定要关闭,关闭顶层流即可。
		}
----------------------------------------------------------------------------
熊少文

xiongshaowen

xuhuifeng

Java程序对子进程进行读写

使用Runtime对象的exec()方法可以运行平台上的其他程序,该方法产生一个Process对象,

Process对象代表由该Java程序启动的子进程。

Process类提供了3个方法,用于让程序和其子进程通信

InputStream getErrorStream():获取子进程的错误流。

InputStream getInputSteeam():获取子进程的输入流。

OutputStream getOutputStream():获取子进程的输出流。
例:获取javac命令的错误提示信息

java 复制代码
public class RunnSubproess {
	public static void main(String[] args) throws IOException {
		Process process=Runtime.getRuntime().exec("javac");
		//从p里,获取到错误信息流,返回InputStream字节输入流,为了方便读取,我们要转换流转换成字符输入流
		InputStreamReader reader =new InputStreamReader(process.getErrorStream(),"GBK");  //GBK,WINDOWS中的编码格式
		//进一步再封装成缓冲流,因为字符缓冲流中有一个方法,可以一次性读一行,十分方便
		BufferedReader br=new BufferedReader(reader);
		String buff = null;
		while((buff=br.readLine())!=null) {
			System.out.println(buff);
		}
		if(br!=null)
			br.close();
	}

}
----------------------------------------------------------------------------
用法: javac <options> <source files>
其中, 可能的选项包括:
  -g                         生成所有调试信息
  -g:none                    不生成任何调试信息
  -g:{lines,vars,source}     只生成某些调试信息
  -nowarn                    不生成任何警告
  -verbose                   输出有关编译器正在执行的操作的消息
  -deprecation               输出使用已过时的 API 的源位置
  -classpath <路径>            指定查找用户类文件和注释处理程序的位置
  -cp <路径>                   指定查找用户类文件和注释处理程序的位置
  -sourcepath <路径>           指定查找输入源文件的位置
  -bootclasspath <路径>        覆盖引导类文件的位置
  -extdirs <目录>              覆盖所安装扩展的位置
  -endorseddirs <目录>         覆盖签名的标准路径的位置
  -proc:{none,only}          控制是否执行注释处理和/或编译。
  -processor <class1>[,<class2>,<class3>...] 要运行的注释处理程序的名称; 绕过默认的搜索进程
  -processorpath <路径>        指定查找注释处理程序的位置
  -parameters                生成元数据以用于方法参数的反射
  -d <目录>                    指定放置生成的类文件的位置
  -s <目录>                    指定放置生成的源文件的位置
  -h <目录>                    指定放置生成的本机标头文件的位置
  -implicit:{none,class}     指定是否为隐式引用文件生成类文件
  -encoding <编码>             指定源文件使用的字符编码
  -source <发行版>              提供与指定发行版的源兼容性
  -target <发行版>              生成特定 VM 版本的类文件
  -profile <配置文件>            请确保使用的 API 在指定的配置文件中可用
  -version                   版本信息
  -help                      输出标准选项的提要
  -A关键字[=值]                  传递给注释处理程序的选项
  -X                         输出非标准选项的提要
  -J<标记>                     直接将 <标记> 传递给运行时系统
  -Werror                    出现警告时终止编译
  @<文件名>                     从文件读取选项和文件名

例:向子进程传递信息,子进程接收信息并保存到指定的文件中。
这里我不在开发工具中执行编译,在cmd中执行javac xxx.java编译java文件

错误: 编码GBK的不可映射字符

处理:这是windows中cmd执行javac xxx.java 编译的文件要保存为ANSI文件格式(GBK),而utf-8文件中又有中文,所以不可执行,

java 复制代码
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.Scanner;

public class RunnSubproess {
	public static void main(String[] args) throws IOException {

//调用子进程,向子进程写信息,子进程把信息保存到一个指定的文件中,这里我再定义一个类,写一个main方法,该main方法是帽本例中调用执行。
		PrintStream ps =null;
		try {
			Process p = Runtime.getRuntime().exec("java ReceiveInfo");   //执行 java ReceiveInfo命令
			OutputStream out =p.getOutputStream();
			ps=new PrintStream(out);    //封成打印流,方便输出
			ps.println("普通字符串!");
			ps.println(123456);
			ps.println(new RunnSubproess());
			
		}catch (IOException e) {
			e.printStackTrace();
		}finally {
			if(ps!=null)
				ps.close();
		}
		
	}	

}

class ReceiveInfo{
	public static void main(String[] args) {
		PrintStream ps =null;
		try {
			OutputStream out=new FileOutputStream("c:\\runtime.txt");
			ps=new PrintStream(out);
			Scanner sc = new Scanner(System.in);  //这里的System.in不是键盘输入,而是父进程的输入流,当然了它绝大多数情况下是键盘输入的
			sc.useDelimiter("\n");     //以换行回车符为分隔符读一行数据
			while(sc.hasNext()) {  //hasNext()会阻塞线程,一直判断
				ps.print(sc.next());
			  }
			}catch(FileNotFoundException ex) {
				ex.printStackTrace();
			}finally {
				if(ps!=null)
					ps.close();
			}
		
	}
}

编译执行主类

相关推荐
Yoyo25年秋招冲冲冲2 天前
【Java回顾】Day7 Java IO|分类(传输方式,数据操作)|零拷贝和NIO
java·开发语言·nio
JWASX7 天前
【源码解析】Java NIO 包中的 ByteBuffer
java·nio·bytebuffer·大端序·小端序
JWASX8 天前
【源码解析】Java NIO 包中的 Buffer
java·nio·buffer·bytebuffer
匠道8 天前
二、BIO、NIO编程与直接内存、零拷贝
nio
静心观复12 天前
Java NIO、AIO分析
java·开发语言·nio
静心观复12 天前
java IO 与 BIO、NIO、AIO
java·nio
程序员小杰@12 天前
Java的 BIO、NIO、AIO?分别的作用和用法
java·python·nio
qq_3340602112 天前
IO模型与NIO基础
nio
w36250126614 天前
Java NIO
java·开发语言·nio