史上最全IO流详解,看着一篇足矣
一:要了解IO,首先了解File类
File类里面的部分常量,方法
No. |
方法或常量 |
类型 |
描述 |
1 |
public static final String pathSeparator |
常量 |
表示路径的分隔符(windows是“;”) |
2 |
public static final String separator |
常量 |
表示路径的分隔符(windows是“\”) |
3 |
public File(String pathname) |
构造 |
创建File类对象,传入完整路径 |
4 |
public boolean createNewFile()throws IOException |
普通 |
创建新文件 |
5 |
public boolean delete() |
普通 |
删除文件 |
6 |
public boolean exists() |
普通 |
判断文件是否存在 |
7 |
public boolean isDirectory() |
普通 |
判断给定的路径是否在一个目录 |
8 |
public long length() |
普通 |
返回文件的大小 |
9 |
public String[] list() |
普通 |
列出指定目录的全部内容,只是名称 |
10 |
public File[] listFiles() |
普通 |
列出指定目录的全部内容,会有路径 |
11 |
Public Boolean mkdir() |
普通 |
创建一个目录 |
12 |
Public Boolean renameTo(File dest) |
普通 |
为已有的文件重命名 |
二:对于File类的基本操作
a.在E盘创建一个hello.txt文件
File file=new File("e:"+File.separator+"hello.txt"); //不存在则创建新文件 if(!file.exists()){ file.createNewFile(); }
b.删除刚刚创建的hello.txt文件
File f=new File("e:"+File.separator+"hello.txt"); //如果存在则删除 if(f.exists()){ f.delete(); }
c.如果此文件存在,则删除,如果不存在,则创建
File f=new File("e:"+File.separator+"hello.txt"); if(f.exists()){ f.delete(); }else{ try { f.createNewFile(); } catch (IOException e) { e.printStackTrace(); } }
d.创建文件夹,使用mkdir()方法;
File f=new File("e:"+File.separator+"hello"); f.mkdir();//创建文件夹
f.列出指定目录的全部文件,使用File里面的2个方法
public String[] list()
public File[] listFiles()
//列出e盘下面所有的文件名(使用的是public String[] list()) File f=new File("e:"+File.separator); String[] str=f.list(); for(String s:str){ System.out.println(s); }
//列出e盘下面所有文件的全路径 File f = new File("E:" + File.separator); File[] files = f.listFiles(); for (File file : files) { System.out.println(file); }
g.判断一个路径是不是文件夹(目录)
File f=new File("e:"+File.separator); if(f.isDirectory()){ System.out.println(f.getPath()+"是目录"); }else{ System.out.println(f.getPath()+"不是目录"); }
h.列出一个路径下面的全部文件,包括子文件下面的所有文件(使用递归)
public static void main(String[] args) { File f = new File("E:" + File.separator); print(f); } public static void print(File f) {
if (f != null) {//判断是不是空
if (f.isDirectory()) {//判断是不是目录 File[] files = f.listFiles(); if (files != null) { for (File file : files) { print(file); } } } else { System.out.println(f); } } }
三:随机流
File类是针对文件本身进行操作,而要对文件内容进行操作,则可以使用RandomAccessFile类(随机读写流)
构造函数(public RandomAccessFile(File file,String mode)throws FileNotFoundException)
mode:模式,总共有3种模式
r:读
w:写
rw:读写,如果文件不存在则会自动创建
1.写入
File f = new File("e:" + File.separator+"test.txt"); RandomAccessFile raf=new RandomAccessFile(f,"rw");//读写模式,如果该路径不存在会自动创建(RandomAccessFile的更多操作请看源码) String name1="mm"; int age1 =20; String name2="gg"; int age2=30;
//写入操作 raf.writeBytes(name1); raf.writeInt(age1); raf.writeBytes(name2); raf.writeInt(age2); raf.close();
2.读取
File f = new File("E:" + File.separator+"test.txt"); RandomAccessFile raf=new RandomAccessFile(f,"r");//以读模式打开 raf.skipBytes(6);//跳过第一个人的信息(第一个人的姓名+年龄+中间的空格为6个字节) byte[] bs=new byte[3]; for(int i=0;i<bs.length;i++){ bs[i]=raf.readByte(); } String name2=new String(bs); int age2=raf.readInt(); System.out.println(name2+" "+age2); raf.seek(0);//指针回到文件开头,读取第二个人的信息 for(int i=0;i<bs.length;i++){ bs[i]=raf.readByte(); } String name1=new String(bs); int age1=raf.readInt(); System.out.println(name1+" "+age1);
注意:java默认字符编码的是unicode,占两个字节,但是在String换转byte[]时用的getBytes()默认用的编码进行转换,那么就会占3个字节
四:字节流和字符流
1.首先看下流的概念:在程序中所有的数据都是以流的方式进行传输或保存的,程序需要数据的时候要使用输入流读取数据,而当程序需要将一些数据保存起来的时候,就要 使用输出流完成,程序中的输入输出都是以流的形式保存的,流中保存的实际上全都是字节文件。
2.字节流和字符流:在java.io包中操作文件内容的主要有两大类:字节流、字符流,两类都分为输入和输出操作。在字节流中输出数据主要是使用OutputStream完成,输入使的 是InputStream,在字符流中输出主要是使用Writer类完成,输入流主要使用Reader类完成。(这四个都是抽象类)
java中提供了专用于输入输出功能的包Java.io,其中包括:
InputStream,OutputStream,Reader,Writer
InputStream 和OutputStream,两个是为字节流设计的,主要用来处理字节或二进制对象,
Reader和 Writer.两个是为字符流(一个字符占两个字节)设计的,主要用来处理字符或字符串.
3.对于字节流和字符流的理解(重点,看懂此处对于字节流和字符流有很大的帮助):
a.字符流处理的单元为2个字节的Unicode字符,分别操作字符、字符数组或字符串,而字节流处理单元为1个字节,操作字节和字节数组。所以字符流是由Java虚拟机将字 节转化为2个字节的Unicode字符为单位的字符而成的,所以它对多国语言支持性比较好!如果是音频文件、图片、歌曲,就用字节流好点,如果是关系到中文(文本)的,用字符流好点
b.所有文件的储存是都是字节(byte)的储存,在磁盘上保留的并不是文件的字符而是先把字符编码成字节,再储存这些字节到磁盘。在读取文件(特别是文本文件)时, 也是一个字节一个字节地读取以形成字节序列
c.字节流可用于任何类型的对象,包括二进制对象,而字符流只能处理字符或者字符串,字节流提供了处理任何类型的IO操作的功能,但它不能直接处理Unicode字符,而字 符流就可以
d.字节流是最基本的,所有的InputStrem和OutputStream的子类都是,主要用在处理二进制数据,它是按字节来处理的 但实际中很多的数据是文本,又提出了字符流的概 念,它是按虚拟机的encode来处理,也就是要进行字符集的转化 这两个之间通过 InputStreamReader,OutputStreamWriter来关联,实际上是通过byte[]和String来关联在实际开发中出现的汉字问题实际上都是在字符流和字节流之间转化不统一而造成的
e.Reader类的read()方法返回类型为int :作为整数读取的字符(占两个字节共16位),范围在 0 到 65535 之间 (0x00-0xffff),如果已到达流的末尾,则返回 -1.inputStream的read()虽然也返回int,但由于此类是面向字节流的,一个字节占8个位,所以返回 0 到 255 范围内的 int 字节值。如果因为已经到达流末尾而没有可用的字节,则返回值-1。因此对于不能用0-255来表示的值就得用字符流来读取!比如说汉字.
4.操作流程
1 使用File类打开一个文件
2 通过字节流或字符流的子类,指定输出的位置
3 进行读/写操作
4 关闭输入/输出
IO操作属于资源操作,一定要记得关闭
5.字节流操作
a.字节输入流:OutputStream
OutputStream是整个IO包中字节输出流的最大父类,此类的定义如下:
public abstract class OutputStream extends Object implements Closeable,Flushable
从以上的定义可以发现,此类是一个抽象类,如果想要使用此类的话,则首先必须通过子类实例化对象,那么如果现在要操作的是一个文件,则可以使用: FileOutputStream类。通过向上转型之后,可以为OutputStream实例化
Closeable表示可以关闭的操作,因为程序运行到最后肯定要关闭
Flushable:表示刷新,清空内存中的数据
FileOutputStream类的构造方法如下:
public FileOutputStream(File file)throws FileNotFoundException
写数据:
File f = new File("e:" + File.separator+hello.txt"); OutputStream out=new FileOutputStream(f);//如果文件不存在会自动创建 String str="Hello World"; byte[] b=str.getBytes(); out.write(b);//因为是字节流,所以要转化成字节数组进行输出 out.close();
也可以一个字节一个字节进行输出,如下:
File f = new File("e:" + File.separator+"hello.txt"); OutputStream out=new FileOutputStream(f);//如果文件不存在会自动创建 String str="Hello World"; byte[] b=str.getBytes(); for(int i=0;i<b.length;i++){ out.write(b[i]); } out.close();
以上输出只会进行覆盖,如果要追加的话,请看FileOutputStream类的另一个构造方法:
public FileOutputStream(File file,boolean append)throws FileNotFoundException
在构造方法中,如果将append的值设置为true,则表示在文件的末尾追加内容。
File f = new File("e:" + File.separator+"hello.txt"); OutputStream out=new FileOutputStream(f,true);//追加内容
//\r\n为换行 String str="\r\nHello World"; byte[] b=str.getBytes(); for(int i=0;i<b.length;i++){ out.write(b[i]); } out.close();
b.字节输入流:InputStream
既然程序可以向文件中写入内容,则就可以通过InputStream从文件中把内容读取进来,首先来看InputStream类的定义:
public abstract class InputStream extends Object implements Closeable
与OutputStream类一样,InputStream本身也是一个抽象类,必须依靠其子类,如果现在是从文件中读取,就用FileInputStream来实现。
观察FileInputStream类的构造方法:
public FileInputStream(File file)throws FileNotFoundException
读文件:
File f = new File("e:" + File.separator+"hello.txt"); InputStream in=new FileInputStream(f); byte[] b=new byte[1024]; int len=in.read(b); in.close(); System.out.println(new String(b,0,len));
但以上方法是有问题的,用不用开辟这么大的一个字节数组,明显是浪费嘛,我们可以根据文件的大小来定义字节数组的大小,File类中的方法:public long length()
File f = new File("e:" + File.separator+"hello.txt"); InputStream in=new FileInputStream(f); byte[] b=new byte[(int) f.length()]; in.read(b); in.close(); System.out.println(new String(b));
一个字节一个字节读入
File f = new File("e:" + File.separator+"hello.txt"); InputStream in=new FileInputStream(f); byte[] b=new byte[(int) f.length()]; for(int i=0;i<b.length;i++){ b[i]=(byte) in.read(); } in.close(); System.out.println(new String(b));
但以上情况只适合知道输入文件的大小,不知道的话用如下方法:
File f = new File("e:" + File.separator+"hello.txt"); InputStream in=new FileInputStream(f); byte[] b=new byte[1024]; int temp=0; int len=0; while((temp=in.read())!=-1){//-1为文件读完的标志 b[len]=(byte) temp; len++; } in.close(); System.out.println(new String(b,0,len));
6.字符流操作
a.Writer本身是一个字符流的输出类,此类的定义如下:
public abstract class Writer extends Object implements Appendable,Closeable,Flushable
此类本身也是一个抽象类,如果要使用此类,则肯定要使用其子类,此时如果是向文件中写入内容,所以应该使用FileWriter的子类。
FileWriter类的构造方法定义如下:
public FileWriter(File file)throws IOException
字符流的操作比字节流操作好在一点,就是可以直接输出字符串了,不用再像之前那样进行转换操作了。
b.写文件
File f = new File("e:" + File.separator+"hello.txt"); Writer out=new FileWriter(f); String str="Hello World"; out.write(str); out.close();
在默认情况下再次输出会覆盖,追加的方法也是在构造函数上加上追加标记
File f = new File("e:" + File.separator+"hello.txt"); Writer out=new FileWriter(f,true);//追加 String str="\r\nHello World"; out.write(str); out.close();
字符输入流:Reader
Reader是使用字符的方式从文件中取出数据,Reader类的定义如下:
public abstract class Reader extends Objects implements Readable,Closeable
Reader本身也是抽象类,如果现在要从文件中读取内容,则可以直接使用FileReader子类。
FileReader的构造方法定义如下:
public FileReader(File file)throws FileNotFoundException
以字符数组的形式读取出数据:
File f = new File("e:" + File.separator+"hello.txt"); Reader input=new FileReader(f); char[] c=new char[1024]; int len=input.read(c); input.close(); System.out.println(new String(c,0,len));
也可以用循环方式,判断是否读到底:
File f = new File("e:" + File.separator+"hello.txt"); Reader input=new FileReader(f); char[] c=new char[1024]; int temp=0; int len=0; while((temp=input.read())!=-1){ c[len]=(char) temp; len++; } input.close(); System.out.println(new String(c,0,len));
7.总结:字节流与字符流的区别
a.字节流在操作的时候本身是不会用到缓冲区(内存)的,是与文件本身直接操作的,而字符流在操作的时候是使用到缓冲区的
b.字节流在操作文件时,即使不关闭资源(close方法),文件也能输出,但是如果字符流不使用close方法的话,则不会输出任何内容,说明字符流用的是缓冲区,并且可以使用flush方法强制进行刷新缓冲区,这时才能在不close的情况下输出内容
8.使用场景:在所有的硬盘上保存文件或进行传输的时候都是以字节的方法进行的,包括图片也是按字节完成,而字符是只有在内存中才会形成的,所以使用字节的操作是最多 的。(例:如果要java程序实现一个拷贝功能,应该选用字节流进行操作(可能拷贝的是图片),并且采用边读边写的方式(节省内存)。)
五:字节流和字符流转换
OutputStreamWriter和InputStreamReader
OutputStreamWriter:是Writer的子类,将输出的字符流变为字节流,即:将一个字符流的输出对象变成字节流的输出对象。
InputStreamReader:是Reader的子类,将输入的字节流变为字符流,即:将一个字节流的输入对象变成字符流的输入对象。
OutputStreamWriter的构造方法:
public OutputStreamWriter(OutputStream out)
例如,将字节的文件输出流,以字符的形式输出
File f = new File("e:" + File.separator+"hello.txt"); Writer out=new OutputStreamWriter(new FileOutputStream(f)); out.write("Hello World!!!"); out.close();
读得时候也可以用字符流形式读取字节流的对象
File f = new File("e:" + File.separator+"hello.txt"); Reader input=new InputStreamReader(new FileInputStream(f)); char[] c=new char[1024]; int len=input.read(c); input.close(); System.out.println(new String(c,0,len));
FileWriter和FileReader的说明
从JDK文档中可以知道FileOutputStream是OutputStream的直接子类。FileInputStream也是InputStream的直接子类,但是在字符流文件的两个操作类却有一些特殊,FileWriter并不直接是Writer的子类,而是OutputStreamWriter的子类,而FileReader也不直接是Reader的子类,而是InputStreamReader的子类,那么从这两个类的继承关系就可以清楚的发现,不管是使用字节流还是字符流实际上最终都是以字节的形式操作输入输出流的。也就是说,传输或者从文件中读取数据的时候,文件里真正保存的数据永远是字节。
六:内存操作流
ByteArrayInputStream和ByteArrayOutputStream
之前所讲解的程序中,输出和输入都是从文件中来得,当然,也可以将输出的位置设置在内存之上,此时就要使用ByteArrayInputStream、ByteArrayOutputStream来完成输入输出功能了
ByteArrayInputStream的主要功能将内容输入到内存之中
ByteArrayOutputStream的主要功能是将内存中的数据输出
ByteArrayInputStream类的定义:
public class ByteArrayInputStream extends InputStream
构造方法:
public ByteArrayInputStream(byte[] buf)
接受一个byte数组,实际上内存的输入就是在构造方法上将数据传入到内存中。
ByteArrayOutputStream:输出就是从内存中写出数据
public void write(int b)
String str="HELLO WORlD!!!"; InputStream input=new ByteArrayInputStream(str.getBytes()); OutputStream output=new ByteArrayOutputStream(); int temp=0; while((temp=input.read())!=-1){ output.write(Character.toLowerCase(temp)); } input.close(); output.close(); System.out.println(output.toString());
七:管道流
管道流的主要作用是可以进行两个线程间的通讯,分为管道输出流(PipedOutputStream)、管道输入流(PipedInputStream),如果想要进行管道输出,则必须要把输出流连在输入流之上,在PipedOutputStream类上有如下的一个方法用于连接管道:
public void connect(PipedInputStream snk)throws IOException
import java.io.IOException; import java.io.PipedInputStream; import java.io.PipedOutputStream; class Send implements Runnable{ private PipedOutputStream pos;//管道输出流 public Send(){ pos=new PipedOutputStream(); } @Override public void run() { String str="Hello World!"; try { pos.write(str.getBytes()); } catch (IOException e) { e.printStackTrace(); } try { pos.close(); } catch (IOException e) { e.printStackTrace(); } } public PipedOutputStream getPos() { return pos; } } class Receive implements Runnable{ private PipedInputStream pis;//管道输入流 public Receive(){ pis=new PipedInputStream(); } @Override public void run() { byte[] b=new byte[1024]; int len=0; try { len=pis.read(b); } catch (IOException e) { e.printStackTrace(); } try { pis.close(); } catch (IOException e) { e.printStackTrace(); } System.out.println(new String(b,0,len)); } public PipedInputStream getPis() { return pis; } } public class Test23 { public static void main(String[] args) { Send send=new Send(); Receive receive=new Receive(); try { send.getPos().connect(receive.getPis());//连接管道 } catch (IOException e) { e.printStackTrace(); } new Thread(send).start();//启动线程 new Thread(receive).start();//启动线程 } }
八:打印流
在整个IO包中,打印流是输出信息最方便的类,主要包含字节打印流(PrintStream)和字符打印流(PrintWrite)。打印流提供了非常方便的打印功能,可以打印任何的数据类型,例如:小数、整数、字符串等等。
使用PrintStream输出信息:
File f = new File("e:" + File.separator+"hello.txt"); PrintStream output=new PrintStream(new FileOutputStream(f)); output.println("Hello World!"); output.print("1+1="+2); output.close();
也就是说此时,实际上是将FileOutputStream类的功能包装了一下,这样的设计在java中称为装饰设计。
File f = new File("e:" + File.separator+"hello.txt"); PrintStream output=new PrintStream(new FileOutputStream(f)); String name="Jim"; int age=20; float score=90.5f; char sex='M'; output.printf("姓名:%s 年龄:%d 成绩:%f 性别:%c", name,age,score,sex); output.close();
你要是觉得%s %d %f %c太麻烦,可以全用%s代替
九:BufferReader和Scanner
如果想要接收任意长度的数据,而且避免乱码产生,就可以使用BufferedReader类
public class BufferedReader extends Reader
因为输入的数据有可能出现中文,所以,此处使用字符流完成。BufferedReader是从缓冲区之中读取内容,所有的输入的字节数据都将放在缓冲区之中。
System.in本身表示的是InputStream(字节流),现在要求接收的是一个字符流,需要将字节流变成字符流才可以,所以要用InputStreamReader
BufferedReader reader=new BufferedReader(new InputStreamReader(System.in)); String str=reader.readLine(); System.out.println(str);
在JDK1.5之后Java提供了专门的输入数据类,此类可以完成BufferedReader类的功能,也可以方便的对输入数据进行验证,此类存放在java.util包中
使用Scanner接收键盘的输入数据:
Scanner s=new Scanner(System.in); String str=s.next(); System.out.println(str);
比直接使用BufferedReader更加方便,但是这个程序是有问题的,如果输入的字符串中存在空格,那么就会截止,如果我们要接收空格的下,将分隔符变成“\n”。
Scanner s=new Scanner(System.in); s.useDelimiter("\n");//使用分隔符 String str=s.next(); System.out.println(str);