Java基础——IO流

1 File类

1.1 概述

File类用于定位文件,获取文件的属性等。File类不能读取文件内容,只能定位文件,读取文件内容使用IO流。

1.2 文件定位

文件定位

/*文件路径的写法:
1、 D:\BaiduNetdiskDownload\新建 文本文档.txt
这种写法注意在代码中需要使用\来转义\,否则遇到n开头的等会出问题,比如n开头的\n会认为是换行符,这样就会导致路径不对。
2、 D:/BaiduNetdiskDownload/新建 文本文档.txt
正斜杠不会存在这样的问题。
windows系统表示通常为反斜杠,Linux通常为正斜杠
3、"D:" + File.separator + "BaiduNetdiskDownload" + File.separator + "新建 文本文档.txt"
可以使用File.separator来生成分隔符,好处是,对应于不同的系统会自动生成其对应的分隔符。
 */
//使用绝对路径定位文件
//File file = new File("D:\\BaiduNetdiskDownload\\新建 文本文档.txt");
//File file = new File("D:/BaiduNetdiskDownload/新建 文本文档.txt");
File file = new File("D:" + File.separator + "BaiduNetdiskDownload" +
        File.separator + "新建 文本文档.txt");
long length = file.length();  //获取文件字节大小
System.out.println(length);

//相对路径,相对的是工程目录
File file2 = new File("src/test1.txt");
System.out.println(file2.length());

定位文件可以使用上述绝对路径和相对路径。文件夹也是,但是文件夹通常不获取其大小,而是判断是否存在。

File file3 = new File("src");
System.out.println(file3.exists());

判断相对路径下的src文件夹是否存在。

1.3 File类常用方法

1.3.1 判断文件类型,获取文件信息

api

//判断是否为文件夹
public boolean isDirectory()
//判断是否为文件
public boolean isFile()
//判断是否存在
public boolean exists()
//获取绝对路径
public String getAbsolutePath()
//将路径转换为字符串
public String getPath()
//获取文件或文件夹名称
public String getName()
//获取最后一次修改的时间毫秒值
public long lastModified()

实例:

//文件
File file1 = new File("src/test1.txt");
System.out.println(file1.getAbsoluteFile());
System.out.println(file1.getPath());
System.out.println(file1.getName());
System.out.println(file1.length());
System.out.println(file1.lastModified());
System.out.println(file1.isFile());
System.out.println(file1.isDirectory());
输出
E:\JavaBase\FileAndIO\FileAndIO\src\test1.txt
src\test1.txt
test1.txt
20
1662129604350
true
false

//文件夹
File file2 = new File("src");
System.out.println(file2.getAbsoluteFile());
System.out.println(file2.getPath());
System.out.println(file2.getName());
System.out.println(file2.exists());
System.out.println(file2.lastModified());
System.out.println(file2.isFile());
System.out.println(file2.isDirectory());
输出
E:\JavaBase\FileAndIO\FileAndIO\src
src
src
true
1662129604350
false
true

1.3.2 创建文件、删除文件

API

//创建一个新文件,如果文件不存在并成功创建,则返回true,文件存在返回false
public boolean createNewFile()
//创建一级目录
public boolean mkdir()
//创建多级目录
public boolean mkdirs()
//删除文件和空文件夹
public boolean delete()

实例:

//创建文件,其中test1.txt存在
File file1 = new File("src/test1.txt");
System.out.println(file1.createNewFile());
File file2 = new File("src/test2.txt");
System.out.println(file2.createNewFile());
输出结果
false
true

这个API只作为了解,使用IO流时会自动创建文件,并不需要手动创建。

//创建一级目录
File file3 = new File("src/aa");
System.out.println(file3.mkdir());
输出
true
//创建多级目录
File file4 = new File("src/a/b");
System.out.println(file4.mkdir());
输出
false

创建一级目录,只能创建一级,如果有多级则创建失败。

File file4 = new File("src/a/b");
System.out.println(file4.mkdir());
输出
true

使用mkdirs创建多级目录,该方法也可以创建一级目录,使用更加灵活。

System.out.println(file1.delete());

删除文件,占用也能删掉,且不进回收站。也可用此删除空文件夹,如果文件夹不为空会删除失败。

1.3.3 遍历文件夹

API

//获取当前目录下所有的一级文件名称到字符串数组中
public String[] list()
//获取当前目录下所有的一级文件对象到文件对象数组中
public File[] listFiles()

实例:

File file1 = new File("D:\\");
String[] list = file1.list();
System.out.println(Arrays.toString(list));
输出
[$RECYCLE.BIN, Applications, BaiduNetdiskDownload, d219409332a2b4f72a737305db85bae3, Installs, jihuo-tool, QMDownload, System Volume Information]

