本文主要是对Java常用BIO(阻塞IO)操作的一个总结,整理一下分享出来,希望对看到的人有所帮助,同时也希望多多拍砖!
一、IO流:
Java IO主要以流的方式操作,流就是数据传输,按传输方向分为输入流和输出流,按数据类型分为字符流和字节流。字节流以字节8bit为单位,字符流以字符为单位,处理多个字节。因此处理纯文本数据,就优先使用字符流,除此之外使用字节流。
字节流分别是InputStream、OutputStream的实现类。字符流分别是Reader、Writer的实现类。
二、数据读取:
(此处记录的部分代码实例忽略了流关闭操作,使用时请自行添加)
1、FileReader:
(1)日常经常会遇到文本文件读取的情况,由于文本中可能包含多种国家的语言,因此当然要使用字符流。
fileReader=new FileReader("test/FileReader.txt");
System.out.println("ready: "+fileReader.ready());
//FileReader不支持mark操作
System.out.println("markSupported: "+fileReader.markSupported());
int temp;
while((temp=fileReader.read())!=-1){
System.out.print((char)temp);
}
//结束后继续read
temp=fileReader.read();
System.out.println("\nread eof:"+temp);
注,mark操作:标记流中的当前位置,对reset()的后续调用将尝试将该流重新定位到此点(FileReader不支持)。
上面程序中是一个字符一个字符的读取,当然如果觉得慢,可以使用BufferedReader类包装FileReader(装饰者模式),即可按行读取,后面会介绍。程序最后读到文件末尾会返回-1(EOF)。
(2)跳过一些字符:
fileReader=new FileReader("test/FileReader.txt");
long skip=fileReader.skip(3);
System.out.println("skip num: "+skip);
while((temp=fileReader.read())!=-1){
System.out.print((char)temp);
}
上面的程序中,跳过了前3个字符,然后继续读取后面的。
(3)直接读入字符数组:
File file=new File("test/FileReader.txt");
fileReader=new FileReader(file);
//如果存储数组小于文件大小,会忽略文件后面的部分
char[] buff=new char[(int)file.length()];
fileReader.read(buff);
for(char c:buff){
System.out.print(c);
}
2、BufferedReader:
如果遇到需要按行读取的情况:
String tempStr="",content="";
//通过下列字符之一即可认为某行已终止:换行('\n')回车('\r')或回车后直接跟着换行.
while((tempStr=bufferedReader.readLine())!=null){
content+=(tempStr+"\n"); //不包含任何行终止符和换行符.
}
System.out.println(content);
3、StringReader:
当然有时不是文件从文件中读取,比如StringReader。
String str="abcdefghigklmn12345中文";
StringReader stringReader=new StringReader(str);
int temp;
try {
while((temp=stringReader.read())!=-1){
System.out.print((char)temp);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
stringReader.close();
}
4、LineNumberReader:
LineNumberReader是BufferedReader的子类,除了支持按行读取外,还支持读取行号。
File file=new File("test/FileReader.txt");
FileReader fileReader = new FileReader(file);
LineNumberReader reader = new LineNumberReader(fileReader);
String content="";
while((content=reader.readLine())!=null){
System.out.println(reader.getLineNumber()+":"+content);
}
5、ByteArrayInputStream:
从字节数组中读取数据,使用ByteArrayInputStream,由于是按字节读取,所以中文不能正常显示。
bais=new ByteArrayInputStream(str.getBytes());
//返回可不发生阻塞地从此输入流读取的字节数.
System.out.println("available: "+bais.available());//一个汉字2个字节,一个\n一个字节.
System.out.println("markSupported: "+bais.markSupported());
//bais.mark(2);//mark(readAheadLimit).JDK_API注:readAheadLimit对于此类(ByteArrayInputStream)没有意义.
int tmp;
while((tmp=bais.read())!=-1){
System.out.print((char)tmp);
}
直接读入另一个字节数组:
bais.reset();
byte[] buff=new byte[(int)file.length()];
bais.read(buff);
System.out.println(new String(buff));
6、BufferedInputStream:
一个带有缓冲区的InputStream,并支持mark和reset的能力。
FileInputStream fis=new FileInputStream("test/abc.txt");
BufferedInputStream in = new BufferedInputStream(fis);
byte[] readbytes1=new byte[5];
byte[] readbytes2=new byte[7];
in.read(readbytes1);//从流中读5字节.
in.read(readbytes2);//继续从流中读7字节.
System.out.println("after read1:"+new String(readbytes1));
System.out.println("after read2:"+new String(readbytes2));
in.close();
7、DataInputStream:
允许应用程序以与机器无关方式从底层输入流中读取基本Java数据类型。
bis=new BufferedInputStream(new FileInputStream(file));
dis=new DataInputStream(bis);
while(dis.available()!=0){
char c=0;
char[] strchar=new char[1024];
int i=0;
while((c=dis.readChar())!='\t'){
strchar[i]=c;
i++;
}
System.out.println(new String(strchar,0,i));
System.out.print((char)dis.readChar()+" ");
System.out.print(dis.readDouble()+" ");
System.out.print(dis.readInt()+" ");
System.out.print(dis.readChar());
System.out.print(dis.readBoolean()+" ");
System.out.print(dis.readUTF());
}
8、PushBackInputStream:
PushBackInputStream回退流。
public static void main(String[] args) throws IOException{
String str = "hello,world!";
PushbackInputStream push = null;
ByteArrayInputStream bat = null;
bat = new ByteArrayInputStream(str.getBytes());
push = new PushbackInputStream(bat);
int temp = 0;
while((temp = push.read()) != -1){
if(temp == ','){
push.unread(temp);
temp = push.read();
System.out.print("(回退" + (char) temp + ") ");
}else{
System.out.print((char) temp);
}
}
}
三、数据输出:
(此处记录的部分代码实示例忽略了流关闭操作,使用时请自行添加)
1、输出到文件:
设置filewriter的追加模式为true,每次都写入文件末尾。
BufferedWriter bw = new BufferedWriter(new FileWriter(new File("test/FileWriter.txt"),true));
bw.write(test);
bw.newLine();
bw.write(test);
bw.close();
2、输出到字节数组:
String testStr="abcdefghijklmnopqrstuvwxyz";
ByteArrayInputStream bais=new ByteArrayInputStream(testStr.getBytes());
ByteArrayOutputStream baos=new ByteArrayOutputStream();
int nextBytes=0;
while((nextBytes=bais.read())!=-1){
baos.write(nextBytes);
}
byte[] bytes=baos.toByteArray();//创建一个新分配数组,其大小是此输出流的当前大小,并且缓冲区的有效内容已复制到该数组中。
System.out.println("bytes:"+new String(bytes));
baos.close();
bais.close();
3、以Java基础数据类型形式输出:
bos=new BufferedOutputStream(new FileOutputStream(file));
dos=new DataOutputStream(bos);
dos.writeChars("中文数据1\t");
dos.writeChar('c');
dos.writeDouble(1234.5d);
dos.writeInt(111);
dos.writeChars("\n");
dos.writeBoolean(true);
dos.writeUTF("中文数据2");
bos.close();
dos.close();
4、合并文件输出:
SequenceInputStream主要用来将2个流合并在一起,比如将两个txt中的内容合并为另外一个txt。
public static void main(String[] args) throws IOException{
File file1 = new File("d:" + File.separator + "hello1.txt");
File file2 = new File("d:" + File.separator + "hello2.txt");
File file3 = new File("d:" + File.separator + "hello.txt");
InputStream input1 = new FileInputStream(file1);
InputStream input2 = new FileInputStream(file2);
OutputStream output = new FileOutputStream(file3);
//合并流
SequenceInputStream sis = new SequenceInputStream(input1, input2);
int temp = 0;
while((temp = sis.read()) != -1){
output.write(temp);
}
input1.close();
input2.close();
output.close();
sis.close();
}
四、字节流到字符流的转换:
有时在进行输入时只能获得字节流,但是又需要通过字符流读取数据的情况:比如在使用第三方类库的情况下又无法修改其源码,这时可以使用IO类库提供的输入输出流适配器InputStreamReader和OutputStreamWriter。
InputStreamReader:InputStreamReader是字节流通向字符流的桥梁,它使用指定的charset读取字节并将其解码为字符。
OutputStreamWriter:OutputStreamWriter是字符流通向字节流的桥梁,可使用指定的charset将要写入流中的字符编码成字节。
注:从JDK文档中知道FileOutputStream是OutputStream 的直接子类,FileInputStream也是InputStream的直接子类,但是在字符流文件中的两个操作类却有一些特殊,FileWriter并不直接是Writer的子类,而是OutputStreamWriter的子类,而FileReader也不直接是Reader的子类,是InputStreamReader的子类,那么从这两个类的继承关系就可以清楚地发现,不管是使用字节流还是字符流实际上最终都是以字节的形式操作输入/输出流的,且最终全部是以字节的形式保存在文件中。
结束语:
上文中介绍的都是Java IO类库中一些常见的输入输出操作,除了几个类之外,Java IO类库还提供了更丰富的API,请查阅文档。另外,Apache的开源项目commons-io也对一些常见的IO操作都进行了封装,使用起来很方便。