什么是流?
流(stream)的概念源于UNIX中管道(pipe)的概念。在UNIX中,管道是一条不间断的字节流,用来实现程序或进程间的通信,或读写外围设备、外部文件等。一个流,必有源端和目的端,它们可以是计算机内存的某些区域,也可以是磁盘文件,甚至可以是 Internet 上的某个 URL。流的方向是重要的,根据流的方向,流可分为两类:输入流和输出流。用户可以从输入流中读取信息,但不能写它。相反,对输出流,只能往输入流写,而不能读它。实际上,流的源端和目的端可简单地看成是字节的生产者和消费者,对输入流,可不必关心它的源端是什么,只要简单地从流中读数据,而对输出流,也可不知道它的目的端,只是简单地往流中写数据。
对流形象的比喻——水流 ,文件======程序 ,文件和程序之间连接一个管道,水流就在之间形成了,自然也就出现了方向:可以流进,也可以流出.便于理解,于是可以这么形象的定义流: 流就是一个管道里面有流水,这个管道连接了文件和程序。
常用流的分类:
在java中流可以按多种方式分类,
1.按流的方向分为:输入流和输出流
输入流:如:inputstream reader用于读取数据
输出流:如:outputstream writer用于写入数据
2.按流的数据单位不同分为:字节流和字符流
字节流:如:inputstream outputsteam用于操作字节级别的数据
字符流:如:writer reader用于操作字符级别的数据
3.按流的功能不同分为:节点流和处理流(包装流)和转换流
节点流:如:Fileinputstream Fileoutputstream Filereader FileWriter用于操作硬盘等设备上的文件
包装流:如:BufferedWriter BufferredReader 等用于操作其他流对象的流进行功能的增强和效率的提高
转换流:如:intputstreamreader outputstreamwriter 用于字符流字节流之间的转换以便于操作数据
流的使用:
输入输出流
1,明确操作的是字节级数据还是字符级数据
如果是字符级数据就用Writer Reader体系
如果是字节级数据就用inputstream outputstream体系
2,明确操作的源或目的是文件还是系统输入
如果是文件就用加前缀File
如果不是文件就不加File
eg1:读取一个硬盘上的文本文件并打印出来
明确是操作字符级数据并且操作的是硬盘上的文件夹所以使用FileReader如下:
class IoDemo{
public static void main(String[] args) throws IOException{
//创建一个文件读取流,并与指定文件相关联。
//要保证该文件是已经存在的,如果存在,会发生异常FileNotFoundException
FileReader fr = new FileReader("demo.txt");
int ch;
while((ch = fr.read())!= -1){ //注意read返回的是ASCLL码(int)
System.out.println((char)ch);
}
}
}
如果demo.txt文件过大那么这个程序就要进行多次循环,导致程序的效率较低于是进行了以下改进方案:
class IoDemo{
public static void main(String[] args) throws IOException{
FileReader fr = new FileReader("IoDemo.java");
char[] buf = new char[1024];
int num;
while((num = fr.read(buf))!= -1){
System.out.println(new String(buf,0,num));
}
}
}
eg2:将c盘的一张图片复制到d盘下
明确操作的是字节级的数据并且是操作硬盘上的文件所以使用FileInputstream和FileOutputStream
class IoDemo{
public static void main(String[] args)throws IOException{
FileInputStream fis = new FileInputStream("c:\\1.jpg");
FileOutputStream fos = new FileOutputStream("d:\\1.jpg");
byte[] buf = new byte[1024];
int len;
while((len = fis.read(buf)) != -1){
fos.write(buf,0,len);
}
fis.close();
fos.close();
}
}
eg3:键盘输入的字母大写输出
明确操作的是字节级的数据并且不是文件所以使用inputsteam
class IoDemo{
public static void main(String[] args)throws IOException{
InputStream in = System.in;
StringBuilder sb = new StringBuilder();
while(true){
int ch = in.read();
if(ch == 13)
continue;
if(ch == 10){
String s = sb.toString();
if("over".equals(s))
break;
System.out.println(s.toUpperCase());
sb.delete(0,sb.length());
}
else
sb.append((char)ch);
}
}
}
缓冲流BuffredReader BufferedWriter
缓冲区的出现是为了提高流的操作效率而出现的。所以在创建缓冲区之前,必须要先有流对象
BufferedWriter缓冲区中提供了一个跨平台的换行方法newline()
BufferedReader缓冲区中提供了读行方法:readline()读到行尾返回null;
eg:
class IoDemo{
public static void main(String[] args) throws IOException{
FileWriter fw = new FileWriter("a.txt");
FileReader fr = new FileReader("a.txt");
BufferedWriter bufw = new BufferedWriter(fw);
BufferedReader bufr = new BufferedReader(fr);
for(int i = 0;i<4;i++){
bufw.write("kch"+i);
bufw.newLine();
}
bufw.close();//此时关掉的就是缓冲区中的流对象,不用再写fw.close()
String s;
while((s = bufr.readLine()) != null){
System.out.println(s);
}
bufr.close();
}
}
我们可以看到不管是Bufferedreader还是BufferedWriter都是在增强的其操作的流对象的功能,提高程序的效率。那么我们可以联想到自己定义一个buffered来实现自己想增强的功能
自定义buffered
eg:自定义一个和bufferedreader功能相似的buffered理解bufferedreader的读行操作原理
class MyBufferedReader{
private FileReader r;
MyBufferedReader(FileReader r){
this.r = r;
}
public String myReadLine() throws IOException{
StringBuilder sb = new StringBuilder();
int ch;
while((ch = r.read())!= -1){
if((char)ch == '\r')
continue;
if((char)ch == '\n')
return sb.toString();
else
sb.append((char)ch);
}
if(sb.length()!=0) //没有这句就会读到最后一句但是不返回最后一句
return sb.toString();
return null;
}
public void myClose() throws IOException{
r.close();
}
}
class IoDemo{
public static void main(String[] args){
MyBufferedReader mybuf = null;
try{
mybuf = new MyBufferedReader(new FileReader("a.txt"));
String line;
while((line = mybuf.myReadLine())!= null){
System.out.println(line);
}
}catch(IOException e){
throw new RuntimeException("读取失败");
}finally{
try{
if(mybuf != null)
mybuf.myClose();
}catch(IOException e){
throw new RuntimeException("关闭失败");
}
}
}
}
转换流
1,为了操作数据方便有时需要使用转换流
比如:System.in 键盘输入,对应的是字节流InputStream。为了操作键盘的文件数据方便,转成字符流按字符串操作是最方便的,所以System.in转成Reader.
InputStreamReader isr = new InputStreamReader(System.in);,
2,想要把录入的数据按照指定的编码表编码并将将数据保存到文件中,或者要读入指定编码的文件时需要转换流,只有转换流可以指定编码,改转换流对象要就收一个字节输出流(只有字节级数据可以重新按指定编码类型编码)
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("a.txt"))
eg:
class IoDemo{
public static void main(String[] args)throws IOException{
//获取键盘录入对象。
InputStream in = System.in;
//将字节流对象转换成字符流对象,使用交换流inputstreamreader
InputStreamReader isr = new InputStreamReader(in);
<span style="white-space:pre"> </span>//使用Buffered增强功能
BufferedReader bufr = new BufferedReader(isr);
//将键盘输入的数据按指定的编码格式写到文件中
BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("a.txt"),"utf-8"));
String line = null;
while((line = bufr.readLine())!=null){
if("over".equals(line))
break;
bufw.write(line.toUpperCase());
bufw.newLine();
bufw.flush();
}
}
}
其他常用的流:
1,PrintWriter
打印流,可以操作字节和字符级的流,如果构造函数中有true,则println print format支持自动刷新可以简化代码书写
eg:
import java.io.*;
public class Test {
public static void main(String[] args) throws IOException{
BufferedReader bufr=new BufferedReader(new InputStreamReader(System.in));
PrintWriter out=new PrintWriter(new FileWriter("123.txt"),true);
String line=null;
while((line=bufr.readLine())!=null){
out.println(line);//本方法带有换行功能 此时不需要手动刷新
if("over".equals(line))
break;
}
bufr.close();
out.close();
}
}
2,sequenceinputstream
合并流,将多个文件合并到一个流中去
eg:
import java.io.*;
import java.util.*;
public class Test {
public static void main(String[] args) throws IOException{
//创建Vector集合
Vector<FileInputStream> v = new Vector<FileInputStream>();
//为集合添加流
v.add(new FileInputStream("1.txt"));
v.add(new FileInputStream("2.txt"));
v.add(new FileInputStream("3.txt"));
//创建集合的迭代
Enumeration<FileInputStream> e = v.elements();
SequenceInputStream sis = new SequenceInputStream(e);
FileOutputStream fos = new FileOutputStream("4.txt");
byte[] buf = new byte[1024];
int len =0;
while((len=sis.read(buf))!=-1)
{
fos.write(buf,0,len);
}
fos.close();
sis.close();
}
}
3,管道流
PipedOutputStream:管道输出流。
PipedInputStream:管道输入流。
方法:
连接管道输入流和输出流
connect(PipedOutputStream pos)
connect(PipedInputStream pis)
管道流通常与多线程结合,数据由某个线程从PipedInputStream对象读取,并由其他线程将其写入到相应的PipedOutputStream对象。不建议对这两个对象使用单个线程,因为这样可能造成死锁。
eg:
import java.io.*;
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException{
PipedInputStream pis = new PipedInputStream();
PipedOutputStream pos = new PipedOutputStream();
//连接管道输出流和输入流
pis.connect(pos);
Read r = new Read(pis);
Write w = new Write(pos);
//创建多线程
Thread t1 = new Thread(r);
Thread t2 = new Thread(w);
t1.start();
t2.start();
}
}
//将PipedInputStream封装到线程
class Read implements Runnable{
private PipedInputStream pis;
Read(PipedInputStream pis){
this.pis=pis;
}
public void run(){
try {
System.out.println("waiting for read data");
byte[] buf=new byte[1024];
int len;
while((len=pis.read(buf))!=-1){
System.out.println("read data done");
System.out.println(new String(buf));
}
pis.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
//将PipedOutputStream封装到线程
class Write implements Runnable{
private PipedOutputStream pos;
Write(PipedOutputStream pos){
this.pos=pos;
}
public void run(){
try {
System.out.println("please waiting");
Thread.sleep(3000);
String str="demo.java";
pos.write(str.getBytes());
pos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
4,RandomAccessFile类
该类不算是IO体系中的子类,而是直接继承Object,但它是IO包成员,因为它具备读写功能,内部封装了一个数组,且通过指针对数组的元素进行操作,同时可通过seek改变指针的位置。该类支持对随机访问文件的读写。自身具备读写方法。随机访问文件的行为类似存储在文件系统中的一个大型byte数组。存在指向该隐含数组的光标或索引,称为文件指针,该文件指针可以通过 getFilePointer 方法读取,并通过 seek 方法设置。输入操作从文件指针开始读取字节,并随着对字节的读取而前移此文件指针。如果随机访问文件以读取/写入模式创建,则输出操作也可用。输出操作从文件指针开始写入字节,并随着对字节的写入而前移此文件指针。写入隐含数组的当前末尾之后的输出操作导致该数组扩展。
构造函数;
RandomAccessFile(File file,String mode):
RandomAccessFile(String name,String mode):
创建从中读取和向其中写入(可选)的随机访问文件流;
注:
如果模式为r(只读),则实例化对象时不会创建文件,会去读取一个已经寻在的文件,如果该文件不存在,则会包异常。
如果模式为rw,而构造函数中文件不存在,会走动创建,如果该文件已存在,则不会覆盖。
特有方法;
getFilePointer(); 获取指针位置
seek(long pos):设置指针位置
skipBytes(int s):指针跳过s个直接位置。如果是为负数,不跳过任何字节,即该方法只会向后跳跃。
eg:
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException{
RandomAccessFile raf = new RandomAccessFile("123.txt", "rw");
//获取指针位置
long pointer = raf.getFilePointer();
System.out.println("pointer:"+pointer);
//设置指针位置
raf.seek(0);
int num = raf.readInt();
System.out.println("num:"+num);
//跳过2个字节
raf.skipBytes(2);
System.out.println((char)raf.read());
raf.close();
}
}
I/O流的应用
学习玩i/o流后写了下面3个生活中常见的用到了流技术的实例:实例1:日志文件
当程序发生异常我们要记录并保存异常的信息到文件中,而不是仅仅在控制台上显示
import java.text.*;
class IoDemo{
public static void main(String[] args)throws IOException{
try{
int[] arr = new int [2];
System.out.println(arr[3]);
}catch(Exception e){
Date d = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd:HH:mm:ss");
String s = sdf.format(d);
PrintStream ps = new PrintStream("a.txt");
ps.println(s);
System.setOut(ps);
e.printStackTrace(System.out);
}
}
}
运行结果:
但我们要想知道某个目录下的所有某种文件的所有路径,不如d:\\demo目录下所用的java文件
class IoDemo{
public static void main(String[] args){
File dir = new File("d:\\demo");
List list = new ArrayList<File>();
fileToList(dir,list);
File file = new File(dir,"javapath.txt");
writeToFile(list,file);
}
public static void fileToList(File dir,List<File> list){
File[] files = dir.listFiles();
for(File file : files){
if(file.isDirectory())
fileToList(file,list);
else{
if(file.getName().endsWith(".java"))
list.add(file);
}
}
}
public static void writeToFile(List<File> list,File file){
BufferedWriter bufw = null;
try{
bufw = new BufferedWriter(new FileWriter(file));
for(File f : list){
String path = f.getAbsolutePath();
bufw.write(path);
bufw.newLine();
bufw.flush();
}
}catch(IOException e){
System.out.println("fause");
}
finally{
try{
if(bufw != null)
bufw.close();
}catch(IOException e){
System.out.println("closefause");
}
}
}
}
运行结果:
我们辛苦开发的一个软件想要别人试用5次后就缴费可以利用配置文件来做
class IoDemo{
public static void main(String[] args)throws IOException{
Properties prop = new Properties();
File file = new File("count.ini");
if(!file.exists())
file.createNewFile();
FileInputStream fis = new FileInputStream(file);
prop.load(fis);
String value = prop.getProperty("time");
int count = 0;
if(value != null){
count = Integer.parseInt(value);
if(count >= 3){
System.out.println("please pay for this soft!");
return;
}
}
count++;
prop.setProperty("time",count+"");
FileOutputStream fos = new FileOutputStream(file);
prop.store(fos,"");
fos.close();
fis.close();
}
}
运行结果: