输入与输出
在java Api
中,可以从其中读入一个字节序列的对象称为输入流,而可以向其中写入一个字节序列的对象称作输出流。这些字节序列的来源地和目的地可以是文件,而且通常都是文件,但也可以是网络连接,甚至是内存块。抽象类InputStream
和OutputStream
构成了输入/输出(I/O
)类层次结构的基础。
因为面向字节的流不便于处理以Unicode
形式存储的信息,所以抽象类Reader
和Writer
中继承出来了一个专门用于处理Unicode
字符的单独的类层次结构。这些类拥有的读入和写出操作都是基于两字节的Char
值的,而不是基于byte
值的。
输入与输出流
读写字节
InputStream
类有一个抽象方法:abstract int read()
,这个方法将读入一个字节,并返回读入的字节,或者在遇到输入源结尾时返回-1。在设计具体的输入流类时,必须覆盖这个方法以提供适用的功能。
OutputStream
类有一个抽象方法:abstract void wirte(int b)
,它可以向某个输出为止写一个字节。
transferTo
方法可以将所有字节从一个输入流传递到一个输出流:inputStream.transferTo(outputStream)
read
和write
方法在执行时都将阻塞,直至字节确实被读入或写出。这就意味着如果流不能被立即访问(通常是因为网络),那么当前的线程将被阻塞。这使得这两个方法等待指定的流变为可用的这段时间里,其他的线程就有机会去执行有用的工作。
available
方法使我们可以去检查当前可读入的字节数量,这意味着像下面这样的代码片段不可能被阻塞:
public static void main(String[] args) {
InputStream file = null;
try {
file = new FileInputStream("src\\eight\\a.txt");
int read = 0;
while ((read = file.available()) > 0) {
byte[] temp = new byte[read];
file.read(temp);
for (byte b : temp) {
System.out.print((char) b);
}
}
} catch (IOException e) {
e.printStackTrace();
}
try {
file.close();
} catch (IOException e) {
e.printStackTrace();
}
}
**当你完成对输入/输出流的读写时,应该通过调用close
方法来关闭它。**关闭一个输出流的同时还会冲刷用于该输出流的缓冲区:所有被临时置于缓冲区中,以便用更大的包的形式传递的字节在关闭输出流时都将被送出。
对于Unicode
文本,可以使用抽象类Reader
和Writer
的子类。Reader
和Writer
的基本方法与InputStream
和OutputStream
中的方法类似。
abstract int read()
abstract void wirte(int b)
read
方法将返回一个Unicode
码元(一个在0~65535之间的整数),或者在碰到文件结尾时返回-1。
write
方法在被调用时,需要传递一个Unicode
码元。
组合输入/输出流过滤器
FileInputStream
和FileOutputStream
可以提供附着在一个磁盘文件上的输入流和输出流,而你只需要向其构造器提供文件名或文件的完整路径名。
InputStream file = new FileInputStream("src\\eight\\a.txt");
而FileInputStream
和FileOutputStream
的子类们可以为它们提供额外的功能。
InputStream file = new FileInputStream("src\\eight\\a.txt");
// 将file 包装成DataInputStream流,提供了额外的方法、
DataInputStream data = new DataInputStream(file);
Long v = data.readLong();
可以构造更加复杂的输入流:
InputStream file = new FileInputStream("src\\eight\\a.txt");
DataInputStream data = new DataInputStream(new BufferedInputStream(file));
有时当多个输入流链接在一起时,你需要跟踪各个中介输入流,如:当读入输入时,你经常需要预览一个字节,以了解它是否时你想要的值。可以通过PushbackInputStream
:
InputStream file = new FileInputStream("src\\eight\\a.txt");
// 将file 包装成DataInputStream流,提供了额外的方法
PushbackInputStream pbin = new PushbackInputStream(new BufferedInputStream(file));
int read = pbin.read();
if(read != '1') pbin.unread(read);
else System.out.println((char) read);
读入和推回时可应用于可回推输入流的仅有的方法。
以文本格式存储对象
public static void main(String[] args) throws IOException {
Employee e = new Employee("Camellia", LocalDate.now(), 20000.0d);
try (
PrintWriter printWriter = new PrintWriter("src\\eight\\a.txt")
) {
writeEmployee(printWriter, e);
}
}
public static void writeEmployee(PrintWriter out, Employee e) {
out.println(e.getName() + " | " + e.getSalary() + " | " + e.getHireDay());
}
读二进制数据
DataInput
和DataOutput
接口用于以二进制格式写数组、字符、boolean值和字符串的方法:
writeChars、writeFloat、writeByte、writeDouble、writeInt、writeChar、writeShort、writeBoolean、writeLong、writeUTF
使用方法
DataInputStream dataInputStream = new DataInputStream(new FileInputStream("src\\eight\\a.txt"));
DataOutputStream dataOutputStream = new DataOutputStream(new FileOutputStream("src\\eight\\a.txt"));
对象输入/输出流与序列化
对象序列化:它可以将任何对象写出到输出流中,并在之后将其读回。
保存和加载序列化对象
保存对象,前提,对象实现了Serializable
接口
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("src\\eight\\a.txt"));
Employee camellia = new Employee("Camellia", LocalDate.now(), 20000.0d);
Employee zcc = new Manager("zcc", LocalDate.now(), 15000.0d, 5000.0d);
out.writeObject(camellia);
out.writeObject(zcc);
out.close();
读取对象,前提,同上
ObjectInputStream in = new ObjectInputStream(new FileInputStream("src\\eight\\a.txt"));
Employee camelliaObj = (Employee) in.readObject();
Employee zccObj = (Employee) in.readObject();
System.out.println(camelliaObj);
System.out.println(zccObj);
in.close();
每个对象都是用一个序列号保存的,下面是其算法:
- 对你遇到的每一个对象引用都关联一个序列号
- 对于每个对象,当第一次遇到时,保存其对象数据到输出流中。
- 如果某个对象之前已经被保存过了,那么只写出与之前保存过的序列号为x的对象相同。
读取对象时,整个过程是反过来的:
- 对于对象输入流中的对象,在第一次遇到其序列号时,构建它,并使用流中数据来初始化它,然后记录这个顺序号和新对象之间的关联。
- 当遇到与之前保存过的序列号为x的对象相同这一标记时,获取与这个序列号相关联的对象引用。
修改默认的序列化机制
某些数据时不可以序列化的,java
中使用transient
标记,来标识需要跳过的对象。
在字段上添加之后,保存会跳过这个字段,值为零值。
序列化单例
为了解决对象序列化之后,会产生一个新的对象的问题,需要定义一中称为readResolve
的特殊序列化方法。如下所示:
public class Test implements Serializable {
private int value;
private static final Test H = new Test(1);
private static final Test V = new Test(2);
public Test(int value) {
this.value = value;
}
protected Object readResolve() {
return value == 1 ? Test.H : Test.V;
}
}
操作文件
Path
和Files
类封装了在用户机器上处理文件系统所需的所有功能。如,Files
类可以用来移除或重命名文件,或者查询文件最后被修改的时间。它们更关注于文件在磁盘上的存储,输入/输出流更关心内容。
Path
Path
:表示的时一个目录名序列,其后还可以跟着一个文件名。路径中的第一个部件可以是根部件,如/
或C:\
,根据文件系统决定。
从根部件开始的路径就是绝对路径;否则就是相对路径。
静态的Path.get
方法接收一个或多个字符串,并将它们用默认文件系统的路径分隔符连接起来。
//Path path = Paths.get("src", "eight");
Path path = Paths.get("src\\eight");
Path fileName = path.getParent();
System.out.println(fileName);
组合或解析路径是司空见惯的操作,调用p.resolve(q)
将按照下列规则赶回一个路径:
- 如果q是绝对路径,则结果就是q;
- 否则,根据文件系统的规则,将p后面跟着q作为结果
Path work = Paths.get("work");
Path resolve = work.resolve(work);
System.out.println(resolve.getFileName());
快捷方式:
Path work = Paths.get("work");
Path resolve = work.resolve("now");
resolveSibling
通过解析指定路径的父路径产生其兄弟路径。
Path work = Paths.get("src\\eight");
// src/nine
Path resolve = work.resolveSibling("nine");
resolve
的对立面是relativize
,调用work.relativize(resolve)
,将产生一个路径relativize
,而relativize
就是resolve
Path resolve = work.resolveSibling("nine");
Path relativize = work.relativize(resolve);
System.out.println(relativize);
toAbsolutePath
方法将产生给定路径的绝对路径。
Path path = Paths.get("src\\\\eight").toAbsolutePath();
System.out.println(path.getParent());
读写文件-Files
使用Files类读取文件内容
Path path = Paths.get("src\\eight\\a.txt");
List<String> lines = Files.readAllLines(path);
lines.stream().forEach(System.out::println);
写出一个字符串到文件中,会替换掉原来的内容
Path path = Paths.get("src\\eight\\a.txt");
Path b = Paths.get("src\\eight\\b.txt");
byte[] lines = Files.readAllBytes(path);
Files.write(b, lines);
追加字符串
Path path = Paths.get("src\\eight\\a.txt");
Path b = Paths.get("src\\eight\\b.txt");
byte[] lines = Files.readAllBytes(path);
Files.write(b, lines, StandardOpenOption.APPEND);
创建文件和目录
创建新目录
Path path = Paths.get("src\\eight\\nihao");
Files.createDirectory(path);
创建路径中间的目录
Path path = Paths.get("src\\eight\\nihao\\test1\\test2");
Files.createDirectories(path);
复制、移动和删除文件
从一个位置复制到另一个位置
Path path2 = Paths.get("src\\eight\\c.txt");
Path path = Paths.get("src\\eight\\nihao\\test1\\test2\\c.txt");
Files.copy(path,path2);
移动文件(即复制并删除)
Path path2 = Paths.get("src\\eight\\c.txt");
Path path = Paths.get("src\\eight\\nihao\\test1\\test2\\c.txt");
Files.move(path,path2);
删除文件
Path path = Paths.get("src\\eight\\c.txt");
Files.delete(path);
获取文件信息
size
方法将返回文件的字节数
Path path = Paths.get("src\\eight\\c.txt");
long size = Files.size(path);
获取文件的基本信息(创建、最后一次以及最后一次修改时间、文件类型、文件尺寸、文件主键)
Path path = Paths.get("src\\eight\\c.txt");
BasicFileAttributes basicFileAttributes = Files.readAttributes(path, BasicFileAttributes.class);
System.out.println(basicFileAttributes.creationTime());
System.out.println(basicFileAttributes.size());
System.out.println(basicFileAttributes.lastAccessTime());
System.out.println(basicFileAttributes.lastModifiedTime());