一、JAVA流式输入/输出原理
Java中,对于数据的输入/输出操作以流的方式进行;jdk提供了各种各样的流类,用来获取不同种类的数据;程序中通过标准的方法输入或者输出数据。
流用来读写数据,File类,封装的是文件的文件名,只是内存里的一个对象,真正的文件是在硬盘上的一块空间,在这个文件里面存放着各种各样的数据,我们想读文件里面的数据怎么办呢?---通过一个流的方式来读,咱们要想从程序读数据,对于计算机来说,无论读什么类型的数据都是以010101101010这样的形式读取的。怎么把文件里面的数据读出来呢?你可以把文件想象成一个小桶,文件就是一个桶,文件里面的数据就相当于是这个桶里面的水,那么我们怎么从这个桶里面取水呢,也就是怎么从这个文件读取数据呢。
常见的取水的办法是我们用一根管道插到桶上面,然后在管道的另一边打开水龙头,桶里面的水就开始哗啦哗啦地从水龙头里流出来了,桶里面的水是通过这根管道流出来的,因此这根管道就叫流,JAVA里面的流式输入/输出跟水流的原理一模一样,当你要从文件读取数据的时候,一根管道插到文件里面去,然后文件里面的数据就顺着管道流出来,这时你在管道的另一头就可以读取到从文件流出来的各种各样的数据了。当你要往文件写入数据时,也是通过一根管道,让要写入的数据通过这根管道哗啦哗啦地流进文件里面去。除了从文件去取数据以外,还可以通过网络,比如用一根管道把我和你的机子连接起来,我说一句话,通过这个管道流进你的机子里面,你马上就可以看得到,而你说一句话,通过这根管道流到我的机子里面,我也马上就可以看到。有的时候,一根管道不够用,比方说这根管道流过来的水有一些杂质,我们就可以在这个根管道的外面再包一层管道,把杂质给过滤掉。从程序的角度来讲,从计算机读取到的原始数据肯定都是010101这种形式的,一个字节一个字节地往外读,当你这样读的时候你觉得这样的方法不合适,没关系,你再在这根管道的外面再包一层比较强大的管道,这个管道可以把010101帮你转换成字符串。这样你使用程序读取数据时读到的就不再是010101这种形式的数据了,而是一些可以看得懂的字符串了。
二、输入输出流分类
io包里面定义了所有的流,所以一说流指的就是io包里面的
什么叫输入流?什么叫输出流?用一根管道一端插进文件里程序里面,然后开始读数据,那么这是输入还是输出呢?如果站在文件的角度上,这叫输出,如果站在程序的角度上,这叫输入。
记住,以后说输入流和输出流都是站在程序的角度上来说。
三、节点流和处理流
要是对原始的流不满意,你可以在这根管道外面再套其它的管道,套在其它管道之上的流叫处理流。为什么需要处理流呢?这就跟水流里面有杂质,你要过滤它,你可以再套一层管道过滤这些杂质一样。
3.1.节点流类型
节点流就是一根管道直接插到数据源上面,直接读数据源里面的数据,或者是直接往数据源里面写入数据。典型的节点流是文件流:文件的字节输入流(FileInputStream),文件的字节输出流(FileOutputStream),文件的字符输入流(FileReader),文件的字符输出流(FileWriter)。
3.2.处理流类型
处理流是包在别的流上面的流,相当于是包到别的管道上面的管道。
四、InputStream(字节输入流)
我们看到的具体的某一些管道,凡是以InputStream结尾的管道,都是以字节的形式向我们的程序输入数据。
4.1.InputStream的基本方法
read()方法是一个字节一个字节地往外读,每读取一个字节,就处理一个字节。read(byte[] buffer)方法读取数据时,先把读取到的数据填满这个byte[]类型的数组buffer(buffer是内存里面的一块缓冲区),然后再处理数组里面的数据。这就跟我们取水一样,先用一个桶去接,等桶接满水后再处理桶里面的水。如果是每读取一个字节就处理一个字节,这样子读取也太累了。
五、OutputStream(输出流)
5.1.OutputStream的基本方法
有的时候,需要立即把数据写入到硬盘,而不是等缓存满了才写出去。 这时候就需要用到flush
六、Reader流
6.1.Reader的基本方法
七、Writer流
7.1.Writer的基本方法
八、节点流讲解
以File(文件)这个类型作为讲解节点流的典型代表
范例:使用FileInputStream流来读取FileInputStream.java文件的内容
package cn.cxy.test;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
public class TestFileInputStream {
public static void main(String[] args) {
int b =0;//装read()方法返回的整数
FileInputStream f = null;
// 使用FileInputStream流来读取有中文的内容时,读出来的是乱码,因为使用InputStream流里面的read()方法读取内容时是一个字节一个字节地读取的,而一个汉字是占用两个字节的,所以读取出来的汉字无法正确显示。
// FileReader f = null;
//使用FileReader流来读取内容时,中英文都可以正确显示,因为Reader流里面的read()方法是一个字符一个字符地读取的,这样每次读取出来的都是一个完整的汉字,这样就可以正确显示了。
try {
f = new FileInputStream("D:\\workspace\\test.txt");
}
catch (FileNotFoundException e) {
// TODO Auto-generated catch block
System.out.println("找不到指定文件");
e.printStackTrace();
}
long num = 0;// 使用变量num来记录读取到的字符数
try {
while((b =f.read()) != -1) {
// 调用int read() throws Exception方法时,返回的是一个int类型
// 循环结束的条件就是返回一个值-1,表示此时已经读取到文件的末尾了。
// System.out.print(b+"\t");//如果没有使用“(char)b”进行转换,那么直接打印出来的b就是数字,而不是英文和中文了
System.out.print((char)b); //(char(b)表示将数字表示的汉字或字母转换成字符输入
num++;
}
f.close();
System.out.println();
System.out.println("总共读取了" + num + "个字节的文件");
} catch (IOException e) {
System.out.println("文件读取错误!");
e.printStackTrace();
}
}
}
范例:使用FileOutputStream流往一个文件里面写入数据
package cn.cxy.test;
import java.io.*;
public class TestFileInputStream {
public static void main(String[] args) {
int b =0;//装read()方法返回的整数
FileInputStream in = null;
FileOutputStream out = null;
// 使用FileInputStream流来读取有中文的内容时,读出来的是乱码,因为使用InputStream流里面的read()方法读取内容时是一个字节一个字节地读取的,而一个汉字是占用两个字节的,所以读取出来的汉字无法正确显示。
// FileReader f = null;
//使用FileReader流来读取内容时,中英文都可以正确显示,因为Reader流里面的read()方法是一个字符一个字符地读取的,这样每次读取出来的都是一个完整的汉字,这样就可以正确显示了。
try {
in = new FileInputStream("D:\\workspace\\test.txt");
out = new FileOutputStream("D:\\workspace\\2.txt");
//指明要写入的数据,不存在会自动创建
while((b =in.read()) != -1) {
// 调用int read() throws Exception方法时,返回的是一个int类型
// 循环结束的条件就是返回一个值-1,表示此时已经读取到文件的末尾了。
// System.out.print(b+"\t");//如果没有使用“(char)b”进行转换,那么直接打印出来的b就是数字,而不是英文和中文了
out.write(b);
// 调用write(int c)方法把读取到的字符全部写入到指定文件中去
}
in.close();
out.close();
System.out.println();
} catch (IOException e) {
System.out.println("文件读取错误!");
e.printStackTrace();
}
}
}
范例:使用FileWriter(字符流)向指定文件中写入数据
package cn.cxy.test;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
/*使用FileWriter(字符流)向指定文件中写入数据
写入数据时以1个字符为单位进行写入*/
import java.io.*;
public class TestFileWriter{
public static void main(String args[]){
/*使用FileWriter输出流从程序把数据写入到Uicode.dat文件中
使用FileWriter流向文件写入数据时是一个字符一个字符写入的*/
FileWriter fw = null;
try{
fw = new FileWriter("D:\\workspace\\test.txt");
//字符的本质是一个无符号的16位整数
//字符在计算机内部占用2个字节
//这里使用for循环把0~60000里面的所有整数都输出
//这里相当于是把全世界各个国家的文字都0~60000内的整数的形式来表示
for(int c=0;c<=60000;c++){
fw.write(c);
//使用write(int c)把0~60000内的整数写入到指定文件内
//调用write()方法时,我认为在执行的过程中应该使用了“(char)c”进行强制转换,即把整数转换成字符来显示
//因为打开写入数据的文件可以看到,里面显示的数据并不是0~60000内的整数,而是不同国家的文字的表示方式
}
/*使用FileReader(字符流)读取指定文件里面的内容
读取内容时是以一个字符为单位进行读取的*/
int b = 0;
long num = 0;
FileReader fr = null;
fr = new FileReader("D:\\workspace\\test.txt");
while((b = fr.read())!= -1){
System.out.print((char)b + "\t");
num++;
}
System.out.println();
System.out.println("总共读取了"+num+"个字符");
}catch(Exception e){
e.printStackTrace();
}
}
}
FileReader和FileWriter这两个流都是字符流,都是以一个字符为单位进行输入和输出的。所以读取和写入占用2个字节的字符时都可以正常地显示出来,以上是以File(文件)这个类型为例对节点流进行了讲解,所谓的节点流指定就是直接把输入流或输出插入到数据源上,直接往数据源里面写入数据或读取数据。
九、处理流讲解
9.1.第一种处理流——缓冲流(Buffering)
带有缓冲区的,缓冲区(Buffer)就是内存里面的一小块区域,读写数据时都是先把数据放到这块缓冲区域里面,减少io对硬盘的访问次数,保护我们的硬盘。可以把缓冲区想象成一个小桶,把要读写的数据想象成水,每次读取数据或者是写入数据之前,都是先把数据装到这个桶里面,装满了以后再做处理。这就是所谓的缓冲。先把数据放置到缓冲区上,等到缓冲区满了以后,再一次把缓冲区里面的数据写入到硬盘上或者读取出来,这样可以有效地减少对硬盘的访问次数,有利于保护我们的硬盘。
移除注释
设计一个方法,用于移除文件中的注释
public void removeComments(File txtFile)
比如,移出以//开头的注释行
File f = new File("d:/LOLFolder/LOL.exe");
System.out.println("当前文件是:" +f);
//文件是否存在
System.out.println("判断是否存在:"+f.exists());
//是否是文件夹
System.out.println("判断是否是文件夹:"+f.isDirectory());
package cn.cxy.test;
import java.io.*;
public class TestStream {
public static void removeComments(File txtFile) {
StringBuffer sb= new StringBuffer();
try (FileReader fr = new FileReader(txtFile);
BufferedReader br = new BufferedReader(fr);){
while (true){
String line = br.readLine();
if (null == line)
break;
if (!line.trim().startsWith("S"))
sb.append(line).append("\r\n");
}
}catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try (FileWriter fw =new FileWriter(txtFile);
PrintWriter pw = new PrintWriter(fw);
)
{
pw.write(sb.toString());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void main(String[] args) {
File txtFile = new File("D:\\workspace\\test.txt");
System.out.println(txtFile.exists());
System.out.println(txtFile.isDirectory());
removeComments(txtFile);
}
}
转换流
转换流非常的有用,它可以把一个字节流转换成一个字符流,转换流有两种,一种叫InputStreamReader,另一种叫OutputStreamWriter。InputStream是字节流,Reader是字符流,InputStreamReader就是把InputStream转换成Reader。OutputStream是字节流,Writer是字符流,OutputStreamWriter就是把OutputStream转换成Writer。把OutputStream转换成Writer之后就可以一个字符一个字符地通过管道写入数据了,而且还可以写入字符串。我们如果用一个FileOutputStream流往文件里面写东西,得要一个字节一个字节地写进去,但是如果我们在FileOutputStream流上面套上一个字符转换流,那我们就可以一个字符串一个字符串地写进去。
String.Trim()方法会去除字符串两端,不仅仅是空格字符,它总共能去除25种字符:
('/t', '/n', '/v', '/f', '/r', ' ', '/x0085', '/x00a0', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '?', '/u2028', '/u2029', ' ', '?')
如果你想保留其中的一个或多个(例如/t制表符,/n换行符,/r回车符等),请慎用Trim方法。
请注意,Trim删除的过程为从外到内,直到碰到一个非空白的字符为止,所以不管前后有多少个连续的空白字符都会被删除掉。
9.2.第二种数据流
package cn.cxy.test;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class TestDataStream {
public static void main(String[] args) {
write();
read();
}
private static void read() {
File f =new File("d:/lol.txt");
try (
FileInputStream fis = new FileInputStream(f);
DataInputStream dis =new DataInputStream(fis);
){
boolean b= dis.readBoolean();
int i = dis.readInt();
String str = dis.readUTF();
System.out.println("读取到布尔值:"+b);
System.out.println("读取到整数:"+i);
System.out.println("读取到字符串:"+str);
} catch (IOException e) {
e.printStackTrace();
}
}
private static void write() {
File f =new File("d:/lol.txt");
try (
FileOutputStream fos = new FileOutputStream(f);
DataOutputStream dos =new DataOutputStream(fos);
){
dos.writeBoolean(true);
dos.writeInt(300);
dos.writeUTF("123 this is gareen");
} catch (IOException e) {
e.printStackTrace();
}
}
}
使用数据流的writeUTF()和readUTF() 可以进行数据的格式化顺序读写
注: 要用DataInputStream 读取一个文件,这个文件必须是由DataOutputStream 写出的,否则会出现EOFException,因为DataOutputStream 在写出的时候会做一些特殊标记,只有DataInputStream 才能成功的读取。
通过dais这个流往外读取数据的时候,是一个字节一个字节地往外读取的,因此读出来的数据无法判断是字符串还是bool类型的值,因此要在它的外面再套一个流,通过dataInputStream把读出来的数据转换就可以判断了。注意了:读取数据的时候是先写进去的就先读出来,因此读ByteArray字节数组数据的顺序应该是先把占8个字节的double类型的数读出来,然后再读那个只占一个字节的boolean类型的数,因为double类型的数是先写进数组里面的,读的时候也要先读它。这就是所谓的先写的要先读。如果先读Boolean类型的那个数,那么读出来的情况可能就是把double类型数的8个字节里面的一个字节读了出来。
打印流
package cn.cxy.test;
import java.io.IOException;
import java.io.PrintStream;
public class TestPrintStream {
public static void main(String[] args) {
try (
PrintStream out = System.out;//创建一个打印流out
//创建又一具有指定文件名的打印流ps(不带自动刷新)
PrintStream ps = new PrintStream("D:\\workspace\\test.txt");
){ 若去掉此句,则内容都会在console面板中输出
//重新分配“标准”输出流,重定向输出到ps对象中(即指定路径名文件中)
System.setOut(ps);
int age = 18;
System.out.println("年龄定义,初始值18");
String sex = "女";
System.out.println("性别定义,女");
String info = "这是一个" + sex + "孩子" + "年龄为" + age;
System.out.println("整合了两个变量" + info);
System.setOut(out);//重定向输出到out对象中(即屏幕上)
System.out.println("程序运行完毕");
}
catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
对象流——Object
package cn.gacl.test;
import java.io.*;
public class TestObjectIo {
public static void main(String args[]) {
T t = new T();
t.k = 8;// 把k的值修改为8
try {
FileOutputStream fos = new FileOutputStream(
"D:/java/TestObjectIo.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
// ObjectOutputStream流专门用来处理Object的,在fos流的外面套接ObjectOutputStream流就可以直接把一个Object写进去
oos.writeObject(t);// 直接把一个t对象写入到指定的文件里面
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream(
"D:/java/TestObjectIo.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
// ObjectInputStream专门用来读一个Object的
T tRead = (T) ois.readObject();
// 直接把文件里面的内容全部读取出来然后分解成一个Object对象,并使用强制转换成指定类型T
System.out.print(tRead.i + "\t" + tRead.j + "\t" + tRead.d + "\t"
+ tRead.k);
ois.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
/*
* 凡是要将一个类的对象序列化成一个字节流就必须实现Serializable接口
* Serializable接口中没有定义方法,Serializable接口是一个标记性接口,用来给类作标记,只是起到一个标记作用。
* 这个标记是给编译器看的,编译器看到这个标记之后就可以知道这个类可以被序列化 如果想把某个类的对象序列化,就必须得实现Serializable接口
*/
class T implements Serializable {
// Serializable的意思是可以被序列化的
private static final long serialVersionUID = 1L; //表示版本序号
int i = 10;
int j = 9;
double d = 2.3;
int k = 15;
// transient int k = 15;
// 在声明变量时如果加上transient关键字,那么这个变量就会被当作是透明的,即不存在。
}
package stream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import charactor.Hero;
public class TestStream {
public static void main(String[] args) {
//创建一个Hero garen
//要把Hero对象直接保存在文件上,务必让Hero类实现Serializable接口
Hero h = new Hero();
h.name = "garen";
h.hp = 616;
//准备一个文件用于保存该对象
File f =new File("d:/garen.lol");
try(
//创建对象输出流
FileOutputStream fos = new FileOutputStream(f);
ObjectOutputStream oos =new ObjectOutputStream(fos);
//创建对象输入流
FileInputStream fis = new FileInputStream(f);
ObjectInputStream ois =new ObjectInputStream(fis);
) {
oos.writeObject(h);
Hero h2 = (Hero) ois.readObject();
System.out.println(h2.name);
System.out.println(h2.hp);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}