本章内容:

1.输入输出流
2.文件与目录管理File类
3.缓冲流
4.字节缓冲流
5.字符缓冲流

1.输入输出流

在前面的代码中,我们总是使用System.out.println()来向屏幕输出一些内容。println是print line的缩写,表示输出并换行。因此,如果输出后不想换行,可以用System.out.print()
格式化输出:
如果要把数据显示成我们期望的格式,就需要使用格式化输出的功能。格式化输出使用System.out.printf(),通过使用占位符%?,printf()可以把后面的参数格式化成指定格式:

1
2
3
4
5
6
7
public class Main{
public static void main(String[] args){
double d = 3.1415927;
System.out.printf("%.2f",d);
System.out.printf("%.4f",d);
}
}
占位符 说明
%d 格式化输出整数
%x 格式化输出十六进制整数
%f 格式化输出浮点数
%e 格式化输出科学计数法表示的浮点数
%s 格式化字符串

输入: 直接看案例
输入类型:
方法     描述
nextBoolean()     从用户输入中读取1个 boolean 值
nextByte()      从用户输入中读取1个 byte 值
nextDouble()     从用户输入中读取1个 double 值
nextFloat()     从用户输入中读取1个 float 值
nextInt()     从用户输入中读取1个 int 值
nextLine()     从用户输入中读取1个 String 值
nextLong()     从用户输入中读取1个 long 值
nextShort()     从用户输入中读取1个 short 值

1
2
3
4
5
6
7
8
9
10
11
12
import java.util.Scanner;

public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in); // 创建Scanner对象
System.out.print("Input your name: "); // 打印提示
String name = scanner.nextLine(); // 读取一行输入并获取字符串
System.out.print("Input your age: "); // 打印提示
int age = scanner.nextInt(); // 读取一行输入并获取整数
System.out.printf("Hi, %s, you are %d\n", name, age); // 格式化输出
}
}

首先,我们通过import语句导入java.util.Scanner,import是导入某个类的语句,必须放到Java源代码的开头

然后,创建Scanner对象并传入System.in。System.out代表标准输出流,而System.in代表标准输入流。直接使用System.in读取用户输入虽然是可以的,但需要更复杂的代码,而通过Scanner就可以简化后续的代码。

有了Scanner对象后,要读取用户输入的字符串,使用scanner.nextLine();要读取用户输入的整数,使用scanner.nextInt()。Scanner会自动转换数据类型,因此不必手动转换。

2.文件与目录管理

File类

Java的标准库java.io提供了File对象来操作文件和目录。我们从之前的知识可以推出java把电脑中的文件和文件夹(目录)封装为了一个File类,我们可以使用File类对文件和文件夹进行操作,并且类中存在构造函数,构造函数里传入的值为文件的路径。要构造一个file对象,需要传入文件路径:构造File对象时,可以是绝对路径(以根目录开头的完整路径),也可以是相对路径

创建File类对象

1
2
3
File file = new File(path):path表示文件路径
File file = new File(parent-path,child-path):将父子路径拼接成完成的文件路径
File file = new File(File parent,child-path):将File类型的父级路径和String类型的字节路径拼接成一个新路径,封装成File对象

注意Windows平台使用\作为路径分隔符,在Java字符串中需要用\\表示一个\。Linux平台使用/作为路径分隔符:

1
2
linux: File f = new File("/usr/bin/javac");
windows: File f = new File("C:\\Windows\\notepad.exe");

File类的动态路径

  • separator:获取当前系统的文件路径分隔符,windows系统为\ ,linux系统为”/“
  • pathSeparator:获取当前系统的环境变量的路径分割符,在UNIX系统上字符为:, 而Windows系统上它是;
1
2
3
4
// 操作路径:路径不能写死了(动态路径)
String path = "C:"+File.separator+"develop"+File.separator+"a"+File.separator+"a.txt";
windows:path为C:\develop\a\a.txt
linux:path为C:/develop/a/a.txt

File类的文件和目录操作方法

File对象既可以表示文件,也可以表示目录。特别要注意的是,构造一个File对象,即使传入的文件或目录不存在,代码也不会出错,因为构造一个File对象,并不会导致任何磁盘操作。只有当我们调用File对象的某些方法的时候,才真正进行磁盘操作。

  • boolean isFile():判断File对象是否是文件
  • boolean isDirectory():判断该File对象是否是已存在的目录
  • boolean canRead():是否可读
  • boolean canWrite():是否可写
  • boolean canExecute():是否可执行
  • long length():文件字节大小
  • boolean exists(): 判断文件或目录是否存在
  • createNewFile():当且仅当原文件不存在时才会创建一个新文件(需要处理IOException)
  • mkdir():创建一个文件夹或者说目录
  • mkdirs():既可以创建单级目录也可以创建多级目录,父目录不存在则会自动创建
  • getAbsolutePath():返回此File的绝对路径名字符串
  • getPath():获取文件完整路径
  • getName():返回由此File表示的文件或目录的名称
  • delete():删除该文件或目录(直接在硬盘上删除,不走回收站,无法找回,需要谨慎

绝对路径与相对路径

  • 绝对路径:是一个完整的路径
  • 相对路径:相对路径指的是相对于当前项目的根目录

Java的相对路径读取文件方式

  1. 在Java开发工具的project中使用相对路径

在project中,相对路径的根目录是project的根文件夹
创建文件的写法是:

1
2
File f = new File("src/com/lavasoft/res/a.txt");
File f = new File("doc/b.txt");

注意:路径不以“/”开头;脱离了IDE环境,这个写法就是错误的,也并非每个IDE都如此,但我见到的都是这样的。

  1. 通过CLASSPATH读取包内文件(推荐)
    读取包内文件,使用的路径一定是相对的classpath路径,比如a,位于包内,此时可以创建读取a的字节流:

InputStream in = ReadFile.class.getResourceAsStream(“/com/lavasoft/res/a.txt”);

注意:这里必须以“/”开头;

通过IDEA开发工具运行代码时,文件路径结果没问题,但如果换成控制台执行,那么使用了项目相对路径的读取方式会失败,原因是,此时已经脱离了项目的开发环境(经测试,确实会在命令行无法使用)—–这个问题常常困扰着一些初学者,代码在开发工具好好的,发布后执行就失败了!

注意:
1.路径是不区分大小写
2.路径中的文件名称分隔符windows使用反斜杠,反斜杠是转义字符,两个反斜杠代表一个普通的反斜杠

注:java不同于Python,HTML引入文件时可以使用. :当前文件.. :当前文件的上一级目录,Java的相对路径是以当前的项目的根目录为准。

遍历文件和目录

File类遍历(文件夹)目录功能

  • String[] list():返回一个String数组,表示该File目录中的所有子文件或目录(获取相对路径)
  • File[] listFiles():返回一个File数组,表示该File目录中的所有的子文件或目录(获取绝对路径)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class Main {
public static void main(String[] args) throws IOException {
// 初始化File类对象
File f1 = new File("src/bytedance/com");
// 获取相对路径的String数组
String[] f2 = f1.list();
if (f2 != null) {
for (String subF: f2) {
System.out.println(subF);
}
}
// 获取绝对路径但File类数组
File[] f3 = f1.listFiles();
if (f3 != null) {
for (File subF: f3) {
System.out.println(subF);
}
}
}
}
/*
程序结果:
list类:
CollectionDemo.java
demo.txt
BasicDataType.java

File类:
src/bytedance/com/CollectionDemo.java
src/bytedance/com/demo.txt
src/bytedance/com/BasicDataType.java
*/

Path

Java标准库还提供了一个Path对象,它位于java.nio.file包。Path对象和File对象类似,但操作更加简单。如果需要对目录进行复杂的拼接、遍历等操作,使用Path对象更方便:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Main {
public static void main(String[] args) throws IOException {
Path p1 = Paths.get(".", "project", "study"); // 构造一个Path对象
System.out.println(p1); // .\project\study
Path p2 = p1.toAbsolutePath(); // 转换为绝对路径
System.out.println(p2); // D:\data\demo2\.\project\study
Path p3 = p2.normalize(); // 转换为规范路径
System.out.println(p3); // D:\data\demo2\project\study
File f = p3.toFile(); // 转换为File对象
System.out.println(f); // D:\data\demo2\project\study
for (Path p : Paths.get("..").toAbsolutePath()) { // 可以直接遍历Path
System.out.println(" " + p);
}
}
}

3.缓冲流

缓冲流,也叫高效流,是对4个基本的FileXxx 流的增强,所以也是4个流,按照数据类型分类:

  • 字节缓冲流:BufferedInputStream,BufferedOutputStream
  • 字符缓冲流:BufferedReader,BufferedWriter

缓冲流的基本原理,是在创建流对象时,会创建一个内置的默认大小的缓冲区数组,通过缓冲区读写,减少系统IO次数,从而提高读写的效率。

4.字节缓冲流

创建方式:

  • BufferedInputStream(InputStream in):创建一个新的缓冲输入流
  • BufferedOutputStream(OutputStream out):创建一个新的缓冲输出流
1
2
3
4
// 创建字节缓冲输入流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("bis.txt"));
// 创建字节缓冲输出流
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("bos.txt"));

BufferedOutputStream

BufferedOutputStream表示字节缓冲输出流

  1. 继承自父类的共性成员方法:
  • public void close():关闭此输出流并释放与此流相关联的任何系统资源。
  • public void flush():刷新此输出流并强制任何缓冲的输出字节被写出。
  • public void write(byte[] b):将 b.length字节从指定的字节数组写入此输出流。
  • public void write(byte[] b, int off, int len):从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流。
  • public abstract void write(int b):将指定的字节输出流。
  1. 构造方法
1
2
3
4
5
6
BufferedOutputStream(OutputStream out)创建一个新的缓冲输出流,以将数据写入指定的底层输出流
BufferedOutputStream(OutputStream out, int size)创建一个新的缓冲输出流,以将具有指定缓冲区大小的数据写入指定的底层输出流

参数:
OutputStream out:字节输出流,我们可以传递FileOutputStream,缓冲流会给FileOutputStream增加一个缓冲区,提高FileOutputStream的写入效率
int size:指定缓冲流内部缓冲区的大小,不指定默认
  1. 使用步骤(重点)
  • 创建FileOutputStream对象,构造方法中绑定要输出的目的地
  • 创建BufferedOutputStream对象,构造方法中传递FileOutputStream对象对象,提高FileOutputStream对象效率
  • 使用BufferedOutputStream对象中的方法write,把数据写入到内部缓冲区中
  • 使用BufferedOutputStream对象中的方法flush,把内部缓冲区中的数据,刷新到文件中
  • 释放资源(会先调用flush方法刷新数据,第4部可以省略)

BufferedInputStream

字节缓冲输入流

  1. 继承自父类的成员方法:
  • int read()从输入流中读取数据的下一个字节
  • int read(byte[] b) 从输入流中读取一定数量的字节,并将其存储在缓冲区数组b中
  • void close() 关闭此输入流并释放与该流关联的所有系统资源
  1. 构造方法:
1
2
3
4
5
BufferedInputStream(InputStream in) 创建一个 BufferedInputStream 并保存其参数,即输入流 in,以便将来使用。
BufferedInputStream(InputStream in, int size) 创建具有指定缓冲区大小的 BufferedInputStream 并保存其参数,即输入流 in,以便将来使用。
参数:
InputStream in:字节输入流,我们可以传递FileInputStream,缓冲流会给FileInputStream增加一个缓冲区,提高FileInputStream的读取效率
int size:指定缓冲流内部缓冲区的大小,不指定默认
  1. 使用步骤(重点):
  • 创建FileInputStream对象,构造方法中绑定要读取的数据源
  • 创建BufferedInputStream对象,构造方法中传递FileInputStream对象,提高FileInputStream对象的读取效率
  • 使用BufferedInputStream对象中的方法read,读取文件
  • 释放资源

普通流与缓存流效率测试:

查询API,缓冲流读写方法与基本的流是一致的,我们通过复制大文件(400MB),测试它的效率

  1. 基本的io流,考虑到文件最好选择二进制的字节流来读写
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class BufferedDemo {
public static void main(String[] args) throws FileNotFoundException {
// 记录开始时间
long start = System.currentTimeMillis();
// 创建流对象
try (
FileInputStream fis = new FileInputStream("jdk9.exe");
FileOutputStream fos = new FileOutputStream("copy.exe")
){
// 读写数据
int b;
while ((b = fis.read()) != -1) {
fos.write(b);
}
} catch (IOException e) {
e.printStackTrace();
}
// 记录结束时间
long end = System.currentTimeMillis();
System.out.println("普通流复制时间:"+(end - start)+" 毫秒");
}
}

十几分钟过去了...
  1. 缓冲流,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class BufferedDemo {
public static void main(String[] args) throws FileNotFoundException {
// 记录开始时间
long start = System.currentTimeMillis();
// 创建流对象
try (
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("jdk9.exe"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("copy.exe"));
){
// 读写数据
int b;
while ((b = bis.read()) != -1) {
bos.write(b);
}
} catch (IOException e) {
e.printStackTrace();
}
// 记录结束时间
long end = System.currentTimeMillis();
System.out.println("缓冲流复制时间:"+(end - start)+" 毫秒");
}
}

缓冲流复制时间:8016 毫秒
  1. 如何更快呢?使用数组的方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class BufferedDemo {
public static void main(String[] args) throws FileNotFoundException {
// 记录开始时间
long start = System.currentTimeMillis();
// 创建流对象
try (
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("jdk9.exe"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("copy.exe"));
){
// 读写数据
int len;
byte[] bytes = new byte[8*1024];
while ((len = bis.read(bytes)) != -1) {
bos.write(bytes, 0 , len);
}
} catch (IOException e) {
e.printStackTrace();
}
// 记录结束时间
long end = System.currentTimeMillis();
System.out.println("缓冲流使用数组复制时间:"+(end - start)+" 毫秒");
}
}
缓冲流使用数组复制时间:666 毫秒

5.字符缓冲流

BufferedWriter

字符缓冲输出流

  1. 继承自父类的共性成员方法:

    • void write(int c) 写入单个字符
    • void write(char[] ch)写入字符数组
    • abstract void write(char[] ch, int off, int len)写入字符数组的某一部分,off数组的开始索引,len写的字符个数
    • void write(String str)写入字符串
    • void write(String str, int off, int len) 写入字符串的某一部分,off字符串的开始索引,len写的字符个数
    • void flush()刷新该流的缓冲
    • void close() 关闭此流,但要先刷新它
  2. 构造方法:

1
2
3
4
5
BufferedWriter(Writer out) 创建一个使用默认大小输出缓冲区的缓冲字符输出流。
BufferedWriter(Writer out, int size) 创建一个使用给定大小输出缓冲区的新缓冲字符输出流。
参数:
Writer out:字符输出流,我们可以传递FileWriter,缓冲流会给FileWriter增加一个缓冲区,提高FileWriter的写入效率
int size:指定缓冲区的大小,不写默认大小
  1. 使用步骤:
    • 创建字符缓冲输出流对象,构造方法中传递字符输出流
    • 调用字符缓冲输出流中的方法write,把数据写入到内存缓冲区中
    • 调用字符缓冲输出流中的方法flush,把内存缓冲区中的数据,刷新到文件中
    • 释放资源
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Demo03BufferedWriter {
public static void main(String[] args) throws IOException {
//System.out.println();
// 1.创建字符缓冲输出流对象,构造方法中传递字符输出流
BufferedWriter bw = new BufferedWriter(new FileWriter("10_IO\\c.txt"));
// 2.调用字符缓冲输出流中的方法write,把数据写入到内存缓冲区中
for (int i = 0; i <10 ; i++) {
bw.write("传智播客");
//bw.write("\r\n");
bw.newLine();
}
// 3.调用字符缓冲输出流中的方法flush,把内存缓冲区中的数据,刷新到文件中
bw.flush();
// 4.释放资源
bw.close();
}
}

BufferedReader

字符缓冲输入流

  1. 继承自父类的共性成员方法:

    • int read() 读取单个字符并返回
    • int read(char[] ch)一次读取多个字符,将字符读入数组
    • void close() 关闭该流并释放与之关联的所有资源
  2. 构造方法:

1
2
3
4
BufferedReader(Reader in)  创建一个使用默认大小输入缓冲区的缓冲字符输入流。
BufferedReader(Reader in, int size) 创建一个使用指定大小输入缓冲区的缓冲字符输入流。
参数:
Reader in:字符输入流,我们可以传递FileReader,缓冲流会给FileReader增加一个缓冲区,提高FileReader的读取效率
  1. 特有的成员方法:

String readLine() 读取一个文本行。读取一行数据,通过下列字符之一即可认为某行已终止:换行 (‘\n’)、回车 (‘\r’) 或回车后直接跟着换行(\r\n)。
返回包含该行内容的字符串,不包含任何行终止符,如果已到达流末尾,则返回null

  1. 使用步骤:
    • 创建字符缓冲输入流对象,构造方法中传递字符输入流
    • 使用字符缓冲输入流对象中的方法read/readLine读取文本
    • 释放资源
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class Demo04BufferedReader {
public static void main(String[] args) throws IOException {
// 1.创建字符缓冲输入流对象,构造方法中传递字符输入流
BufferedReader br = new BufferedReader(new FileReader("10_IO\\c.txt"));

// 2.使用字符缓冲输入流对象中的方法read/readLine读取文本
/*String line = br.readLine();
System.out.println(line);

line = br.readLine();
System.out.println(line);*/

/*
发现以上读取是一个重复的过程,所以可以使用循环优化
不知道文件中有多少行数据,所以使用while循环
while的结束条件,读取到null结束
*/
String line;
while((line = br.readLine())!=null){
System.out.println(line);
}

//3.释放资源
br.close();
}
}

参考文章


 评论

联系我 | Contact with me

Copyright © 2019-2020 谁知你知我,我知你知深。此恨经年深,比情度日久

博客内容遵循 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 协议