IO流
File类
存储在变量,数组和对象中的数据是暂时的,当程序终止时他们就会丢失.为了能够永久的保存程序中创建的数据,需要将他们存储到硬盘或光盘的文件中.这些文件可以移动,传送,亦可以被其他程序使用.由于数据存储在文件中,所以我们需要学习一个和文件有密切关系的类,叫做File类,将要掌握获取文件的属性以及删除和重命名文件.最终如何向文件中写入数据和从文件中读取数据.
File类描述的是一个文件或文件夹。(文件夹也可以称为目录)
该类的出现是对文件系统的中的文件以及文件夹进行对象的封装。可以通过对象的思想来操作文件以及文件夹。
可以用面向对象的处理问题,通过该对象的方法,可以得到文件或文件夹的信息方便了对文件与文件夹的属性信息进行操作。
文件包含很多的信息:如文件名、创建修改时间、大小、可读可写属性等。
File file = new File("c:\\bb.txt");
构建一个File类的实例并不会在机器上创建一个文件.不管文件是否存在都可以创建任意文件名的File实例,可以调用File实例的exists方法判断文件或目录是否存在。
构造一个File类实例
new File(String pathname); 通过将给定路径来创建一个新File实例。
new File(String parent, String child); 根据parent路径名字符串和child路径名创建一个新File实例。
parent是指上级目录的路径,完整的路径为parent+child.
new File(File parent, String child); 根据parent抽象路径名和child路径名创建一个新File实例。
parent是指上级目录的路径,完整的路径为parent.getPath()+child.
说明: 如果指定的路径不存在(没有这个文件或是文件夹),不会抛异常,这时file.exists()返回false。
File类的对象,既可以代表文件也可以代表文件夹。
路径
路径就是文件或文件夹所在的位置。
路径分割符:
上下级文件夹之间使用分隔符分开:
在Windows中分隔符为'\',在Unix/Linux中分隔符为'/'。
跨平台的目录分隔符
更专业的做法是使用File.separatorChar,这个值就会根据系统得到的相应的分割符。
例:new File("c:" + File.separatorChar + "a.txt");
注意,如果是使用"\",则需要进行转义,写为"\\"才可以,如果是两个"\",则写为"\\\\"。
绝对路径与相对路径
对于UNIX平台,绝对路径名的前缀是"/"。相对路径名没有前缀。
对于Windows平台,绝对路径名的前缀由驱动器号和一个":"组成,例"c:\\..."。相对路径没有盘符前缀。
相对路径:
相对路径是指相对于某位置的路径,是指相对于当前目录。
在执行Java程序时,相对路径为执行java命令时当前所在的目录。
File类中常用的方法
创建:
createNewFile() 在指定位置创建一个空文件,成功就返回true,如果已存在就不创建然后返回false
mkdir() 在指定位置创建目录,这只会创建最后一级目录,如果上级目录不存在就抛异常。
mkdirs() 在指定位置创建目录,这会创建路径中所有不存在的目录。
renameTo(File dest) 重命名文件或文件夹,也可以操作非空的文件夹,文件不同时相当于文件的剪切,剪切时候不能操作非空的文件夹。移动/重命名成功则返回true,失败则返回false。
删除:
delete() 删除文件或一个空文件夹,如果是文件夹且不为空,则不能删除,成功返回true,失败返回false。
deleteOnExit() 在虚拟机终止时,请求删除此抽象路径名表示的文件或目录,保证程序异常时创建的临时文件也可以被删除
判断:
exists() 文件或文件夹是否存在。
isFile() 是否是一个文件,如果不存在,则始终为false。
isDirectory() 是否是一个目录,如果不存在,则始终为false。
isHidden() 是否是一个隐藏的文件或是否是隐藏的目录。
isAbsolute() 测试此抽象路径名是否为绝对路径名。
获取:
getName() 获取文件或文件夹的名称,不包含上级路径。
getPath() 返回绝对路径,可以是相对路径,但是目录要指定
getAbsolutePath() 获取文件的绝对路径,与文件是否存在没关系
length() 获取文件的大小(字节数),如果文件不存在则返回0L,如果是文件夹也返回0L。
getParent() 返回此抽象路径名父目录的路径名字符串;如果此路径名没有指定父目录,则返回null。
lastModified() 获取最后一次被修改的时间。
文件夹相关:
staic File[] listRoots() 列出所有的根目录(Window中就是所有系统的盘符)
list() 返回目录下的文件或者目录名,包含隐藏文件。对于文件这样操作会返回null。
list(FilenameFilter filter) 返回指定当前目录中符合过滤条件的子文件或子目录。对于文件这样操作会返回null。
listFiles() 返回目录下的文件或者目录对象(File类实例),包含隐藏文件。对于文件这样操作会返回null。
listFiles(FilenameFilter filter) 返回指定当前目录中符合过滤条件的子文件或子目录。对于文件这样操作会返回null。
问题1:/列出指定目录中所有的子文件名与所有的子目录名。
public static void listAllFilesAndDirs2(String path) {
// 1.创建File对象,表示这个目录
File dir = new File(path);
// 2通过listFiles方法得到所包含的所有子目录与子文件名称
File[] names = dir.listFiles();
// 3,分别显示文件名与文件夹名
for (int i = 0; i < names.length; i++) {
File file = names[i];
if (file.isFile()) {
System.out.println(("子文件:"));
System.out.println("\t" + file.getName());
} else if (file.isDirectory()) {
System.out.println(("子目录:"));
System.out.println("\t" + file.getName());
}
}
}
问题2:列出指定目录中所有扩展名为.java的文件。
public static void listtAllFiles(File dir, String extension) {
// 1.获取所有的子文件和子文件夹
File[] files = dir.listFiles();
// 2.从中找出符合条件的文件并显示出来
for (int i = 0; i < files.length; i++) {
File file = files[i];
// 3.需要以指定文件后缀结尾才算符合条件
if (file.getName().endsWith(extension)) {
System.out.println(file.getName());
}
}
}
IO流简介
I/O类库中使用“流”这个抽象概念。Java对设备中数据的操作是通过流的方式。
表示任何有能力产出数据的数据源对象,或者是有能力接受数据的接收端对象。“流”屏蔽了实际的I/O设备中处理数据的细节。IO流用来处理设备之间的数据传输。设备是指硬盘、内存、键盘录入、网络等。
Java用于操作流的对象都在IO包中。IO流技术主要用来处理设备之间的数据传输。
由于Java用于操作流的对象都在IO包中。所以使用IO流需要导包如:import java.io.*;
IO流的分类
流按操作数据类型的不同分为两种:字节流与字符流。
流按流向分为:输入流,输出流(以程序为参照物,输入到程序,或是从程序输出)。
字节流
字节流的抽象基类:
输入流:java.io.InputStream
输出流:java.io.OutputStream
特点:
字节流的抽象基类派生出来的子类名称都是以其父类名作为子类名的后缀。
如:FileInputStream, ByteArrayInputStream等。
说明:
字节流处理的单元是一个字节,用于操作二进制文件(计算机中所有文件都是二进制文件)。
InputStream
使用输入流,FileInputStream。
第一步:打开流(即创建流)
第二步:通过流读取内容
第三步:用完后,关闭流资源
流是Java中的一类对象,要打开流其实就是创建具体流的对象,由于是读取硬盘上的文件,应该使用输入流。所以找到了InputStream类,但是InputStream是抽象类,需要使用它的具体实现类来创建对象就是FileInputStream。通过new 调用FileInputStream 的构造方法来创建对象。发现FileInputStream的构造方法需要指定文件的来源。查看构造方法,可以接受字符串也可以接受File对象。我们通过构建File对象指定文件路径。使用流就像使用水管一样,要打开就要关闭。所以打开流和关闭流的动作是比不可少的。如何关闭流?使用close方法即可,当完成流的读写时,应该通过调用close方法来关闭它,这个方法会释放掉十分有限的操作系统资源.如果一个应用程序打开了过多的流而没有关闭它们,那么系统资源将被耗尽。
输入流读取方式1:
read方法():一次读取一个字节,读到文件末尾返回-1.
private static void showContent(String path) throws IOException {
// 打开流
FileInputStream fis = new FileInputStream(path);
int len;
while ((len = fis.read()) != -1) {
System.out.print((char) len);
}
// 使用完关闭流
fis.close();
}
输入流读取方式2:
read(byte[] b) :使用缓冲区读取字节。使用read方法的时候,流需要读一次就处理一次,可以将读到的数据装入到字节数组中,一次性的操作数组,可以提高效率。
private static void showContent2(String path) throws IOException {
// 打开流
FileInputStream fis = new FileInputStream(path);
// 通过流读取内容
byte[] byt = new byte[1024];
int len = fis.read(byt);
for (int i = 0; i < byt.length; i++) {
System.out.print((char) byt[i]);
}
// 使用完关闭流
fis.close();
}
出现问题:为什么打印的不是字母而是数字?
是字母对应的码值。如果要显示字符,强转为char即可
注意:回车和换行的问题。
windows的换车和换行是"\r\n" 对应码表是13和10 。
问题:一旦数据超过1024个字节,数组就存储不下。如何将文件的剩余内容读完?
答:可以通过通过循环保证文件读取完。
private static void showContent7(String path) throws IOException {
FileInputStream fis = new FileInputStream(path);
byte[] byt = new byte[1024];
int len = 0;
while ((len = fis.read(byt)) != -1) {
System.out.println(new String(byt, 0, len));
}
}
OutputStream字节输出流
流程:
1:打开文件输出流,流的目的地是指定的文件
2:通过流向文件写数据
3: 用完流后关闭流
输入流写出方式1
使用write(int b)方法,一次写出一个字节.
import java.io.FileOutputStream;
import java.io.IOException;
public class IoTest2 {
public static void main(String[] args) throws IOException {
String path = "c:\\a.txt";
writeTxtFile(path);
}
private static void writeTxtFile(String path) throws IOException {
// 1:打开文件输出流,流的目的地是指定的文件
FileOutputStream fos = new FileOutputStream(path);
// 2:通过流向文件写数据
fos.write('j');
fos.write('a');
fos.write('v');
fos.write('a');
// 3:用完流后关闭流
fos.close();
}
}
测试:将c盘下的a.txt文件删除,发现当文件不存在时,会自动创建一个,但是创建不了多级目录。
注意:使用write(int b)方法,虽然接收的是int类型参数,但是write 的常规协定是:向输出流写入一个字节。要写入的字节是参数 b 的八个低位。b 的 24 个高位将被忽略。
FileOutputStream类中的构造方法中有一个构造可以实现追加的功能FileOutputStream(File file, boolean append) 第二个参数,append - 如果为 true,则将字节写入文件末尾处,而不是写入文件开始处。
public class Test2 {
public static void main(String[] args) throws IOException {
String path = "c:\\a.txt";
writeTxtFile(path);
}
private static void writeTxtFile(String path) throws IOException {
// 1:打开文件输出流,流的目的地是指定的文件
FileOutputStream fos = new FileOutputStream(path,true);
// 2:通过流向文件写数据
byte[] byt = "java".getBytes();
fos.write(byt);
// 3:用完流后关闭流
fos.close();
}
}
字节流拷贝文件实现
public class IoTest3 {
public static void main(String[] args) throws IOException {
String srcPath = "c:\\秋.jpg";
String destPath = "d:\\秋.jpg";
copyFile(srcPath, destPath);
}
public static void copyFile(String srcPath, String destPath) {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream(srcPath);
fos = new FileOutputStream(destPath);
byte[] byt = new byte[1024 * 1024];
int len = 0;
while ((len = fis.read(byt)) != -1) {
fos.write(byt, 0, len);
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
if (fis != null) {
fis.close();
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}
}
字节缓冲流
Java其实提供了专门的字节流缓冲来提高效率:BufferedInputStream和BufferedOutputStream
BufferedOutputStream和BufferedOutputStream类可以通过减少读写次数来提高输入和输出的速度。它们内部有一个缓冲区,用来提高处理效率。查看API文档,发现可以指定缓冲区的大小。其实内部也是封装了字节数组。没有指定缓冲区大小,默认的字节是8192。
显然缓冲区输入流和缓冲区输出流要配合使用。首先缓冲区输入流会将读取到的数据读入缓冲区,当缓冲区满时,或者调用flush方法,缓冲输出流会将数据写出。
注意:当然使用缓冲流来进行提高效率时,对于小文件可能看不到性能的提升。但是文件稍微大一些的话,就可以看到实质的性能提升了。
public class IoTest5 {
public static void main(String[] args) throws IOException {
String srcPath = "c:\\a.mp3";
String destPath = "d:\\copy.mp3";
copyFile(srcPath, destPath);
}
public static void copyFile(String srcPath, String destPath)
throws IOException {
// 打开输入流,输出流
FileInputStream fis = new FileInputStream(srcPath);
FileOutputStream fos = new FileOutputStream(destPath);
// 使用缓冲流
BufferedInputStream bis = new BufferedInputStream(fis);
BufferedOutputStream bos = new BufferedOutputStream(fos);
// 读取和写入信息
int len = 0;
while ((len = bis.read()) != -1) {
bos.write(len);
}
// 关闭流
bis.close();
bos.close();
}
}
字符流
常见的码表如下:
ASCII:美国标准信息交换码。用一个字节的7位可以表示。
ISO8859-1: 拉丁码表。欧洲码表,用一个字节的8位表示。又称Latin-1(拉丁编码)或“西欧语言”。ASCII码是包含的仅仅是英文字母,并且没有完全占满256个编码位置,所以它以ASCII为基础,在空置的0xA0-0xFF的范围内,加入192个字母及符号,
藉以供使用变音符号的拉丁字母语言使用。从而支持德文,法文等。因而它依然是一个单字节编码,只是比ASCII更全面。
GB2312: 英文占一个字节,中文占两个字节.中国的中文编码表。
GBK: 中国的中文编码表升级,融合了更多的中文文字符号。
Unicode: 国际标准码规范,融合了多种文字。所有文字都用两个字节来表示,Java语言使用的就是unicode。
UTF-8: 最多用三个字节来表示一个字符。
常用的为:gbk、utf-8
GBK: 两个字节包含了英文字符和扩展的中文 ISO8859-1+中文字符
UTF-8 万国码表,推行的。是1~3个字节不等长。英文存的是1个字节,中文存的是3个字节,是为了节省空间。
Reader
方法:
1,int read():读取一个字符。返回的是读到的那个字符。如果读到流的末尾,返回-1.
2,int read(char[]):将读到的字符存入指定的数组中,返回的是读到的字符个数,也就是往数组里装的元素的个数。如果读到流的末尾,返回-1.
3,close()读取字符其实用的是window系统的功能,使用完毕后,进行资源的释放。由于Reader也是抽象类,所以想要使用字符输入流需要使用Reader的实现类。查看API文档。找到了FileReader。
构造函数:在读取流对象初始化的时候,必须要指定一个被读取的文件。
如果该文件不存在会发生FileNotFoundException.
Writer
Writer中的常见的方法:
1,write(ch): 将一个字符写入到流中。
2,write(char[]): 将一个字符数组写入到流中。
3,write(String): 将一个字符串写入到流中。
4,flush():刷新流,将流中的数据刷新到目的地中,流还存在。
5,close():关闭资源:在关闭前会先调用flush(),刷新流中的数据去目的地。然流关闭。
同样道理Writer是抽象类无法创建对象。查阅API文档,找到了Writer的子类FileWriter
字符流拷贝文件
public static void main(String[] args) throws Exception {
String path1 = "c:/a.txt";
String path2 = "c:/b.txt";
copyFile(path1, path2);
}
public static void copyFile3(String path1, String path2) throws Exception {
Reader reader = new FileReader(path1);
Writer writer = new FileWriter(path2);
int ch = -1;
char [] arr=new char[1024];
while ((ch = reader.read(arr)) != -1) {
writer.write(arr,0,ch);
}
reader.close();
writer.close();
}
其他流
SequenceInputStream (序列流)
SequenceInputStream 表示其他输入流的逻辑串联。它从输入流的有序集合开始,并从第一个输入流开始读取,直到到达文件末尾,接着从第二个输入流读取,依次类推,直到到达包含的最后一个输入流的文件末尾为止。
构造函数:SequenceInputStream(InputStream s1, InputStream s2)
双文件合并
private static void testSequenceInputStream() throws IOException {
FileInputStream fis1 = new FileInputStream("c:\\a.txt");
FileInputStream fis2 = new FileInputStream("c:\\b.txt");
SequenceInputStream s1 = new SequenceInputStream(fis1, fis2);
int len = 0;
byte[] byt = new byte[1024];
FileOutputStream fos = new FileOutputStream("c:\\z.txt");
while ((len = s1.read(byt)) != -1) {
fos.write(byt, 0, len);
}
s1.close();
}
多个文件合并
public static void testSequenceInputStream() throws Exception {
InputStream in1 = new FileInputStream("c:/a.txt");
InputStream in2 = new FileInputStream("c:/b.txt");
InputStream in3 = new FileInputStream("c:/c.txt");
LinkedHashSet<InputStream> set = new LinkedHashSet<InputStream>();
set.add(in1);
set.add(in2);
set.add(in3);
final Iterator<InputStream> iter = set.iterator();
SequenceInputStream sin = new SequenceInputStream(
new Enumeration<InputStream>() {
@Override
public boolean hasMoreElements() {
return iter.hasNext();
}
@Override
public InputStream nextElement() {
return iter.next();
}
});
FileOutputStream out = new FileOutputStream("c:/z.txt");
for (int b = -1; (b = sin.read()) != -1;) {
out.write(b);
}
sin.close();
out.close();
}
MP3文件切割和合并
public class Demo2 {
public static void main(String[] args) throws IOException {
split(new File("c:\\a.mp3"), 10, new File("c:\\"));
System.out.println("切割完毕");
LinkedHashSet<InputStream> hs = new LinkedHashSet<InputStream>();
hs.add(new FileInputStream(new File("c:\\part.1.mp3")));
hs.add(new FileInputStream(new File("c:\\part.2.mp3")));
hs.add(new FileInputStream(new File("c:\\part.3.mp3")));
hs.add(new FileInputStream(new File("c:\\part.4.mp3")));
merage(hs, new File("c:\\merage.mp3"));
System.out.println("合并完毕");
}
private static void merage(LinkedHashSet<InputStream> hs, File dest)
throws IOException {
final Iterator<InputStream> it = hs.iterator();
FileOutputStream fos = new FileOutputStream(dest);
SequenceInputStream seq = new SequenceInputStream(
new Enumeration<InputStream>() {
@Override
public boolean hasMoreElements() {
return it.hasNext();
}
@Override
public InputStream nextElement() {
return it.next();
}
});
byte[] byt = new byte[1024 * 1024];
int len = 0;
while ((len = seq.read(byt)) != -1) {
fos.write(byt, 0, len);
}
seq.close();
fos.close();
}
// 1. 切割文件
/*
* 切割文件,切割份数, 切割后保存路径
*/
private static void split(File src, int count, File dir) throws IOException {
FileInputStream fis = new FileInputStream(src);
FileOutputStream fos = null;
byte[] byt = new byte[1024 * 1024];
int len = 0;
for (int i = 1; i <= count; i++) {
len = fis.read(byt);
if (len != -1) {
fos = new FileOutputStream(dir + "part." + i + ".mp3");
fos.write(byt, 0, len);
}
// fos.close();
}
fis.close();
}
}
对象的序列化
对象的序列化: 将内存中的对象直接写入到文件设备中
对象的反序列化: 将文件设备中持久化的数据转换为内存对象
基本的序列化由两个方法产生:一个方法用于序列化对象并将它们写入一个流,另一个方法用于读取流并反序列化对象。
类通过实现 java.io.Serializable 接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。可序列化类的所有子类型本身都是可序列化的。序列化接口没有方法或字段,仅用于标识可序列化的语义。