一、概述
I/O的本质是通信。
有多种源端和接收端:文件(硬盘)、键盘/控制台、网络链接等
有多种不同的通信方式:顺序、随机存取、缓冲、二进制、按字符、按行、按字等。
java设计了大量的类来解决 这个通信问题。
在电脑上的数据有三种存储方式,一种是外存,一种是内存,一种是缓存。缓存用于提升计算机工作效率。
将数据冲外存中读取到内存中的称为输入流,将数据从内存写入外存中的称为输出流。
I/O类图:
二:File类
1、概述:
既能代表一个文件的名称,也能代表一个目录。
File f1 = new File(“c:\java\demo.txt”); File f2 = new File(“c:\java”);
2、各种方法:
1:创建。
-
boolean createNewFile():在指定目录下创建文件,如果该文件已存在,则不创建。而对操作文件的输出流而言,输出流对象已建立,就会创建文件,如果文件已存在,会覆盖。除非续写。
boolean mkdir():创建此抽象路径名指定的目录。 boolean mkdirs() :创建多级目录。
2 :删除。
-
boolean delete():删除此抽象路径名表示的文件或目录。
void deleteOnExit():在虚拟机退出时删除。
注意:在删除文件夹时,必须保证这个文件夹中没有任何内容,才可以将该文件夹用 delete删除。
window的删除动作,是从里往外删。 注意:java删除文件不走回收站。要慎用。
3:获取.
-
long length():获取文件大小。
String getName():返回由此抽象路径名表示的文件或目录的名称。
String getPath():将此抽象路径名转换为一个路径名字符串。
String getAbsolutePath():返回此抽象路径名的绝对路径名字符串。
String getParent():返回此抽象路径名父目录的抽象路径名,如果此路径名没有指定父目录,则返回 null。
long lastModified():返回此抽象路径名表示的文件最后一次被修改的时间。
File.pathSeparator:返回当前系统默认的路径分隔符, windows默认为 “;”。
File.Separator:返回当前系统默认的目录分隔符, windows默认为 “\”。
4 :判断:
-
boolean exists():判断文件或者文件夹是否存在。
boolean isDirectory():测试此抽象路径名表示的文件是否是一个目录。
boolean isFile():测试此抽象路径名表示的文件是否是一个标准文件。
boolean isHidden():测试此抽象路径名指定的文件是否是一个隐藏文件。
boolean isAbsolute():测试此抽象路径名是否为绝对路径名。
5 :重命名。
-
boolean renameTo(File dest) :可以实现移动的效果: 剪切 +重命名。
6.目录的操作:
-
当file对象代表目录时,实质上是代表该目录下一组文件的名称,是一个文件集合,可以用listFiles方法获得一个File类型的数组:
File[ ] f2list = f2.listFiles( );
-
当要获取所有文件目录包括子目录时,可以采用递归的方式:
public static void showDir(File dir,int level)
{
System.out.println(getLevel(level)+dir.getName());
level++;
File[] files = dir.listFiles();
for(int x=0; x<files.length; x++)
{
if(files[x].isDirectory())
showDir(files[x],level);
else
System.out.println(getLevel(level)+files[x]);
}
}
public static String getLevel(int level)
{
StringBuilder sb = new StringBuilder();
sb.append("|--");
for(int x=0; x<level; x++)
{
//sb.append("|--");
sb.insert(0,"| ");
}
return sb.toString();
}
三、RandomAcessFile类:
非流类,主要提供随机访问文件的功能,内部封装了格式化输入输出流(DataInputStream和DataOutputStream),必须文件的结构是已知的,才能使用本类来操作。
使用方法:通过getFilePointer获取指针位置,利用seek()方法在文件中移动指针
可以指定操作模式:只读或读写。读写时,如果文件存在,不会像File,PrintWriter那样覆盖。
提供了如下方法:
(1)RandomAccessFile raf = new RandomAccessFile(“file”,”r或者rw”);
(2)raf.seek(long pos); 把文件指针设定在pos处
(3)long raf.getFilePointer():返回文件指针
(4)long raf.length();返回长度
(5)raf.skipBytes(long);
四、字节流
InputStream接口提供的方法:
-
(1)int read(); 读取1个字节数据,然后返回该字节转换成的0-255范围的int值,读取完为-1
注意:这里read方法中的byte转换成int和一般类型转换的byte转int是不同的,这里的int范围一定要在0-255之间。
(2)int read(byte[]b); 读取流中数据写入字节数组,返回本次读入的长度int值
(3)int read(byte[]b,int off,int len) 读取len长度的字节数,写入b的off开始的字节数组中
注意:如果考虑效率方面,则可以使用(2)(3)的方法,因为能够批量读取。
(4)void close(); 关闭流
OutputStream提供了如下方法:
-
(1)write(int b); 将b先转型成byte即截取低八位字节,并写入输出流,例如如果b=257,则实际写入的只是1
(2)write(byte[]b); 将byte数组写入到输出流中。
(3)write(byte[]b,int off,int len);
(4)void flush();
(5)void close();关闭前会自行刷新一次
五、字符流
与字节流的对比:
-
字符流处理的单元为2个字节的Unicode字符,分别操作字符、字符数组或字符串;而字节流处理单元为1个字节, 操作字节和字节数组。
所有文件的储存是都是字节(byte)的储存,在磁盘上保留的并不是文件的字符而是先把字符编码成字节,再储存这些字节到磁盘。在读取文件时,也是一个字节一个字节地读取以形成字节序列.
字节流可用于任何类型的对象,包括二进制对象,而字符流只能处理字符或者字符串; 2. 字节流提供了处理任何类型的IO操作的功能,但它不能直接处理Unicode字符,而字符流就可以。
Reader:用于读取字符流的抽象类。子类必须实现的方法只有 read(char[], int, int) 和 close()。
-
read()返回的值为int型,是读入该char[]数组的字符数,不同于字节流的单个字节的int值。
Writer:写入字符流的抽象类。子类必须实现的方法仅有 write(char[], int, int)、flush() 和 close()。
编码问题:
在Java中,字符串用统一的Unicode编码,每个字符占用两个字节,与编码有关的两个主要函数为:
-
1)将字符串用指定的编码集合解析成字节数组,完成Unicode-〉charsetName转换
public byte[] getBytes(String charsetName)
2)将字节数组以指定的编码集合构造成字符串,完成charsetName-〉Unicode转换
public new String(byte[] bytes, String charsetName)
流编码过程:
-
从文件读取:源bytes–>根据指定的或默认的编码表编码后的字符–>Unicode字符(java中最终的char)
写入到文件:与String.getBytes([encode])同理:Unicode字符–>encode字符–>bytes。
Unicode统一采用2个字节编码,UTF-8是Unicode的改进,原本ASCII码的字符还是一个字节。
Unicode与各编码之间的直接转换
-
Unicode和GBK
每个汉字转换为两个字节,且是可逆的,即通过字节可以转换回字符串
Unicode和UTF-8
测试结果如下,每个汉字转换为三个字节,且是可逆的,即通过字节可以转换回字符串
Unicode和ISO-8859-1
测试结果如下,当存在汉字时转换失败,非可逆,即通过字节不能再转换回字符串
Unicode与各编码之间的交叉转换
在上面直接转换中,由字符串(Unicode)生成的字节数组,在构造回字符串时,使用的是正确的编码集合,如果使用的不是正确的编码集合会怎样呢?会正确构造吗?如果不能正确构造能有办法恢复吗?会信息丢失吗?
能够正确显示的中间不正确转换
-
String-GBK〉ByteArray-ISO-8859-1〉String-ISO-8859-1〉ByteArray-GBK〉String
String-UTF-8〉ByteArray-ISO-8859-1〉String-ISO-8859-1〉ByteArray-UTF-8〉String
String-UTF-8〉ByteArray-GBK〉String-GBK〉ByteArray-UTF-8〉String
六、装饰器之转换流
InputStreamReader和InputStreamWriterInputStreamReader:
以字节流为数据源,经过编码后变成字符。编码方式如未指定,使用的是系统默认的编码表。
InputStreamReader in2 = new InputStreamReader(new FileInputStream("Reader.txt"),"UTF-8");
七、装饰器之缓冲功能:
BufferedInputStream和BufferedOutputStream:
缓冲流作为过滤流的中间层,构建缓冲区,提高效率
BufferedInputStream in = new BufferedInputStream(new FileInputStream("1.txt"));
BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream("1.txt"));
BufferedReader 和BufferedWriter :
特点:提供了readLine()方法,可以一行一行地读取文本。
FileReader fr = new FileReader("bufdemo.txt");
BufferedReader bufr = new BufferedReader(fr);
String line = null;
while((line=bufr.readLine())!=null)
{
System.out.println(line); //readLine方法返回的时候是不带换行符的。
}
bufr.close();
FileWriter fw = new FileWriter("bufdemo.txt");
BufferedWriter bufw = new BufferedWriter(fw);//让缓冲区和指定流相关联。
for(int x=0; x<4; x++)
{
bufw.write(x+"abc");
bufw.newLine(); //写入一个换行符,这个换行符可以依据平台的不同写入不同的换行符。
bufw.flush();//对缓冲区进行刷新,可以让数据到目的地中。
}
bufw.close();//关闭缓冲区,其实就是在关闭具体的流。
八、文件的读写:文件流:
根据文件的类型选择相应的流对象:
-
文本文件选用字符流,其他选择字节流。且为提高效率,多用缓冲功能。输出流多采用打印流简化书写。
字节流:FileInputStream和FileOutputStream:
BufferedInputStream in = new BufferedInputStream(new FileInputStream("c:\\1.bmp"));
PrintStream out = new PrintStream("d:\\1.bmp",true);
字符流:FileReader和FileWriter
写文本文件时,若需指定编码表,则使用FileWriter+转换流,若无需指定,使用PrintWriter简化书写。
BufferedReader bufr = new BufferedReader(new FileReader("c:\\1.txt"));
PrintWriter pw = new PrintWriter(new FileWriter("d:\\1.txt"));//使用默认编码表编码,若文件存在,则覆盖。
九、标准输入输出:
System.in:键盘录入封装成一个InputStream,常用转换流转换成字符,一行行读取
System.out: 控制台输出,封装成一个OutputStream,提供了print和println方法。
十、数据格式化输入输出流:
DataInputStream和DataOutputStream
实现了DataInput和DataOutput接口的类。
DataInput提供了如下方法:
(1)Xxx readXxx();读取基本数据类型
(2)int read(byte[]b);读取至字节数组中,返回实际读取的长度
(3)readChar()读取一个字符即两个字节
(4)String readUTF();
DataOutput提供了如下方法:
(1)void wrtieXxx(Xxx )写入基本数据类型
(2)void writeBytes(String)能够以字节方式写入String,随后可以用read读取。
(3)void writeChars(String)以字符方式写入,一个字符是两个字节
(4)void writeUTF(String );
十一、打印流
PrintStream和PrintWriter: 简化了书写,且指定输出流时,可定义自动刷新
PrintStream out = new PrintStream(OutputStream o,boolean autoflush);
提供了方法:
out.print(Xxx);
out.println(Xxx);
十二、序列流
序列流,作用就是将多个读取流合并成一个读取流。实现数据合并。
表示其他输入流的逻辑串联。它从输入流的有序集合开始,并从第一个输入流开始读取,直到到达文件末尾,接着从第二个输入流读取,依次类推,直到到达包含的最后一个输入流的文件末尾为止。
这样做,可以更方便的操作多个读取流,其实这个序列流内部会有一个有序的集合容器,用于存储多个读取流对象。
SequenceInputStream(InputStream s1, InputStream s2)
SequenceInputStream(Enumeration<? extends InputStream> e)
对象的构造函数参数是枚举,想要获取枚举,需要有Vector集合,但不高效。需用ArrayList,但ArrayList中没有枚举,只有自己去创建枚举对象。
但是方法怎么实现呢?因为枚举操作的是具体集合中的元素,所以无法具体实现,但是枚举和迭代器是功能一样的,所以,可以用迭代替代枚举。
合并原理:多个读取流对应一个输出流。
切割原理:一个读取流对应多个输出流。
十二、操作对象的流与对象序列化
目的:将一个具体的对象进行持久化,写入到硬盘上。
注意:静态数据不能被序列化,因为静态数据不在堆内存中,是存储在静态方法区中。
如何将非静态的数据不进行序列化?用transient 关键字修饰此变量即可。
Serializable:用于启动对象的序列化功能,可以强制让指定类具备序列化功能,该接口中没有成员,这是一个标记接口。这个标记接口用于给序列化类提供UID。这个uid是依据类中的成员的数字签名进行运行获取的。如果不需要自动获取一个uid,可以在类中,手动指定一个名称为serialVersionUID id号。依据编译器的不同,或者对信息的高度敏感性。最好每一个序列化的类都进行手动显示的UID的指定。
相关流:ObjectInputStream和ObjectOutputStream:
对象流实现了DataInput和DataOutput接口
对于对象:
(1)writeObject(Object);
(2)Object readObject(); 读取后需要强制类型转换
对于基本类型:使用readXxx和writeXxx方法
十三、管道流
管道读取流和管道写入流可以像管道一样对接上,管道读取流就可以读取管道写入流写入的数据。
注意:需要加入多线程技术,因为单线程,先执行read,会发生死锁,因为read方法是阻塞式的,没有数据的read方法会让线程等待。
public static void main(String[] args) throws IOException
{
PipedInputStream pipin = new PipedInputStream();
PipedOutputStream pipout = new PipedOutputStream();
pipin.connect(pipout);
new Thread(new Input(pipin)).start();
new Thread(new Output(pipout)).start();
}