内存操作流
定义:除了文件之外,IO操作也可以发生在内存中,发生在内存中的操作流称为内存流。
文件流的操作里面一定会产生一个文件数据(不管后这个文件数据是否被保留)。
如果现在需求是:需要进行IO处理,但是又不希望产生文件,这种情况下就可以使用内存作为操作终端。
内存流也分为两类:
- 字节内存流:
ByteArrayInputStream
、ByteArrayOutputStream
- 字符内存流:
CharArrayReader
、CharArrayWriter
首先来观察ByteArrayInputStream和ByteArrayOutputStream的构造方法:
public
class ByteArrayInputStream extends InputStream {
/**
* Creates a <code>ByteArrayInputStream</code>
* so that it uses <code>buf</code> as its
* buffer array.
* The buffer array is not copied.
* The initial value of <code>pos</code>
* is <code>0</code> and the initial value
* of <code>count</code> is the length of
* <code>buf</code>.
*
* @param buf the input buffer.
*/
public ByteArrayInputStream(byte buf[]) {
this.buf = buf;
this.pos = 0;
this.count = buf.length;
}
public class ByteArrayOutputStream extends OutputStream {
/**
* Creates a new byte array output stream. The buffer capacity is
* initially 32 bytes, though its size increases if necessary.
*/
public ByteArrayOutputStream() {
this(32);
}
范例:通过内存流实现大小写转换
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
public class TestFileStream{
public static void main(String[] args) throws Exception{
String msg = "hello world";
ByteArrayInputStream inputStream = new ByteArrayInputStream(msg.getBytes());
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
int len = 0;
while ((len = inputStream.read()) != -1){
outputStream.write(Character.toUpperCase(len));
}
System.out.println(outputStream);
inputStream.close();
outputStream.close();
}
}
打印流
打印流解决的就是OutputStream
的设计缺陷,属于OutputStream
功能的加强版。
如果操作的不是二进制数据, 只是想通过程序向终端目标输出信息的话,OutputStream
不是很方便,其缺点有两个:
- 所有的数据必须转换为字节数组。
- 如果要输出的是int、double等类型就不方便了。
打印流概念
打印流设计的主要目的是为了解决OutputStream的设计问题,其本质不会脱离OutputStream。
范例:设计一个简单打印流
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
class PrintUtil{
private OutputStream outputStream;
public PrintUtil(OutputStream outputStream) {
this.outputStream = outputStream;
}
public void print(String str){
try {
outputStream.write(str.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
public void println(String str){
this.print(str+"\r\n");
}
public void print(int num){
this.print(num);
}
public void println(int num){
this.print(num+"\r\n");
}
public void print(double data){
this.print(data);
}
public void println(double data){
this.print(data+"\r\n");
}
}
public class TestFileStream {
public static void main(String[] args)throws Exception {
PrintUtil printUtil = new PrintUtil(new FileOutputStream
(new File("C:" + File.separator + "Users" + File.separator
+ "DELL" + File.separator + "Desktop" + File.separator + "Test.txt")));
printUtil.print("姓名:");
printUtil.println("刘德华") ;
printUtil.print("年龄:") ;
printUtil.println(50) ;
printUtil.print("工资:") ;
printUtil.println(1000.123456789);
}
}
经过简单处理之后,让OutputStream的功能变的更加强大了,其实本质就只是对OutputStream的功能做了一个 封装而已。java提供有专门的打印流处理类:PrintStream
、PrintWriter
使用系统提供的打印流
打印流分为字节打印流:PrintStream
、字符打印流:PrintWriter
。
首先来观察这两 个类的继承结构与构造方法:
此时看上图继承关系我们会发现,有点像之前讲过的代理设计模式,但是代理设计模式有如下特点:
- 代理是以接口为使用原则的设计模式。
- 最终用户可以调用的方法一定是接口定义的方法。
打印流优缺点:
- 优点:扩展功能特别方便,需要不同的功能时只需要更换装饰类即可。
- 缺点:类结构复杂。
打印流应用的是装饰设计模式(基于抽象类的设计模式):核心依然是某个类(OutputStream)的功能(writer()),但是为了得到更好的操作效果,让其支持的功能更多。
范例:使用打印流
public class TestFileStream {
public static void main(String[] args)throws Exception {
PrintStream printUtil = new PrintStream(new FileOutputStream
(new File("C:" + File.separator + "Users" + File.separator
+ "DELL" + File.separator + "Desktop" + File.separator + "Test.txt")));
printUtil.print("姓名:");
printUtil.println("刘德华") ;
printUtil.print("年龄:") ;
printUtil.println(50) ;
printUtil.print("工资:") ;
printUtil.println(1000.123456789);
}
}
格式化输出
C语言有一个printf()
函数,这个函数在输出的时候可以使用一些占位符,例如:字符串(%s)、数字(%d)、小数 (%m.nf)、字符(%c)等。从JDK1.5开始,PrintStream
类中也追加了此种操作。
- 格式化输出:
/**
* A convenience method to write a formatted string to this output stream
* using the specified format string and arguments.
*
* <p> An invocation of this method of the form <tt>out.printf(format,
* args)</tt> behaves in exactly the same way as the invocation
*
* <pre>
* out.format(format, args) </pre>
*
* @param format
* A format string as described in <a
* href="../util/Formatter.html#syntax">Format string syntax</a>
*
* @param args
* Arguments referenced by the format specifiers in the format
* string. If there are more arguments than format specifiers, the
* extra arguments are ignored. The number of arguments is
* variable and may be zero. The maximum number of arguments is
* limited by the maximum dimension of a Java array as defined by
* <cite>The Java™ Virtual Machine Specification</cite>.
* The behaviour on a
* <tt>null</tt> argument depends on the <a
* href="../util/Formatter.html#syntax">conversion</a>.
*
* @throws java.util.IllegalFormatException
* If a format string contains an illegal syntax, a format
* specifier that is incompatible with the given arguments,
* insufficient arguments given the format string, or other
* illegal conditions. For specification of all possible
* formatting errors, see the <a
* href="../util/Formatter.html#detail">Details</a> section of the
* formatter class specification.
*
* @throws NullPointerException
* If the <tt>format</tt> is <tt>null</tt>
*
* @return This output stream
*
* @since 1.5
*/
public PrintStream printf(String format, Object ... args) {
return format(format, args);
}
- 格式化字符串:
/**
* Writes a formatted string to this output stream using the specified
* format string and arguments.
*
* <p> The locale always used is the one returned by {@link
* java.util.Locale#getDefault() Locale.getDefault()}, regardless of any
* previous invocations of other formatting methods on this object.
*
* @param format
* A format string as described in <a
* href="../util/Formatter.html#syntax">Format string syntax</a>
*
* @param args
* Arguments referenced by the format specifiers in the format
* string. If there are more arguments than format specifiers, the
* extra arguments are ignored. The number of arguments is
* variable and may be zero. The maximum number of arguments is
* limited by the maximum dimension of a Java array as defined by
* <cite>The Java™ Virtual Machine Specification</cite>.
* The behaviour on a
* <tt>null</tt> argument depends on the <a
* href="../util/Formatter.html#syntax">conversion</a>.
*
* @throws java.util.IllegalFormatException
* If a format string contains an illegal syntax, a format
* specifier that is incompatible with the given arguments,
* insufficient arguments given the format string, or other
* illegal conditions. For specification of all possible
* formatting errors, see the <a
* href="../util/Formatter.html#detail">Details</a> section of the
* formatter class specification.
*
* @throws NullPointerException
* If the <tt>format</tt> is <tt>null</tt>
*
* @return This output stream
*
* @since 1.5
*/
public PrintStream format(String format, Object ... args) {
try {
synchronized (this) {
ensureOpen();
if ((formatter == null)
|| (formatter.locale() != Locale.getDefault()))
formatter = new Formatter((Appendable) this);
formatter.format(Locale.getDefault(), format, args);
}
} catch (InterruptedIOException x) {
Thread.currentThread().interrupt();
} catch (IOException x) {
trouble = true;
}
return this;
}
System类对IO的支持
- 输出(均是打印流
PrintStream
的对象)
- 标准输出(显示器):
System.out
- 错误输出:
System.err
-
输入(输入流
InputStream
的对象)标准输入(键盘):
System.in
系统输出
系统输出一共有两个常量:out、err,并且这两个常量表示的都是PrintStream类的对象。
- out输出的是希望用户能看到的内容
- err输出的是不希望用户看到的内容
这两种输出在实际的开发之中都没用了,取而代之的是"日志。
System.err
只是作为一个保留的属性而存在,现在几乎用不到。唯一可能用到的就是System.out
。
由于System.out是PrintStream的实例化对象,而PrintStream又是OutputStream的子类,所以可以直接使用 System.out直接为OutputStream实例化,这个时候的OutputStream输出的位置将变为屏幕。
范例:使用System.out为OutputStream实例化
import java.io.OutputStream;
public class TestFileStream {
public static void main(String[] args)throws Exception {
OutputStream outputStream = System.out;
outputStream.write("你好,中国".getBytes());
outputStream.close();
}
}
系统输入
System.in
对应的类型是InputStream
,而这种输入流指的是由用户通过键盘进行输入(用户输入)。java本身并没有直接的用户输入处理,如果要想实现这种操作,必须使用java.io的模式来完成。
范例:利用InputStream实现数据输入
import java.io.InputStream;
public class TestFileStream {
public static void main(String[] args)throws Exception {
InputStream inputStream = System.in;
byte[] data = new byte[1024];
System.out.println("请输入内容:");
int temp = inputStream.read(data);
System.out.println("输入内容为:" + new String(data,0,temp));
inputStream.close();
}
}
现在发现当用户输入数据的时候程序需要暂停执行,也就是程序进入了阻塞状态。直到用户输入完成(按下回车), 程序才能继续向下执行。
以上的程序本身有一个致命的问题,核心点在于:开辟的字节数组长度固定,如果现在输入的长度超过了字节数组长度,那么只能够接收部分数据。这个时候是由于一次读取不完所造成的问题,所以此时好的做法是引入内存操作流来进行控制,这些数据先保存在内存流中而后一次取出。
范例:引入内存流
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
public class TestFileStream {
public static void main(String[] args)throws Exception {
InputStream inputStream = System.in;
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] data = new byte[1024];
System.out.println("请输入内容:");
int temp = 0;
while ((temp = inputStream.read(data)) != -1){
byteArrayOutputStream.write(data,0,temp);
if (temp < data.length){
break;
}
}
inputStream.close();
byteArrayOutputStream.close();
System.out.println("输出内容为:" + new String(byteArrayOutputStream.toByteArray()z));
}
}
现在虽然实现了键盘输入数据的功能,但是整体的实现逻辑过于混乱了,即java提供的System.in并不好用,还要结 合内存流来完成,复杂度很高。
如果要想在IO中进行中文的处理,最好的做法是将所有输入的数据保存在一起再处理,这样才可以保证不出现乱码。
两种输入流(原生InputStream的进化版)
BufferedReader类
BufferedReader类属于一个缓冲的输入流,而且是一个字符流的操作对象。
在java中对于缓冲流也分为两类:字节缓冲流(BufferedInputStream
)、字符缓冲流(BufferedReader
)。
之所以选择BufferedReader类操作是因为在此类中提供有如下方法(读取一行数据):
/**
* Reads a line of text. A line is considered to be terminated by any one
* of a line feed ('\n'), a carriage return ('\r'), or a carriage return
* followed immediately by a linefeed.
*
* @return A String containing the contents of the line, not including
* any line-termination characters, or null if the end of the
* stream has been reached
*
* @exception IOException If an I/O error occurs
*
* @see java.nio.file.Files#readAllLines
*/
public String readLine() throws IOException {
return readLine(false);
}
readLine()
这个方法可以直接读取一行数据(以回车为换行符)
但是这个时候有一个非常重要的问题要解决,来看BufferedReader类的定义与构造方法:
public class BufferedReader extends Reader {
/**
* Creates a buffering character-input stream that uses an input buffer of
* the specified size.
*
* @param in A Reader
* @param sz Input-buffer size
*
* @exception IllegalArgumentException If {@code sz <= 0}
*/
public BufferedReader(Reader in, int sz) {
super(in);
if (sz <= 0)
throw new IllegalArgumentException("Buffer size <= 0");
this.in = in;
cb = new char[sz];
nextChar = nChars = 0;
}
/**
* Creates a buffering character-input stream that uses a default-sized
* input buffer.
*
* @param in A Reader
*/
public BufferedReader(Reader in) {
this(in, defaultCharBufferSize);
}
而System.in是InputStream类的子类,这个时候与Reader没有关系,要建立起联系就要用到InputStreamReader 类。如下:
范例:利用BufferedReader实现键盘输入
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class TestFileStream {
public static void main(String[] args)throws Exception {
BufferedReader bufferedReader = new BufferedReader(
new InputStreamReader(System.in));
System.out.println("请输入信息:");
String str = bufferedReader.readLine();
System.out.println("输入信息为:" + str);
}
}
以上形式实现的键盘输入有一个大特点:由于接收的数据类型为String,可以使用String类的各种操作进行数据处理并且可以变为各种常见数据类型。
java.util.Scanner类
打印流解决的是OutputStream类的缺陷,BufferedReader解决的是InputStream类的缺陷。而Scanner解决的是 BufferedReader类的缺陷(替换了BufferedReader类) 。Scanner是一个专门进行输入流处理的程序类,利用这个类可以方便处理各种数据类型,同时也可以直接结合正则表达式进行各项处理。
在这个类中主要关注以下方法:
- 判断是否有指定类型输入
public boolean hasNextXXX()
- 取得指定类型数据
public 数据类型 nextXXX()
- 自定义分隔符
public Scanner useDelimiter(Pattern pattern)
- 构造方法
public Scanner(InputStream source)
范例:使用Scanner实现数据输入
import java.util.Scanner;
public class TestFileStream {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入信息:");
if (scanner.hasNext()){
System.out.println("输出内容为:" + scanner.next());
}
scanner.close();
}
}
使用Scanner还可以接收各种数据类型,并且帮助用户减少转型处理。
范例:接收其他类型数据
import java.util.Scanner;
public class TestFileStream {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入年龄:");
if (scanner.hasNextInt()){
System.out.println("年龄为:" + scanner.next());
}else {
System.out.println("输入的不是数字!"); ;
}
scanner.close();
}
}
最为重要的是,Scanner可以对接收的数据类型使用正则表达式判断 。
范例:利用正则表达式进行判断
import java.util.Scanner;
public class TestFileStream {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入出生日期:");
if (scanner.hasNext("\\d{4}-\\d{2}-\\d{2}")){
System.out.println("生日为:" + scanner.next());
}else {
System.out.println("输入的格式非法,不是生日");
}
scanner.close();
}
}
范例:使用Scanner操作文件
import java.io.File;
import java.util.Scanner;
public class TestFileStream {
public static void main(String[] args) throws Exception{
Scanner scanner = new Scanner(new File("C:" + File.separator + "Users" + File.separator
+ "DELL" + File.separator + "Desktop" + File.separator + "Test.txt"));
scanner.useDelimiter("\n") ; // 自定义分隔符
while (scanner.hasNext()){
System.out.println(scanner.next());
}
scanner.close();
}
}
总结:以后除了二进制文件拷贝的处理之外,那么只要是针对程序的信息输出都是用打印流(PrintStream
、 PrintWriter
),信息输出使用Scanner
。