一、内存流
1.定义
IO操作发生在内存中的这种流称为内存操作流。
2.应用场景
需要进行IO处理,但是又不希望产生文件。这种情况下就可以使用内存作为操作终端。
3.内存流的分类
字节内存流:ByteArrayInputStream和ByteArrayOutputStream
字符内存流:CharArrayReader和CharArrayWriter
4.字节内存流
ByteArrayOutputStream流的构造方法:
public ByteArrayOutputStream();//默认字节数组长度为32.
public ByteArrayOutputStream(int size);//用户自定义字节数组大小。
ByteArrayInputStream流的构造方法:
public ByteArrayInputStream(byte buf[]);//创建ByteArrayInputStream,使用buf作为其缓冲器阵列。
public ByteArrayInputStream(byte buf[], int offset, int length);//创建ByteArrayInputStream,使用buf作为其缓冲器阵列。
package com.xunpu.memory;
import java.io.*;
/**
* 给定内容,不通过文件,将给定的内容转换为大写并输出。
* 步骤:
* 1.创建输入流,将给定的内容转换为字节数组。
* 2.创建输出流,将读到的内容转换为要求的内容。
* 3.打印(直接输出out对象)。
* 4.关闭流
*/
public class Demo1 {
public static void main(String[] args) throws IOException {
String msg="Today is Friday";
//1.创建输入流,将给定的内容转换为字节数组。
InputStream in=new ByteArrayInputStream(msg.getBytes());
//2.创建输出流,将读到的内容转换为要求的内容。
OutputStream out=new ByteArrayOutputStream();
//读取内容
int len=0;
while((len=in.read())!=-1){//一次只读取1个字节
out.write(Character.toUpperCase(len));
}
//直接输出out对象
System.out.println(out);
//关闭流
in.close();
out.close();
}
}
此时发生了IO操作,但是没有文件产生。
之前Ajax刚形成的时候,此类操作非常多。后来出现了新的代码工具,这类代码就出现地较少了。
5.案例:两个文件合并(文件量不大)
假设现在有两个文件,分别是商品文件(aaa.txt)和商品价格文件(bbb.txt),商品和商品价格之间均用空格分隔。bbb.txt是按照商品的顺序来写商品价格的。现在要求将商品和商品对应的价格输出。
思路:
1)判断两个文件是否存在。不存在,抛出FileNotFoundException异常;存在,进行下一步。
2)依次读取每个文件,并将文件中的内容转换为字符串。(*****)
2.1 创建输入流对象,便于读取数据
2.2 创建内存输出流对象,便于写数据。
2.3 每次读取一定字节的数据,同时写到内存输出流中。
2.4 关闭流。
2.5 将输出流对象转换为字符串返回。(new String(out.toByteArray()))
3)将商品和商品价格取出(String.split(" "))。
4)拼接每个商品和商品对应的价格。
package com.xunpu.memory;
import java.io.*;
/**
* 合并两个文件量较小的文件.
*/
public class Demo2 {
public static void main(String[] args) throws IOException {
File file1=new File("E:"+File.separator+"JavaFile"+File.separator+"Directory"+File.separator+"bbb"
+File.separator+"aaa.txt");
File file2=new File("E:"+File.separator+"JavaFile"+File.separator+"Directory"+File.separator+"bbb"
+File.separator+"bbb.txt");
//1.判断两个文件是否都存在
if(!(file1.exists()&&file2.exists())){
System.out.println("文件不存在,合并失败");
throw new FileNotFoundException();
}
//进行文件合并操作
String res=mergeFiles(file1,file2);
System.out.println(res);
}
//合并两个文件内容
private static String mergeFiles(File file1, File file2) throws IOException {
String s1=readFile(file1);
String s2=readFile(file2);
StringBuffer sb=new StringBuffer();
String[] str1=s1.split(" ");
String[] str2=s2.split(" ");
int min=str1.length<str2.length?str1.length:str2.length;
int i=0;
for(;i<min;i++){
sb.append(str1[i]).append(":").append(str2[i]).append(" ").append("\n");
}
if(min==str1.length) {
for(int k=i;k<str2.length;k++)
sb.append(str2[k]).append(" ");
}else{
for(int k=i;k<str1.length;k++)
sb.append(str1[k]).append(" ");
}
return new String(sb);
}
//读取文件内容
private static String readFile(File file2) throws IOException {
//创建输入流对象,便于读取数据
InputStream in=new FileInputStream(file2);
//创建内存输出流对象,便于写数据。
ByteArrayOutputStream out=new ByteArrayOutputStream();
int len=0;
byte[] data=new byte[20];//因为文件较小,故设置20个字节
while((len=in.read(data))!=-1){//每次读20个字节,只要没有读到结尾,就继续读并且写到内存流中
out.write(data,0,len);
}
//关闭流
out.close();
in.close();
//返回内存流的数据
return new String(out.toByteArray());
}
}
二、打印流
如果操作的不是二进制数据,要想通过程序向终端目标输出信息,OutputStream不是很方便。它有两大缺陷:
1)所有的数据必须转换为字节数组。2)如果要输出的是int、double等数值型数据就不方便了。
打印流就是解决OutputStream流的缺陷的。
1.自定义实现打印流
package com.xunpu.memory;
import java.io.*;
/**
* 自定义一个打印流
* 实现:
* 打印换行
* 打印整型数据(换行/不换行)
* 打印浮点型数据(换行/不换行)
* 数据最终输出到指定的文件中。
*/
class MyPrint{
private OutputStream out;//通过out输出流实现打印
//由外部传入要输出的目标终端
public MyPrint(OutputStream out){
this.out=out;
}
//核心功能
public void print(String data) throws IOException {
this.out.write(data.getBytes());//将字符串转为字节数组,并写到输出流中。
}
//换行
public void println(String data) throws IOException {
this.print(data+"\n");
}
//打印整数
public void print(int data) throws IOException {
this.print(String.valueOf(data));
}
public void println(int data) throws IOException {
this.println(String.valueOf(data));
}
//打印浮点数
public void print(double data) throws IOException {
this.print(String.valueOf(data));
}
public void println(double data) throws IOException {
this.println(String.valueOf(data));
}
}
public class Demo3 {
public static void main(String[] args) throws IOException {
File file=new File("E:"+File.separator+"JavaFile"+File.separator+"Directory"+File.separator+"bbb"
+File.separator+"aaa.txt");
OutputStream out=new FileOutputStream(file);
MyPrint myPrint=new MyPrint(out);
myPrint.print("姓名:");
myPrint.println("自定义打印流.");
myPrint.print("年龄:");
myPrint.println(1);
myPrint.print("售价:");
myPrint.println(9999.99);
}
}
2.java.io包中提供的打印流
字节打印流:PrintStream 字符打印流:PrintWriter(常用)
打印流的设计属于装饰设计模式,核心依然是某个类的功能,但是为了更好的操作结果,让其支持的功能更多一些。
PrintWriter的声明和构造方法
public class PrintWriter extends Writer;
构造方法:
public PrintWriter (Writer out);
public PrintWriter(OutputStream out);
PrintStream的声明和构造方法
public class PrintStream extends FilterOutputStream implements Appendable, Closeable;
构造方法:
public PrintStream(OutputStream out);
public PrintStream(String fileName);
使用系统提供的字符输出流:
package com.xunpu.memory;
import java.io.*;
/**
* 测试系统提供的打印流-PrintWriter
*/
public class TestPrint {
public static void main(String[] args) throws FileNotFoundException {
OutputStream out=new FileOutputStream("E:"+File.separator+"JavaFile"+File.separator+"Directory"+File.separator+"bbb"
+File.separator+"aaa.txt");
PrintWriter printDemo=new PrintWriter(out);
printDemo.print("姓名:");
printDemo.println("系统提供的字符打印流");
printDemo.print("年龄:");
printDemo.println(2);
printDemo.print("售价:");
printDemo.println(9999.99);
printDemo.close();
}
}
PrintStream流实现格式化输出:(JDK1.5)
public PrintStream printf(String format, Object ... args);
package com.xunpu.memory;
import java.io.*;
/**
* 测试系统提供的打印流-PrintStream
*/
public class TestPrint {
public static void main(String[] args) throws FileNotFoundException {
OutputStream out=new FileOutputStream("E:"+File.separator+"JavaFile"+File.separator+"Directory"+File.separator+"bbb"
+File.separator+"aaa.txt");
PrintStream printStream=new PrintStream(out);
String name="张三";
int age=20;
double salary=6666.66;
printStream.printf("姓名:%s 年龄:%d 工资:%.2f\n",name,age,salary);
printStream.close();
}
}
String类也提供了格式化字符串方法:
public static String format(String format, Object... args);
示例:
String str=String.format("姓名:%s 年龄:%d 工资:%.2f\n",name,age,salary);
System.out.println(str);
str=String.format("%2$d %1$.2f",74.503,22,31);//2$:第2个值,1$.2f:第一个数,且保留两位小数。
System.out.println(str);
三、System类对IO的支持
实际上,我们一直使用的系统输出就是利用了IO流的模式完成。在System类中定义了三个操作常量:
public final static PrintStream out;//标准输出(显示器)
public final static PrintStream err;//错误输出
public final static InputStream in;//标准输入(键盘)
一直在使用的System.out.println()中,System是类,out是其中的属性,也是PrintSream类的一个对象。
1.系统输出
out和err都是系统输出的属性,out输出的是希望用户能看到的内容,err输出的是字样为红色,希望用户可以重视这个错误信息。
package com.xunpu.testsystem;
/**
* 测试系统输出 out err
*/
public class Test1 {
public static void main(String[] args) {
try{
Integer.parseInt("abc");
}catch(Exception e){
System.out.println(e);
System.err.println(e);//输出的字样为红色
}
}
}
现在,这两种输出在实际开发中都不会用到了,取而代之的是“日志"。
out属性是PrintStream字节打印流的对象,PrintStream继承自FileOutputStream类,因此可以直接使用out为OutputStream类的实例化对象,此时OutputStream的输出位置为屏幕。
package com.xunpu.testsystem;
import java.io.IOException;
import java.io.OutputStream;
/**
* 测试系统输出 --将System.out对象赋给OutputStream,输出位置变为屏幕。
*/
public class Test1 {
public static void main(String[] args) throws IOException {
OutputStream out=System.out;
out.write("hello world".getBytes());
}
}
2.系统输入
Java本身并没有提供直接的用户输入操作,但是可以通过java.io模式实现用户通过键盘来进行输入。当程序发现需要用户输入数据时,就会等待用户输入,此时程序阻塞直到用户输入完成(按下回车)。
package com.xunpu.testsystem;
import java.io.IOException;
import java.io.InputStream;
/**
* 测试用户从键盘输入
*/
public class TestIn {
public static void main(String[] args) throws IOException {
InputStream in=System.in;
byte[] data=new byte[10];
System.out.println("请输入信息:");
//发现需要用户输入信息,程序暂停执行,进入阻塞状态,直到用户输入完成(按回车),程序才能继续向下执行。
int len=in.read(data);
System.out.println(new String(data,0,len));
}
}
可以发现,信息并没有完全打印出来。出现这种情况的原因是:开辟的字节数组长度小于用户从键盘输入的长度,此时只能够接收部分数据。这是由于一次读取不完造成的,最好的做法是引入内存操作流来控制,数据先保存在内存流中,然后再一次性输出。
package com.xunpu.testsystem;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* 测试用户从键盘输入
*/
public class TestIn {
public static void main(String[] args) throws IOException {
InputStream in=System.in;
ByteArrayOutputStream out=new ByteArrayOutputStream();
byte[] data=new byte[10];
System.out.println("请输入信息:");
//发现需要用户输入信息,程序暂停执行,进入阻塞状态,直到用户输入完成(按回车),程序才能继续向下执行。
int len=0;
while((len=in.read(data))!=-1){
out.write(data,0,len);
//当读到的数据长度小于data的长度时,说明此时已经读完,跳出循环
if(len<data.length){
break;
}
}
System.out.println(out);
}
}
四、两种输入流
1.BufferedReader流
缓冲的字符输入流。此外,还有字节输入流BufferedInputStream。一般会选用BufferedReader操作,这是因为它有
public String readLine() throws IOException;//读取一行数据
BufferedReader的定义和构造方法:
public class BufferedReader extends Reader;//定义
public BufferedReader(Reader in);//构造方法
要想使用BufferedReader类,就必须先定义一个Reader类,要和System.in联系起来,System.in是InputStream类。因此,需要用到InputStreamReader类,实现将字节流(in)转为字符流(BufferedReader)。
package com.xunpu.testsystem;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
/**
* 使用BufferedReader实现键盘输入
*/
public class TestInput {
public static void main(String[] args) throws IOException {
InputStream in=System.in;
InputStreamReader reader=new InputStreamReader(in);
BufferedReader bufferedReader=new BufferedReader(reader);
System.out.println("请输入内容:");
String str=bufferedReader.readLine();//默认使用回车换行
System.out.println(str);
}
}
默认的换行模式是BufferedReader的最大缺陷。
优点:接收的数据类型为String类型,因此可以使用String类的各种操作进行数据处理并且转为不同的数据类型。
2.java.util.Scanner类
Scanner类是一个专门进行输入流处理的类,可以处理各种数据类型,同时可以结合正则表达式进行各种处理。
Scanner类的方法:
public boolean hasNextXxx();//判断是否有指定类型的数据
public 数据类型 nextXxx();//取得指定类型的数据
public Scanner useDelimiter(Pattern pattern);//定义分隔符
public Scanner(InputStream source);//构造方法
使用Scanner类的方法:
package com.xunpu.testsystem;
import java.util.Scanner;
/**
* Scanner类中 方法的使用
*/
public class TestScanner {
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
System.out.println("请输入年龄:");
if(sc.hasNextInt()){
int age=sc.nextInt() ;
System.out.println("输入的年龄为:"+age);
}else{
System.out.println("输入的不是数字,不符合要求!!");
}
//正则表达式的使用 要求输入的日期格式为:XXXX-XX-XX
System.out.println("请输入出生日期:");
if(sc.hasNext("\\d{4}-\\d{2}-\\d{2}")){
String birthday=sc.next();
System.out.println("输入的出生日期为:"+birthday);
}else{
System.out.println("输入的格式不符合要求!!");
}
sc.close();
}
}
通过Scanner类读文件:
package com.xunpu.testsystem;
import java.io.*;
import java.util.Scanner;
/**
* 通过Scanner类来读文件
*/
public class UseScanner {
public static void main(String[] args) throws FileNotFoundException {
InputStream in=new FileInputStream(new File("E:"+File.separator+"JavaFile"+File.separator+"Directory"+File.separator+"bbb"
+File.separator+"aaa.txt"));
Scanner sc=new Scanner(in);
sc.useDelimiter("\n");//自定义分隔符
while(sc.hasNext()){
System.out.println(sc.next());
}
}
}
设置文件编码均为GBK后,得到:
五、总结
打印流(PrintStream&PrintWriter)解决的是OutputStream的缺陷,BufferedReader解决的是InputStream的缺陷,Scanner解决的是BufferedReader的缺陷。
之后除了二进制文件拷贝的处理外,只要是针对程序的信息输出都是使用打印流(PrintStream、PrintWriter),信息输出使用Scanner。