java IO之 File类+字节流 (输入输出 缓冲流 异常处理)

时间:2021-07-13 19:40:33

1. File类

java IO之 File类+字节流      (输入输出 缓冲流 异常处理)

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。

2. 字节流  

  什么事字节流:

    计算机都是二进制数据,一个字节是8个2进制位。字节可以表示所有的数据,比如

  本,音频,视频,图片,都是作为字节存在的。也就是说字节流处理的数据非常多。

    在文本文件中存储的数据是以我们能读懂的方式表示的。而在二进制文件中存储的数据

  是用二进制形式表示的。我们是读不懂二进制文件的,因为二进制文件是为了让程序来读取

  而设计的。例如:Java的源程序(.java源文件)存储在文件文本中,可以使用文本编辑器

  阅读,但是Java的类(字节码文件)存储在二进制文件中,可以被Java虚拟机阅读。二进

  制文件的优势在于它的处理效率比文本文件高。

    我们已经知道File对象封装的是文件或者路径属性,但是不包含向(从)文件读(写)

  数据的方法。为了实现对文件的读和写操作需要学会正确的使用Java的IO创建对象。

  字节流的抽象基类:

  输入流:java.io.InputStream

  输入流:java.io.OutputStream

  特点:  

      字节流的抽象基类派生出来的子类名称都是以其父类名作为子类名的后缀。

    如:FileInputStream,ByteArrayInputStream

  说明:

    字节流处理的单元是一个字节:用于操作二进制文件(计算机中所有文件都是二进制文件)

输入流读取方式1:

  read方法()  

    一次读取一个字节,读到文件末尾返回-1.

  仔细查看api文档发现read方法如果读到文件的末尾会返回-1。那么就可以通过read方法的返回值是否是-1来控制我们的循环读取。

private static void showContent(String path) throws IOException {
// 打开流
FileInputStream fis = new FileInputStream(path);
int len = fis.read();
while (len != -1) {
System.out.print((
char)len);
len
= fis.read();
}
// 使用完关闭流
fis.close();
}

 

写成:

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[5];
int len = fis.read(byt);
for (int i = 0; i < byt.length; i++) {
System.out.print((
char) byt[i]);
}
// 使用完关闭流
fis.close();
}

 

问题1:缓冲区大小

  那么字节数组如何定义?定义多大?

  可以尝试初始化长度为5byte数组。通过read方法,往byte数组中存内容

  那么该read方法返回的是往数组中存了多少字节。

数据读取不完.

  测试发现问题,由于数组太小,只装了5个字节。而文本的字节大于数组的长度。那么很显然可以将数组的长度定义大一些。例如1024个。

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(byt[i]);
}
// 使用完关闭流
fis.close();
}

 缓冲区有默认值.

测试,打印的效果打印出了很多0,因为数组数组有默认初始化值,所以,我们将数组的数据全部都遍历和出来.现在需要的是取出数组中的部分数据.需要将循环条件修改仔细查看api文档。发现该方法read(byte[] b)返回的是往数组中存入了多少个字节。就是数组实际存储的数据个数。

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 <len; i++) {
System.out.print(byt[i]);
}
// 使用完关闭流
fis.close();
}

 

 

总结:

 

问题一:为什么打印的不是字母而是数字,

 

        是字母对应的码值。

 

        如何显示字符,强转为char即可

 

问题二:注意:回车和换行的问题。

 

        windows的换车和换行是"\r\n" 对应码表是1310

 

 

输入流读取方式3:

使用read(byte[] b,int off,int len)查看api文档,

  b显然是一个byte类型数组,当做容器来使用

 

  off,是指定从数组的什么位置开始存字节

 

  len,希望读多少个

  其实就是把数组的一部分当做流的容器来使用。告诉容器,从什么地方开始装要装多少。

 

private static void showContent3(String path) throws IOException {
// 打开流
FileInputStream fis = new FileInputStream(path);

// 通过流读取内容
byte[] byt = new byte[1024];
// 从什么地方开始存读到的数据
int start = 5;

// 希望最多读多少个(如果是流的末尾,流中没有足够数据)
int maxLen = 6;

// 实际存放了多少个
int len = fis.read(byt, start, maxLen);

for (int i = start; i < start + maxLen; i++) {
System.out.print((
char) byt[i]);
}

// 使用完关闭流
fis.close();
}

 

测试skip方法

  通过Io,读取"c:/a.txt"文件中的第9个字节到最后所有的内容并在控制台显示出来。

  分析:其实就是要跳过文件中的一部分字节,需要查找API文档。可以使用skip方法skip(long n),参数跟的是要跳过的字节数。

  我们要从第9个开始读,那么要跳过前8个即可。

private static void showContent4(String path) throws IOException {
// 打开流
FileInputStream fis = new FileInputStream(path);

// 通过流读取内容
byte[] byt = new byte[1024];
fis.skip(
8);
int len = fis.read(byt);
System.out.println(len);
System.out.println(
"**********");
for (int i = 0; i < len; i++) {
System.out.println((
char) byt[i]);
}
// 使用完关闭流
fis.close();
}

 

输入流读取方式4:

使用缓冲(提高效率),并循环读取(读完所有内容).

总结:读完文件的所有内容。很显然可以使用普通的read方法,一次读一个字节直到读到文件末尾。为了提高效率可以使用read(byte[] byt);方法就是所谓的使用缓冲提高效率。我们可以读取大文本数据测试(大于1K的文本文件.)

private static void showContent5(String path) throws IOException {
FileInputStream fis
= new FileInputStream(path);
byte[] byt = new byte[1024];
int len = fis.read(byt);
System.out.println(len);
String buffer
= new String(byt, 0, len);
System.out.print(buffer);
}

 

    注意:如何将字节数组转成字符串?可以通过创建字符串对象即可。

  发现:一旦数据超过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));
}
}

 

输出流写出方式1:

  使用write(int b)方法,一次写出一个字节.

  在C盘下创建a.txt文本文件

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();
}
}

 

注意:使用write(int b)方法,虽然接收的是int类型参数,但是write 的常规协定是:向输出流写入一个字节。要写入的字节是参数 b 的八个低位b 24 个高位将被忽略。

 

 

输出流写出方式2:

使用write(byte[] b),就是使用缓冲.提高效率.

 

上述案例中的使用了OutputStram write方法,一次只能写一个字节。成功的向文件中写入了内容。但是并不高效,如和提高效率呢?是否应该使用缓冲,根据字节输入流的缓冲原理,是否可以将数据保存中字节数组中。通过操作字节数组来提高效率。查找API文档,在OutputStram类中找到了write(byte[] b)方法,将 b.length 个字节从指定的 byte 数组写入此输出流中。

如何将字节数据保存在字节数组中,以字符串为例,”hello , world” 如何转为字节数组。显然通过字符串的getBytes方法即可。

 

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:通过流向文件写数据
byte[] byt = "java".getBytes();
fos.write(byt);
// 3:用完流后关闭流
fos.close();
}
}

 

 仔细查看a.txt文本文件发现上述程序每运行一次,老的内容就会被覆盖掉。,那么如何不覆盖已有信息,能够往a.txt里追加信息呢。查看API文档,发现FileOutputStream类中的构造方法中有一个构造可以实现追加的功能FileOutputStream(File file, boolean append)  第二个参数,append - 如果为 true,则将字节写入文件末尾处,而不是写入文件开始处

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 path
= "c:\\b.txt";
String content
= "hello java";
writeFile(path, content);
readFile(path);
}

public static void writeFile(String path, String content)
throws IOException {
// 打开文件输出流
FileOutputStream fos = new FileOutputStream(path);
byte[] buffer = content.getBytes();
// 向文件中写入内容
fos.write(buffer);
// 关闭流
fos.close();
}
public static void readFile(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));
}
// 关闭流
fos.close();
}
}

 

注意输出流的细节:

    这个输出流显然只适合小数据的写入,如果有大数据想要写入,我们的byte数组,该如何定义?

 

上述案例中我们将输入流和输出流进行和综合使用,如果尝试进输出流换成文本文件就可以实现文件的拷贝了.

  什么是文件拷贝?很显然,先开一个输入流,将文件加载到流中,再开一个输出流,将流中数据写到文件中。就实现了文件的拷贝。

 

   字节流拷贝文件实现1

读一个字节写一个字节read write

 

 

java IO之 File类+字节流      (输入输出 缓冲流 异常处理)

 

 

 

 

   字节流拷贝文件实现2

