在java程序中,对于数据的输入/输出操作以“流”(stream)的方式进行;JDK提供了各式各样的“流”类,用以获取不同种类的数据;程序中通过标准的方法输入或输出数据。
(一)Java流式输入/输出原理(把流想象成管道)
(二)输入/输出流的分类
java.io包中定义了多个流类型(类或抽象类)来实现输入/输出功能;可以从不同的角度对其进行分类;
按数据流的方向不同可以分为输入流和输出流(站在程序的角度上来讲是输入还是输出流)。
按处理数据单位不同可以分为字节流(0101...一个字节一个字节的读)和字符流(一个字符两个字节,一个字符一个字符的读)。
按功能不同可以分为节点流和处理流。
1)节点流为可以从一个特定的数据源(节点)读写数据(如:文件,内存)
节点流的类型(一般以Stream结尾的都是字节流,以Reader、Writer结尾的都是字符流)
类型 | 字符流 | 字节流 |
File文件 | FileReader FileWriter | FileInputStream FileOutStream |
Memory Array | CharArrayReader CharArrayWriter | ByteArrayInputStream ByteArrayOutputStream |
Memory String | StringReader StringWriter | ------ |
Pipe(管道) | PipeReader PipeWriter | PipedInputStream PipedOutputStream |
2)处理流是“连接”在已存在的流(节点流或处理流)之上,通过对数据的处理为程序提供更为强大的读写功能。(想象成管道套管道)
处理流类型:
处理类型 | 字符流 | 字节流 |
Buffering | BufferedReader BufferedWriter | BufferedInputStream BufferedOutputStream |
Filtering | FilterReader FilterWriter | FilterInputStream FilterOutputStream |
Converting between bytes and character | InputStreamReader OutputStramWriter | ----- |
Object Serialization | ----- | ObjectInputStream ObjectOutputStream |
Data conversion | ----- | DataInputStream DataOutputStream |
Counting | LineNumberReader | LineNumberInputStream |
Peeking ahead | PushbackReader | PushbackInputStream |
Pringting | PrintWriter | PrintStream |
JDK所提供的所有流类型位于包java.io内都分别继承自以下四种抽象流类型。
字节流 | 字符流 | |
输入流 | InputStream | Reader |
输出流 | OutputStream | Writer |
①InputStream
继承自InputStream的流都是用于向程序中输入数据,且数据的单位为字节(8bit);
InputStream的基本方法
//读取一个字节并以整数的形式返回(0-255),如果返回-1表示已到输入流的末尾
public abstract int read() throws IOException;
//读取一系列字节并存储到一个数组buffer,返回实际读取的字节数,如果读取前已到输入流的末尾返回-1
//等byte数组被填满之后再处理(换句话说就是用于缓冲)
public int read(byte b[]) throws IOException {
return read(b, 0, b.length);
}
//读取len个字节并存储到一个字节数组上,从len位置开始返回实际读取的字节数,如果读取前已到输入流末
//尾返回-1
public int read(byte b[], int off, int len) throws IOException
//跳过n个字节不读,返回实际跳过的字节数
public long skip(long n) throws IOException
//关闭流释放内存资源
public void close() throws IOException {}
②OutputStream
继承自OutputSteam的流是用于程序输入数据,且数据的单位为字节(8bit)。
OutputStream的基本方法:
//向输出流中写入一个字节数据,该字节数据为参数b的低8位
public abstract void write(int b) throws IOException;
//将一个字节类型的数组中数据写入输出流
public void write(byte b[]) throws IOException {
write(b, 0, b.length);
}
//将一个字节类型的数组中的从指定位置(off)开始的len个字节写入到输出流
public void write(byte b[], int off, int len) throws IOException
//将输出流中缓冲的数据全部写出到目的地
public void flush() throws IOException {}
//关闭流释放内存资源
public void close() throws IOException {}
③Reader
继承自Reader的流都是用于向程序中输入数据,且数据的单位为字符(16bit)。
Reader的基本方法:
//读取一个字符并以整数的形式返回(0~255)
public int read() throws IOException
//读取一系列字符并存储到一个数组buffer,返回实际读取的字符数,如果读取前已到输入流的末尾返回-1
public int read(char cbuf[]) throws IOException
//读取len个字符,并存储到一个数组buffer,从len位置开始返回实际读取的字符数,如果读取前已到输入流
//的末尾返回-1
abstract public int read(char cbuf[], int off, int len) throws IOException;
//跳过n个字符不读,返回实际跳过的字节数
public long skip(long n) throws IOException
//关闭流释放内存资源
abstract public void close() throws IOException;
④Writer
继承自Writer的流都是用于程序中输出数据,且数据的单位为字符(16bit)。
Writer的基本方法
//向输出流中写入一个字符数据,该字节数据为参数c的低16位
public void write(int c) throws IOException
//将一个字符类型的数组中的数据写入输出流
public void write(char cbuf[]) throws IOException
//将一个字符类型的数组中从指定位置(off)开始的length个字符写入到输出流
abstract public void write(char cbuf[], int off, int len) throws IOException;
//将一个字符串中的字符写入到输出流
public void write(String str) throws IOException
//将一个字符串从off开始的len个字符写入到输出流
public void write(String str, int off, int len) throws IOException
//将输出流中缓冲的数据全部写出到目的地
abstract public void flush() throws IOException;
//关闭流释放内存资源
abstract public void close() throws IOException;
(三)访问文件
FileInputStream和FileOutputStream分别继承自InputStream和OutputStream用于向文件中输入和输出字节。FileInputStream和FileOutputStream类支持其父类所提供的数据读写方法。
FileInputStream和FileOutputStream的常用构造方法:
public FileInputStream(String name) throws FileNotFoundException
public FileInputStream(File file) throws FileNotFoundException
public FileOutputStream(String name) throws FileNotFoundException
public FileOutputStream(File file) throws FileNotFoundException
public FileOutputStream(File file, boolean append) throws FileNotFoundException
注意:在实例化FileInputStream和FileOutputStream流时要用try-catch语句以处理其可能抛出的FileNotFoundException。
在读写数据时也要用try-catch语句以处理可能抛出的IOException。
FileNotFoundException是IOException的子类。
1)实验一FileInputStream流使用读取文件中字节(把每一个流都想象成一根管道)
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
/**
* 说明:用FileInputStream读取文件中的内容
*
* @author huayu
* @date 2018/9/13 2:56 PM
*/
public class FileInputStreamDemo {
public static void main(String[] args) {
int b=0;
FileInputStream fileInputStream=null;
try {
//想象成一根管子怼在了d.txt文件上,接下来的动作就是该用管子来往外吸东西了
fileInputStream = new FileInputStream("/Users/huayu/Desktop/A/d.txt");
}catch (FileNotFoundException e){
e.printStackTrace();
System.out.println("找不到指定文件");
System.exit(-1);
}
long num=0;
try {
while ((b=fileInputStream.read())!=-1){
//这儿不转型的话输出的该是对应字节的ASCII码49\50\51(1\2\3)
System.out.print((char)b);
num++;
}
System.out.println();
System.out.println("共读取了 "+num+"个字节");
}catch (IOException e){
e.printStackTrace();
System.out.println("文件读取错误");
System.exit(-1);
}finally {
try {
//流用完后记得关闭释放资源,最好在finally块中处理
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
结果:(注意如果文件中存在中文文字(1个汉字占2个字节)肯定会乱码,因为这儿用的是字节流,如果想解决乱码问题就可以用字符流)
123
共读取了 3个字节
2)实验二FileOutStream
/**
* 说明:用FileInputStream与FileOutStream流实现对文件的读写(复制)
* 思路:先用FileInputStream流的方法读出来再用FileOutStream流的方法写入
* @author huayu
* @date 2018/9/13 4:08 PM
*/
public class FileOutputStreamDemo {
public static void main(String[] args) {
int b=0;
FileInputStream fileInputStream=null;
FileOutputStream fileOutputStream=null;
try {
//读取路径/Users/huayu/Desktop/A/d.txt原文件位置
fileInputStream=new FileInputStream("/Users/huayu/Desktop/A/d.txt");
//写出路径/Users/huayu/Desktop/A/B/d-d.txt复制后文件的位置
fileOutputStream=new FileOutputStream("/Users/huayu/Desktop/A/B/d-d.txt");
while((b=fileInputStream.read())!=-1){
fileOutputStream.write(b);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
System.out.println("找不到指定文件");
//出错后一定要记得退出程序
System.exit(-1);
} catch (IOException e) {
e.printStackTrace();
System.out.println("文件复制出错");
System.exit(-1);
}finally {
try {
fileInputStream.close();
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
System.out.println("文件已复制完成");
}
}
结果:
文件已复制完成
正常打印出这句话后你在相应的文件路径中应该能看到你复制的文件(若文件没有,文件会根据文件名自动创建)
3)实验三FileReader
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
/**
* 说明:使用字符流FileReader读取文件中字符
*
* @author huayu
* @date 2018/9/13 4:41 PM
*/
public class FileReaderDemo {
public static void main(String[] args) {
int b=0;
FileReader fileReader=null;
try {
fileReader=new FileReader("/Users/huayu/Desktop/A/d.txt");
while((b=fileReader.read())!=-1){
System.out.println((char) b);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
System.out.println("找不到指定文件");
} catch (IOException e) {
e.printStackTrace();
System.out.println("文件读取错误");
}finally {
try {
fileReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
结果:
1
2
3
于
华
4)实验四FileWriter字符流
import java.io.FileWriter;
import java.io.IOException;
/**
* 说明:用FileWriter字符流写入一个文件
*
* @author huayu
* @date 2018/9/13 4:54 PM
*/
public class FileWriterDemo {
public static void main(String[] args) {
FileWriter fileWriter=null;
//utf-8编码汉字占3个字节
String[] strings={"我","shi","于","hua"};
try {
// 文件不存在他会给自动创建一个,但是注意目录必须是已经建好了
fileWriter=new FileWriter("/Users/huayu/Desktop/A/yuhua.txt");
for (int i = 0; i < strings.length; i++) {
System.out.println(strings[i]);
fileWriter.write(strings[i]);
}
} catch (IOException e) {
e.printStackTrace();
System.out.println("文件写入错误");
System.exit(-1);
}finally {
try {
fileWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
System.out.println("文件写入完成");
}
}
(四)处理流
1)缓冲流
缓冲流要“套接”在相应的节点流之上,对读写的数据提供了缓冲的功能,提高了读写的效率,同时增加了一些新的方法。
JDK提供了四种缓冲流,其常用的构造方法为:
public BufferedReader(Reader in)
//sz为自定义缓存区的大小
public BufferedReader(Reader in, int sz)
public BufferedWriter(Writer out)
public BufferedWriter(Writer out, int sz)
public BufferedInputStream(InputStream in)
public BufferedInputStream(InputStream in, int size)
缓冲流输入流支持其父类的mark和reset方法。
BufferedReader提供了readLine方法用于读取一行字符串(以\r或\n分隔)。
BufferWriter提供了newLine用于写入一个行分隔符。
对于输出的缓冲流,写出的数据会先在内存中缓存,使用flush方法将会使内存中的数据立刻写出。
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
/**
* 说明:使用小实验
*
* @author huayu
* @date 2018/9/19 1:42 PM
*/
public class BufferStreamDemo {
public static void main(String[] args) {
try {
//形象思维,想这一根管道怼在了yuhua.txt这个文件上,准备从里面往外读数据
FileInputStream fileInputStream = new FileInputStream("/Users/huayu/Desktop/A/yuhua.txt");
//执行这句后相当于在上面那根管道上又接了一块粗的管道
BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
int b = 0;
System.out.println(bufferedInputStream.read());
System.out.println(bufferedInputStream.read());
//从100个数据开始往外读
bufferedInputStream.mark(100);
for (int i = 0; i <= 10 && (b = bufferedInputStream.read()) != -1; i++) {
System.out.println((char)b + " ");
}
System.out.println();
//reset一下就会又回到我们标记的那个点
bufferedInputStream.reset();
for (int i = 0; i <= 10 && (b = bufferedInputStream.read()) != -1; i++) {
System.out.println((char)b+" ");
}
bufferedInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
2)转换流
InputStreamReader和OutputStreamWriter用与字节数据到字符数据之间的转换。
InputStreamReader需要和InputStream“套接”;
OutputStreamWriter需要和OutputStream“套接”;
转换流在构造时可以指定其编码集。
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
/**
* 说明:转换流实验
*
* @author huayu
* @date 2019/6/26 10:39 AM
*/
public class TransFormTest {
public static void main(String[] args) {
OutputStreamWriter osw = null;
try {
osw = new OutputStreamWriter(new FileOutputStream("/Users/huayu/Desktop/osw.txt"));
osw.write("File was built by yuhua");
System.out.println("first:"+osw.getEncoding());
osw.close();
//true 的意思是指在原来的文件上追加,前提是上次流资源关闭,若未关闭下次写入则会覆盖掉上次写入(注视掉上一行代码可验证)
//ISO8859_1包含了西欧语言
osw = new OutputStreamWriter(new FileOutputStream("/Users/huayu/Desktop/osw.txt",true),"ISO8859_1");
osw.write("File1 was built by yuhua");
System.out.println("second:"+osw.getEncoding());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
osw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
输出结果:
first:UTF8 //本系统为MacOS
second:ISO8859_1
文件中内容为:File was built by yuhuaFile1 was built by yuhua
(若注释第一个 osw.close()代码,则文件内容为File1 was built by yuhua)
3)数据流
DataInputStream和DataOutputStream分别继承自InputStream和OutoutStream,它属于处理流,需要套接在InputStream和OutputStream类型的节点流上(DataInputStream(InputStream in),DataOutputStream(OutputStream out))。
DataInputStream和DataOutputStream提供了可以存取与机器无关的Java原始类数据(如:int,double等)的方法。
/**
* 说明:数据流演示
*
* @author huayu
* @date 2019/7/5 12:56 PM
*/
public class DataStreamTest {
public static void main(String[] args) {
//分配了一个字节数组(32),打开流
ByteArrayOutputStream baos=new ByteArrayOutputStream();
DataOutputStream dos=new DataOutputStream(baos);
try {
dos.writeDouble(Math.random());
dos.writeBoolean(true);
ByteArrayInputStream bais=new ByteArrayInputStream(baos.toByteArray());
//输出bais中有效字节数
System.out.println(bais.available());
DataInputStream dis=new DataInputStream(bais);
System.out.println(dis.readDouble());
System.out.println(dis.readBoolean());
dos.close();
dis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
输出:
9
0.4022124758427842
true
4)print流
PrintWriter和PrintStream都属于输出流,分别针对字符和字节。他们提供了重载的print方法。他们在进行输出操作时不会抛出异常,用户通过检测错误状态获取错误信息。PrintWriter和PrintStream有自动flush功能。
/**
* 说明:测试Print流,往print.txt文件中写入前60000字符
*
* @author huayu
* @date 2019/7/5 1:32 PM
*/
public class PrintStreamTest {
public static void main(String[] args) {
PrintStream ps=null;
try {
FileOutputStream fos=new FileOutputStream("/Users/huayu/Desktop/print.txt");
ps=new PrintStream(fos);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
if(ps!=null){
//System.Out默认在终端打印,在这儿重新设置将其指向ps
System.setOut(ps);
}
int in=0;
for (char i = 0; i <=60000 ; i++) {
System.out.print(i+ " ");
if(in++>=100){
System.out.println();
in=0;
}
}
}
}
结果:
控制台无输出,在print.txt文件中已经打印出60000个字符
/**
* 说明:测试Print流(模仿日志打印小程序)
*
* @author huayu
* @date 2019/7/5 1:54 PM
*/
public class PrintStream1 {
public static void main(String[] args) {
String str = null;
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
try {
FileWriter fw = new FileWriter("/Users/huayu/Desktop/print.log", true);
PrintWriter log = new PrintWriter(fw);
while ((str = br.readLine()) != null) {
//当exit的时候则不记录exit,break退出程序
if (str.equalsIgnoreCase("exit")) {
break;
}
log.println("***");
log.println(str.toUpperCase());
log.flush();
}
log.println("***" + new Date() + "***");
log.flush();
log.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
在从控制台输入内容:
hello
hello world
exit
结果:
在print.log文件中记录信息
HELLO
***
HELLO WORLD
//默认格林威治时间
***Fri Jul 05 14:54:39 CST 2019***
5)Object流
直接将Object(对象)写入硬盘或读出。
/**
* 说明:对象类,实现了序列化接口,本类可被序列化
* Serializable在java中称为标记性接口,只是一个空接口,可被编译器识别。
* 序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程。
* transient(透明的)关键字修饰的变量 在往硬盘上写的时候不予考虑
* @author huayu
* @date 2019/7/5 3:11 PM
*/
public class TestObject implements Serializable{
transient int a=1;
int b=2;
double c=3.0;
}
/**
* 说明:测试Object 流
*
* @author huayu
* @date 2019/7/5 3:10 PM
*/
public class ObjectIoTest {
public static void main(String[] args) {
TestObject to=new TestObject();
to.a=2;
try {
FileOutputStream fos=new FileOutputStream("/Users/huayu/Desktop/object.dat");
ObjectOutputStream oos=new ObjectOutputStream(fos);
oos.writeObject(to);
oos.flush();
oos.close();
FileInputStream fis=new FileInputStream("/Users/huayu/Desktop/object.dat");
ObjectInputStream ois=new ObjectInputStream(fis);
TestObject readObject = (TestObject) ois.readObject();
System.out.println(readObject.a+" "+readObject.b+" "+readObject.c);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
结果:
在a值不使用transient关键字修饰时,结果为:2 2 3.0
在a值使用transient关键字修饰时,结果为0 2 3.0
/**
* Externalizable实现了Serializable,使用该接口开发者可自己控制序列化过程,不建议使用。
* Serializable是由jdk控制
*/
public interface Externalizable extends Serializable {
void writeExternal(ObjectOutput var1) throws IOException;
void readExternal(ObjectInput var1) throws IOException, ClassNotFoundException;
}