只是返回一级文件的名称字符串。

File[] files = file1.listFiles();
for (File f : files) {
    System.out.println(f.getAbsoluteFile());
}
输出
D:\Applications
D:\BaiduNetdiskDownload
D:\d219409332a2b4f72a737305db85bae3
D:\Installs
D:\jihuo-tool
D:\QMDownload
D:\System Volume Information

返回File类对象数组

listFile注意事项:

  • 当调用者不存在时,返回null
  • 当调用者是文件时,返回null
  • 当调用者是一个空文件夹时,返回一个长度为0的数组
  • 当文件夹不为空时,会将所有的一级文件和文件夹的File对象返回到数组中,包含隐藏文件
  • 当调用者是隐藏文件夹时,会将所有的文件和文件夹放到数组中,包含隐藏文件

2 递归

2.1 概述

方法直接或间接调用自己的形式称为方法递归,是一种算法。
直接递归:方法自己调用自己
间接递归:方法调用其他方法,其他犯法又回调方法自己
存在问题:如果没有控制好终止条件,会出现死循环,导致栈内存溢出。

2.2 递归三要素总结

递归的公式
递归的终止条件
递归的方向必须走向终止条件

2.3 规律化递归经典问题:猴子吃桃

问题描述:猴子第一天摘了一堆桃,第二天吃了一半,觉得不过瘾,又多吃了一个,此后每天如此,到第十天,还剩一个桃子,求第一天摘了多少桃子。
分析:
终止条件:n==10,return 1
公式:f(n) - f(n)/2 -1 = f(n+1) -> f(n) = 2*f(n+1) + 2
代码示例:

public static int f(int n) {
    if (n == 10) {
        return 1;
    } else {
        return 2 * f(n + 1) + 2;
    }
}

2.4 非规律化递归

2.4.1 文件搜索

文件搜索就是一个非规律化递归的例子。没有公式,自己设置递归的终止条件。
查找指定文件示例:

public static void searchFile(File dir, String fileName) {
    if (dir != null && dir.isDirectory()) {
        File[] files = dir.listFiles();
        if (files != null && files.length > 0) {
            for (File file : files) {
                if (file.isFile()) {
                    if (file.getName().contains(fileName)) {
                        System.out.println("找到了" + file.getAbsolutePath());
                        try {
                            Runtime r = Runtime.getRuntime();
                            r.exec(file.getAbsolutePath());
                        } catch (IOException e) {
                            throw new RuntimeException(e);
                        }
                    }
                } else {
                    searchFile(file, fileName);
                }
            }
        }
    } else {
        System.out.println("不是文件夹");
    }
}

这里是找到可执行程序并启动,使用Runtime启动只能启动可执行程序。

2.4.2 啤酒问题

问题描述:用10块钱买啤酒,两个空瓶子可以换一瓶啤酒,四个瓶盖可以换一瓶啤酒,问以一共能买多少瓶啤酒,还剩多少个空酒瓶和瓶盖。

private static int totalNum;
private static int lastBottle;
private static int lastCover;

public static void main(String[] args) {
    buy(10);
    System.out.println("一共买了:" + totalNum);
    System.out.println("还剩瓶子:" + lastBottle);
    System.out.println("还剩盖子:" + lastCover);
}

public static void buy(int money) {
    int buyNum = money / 2;
    totalNum += buyNum;

    int coverNum = lastCover + buyNum;
    int bottleNum = lastBottle + buyNum;

    int allMoney = 0;
    if (coverNum >= 4) {
        allMoney += (coverNum / 4) * 2;
    }
    lastCover = coverNum % 4;

    if (bottleNum >= 2) {
        allMoney += (bottleNum / 2) * 2;
    }
    lastBottle = bottleNum % 2;

    if (allMoney >= 2) {
        buy(allMoney);
    }
}

使用静态变量来记录最终要输出的数据,每次买完后将空瓶和盖子换成对应的钱数量进行递归购买,最终不够2元就终止递归。

3 字符集

3.1 概述

计算机底层是二进制,字符的存储需要进行编码成为二进制数据进行存储。
常见的字符集有ASCII、GBK、Unicode等。
1 ASCII
包含数字、英文和符号,使用一个字节来存储一个字符,总共可以表示128个字符信息。
2 GBK
中文windows默认的码表,兼容ASCII编码,包含几万个汉字,并支持繁体汉字以及部分日韩文字。
采用两个字节存储一个中文,英文采用一个字节存储。
3 Unicode
全球通用的,包含了绝大部分的文字。
Unicode会通过UTF-8,UTF-16,UTF-32编码成二进制后存储到计算机中。
UTF-8使用三个字节表示一个中文,兼容ASCII编码,ASCII还是占用一个字节。

注意:英文和数字不会乱码,因为在所有编码中都遵循ASCII码,占一个字节。

3.2 编码和解码

编码和解码的API都在String类中。
编码使用getBytes方法,该方法有多个重载,默认没有参数的会按照默认字符集进行编码,带参数的可以指定字符集进行编码,返回的是编码后的字符数组。
解码使用String的构造器,传入字符数组,默认使用默认字符集进行解码,也可以加参数指定解码的字符集。
编码前后需要使用相同的字符集,否则会出现中文乱码。

public static void main(String[] args) throws UnsupportedEncodingException {
    String str = "adc您好世界";
    byte[] bytes = str.getBytes("GBK");
    System.out.println(bytes.length);
    System.out.println(Arrays.toString(bytes));

    String str1 = new String(bytes);
    System.out.println(str1);
}
输出
11
[97, 100, 99, -60, -6, -70, -61, -54, -64, -67, -25]
adc��������

编码使用getByte并可指定编码格式,返回编码后的字节数组。这里使用GBK编码,两个字节表示一个汉字。中文编译后的字节数组为负数。使用默认编码方式UTF-8进行解码会出现乱码,只有使用GBK方式解码才能返回正确的字符。

4 基础流

4.1 IO流分类

根据流的方向可分为:
输入流
输出流
根据数据大小可分为
字节流
字符流
综上可分为四种
字节输入流:InpuStream
字节输出流:OutputStream
字符输入流:Reader
字符输出流:Writer
上述的四个都是抽象类,不能实例化,需要通过其派生类进行实例化使用。

4.2 字节流

4.2.1 字节输入流

使用字节流进行数据的读写,不能直接使用InputStream,因为它是抽象类,这里介绍文件的读写流,文件字节输入输出流。

API
public int read() throws IOException
示例:
InputStream is = new FileInputStream("src/test2.txt");
int b;
while((b = is.read()) != -1) {
    System.out.print((char)b);
}
输出
fdafafafdsa爱

字节读取,需要注意的是这里最后是中文字符,由于中文字符的utf-8占8个字节,而读取的时候是按字节读取,并翻译成char,所以会存在乱码问题。

API
public int read(byte b[]) throws IOException
示例:
InputStream is = new FileInputStream("src/test2.txt");

byte[] buffer = new byte[3];
int len;
while ((len = is.read(buffer)) != -1) {
    System.out.print(new String(buffer, 0, len));
}

使用指定长度数组去读取,也会存在乱码问题

对于乱码问题,可以有两种方法解决
一次性读取所有字节
使用字符输入流

一次性读取所有字节

InputStream is = new FileInputStream("src/test2.txt");
File file = new File("src/test2.txt");
byte[] buffer = new byte[(int)file.length()];
int len = is.read(buffer);
System.out.println(len);
System.out.println(new String(buffer));
同时,也存在一个api有一样的效果
byte[] buff = is.readAllBytes();
System.out.println(new String(buff))

这样就不会出现乱码,但是如果文件过大,字节数组可能溢出。

4.2.2 字节输出流

和字节输入流一样,OutputStream也是抽象类,有很多实现类,这里以文件输出流为例。

API
public void write(int b) throws IOException
示例:
OutputStream os = new FileOutputStream("src/test3.txt");

os.write('a');
os.write(98);
os.write('夏');

os.flush();
os.close();

注意,虽然这里中文没有报错,但是写进去是乱码,因为只写了一个字节。
使用输出流的时候不用文件存在,会自动创建文件。
需要刷新,数据才能存入磁盘,否则是暂时存在于内存中的。close是关闭资源,因为流是重要的内存资源,使用完成后需要关闭,close默认会包含flush操作。\r

除此之外,还可以使用字符数组进行写入

API
public void write(byte b[]) throws IOException
示例:
OutputStream os = new FileOutputStream("src/test3.txt");

byte[] buffer = {'a',98,99,100};
os.write(buffer);

byte[] buffer1 = "我是中国人".getBytes();
os.write(buffer);

可以将字符存入字符数组中,或是将字符串转换为字符数组写入。
需要注意的是这个流是覆盖式的,重新写入后,之前的数据消失。如果要追加文件,则在后面加个参数true。
同时可以write可以部分写入字节数组,只需要加参数起始位置和长度。
如果要回车换行则也需要写入对应字节"\r\n"。

4.2.3 文件拷贝

所有文件存储都是字节的方式存储的,则可以使用字节流来进行拷贝。