使用字节数组作为缓冲区

 

java IO之 File类+字节流      (输入输出 缓冲流 异常处理)

问题1: 使用缓冲(字节数组)拷贝数据,拷贝后的文件大于源文件.

  测试该方法,拷贝文本文件,仔细观察发现和源文件不太一致。

打开文件发现拷贝后的文件和拷贝前的源文件不同,拷贝后的文件要比源文件多一些内容问题就在于我们使用的容器,这个容器我们是重复使用的,新的数据会覆盖掉老的数据,显然最后一次读文件的时候,容器并没有装满,出现了新老数据并存的情况。

所以最后一次把容器中数据写入到文件中就出现了问题。

如何避免?使用FileOutputStream write(byte[] b, int off, int len)

b 是容器,off是从数组的什么位置开始,len是获取的个数,容器用了多少就写出多少。

 

public static void copyFile2(String srcPath, String destPath)
throws IOException {
// 打开输入流,输出流
FileInputStream fis = new FileInputStream(srcPath);
FileOutputStream fos
= new FileOutputStream(destPath);
// 读取和写入信息
int len = 0;
// 使用字节数组,当做缓冲区
byte[] byt = new byte[1024];
while ((len = fis.read(byt)) != -1) {
fos.write(byt,
0, len);
}
// 关闭流
fis.close();
fos.close();
}

 

使用缓冲拷贝视频,可以根据拷贝的需求调整数组的大小,一般是1024的整数倍。发现使缓冲后效率大大提高。

字节流的异常处理  

上述案例中所有的异常都只是进行了抛出处理,这样是不合理的。所以上述代码并不完善,因为异常没有处理。

当我们打开流,读和写,关闭流的时候都会出现异常,异常出现后,后面的代码都不会执行了。假设打开和关闭流出现了异常,那么显然close方法就不会再执行。那么会对程序有什么影响?

public class IoTest4 {
public static void main(String[] args) throws IOException,
InterruptedException {
String path
= "c:\\b.txt";
readFile(path);
}

private static void readFile(String path) throws IOException,
InterruptedException {
FileInputStream fis
= new FileInputStream(path);
byte[] byt = new byte[1024];
int len = fis.read(byt);
System.out.println(
new String(byt, 0, len));
// 让程序睡眠,无法执行到close方法。
Thread.sleep(1000 * 10);
fis.close();
}
}

 

 

 

在执行该程序的同时我们尝试去删除b.txt文件。如果在该程序没有睡醒的话,我们是无法删除b.txt 文件的。因为b.txt还被该程序占用着,这是很严重的问题,所以一定要关闭流。

目前我们是抛出处理,一旦出现了异常,close就没有执行,也就没有释放资源。那么为了保证close的执行该如何处理呢。

 

那么就需要使用try{} catch(){}finally{}语句。try中放入可能出现异常的语句,catch是捕获异常对象,fianlly是一定要执行的代码

public class IoTest4 {
public static void main(String[] args) throws IOException,
InterruptedException {
String path
= "c:\\b.txt";
readFile(path);
}
private static void readFile(String path) {
FileInputStream fis
= null;
try {
fis
= new FileInputStream(path);
byte[] byt = new byte[1024];
int len = fis.read(byt);
System.out.println(
new String(byt, 0, len));
}
catch (IOException e) {
// 抛出运行时异常
throw new RuntimeException(e);
}
finally {
// 把close方法放入finally中保证一定会执行
// 先判断是否空指针
if (fis != null) {
try {
fis.close();
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
}

 

文件拷贝的异常处理:

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 {
if (fis != null) {
try {
fis.close();
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
if (fos != null) {
try {
fos.close();
}
catch (IOException e) {
throw new RuntimeException(e);
}
}

}

}

 

注意:

在最后的close代码中可能会有问题,两个close,如果第一个close方法出现了异常,并抛出了运行时异常,那么程序还是停止了。下面的close方法就没有执行到。

那么为了保证close的执行,将第二个放到fianlly中即可。

 

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其实提供了专门的字节流缓冲来提高效率.

BufferedInputStreamBufferedOutputStream

BufferedOutputStreamBufferedOutputStream类可以通过减少读写次数来提高输入和输出的速度。它们内部有一个缓冲区,用来提高处理效率。查看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();
}

}