图片来源于互联网
一、IO流概述
IO流用于处理设备之间的数据传输问题。Java对数据的操作,通过流的形式。操作存储设备中的数据,流技术实现,程序和设备之间建立连接通道,数据流。I -> Input 输入 O -> Output输出
- 字符流: 开始JDK1.1 字符流专门用于操作文本文件,记事本可以打开的,人可以看懂的文件。 每次操作1个字符 16个二进制,去查询本机默认的编码表。方便Java程序操作纯文本。( .txt .java .html .xml .css )
- 字节流:开始JDK1.0 字节流操作文件是任意文件。每次操作1个字节8个二进制。不查询编码表 。
二、IO流的分类
-
字符输出流: 写文本文件的,抽象基类java.io.Writer
写的方法
write
,很多重载形式,写字符数组,单个字符,字符串,字符数组一部分,字符串的一部分。flush
刷新流的缓冲close
关闭流对象
-
字符输入流:读取文本文件的,抽象基类java.io.Reader
读的方法
read
,很多重载形式,读取单个字符,字符数组,字符数组一部分。close
关闭流对象 -
字节输出流:写任意的文件,抽象基类java.io.OutputStream
写的方法
write
,很多重载形式,写单个字节,字节数组,字节数组一部分。close
关闭流对象 -
字节输入流:读取任意文件,抽象基类java.io.InputStream
读的方法
read
,很多重载形式,读取单个字节,字节数组,字节数组一部分。close
关闭流对象
IO流中的所有类的命名法则:后缀都是父类名,前面都是可以操作的文件名,流向名
FileInputStream
FileReader
ObjectInputStream
InputStreamReader
三、书写注意
- IO使用,导入java.io包
- IO流类中的方法,大多数都会抛出异常,调用者
try ...throws
- IO流对象,没有实际的功能操作文件,依赖操作系统来实现功能,流对象用完后,一定要关闭资源
四、字符流输出
1. 写文件
数据输出是java.io.Writer类(抽象的),具体操作找子类 FileWriter 来写入字符文件的便捷类。
- 创建子类对象,需要使用子类的构造器,传递String文件名
- 子类对象调用父类方法
write()
写文件 - 关闭资源(一旦流对象关闭,不能再次使用)
注意:创建对象,调用方法写,write方法是把数据写在了内存中,flush刷新该流的缓冲,把数据从内存刷到目的文件。字符流写文件必须刷新,如果不刷新数据可能会留在内存中,但是只要进行了刷新数据肯定走向目的文件
2. 追加的形式写文件
当我们不能覆盖写入的时候,就需要进行后续的追加。利用FileWriter类的构造方法,传递true
可以实现文件的追加写入
三种写入的方法:
- 写单个的字符
write(int i)
- 写字符数组
write(char[] c)
- 写字符数组一部分
write(char[] c,int start,int length)
demo:
package io;
import java.io.*;
public class FileWriteDemo {
public static void main(String[] args) throws IOException {
//构造方法抛异常
FileWriter write = new FileWriter("test.txt");
//write方法,三种重载形式(string s) (int i ) (char [])
write.write("this is a test");
write.flush();
write.close();
//打开进行追加,文件存在时,不会覆盖原来的内容
FileWriter fw = new FileWriter("test.txt",true);
fw.write("ok, append something");
fw.flush();
fw.close();
}
}
五、字符流读取文件
读取文件是java.io.Reader类,也是抽象类,具体使用要找子类,字符流读取使用子类FileReader。
- 创建子类对象,需要使用子类构造器,传递String文件名
- 在调用父类方法
read
读取文件 - 关闭资源
一般两种读取的方式:
- 读取单个字符
int read()
每次读取1个字符,返回码值,read
方法每执行一次,自动的向后读取一个字符,读取到文件末尾,返回-1。 - 数组读取
int read(char[])
,一次性读取数组长度个字符,返回读取的长度,到达文件末尾时返回-1
demo:
package io;
/*
* 字符输入流
*
* 1. int reader() 读取单个字符,返回字符对应的int值,每次读取,指针就会向后移动,指向下一个字符,到文件末尾时,返回-1
* 2. int reader(char []) 读取一个数组的长度,具体长度由数组的长度决定,返回读取的长度,到文件末尾时,返回-1
*
*/
import java.io.*;
public class FileReaderDemo {
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("test.txt");
System.out.println((char)fr.read());
System.out.println(fr.read());
System.out.println(fr.read());
System.out.println(fr.read());
fr.close();
method();
}
//读取数组
public static void method ()throws IOException {
FileReader fe = new FileReader("test.txt");
char[] ch = new char[2];
int i = 0;
while((i = fe.read(ch)) != -1){
String s = new String(ch,0,i);
System.out.print(s);
}
fe.close();
}
}
六、缓冲区
缓冲区是为了提高流的读写效率而出现,比如BufferedWriter,也是Writer的子类,Writer类的方法都可以用的。但是使用缓冲区对象,必须有一个流对象配合使用。
1. 字符输出流
BufferedWriter类的构造方法BufferedWriter(Writer w)
参数是一个字符输出流,将输出流FileWriter类的对象传递给BufferedWriter的构造方法 BfferedWriter(new FileWriter)
,BufferedWriter这个缓冲区,将提高FileWriter的写如效率。
缓冲区特有方法 void newLine()
文本中,写换行。可以写\r\n
还是用newLine()
,如果需要文本的换行,需要使用newLine()
实现,这个方法具有跨平台性。
Windows,换行\r\n Linux换行\n . 如果安装的JDK是Windows版本的,newLine()方法中写的就是\r\n, 安装的JDK是Linux版本,newLine()方法写的就是\n
Demo:
package io;
/*
* 缓冲区demo
*/
import java.io.*;
public class BufferedWriterDemo {
public static void main(String[] args) throws IOException{
//创建字符输出流
FileWriter fw = new FileWriter("E:\\test.txt");
BufferedWriter bw = new BufferedWriter(fw);
bw.write("test!\r132");
bw.newLine();//跨平台性 win下是\r\n linux下是\n
bw.write("new test!");
bw.flush();
bw.close();
}
}
2. 字符输入流
BufferedReader类也是Reader的子类,Reader的方法当然都可以用,需要一个输入流对象,配合使用。
BufferedReader构造方法BufferedReader(Reader r)
参数是一个字符输入流,方法String readLine()
读取文本一行,返回字符串。
demo:
package io;
import java.io.*;
/*
* BufferedReader
* String readerLine() 一次读取一行
*/
public class BufferedReaderDemo {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new FileReader("E:\\test.txt"));
String s = null;
while( (s = br.readLine()) != null){
System.out.println(s.length() +"..." + s);
}
br.close();
}
}
练习demo(复制一个文件,使用缓冲区实现):
package io;
import java.io.*;
/*
* 一个练习,buffered读行,写行,换行
* 处理异常
* 目标文件:e:\temp.html
*/
public class CopyTextByBuffered {
public static void main(String[] args) {
BufferedReader bfr = null;
BufferedWriter bfw = null;
try{
//初始化需要用的文件和缓冲区的读取和写入的对象
bfr = new BufferedReader(new FileReader("E:\\temp.html"));
bfw = new BufferedWriter(new FileWriter("E:\\newTemp.html"));
String s = null;
while((s = bfr.readLine()) != null){
bfw.write(s);
bfw.newLine();
bfw.flush();
}
}catch(IOException e){
//
throw new RuntimeException("文件copy失败");
}finally{ //关闭缓冲区
try{
bfw.close();
}catch(IOException e){
throw new RuntimeException("关闭文件写入缓冲区异常");
}
try{
bfr.close();
}catch(IOException e){
throw new RuntimeException("关闭文件读取缓冲区异常");
}
}
}
}
七、装饰设计模式
装饰设计模式出现的意义就是增强原有对象的功能。举个例子,BufferedReader,BufferedWriter 装饰类的出现,就是增强了原有的流对象Reader和Writer的功能,装饰设计在java的IO中使用相当广泛。
说道这里,那继承覆盖和装饰的区别在哪里,举个例子进行说明。
现在有一个父类Reader,下面有四个子类分别是:
- 文本类 TextReader
- 视频类 VideoReader
- 音乐类 SoundReader
- 游戏类 GameReader
如果此时因为这四个类的功能太少要进行扩展,采用继承的话就需要再写四个子类来进行继承和重写和加强,因此就会有八个类出现。这对开发和新手接触都是相当麻烦的,要额外的增加相当多的成本。那此时如果采用装饰类的话,只需要一个BufferedReader装饰类将继承自Reader的所有类都装饰起来,简单简洁,适合新手接触和开发。
八、实现readerLine()练习
依照BufferedReader类的readLine方法原理,自己进行实现,要求功能上和原方法相同。
Demo:
package io;
/*
* 自己实现一个类似于BufferReader 里面的 readerline方法
* 只是练习,直接把异常抛出去,没有做相应的处理
*/
import java.io.*;
public class ReaderLineTest {
public static void main(String[] args) throws IOException{
//初始化一个FileReader对象
FileReader fr = new FileReader("E:\\test.txt");
//讲文件内的内容全部打印出来,使用的文件是BufferedWriter中创建的文件
String s = null;
while((s = MeReaderLine(fr)) != null){
System.out.println(s);
}
fr.close();
}
/*
* 传入一个流对象,返回一个String
*/
public static String MeReaderLine(FileReader fr) throws IOException{
StringBuffer bf = new StringBuffer();
if(fr == null)
return null;
int temp = 0;
try{
while((temp = fr.read()) != -1){
if(temp == 13)
continue;
if(temp == 10)
return bf.toString();
bf.append((char)temp);
}
if(bf.length() == 0)
return null;
else
return bf.toString();
}catch(IOException e){
throw new RuntimeException("读取失败");
}
}
}
九、字节流输入输出
1. 字节输入流--InputStream
InputStream是字节输入流,可读取任意文件。read
可 读取字节数组,可读单个字节。此类是抽象类,不能直接用,所以找子类 FileInputStream。
-
FileInputStream()
构造方法,传递String的文件名 -
read()
方法读取文件结尾返回-1 -
read(字节数组)
利用数组作为缓冲,提高流的读取效率
根据字符流的原理,字节流中的read()读取到的字节存储到字节数组中,返回读取一次的字节数中有效字节个数,文件末尾返回-1
-
int available()
,返回字节输入流中封装的文件的字节数
2. 字节输出流--OutpuStream
OutpuStream字节输出流,可以写入任意文件。write(byte) \write(byte[])
写字节数组或者单个字节。OutpuStream是抽象类,不能直接用,实现找子类 FileOutputStream。FileOutputStream()
构造方法,传递String
的文件名。
字节流写数据的时候,不需要刷新,但是要关闭
3. 字节流缓冲区(不是很重要,会用就好了)
字节流缓冲区和字符流缓冲区用法很像,注意字节流是没有行概念的。
-
BufferedOutputStream()
构造方法,传递字节输出流,写入的方法写单个字节,字节数组,没有行!! -
BufferdInputStream()
构造方法,传递字节输入流,读取的方法,单个字节,字节数组
下面是一个字节流缓冲区复制文件的demo,并没有使用buffered...:
package io;
/*
* 使用字节流,实现任意文件的copy
*/
import java.io.*;
public class CopyFileByFileStream {
public static void main(String[] args){
FileInputStream fis = null;
FileOutputStream fos = null;
long s = System.currentTimeMillis();
try{
//初始化字节流输入输出对象
fis = new FileInputStream("E:\\迅雷下载\\VSCodeSetup-stable.exe");
fos = new FileOutputStream("E:\\vscode.exe");
//byte[]用来缓存字节流数据,1024刚好1kb,效率和内存占用上都还行
byte[] by = new byte[1024];
int len = 0;
while((len =fis.read(by) )!= -1){
fos.write(by,0,len);
}
//下面为异常处理,在finally中关闭流对象即可
}catch(IOException e){
throw new RuntimeException("文件复制失败");
}finally{
try{
if(fis != null)
fis.close();
}catch(IOException e){
throw new RuntimeException("源文件关闭失败");
}
try{
if(fos != null)
fos.close();
}catch(IOException e){
throw new RuntimeException("新文件关闭失败");
}
}
long e = System.currentTimeMillis();
System.out.println(e-s);
}
}
十、键盘输入
个人感觉java的键盘输入其实和C/C++还是有很大区别的,通过一个demo进行练习和实现,具体的实现思路和方法都在下面的这个demo中,核心利用的是System.in返回的BufferedInputStream进行读取输入的字符。
其实字符流的read()方法是阻塞式的,之前一直因为直接使用文件进行读取,所以程序并不会停下来。现在直接调用read方法,因为没有任何的输入源,所以程序就会停下来,等待输入,键盘输入后才会继续执行。
package io;
/*
* 键盘输入练习
* 使用StringBuffer实现内容的不定长读取
* 1.while永真循环,遇到回车结束,判断\n实现
* 2.while永真循环,用到规定的字符over结束,用endsWith判断实现
* 3.使用转换流实现 InputStramReader
*/
import java.io.*;
public class TranseDemo {
public static void main(String[] args) throws IOException{
//getByKeyboard();
getBygetByKeyboardAndInputStramReader();
}
/*
* 第3种、转换流思路实现
* 规定:over结尾,结束read
* 以over结尾的同时,在over之前如果有字符也予以read
*/
public static void getBygetByKeyboardAndInputStramReader() throws IOException{
/*
* System.in返回一个BufferedInputStream对象,也就是字节流的子类
* 把System.in返回的字节流对象传给InputStreamReader的构造方法,得到转换流
* 把转换流传入BufferedReader的构造方法中就得到了BufferedReader的实例对象br
*/
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String line = null;
while((line = br.readLine()) != null){
if(line.endsWith("over")){
if(line.length() != 4){
System.out.println(line.substring(0, line.length()-4));
}
break;
}
System.out.println(line);
}
}
/*
* 1.2思路实现
*/
public static void getByKeyboard() throws IOException{
InputStream in = System.in;
StringBuffer sb = new StringBuffer();
int len = 0;
while(true){
len = in.read();
if(len == 13)
continue;
if(len == 10){
if(sb.toString().endsWith("over"))
break;
System.out.println(sb.toString());
sb.delete(0, sb.length());
}
else{
sb.append((char)len);
}
}
}
}
十一、转换流
控制台输出System.out 引用类型的成员变量,返回一个PrintStream,这个类的名字后缀是一个Stream,所以是字节流,继承OutputStream,肯定是一个字节输出流对象。刚才我们的程序,读取的控制台输入,用的是字符流的readLine读取的键盘输入,将读取到的字符,输出到System.out中去,肯定不行,readLine读取到的是字符数据, System.out返回的是字节输出流,所以想将接受到的数据打印回控制台,还是需要转换流。
OutputStreamWriter , 继承Writer,也是一个字符流,字符流向字节的桥梁。
构造方法,传递字节输出流OutputStreamWriter(OutputStream out)构造器中,可以传递任意的字节输出流
下面给出一个demo,实现控制台的输入输出以及其中的转换流操作等等。
package io;
/*
* 转换流demo:
* 字节流 -> 字符流 -> 程序处理字符流后 -> 字流节
*
* 字符数据输出到字节流中,使用OutputStreamWriter
*
* 程序直接全部使用匿名类对象,要改变输入源和输出源
* 只需要更改InputStreamReader构造方法传入的参数就OK
* 因为是示例程序,所以直接把IOException扔出去了
*
* 注意:此demo外部的输入输出都是字节流,内部使用两次转换流
*/
import java.io.*;
public class TranceDemo2 {
public static void main(String[] args) throws IOException {
/*
* 字节流-》字符流
* System.in返回一个BufferedInputStream对象,也就是字节流的子类
* 把System.in返回的字节流对象传给InputStreamReader的构造方法,得到转换流
* 把转换流传入BufferedReader的构造方法中就得到了BufferedReader的实例对象br
*/
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
/* 字符流-》字节流
* System.out返回PrintStream,是OutputStream的子类,也就是字节输出流
* PrintStream传入OutputStreamWriter的构造方法,得到一个转换流,此转换流将输出的字符流转为为字节流
* 最后使用BufferedWriter装饰类操作
*/
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
String line = null;
while((line = br.readLine()) != null){
if(line.endsWith("#")){
if(line.length() != 1){
bw.write(line.substring(0, line.length()-1));
bw.newLine();
bw.flush();
}
break;
}
else{
bw.write(line);
bw.newLine();
bw.flush();
}
}
//关闭流对象
br.close();
bw.close();
}
}
十二、转换流编码
当流对象没有指定查询哪一个码表,默认走操作系统的中GBK。
- 转换流OutputStreamWriter构造方法中,写一个字节输出流,写编码表的名字(String)不区分大小写,转换流会将文本数据,以指定的编码形式写入文件
- 转换流InputStreamReader构造方法中,写一个字节输让流,写编码表的名字(String)不区分大小写,转换流会将文本数据,以指定的编码形式读取文件
demo
package io;
/*
* 转换流的编码效果
*
* 1.实现多种编码输出到文件
* 2.实现多种编码下的转换流读取
*/
import java.io.*;
public class TranseEncodingDemo {
public static void main(String[] args) throws IOException {
writeWithGBK();
writeWithUTF();
readWithUTF();
}
/*
* 在win下GBK实际上是默认的读取方式
* 单还是显示实现进行对比
* 注意:传入的编码String不区分大小写
*/
public static void writeWithGBK() throws IOException{
//初始化一个转换流对象
OutputStreamWriter ow = new OutputStreamWriter(new FileOutputStream("E:\\textByGBK.txt"),"GBK");
ow.write("这是一个测试!");
ow.flush();
ow.close();
}
/*
* 实现UTF-8的编码输出到文件
* 注意:传入的编码String不区分大小写
*/
public static void writeWithUTF() throws IOException{
//初始化一个转换流对象
OutputStreamWriter ow = new OutputStreamWriter(new FileOutputStream("E:\\textByUTF.txt"),"UTF-8");
ow.write("这是一个测试!");
ow.flush();
ow.close();
}
/*
* 读取gbk就不写了,直接实现读取utf8
* 按照默认的gbk编码读取输出:杩欐槸涓?涓祴璇曪紒 (乱码了)
* 按照指定的编码utf-8输出:这是一个测试! (正确)
*/
public static void readWithUTF() throws IOException{
InputStreamReader isr = new InputStreamReader(new FileInputStream("E:\\textByUTF.txt"),"UTF-8");
char[] ch = new char[20];
int len = isr.read(ch);
System.out.println(new String(ch).substring(0, len).toString());
isr.close();
}
}
十三、字符的编码和解码
常见的编码表有以下几种:
- 拉丁文 iso8859-1,用于 java网络服务器Tomcat
- GBK 中文编码表,2个字节 对应1个汉字 20000个汉字
- UTF-8 3个字节对应1个汉字
汉字进行编码 :
byte[] b = "汉字内容".getBytes("编码类型");
汉字进行解码:
String s = new String(byte[] ,"编码类型");
demo
package io;
/*
* 汉字的编码和解码练习
* 编码
* utf-8 : -26 -99 -88 -26 -81 -108 -24 -67 -87
* gbk : -47 -18 -79 -56 -48 -7
* Unicode: -2 -1 103 104 107 -44 -113 105
*
* 解码和编码会有异常,懒得处理,直接抛了
*/
public class EncodingDemo {
public static void main(String[] args) throws Exception {
String s = "杨比轩";
byte[] by = s.getBytes("gbk");
for(byte b : by){
System.out.print(b + " ");
}
System.out.println("\n" + decoding(by,"gbk"));
}
/*
* 解码方法
*/
public static String decoding(byte[] by, String coding) throws Exception{
return new String(by,coding);
}
}
附:
java--IO(二)