InputStream is = null;
OutputStream os = null;
try {
    is = new FileInputStream("E:\\JavaBase\\08、文件字符输入流、文件字符输出流.mp4");
    os = new FileOutputStream("src/new.avi");

    byte[] buffer = new byte[1024];
    int len;
    while ((len = is.read(buffer)) != -1) {
        os.write(buffer, 0, len);
    }
    System.out.println("复制成功");
} catch (Exception e) {
    e.printStackTrace();
} finally {
    try {
        if (os != null) {
            os.close();
        }
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
    try {
        if (is != null) {
            is.close();
        }
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

使用try-catch-finally处理异常和关闭流。流的关闭需要判空,防止在流初始化之前出现异常。finally不管是否出现异常都会执行,唯一不执行的方式是调用了exit(0)使程序结束。
JDK1.7做了修改,可以这样写

try(InputStream is = new FileInputStream("E:\\JavaBase\\08、文件字符输入流、文件字符输出流.mp4");
    OutputStream os = new FileOutputStream("src/new.avi");) {
    byte[] buffer = new byte[1024];
    int len;
    while ((len = is.read(buffer)) != -1) {
        os.write(buffer, 0, len);
    }
    System.out.println("复制成功");
} catch (Exception e) {
    e.printStackTrace();
}

将资源放入try的括号中,当资源使用完毕会自动关闭资源。只有实现了CLoseable或AutoCloseable接口的才叫做资源,没有实现这个接口的方法这个里面会报错。
JDK9又做了优化,将定义放到try-catch外面,try括号里面只写变量名。

public static void main(String[] args) throws FileNotFoundException {
    InputStream is = new FileInputStream("E:\\JavaBase\\08、文件字符输入流、文件字符输出流.mp4");
    OutputStream os = new FileOutputStream("src/new.avi");
    try(is;os) {
        byte[] buffer = new byte[1024];
        int len;
        while ((len = is.read(buffer)) != -1) {
            os.write(buffer, 0, len);
        }
        System.out.println("复制成功");
    } catch (Exception e) {
        e.printStackTrace();
    }
}

但这种方式并不实用,因为定义也会抛异常,即使把is和os放到了外面,try-catch代码块后面也不能使用,因为流已经关闭了。最常用的还是JDK1.7的改动方式。

4.3 字符流

4.3.1 字符输入流

字节流读取中文会乱码,字符流根据单个字符来读取,能解决乱码问题。
Writer和Reader都是抽象类,需要使用其实现类,这里以文件字符输入输出流为例。

API
public int read() throws IOException
示例:
Reader fr = new FileReader("src/test2.txt");
int code;
while ((code = fr.read()) != -1){
    System.out.print((char)code);
}

一次读一个字符,但是要求是代码中指定的编码和文件中的编码一致。这里是默认的都是utf-8。

也可以是用字节数组去读取

API
public int read(char[] cbuf) throws IOException
示例:
Reader fr = new FileReader("src/test2.txt");
char[] ch = new char[3];
int len;
while ((len = fr.read(ch)) != -1){
    System.out.print(new String(ch,0,len));
}

4.3.2 字符输出流

Writer fw = new FileWriter("src/test4.txt");

fw.write('a');
fw.write(98);
fw.write("我是中国人".toCharArray());
fw.write("abc我是中国人",0,5);
fw.close();

字符输入流的一些方式,与字节输入流相同,需要关闭流刷新才能写入。也是覆盖的,追加在后面加参数true。

5 缓冲流

缓冲流是自带字节或字符输入输出缓冲区的流,能够提高基础流的传输速度。缓冲流也有多种类型,包装字节的缓冲字节输入输出流和包装字符的缓冲字节输入输出流。
下面是使用缓冲流进行拷贝的示例:

public static void copy() {
    long startTime = System.currentTimeMillis();
    try (InputStream is = new FileInputStream("F:\\CentOS-7-x86_64-Everything-2009.iso");
         InputStream bis = new BufferedInputStream(is);

         OutputStream os = new FileOutputStream("src/new1.iso");
         OutputStream bos = new BufferedOutputStream(os)
    ) {
        byte[] buffer = new byte[1024];
        int len;
        while ((len = bis.read(buffer)) != -1) {
            bos.write(buffer);
        }
        System.out.println("复制完成");
    } catch (Exception e) {
        e.printStackTrace();
    }
    long endTime = System.currentTimeMillis();
    System.out.println("copy1消耗时间" + (endTime - startTime)/1000.0 + "s");
}

缓冲流创建的时候传入一个基础流,并且基础流的读取速度也会决定整体的读取速度。这里基础流的读取是一次读取1KB数据,与缓冲流的缓冲区并不冲突。缓冲区相当于在内存中,缓存了数据后基础流就直接在缓冲区读取对应数量的字节。而基础流使用字节数组去传入缓冲区或者从缓冲区读取则又会增加传输速度,所以缓冲流通常是与基础流的字节读取方式配合使用的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值