IO体系
4 个抽象基类:InputStream、OutputStream、Reader、Writer
笔记:字节流与字符流、输入流与输出流、节点流与处理流,是在IO体系基础和抽象基类上加入其他条件做的区分
流的概念
流是一种抽象的概念,是对数据传输的总称,数据在设备间的传输称为流(类比于水管中的水),更具体一点,是内存与数据之间传输数据的通道(包含水管和水)。
Java中的IO流:数据在内存与存储设备之间的传输可以看做是数据在流动,把内存作为参考对象,流向内存方向的是输入流(Input),流出内存方向的是输出流(Output)。
IO的作用
用来处理设备间文件传输问题,常见场景:文件上传、文件下载
使用场景
如果操作的是纯文本文件,优先使用字符流;
如果操作的二进制文件,优先使用字节流;
如果不确定,优先使用字节流。
原因:
- 字符流:字节流操作的数据单元是8位的字符;以字节为单位,可以读写所有数据
- 字节流:字节流操作的数据单元是16位的字符(2个字节);以字符为单位,只能读写文本数据
IO流的使用步骤
步骤:
- 创建节点对象
- 创建 IO 流对象
- 具体的 IO 操作(read()、write())
- 关闭 IO 流对象
相关知识
文件名称分隔符
Linux: /
windows : \
行分隔符
UNIX/LINUX/BSD : \n
Windows: \r
字节流与字符流
区分规则:按单位分
-
字符流(Character Stream):字符流主要用于处理文本数据,如字符串、文件等。它以字符为单位进行读写操作,每个字符占用多少字节由编码方式决定。字符流的编码方式可以是UTF-8、GBK等。
- ASCII编码:ASCII编码中,每个字符占用一个字节,共128个字符(包括英文字母、数字、标点符号和控制字符)。
- UTF-8编码:UTF-8编码是一种变长的编码方式,它可以根据不同的字符使用1到4个字节进行编码。例如,英文字母、数字和常用标点符号占用一个字节,而中文汉字等复杂字符占用3个或4个字节。
- GBK编码:GBK编码是一种双字节编码方式,每个字符占用两个字节。GBK编码主要用于中文字符的表示。
在Java中,常用的字符流类有InputStreamReader、OutputStreamWriter、FileReader、FileWriter等。
-
字节流(Byte Stream):字节流主要用于处理二进制数据,如图片、音频、视频等。它以字节为单位进行读写操作,每个字节占用一个字节。字节流的编码方式可以是任意的,例如ISO-8859-1、UTF-16等。在Java中,常用的字节流类有InputStream、OutputStream、FileInputStream、FileOutputStream等。
总结:字符流主要用于处理文本数据,而字节流主要用于处理二进制数据。
节点流与处理流
区分根据:按功能分
- 节点流:节点流是指直接连接到数据源或目标的流,例如文件、网络连接等。节点流可以读取或写入基本类型的数据或对象。Java中常用的节点流有FileInputStream、FileOutputStream、Socket等。
- 处理流:处理流是对一个已存在的流进行连接和封装,以便对数据进行处理。处理流提供了一些额外的功能,例如缓冲、编码、解码等。Java中常用的处理流有BufferedReader、BufferedWriter、DataInputStream、DataOutputStream等。
在Java中,可以使用处理流来包装节点流,以实现更高级的功能。例如,可以使用BufferedReader来包装FileReader,以便对文件进行高效的读取操作。
总结:处理流在节点流的基础上进行包装提供了更多功能
输入流与输出流
区分规则:以内存为参考,按数据流的方向分
- 输入流(InputStream):输入流用于从数据源中读取数据,例如文件、网络连接等。它提供了一系列的读取方法,如read()、read(byte[] b)等。Java中常用的输入流有FileInputStream、ByteArrayInputStream、BufferedInputStream等。
- 输出流(OutputStream):输出流用于将数据写入到目标位置,例如文件、网络连接等。它也提供了一系列的写入方法,如write(int b)、write(byte[] b)等。Java中常用的输出流有FileOutputStream、ByteArrayOutputStream、BufferedOutputStream等。
输入流:InputStream(字节输入流)和Reader(字符输入流)
-
创建字节/字符数组作为缓冲区
,read in
int read()
:从输入流中读取一个字节/字符,返回所读取的字节数据/字符数据,如果已读到流的末尾,则返回 -1int read(byte[]/char[] buf)
:从输入流中最多读取 buf.Iength 个字节/字符的数据,并将其存储在字节数组/字符数组 buf 中,返回实际读取的字节数/字符总数,如果已读到流的末尾,则返回 -1int read(byte[]/char[] buf, int off, int len)
:从输入流中最多读取 len 个字节/字符的数据,并将其存储在字节数组/字符数组 buf 中,从数组的 off 位置开始放入,返回实际读取的字节数/字符总数,如果已读到流的末尾,则返回 -1
-
标准字节输入流,读取键盘的输入:System.in
输出流:OutputStream(字节输出流)和Writer(字符输出流)
- write out
void write(int c)
:将指定的字节/字符 c 输出到输出流中void write(byte[]/char[] buf)
:将字节数组/字符数组 buf 中的数据输出到指定输出流中void write(byte[]/char[] buf,int off,int len)
:将字节数组/字符数组 buf 中从 off 位置开始,长度为 len 的字节/字符输出到输出流中void flush()
:刷新此输出流并强制写出所有缓冲的输出字节/字符(仅对缓冲输出流和字符输出流起作用)
- Writer 类(字符输出流)中还包含如下两个方法
void write(String str)
:将 str 字符串里包含的字符输出到指定输出流中void write(String str, int off, int len)
:将 str 字符串里从 off 位置开始,长度为 len 的字符输出到指定输出流中
文件
File类
在java.io包下,只能操作文件和目录,不能访问文件本身
全局静态常量
-
路径分隔符:char pathSeparatorChar、String pathSeparator
-
文件名称分隔符:char separatorChar、String separator
-
构造器
- File(String pathname):将给定的路径名字符串pathname转换为抽象路径名来创建File对象
- File(String parent, String child):File(File parent, String child):根据 parent 抽象路径名和 child 路径名字符串创建 File 对象
访问文件名
File file = new File("C:/abc/123.txt");
String getName()
:返回此 File 对象所表示的文件名或目录名,123.txtString getPath()
:返回此 File 对象所对应的路径名,C:/abc/123.txtFile getAbsoluteFile()
:返回此 File 对象的绝对路径String getAbsolutePath()
:返回此 File 对象所对应的绝对路径名,C:/abc/123.txtFile getParentFile()
:返回此 File 对象所对应的父路径,如果没有,返回 null,C:/abcString getParent()
:返回此 File 对象所对应的父路径名
检测文件
boolean exists()
:判断 File 对象所对应的文件或目录是否真实存在boolean isFile()
:判断 Hie 对象所对应的是否是文件boolean isDirectory()
:判断 File 对象所对应的是否是目录boolean isAbsolute()
:判断 File 对象是否是绝对路径boolean canRead()
:判断 File 对象所对应的文件和目录是否可读boolean canWrite()
:判断 File 对象所对应的文件和目录是否可写boolean canExecute()
:判断应用程序是否可以执行此 File 对象所对应的文件或目录
获取文件常规信息
long length()
:获取文件内容的长度(单位为字节)(如果此路径名表示一个目录,则返回值是不确定的)long lastModified()
:获取文件的最后修改时间
操作文件
boolean createNewFile()
:当此 File 对象所对应的文件不存在但对应的父路径存在时,新建一个该 File 对象所指定的文件static File createTempFile(String prefix, String suffix)
:在默认的临时文件目录中创建一个临时的文件,使用给定前缀、 系统生成的随机数和给定后缀作为文件名(prefix 参数至少是 3 字节长,suffix 参数为 null 时,将使用默认的后缀“.tmp”)static File createTempFile(String prefix, String suffix, File directory)
:在 directory 所指定的目录中创建一个临时的文件,使用给定前缀、 系统生成的随机数和给定后缀作为文件名boolean mkdir()
:当此 File 对象所对应的目录不存在但对应的父目录存在时,新建一个 File 对象所对应的目录boolean mkdirs()
:新建一个 File 对象所对应的目录(包括所有必需但不存在的父目录)boolean delete()
:删除 File 对象所对应的文件或目录void deleteOnExit()
:注册一个删除钩子,指定当 Java 虚拟机退出时,删除 File 对象所对应的文件或目录boolean renameTo(File dest)
:用指定的路径名重新命名对 File 对象所对应的文件或路径File[] listFiles()
:列出 File 对象的所有子文件和子目录的绝对路径,返回 File 数组String[] list()
:列出 File 对象的所有子文件名和子目录的名称,返回 String 数组static File[] listRoots()
:列出系统所有的根路径
文件过滤器
-
String[] list(FilenameFilter filter)
-
File[] listFiles(FilenameFilter filter)
:列出 File 对象的所有符合条件的子文件和目录,返回 File 数组 -
FilenameFilter 接口中的抽象方法:
boolean accept(File dir, String name)
-
底层依次对指定 File 的所有子目录或者文件进行迭代,如果该方法返回 true,则 list() 方法会列出该子目录或者文件
-
Java文件过滤器是一种用于过滤文件的方法,通常与
File
类的listFiles()
方法一起使用。通过实现FilenameFilter
接口并重写accept()
方法,可以自定义文件过滤规则。import java.io.File; import java.io.FilenameFilter; public class FileFilterExample { public static void main(String[] args) { // 创建一个目录对象 File directory = new File("D:/example"); // 创建一个文件过滤器对象,过滤出扩展名为.txt的文件 FilenameFilter filter = new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.endsWith(".txt"); } }; // 使用过滤器获取目录下符合条件的文件列表 File[] files = directory.listFiles(filter); // 遍历文件列表并打印文件名 for (File file : files) { System.out.println(file.getName()); } } }
在这个示例中,首先创建了一个
File
对象,表示要过滤的目录。然后,创建了一个实现了FilenameFilter
接口的匿名类,并重写了accept()
方法。在accept()
方法中,检查文件名是否以".txt"结尾,如果是,则返回true
,否则返回false
。最后,使用listFiles()
方法和自定义的过滤器来获取目录下符合条件的文件列表,并遍历打印文件名。
其他补充
节点流
节点流——文件流
访问文件:
FileInputStream和FileReader
- 构造器(当指定的File对象对应的文件不存在时,会在对应的目录下新建该文件)
- FileInputStream(File file)
- FileInputStream(String name)
- FileInputReader(File file)
- FileInputReader(String name)
FileOutputStream 和 FileWriter
- 构造器(当指定File对象对应的文件不存在时,会在对应的目录下新建文件)
- FileOutputStream(File file, boolean append)
- FileOutputStream(String name, boolean append)
- FileWriter(File file, boolean append)
- FileWriter(String fileName, boolean append)
数组流(内存流)
- 访问数组:字节流以字节数组为节点,字符流以字符数组为节点ByteArrayInputStream、ByteArrayOutputStreamCharArrayReader、CharArrayWriter
- 访问字符串StringReader、StringWriter
管道流(线程通讯流)
- 将 A 线程的管道输出流连和 B 线程的管道输入流连接(connect)来实现线程之间通信功能
- PipedlnputStream、 PipedOutputStreamPipedReader、 PipedWriter
Scanner 类进行输入操作
- java.util 包中,可以使用正则表达式来解析基本类型和字符串的简单文本扫描器
- 构造器Scanner(File source, String charsetName)Scanner(InputStream source, String charsetName)Scanner(String source):创建一个从指定字符串扫描的扫描器
- 实例方法:(xxx表示数据类型,如byte、int 、boolean等)
boolean hasNextXxx()
:判断此扫描器输入信息中的下一个标记是否可以解释为一个 xxx 值Xxx nextXxx()
:将输入信息的下一个标记扫描为一个 xxx 值boolean hasNextLine()
:如果在此扫描器的输入中存在另一行,则返回 trueString nextLine()
:此扫描器执行当前行,并返回跳过的输入信息Scanner useDelimiter(Pattern pattern)
:将此扫描器的分隔模式设置为指定模式,返回 this
Properties 类加载配置文件
void load(InputStream inStream)``void store(OutputStream out, String comments)
访问其它进程
- 以子进程为节点:用于本进程读写其他进程中的数据
- Process 类中用于让程序和其子进程进行通信的实例方法:
InputStream getInputStream()
:获取子进程的输入流OutputStream getOutputStream()
:获取子进程的输出流InputStream getErrorStream()
:获取子进程的错误流
RandomAccessFile
- 可以自由访问文件的任意地方
- 构造器RandomAccessFile(File file, String mode)RandomAccessFile(String name, String mode)模式参数:“r”、“rw”、“rws” 或 “rwd”
- 实例方法
void writeXxx(xxx v)
:按 x 个字节将 v 入该文件void readXxx(xxx v)
:从此文件读取一个 xxx(注意:writeXxx 和 readXxx 必须要对应起来,writeByte 写出的数据,此时只能使用 readByte 读取回来)void writeUTF(String str)
:使用 modified UTF-8 编码将一个字符串写入该文件(比 UTF-8 多 2 个字节)String readUTF()
:从此文件读取一个字符串long getFilePointer()
:返回此文件中的当前偏移量void seek(long pos)
:设置到此文件开头测量到的文件指针偏移量,在该位置发生下一个读取或写入操作int skipBytes(int n)
:尝试跳过输入的 n 个字节以丢弃跳过的字节long length()
:返回此文件的长度void setLength(long newLength)
:设置此文件的长度
处理流
缓冲流
- 缓冲区大小为 8192 个字节或字符
BufferedInputStream 和 BufferedReader
- 构造器BufferedInputStream(InputStream in)、BufferedReader(Reader in)
- BufferedReader 类(字符缓冲输入流)中特有的方法
String readLine()
:读取一个文本行(换行 ‘\n’ 或回车 ‘\r’),如果已到达流末尾,则返回 null
BufferedOutputStream 和 BufferedWriter
- 构造器BufferedOutputStream(OutputStream out)、BufferedWriter(Writer out)
- BufferedWriter 类(字符缓冲输出流)中特有的方法
void newLine()
:写入一个行分隔符(由系统属性 line.separator 定义)
转换流
- 将字节流转换成字符流:InputStreamReader、OutputStreamWriter
- 构造器InputStreamReader(InputStream in, String charsetName)OutputStreamWriter(OutputStream out, String charsetName)
- 转换流特有的方法
String getEncoding()
:返回此流使用的字符编码的名称
顺序流(合并流)
- 把多个字节输入流合并成一个字节输入流对象
- SequenceInputStream构造器:SequenceInputStream(InputStream s1, InputStream s2)
对象流
- ObjectInputStream构造器:ObjectInputStream(InputStream in)实例方法:
Object readObject()
:从 ObjectInputStream 读取对象(可能需要强转) - ObjectOutputStream构造器:ObjectOutputStream(OutputStream out)实例方法:
void writeObject(Object obj)
:将指定的对象写入 ObjectOutputStream - 对象序列化机制允许将内存中实现序列化的 Java 对象转换成字节序列(二进制流),使得对象可以脱离程序的运行而独立存在(保存到磁盘上或者通过网络传输)
- 对象的序列化(Serialize) :将一个 Java 对象写入二进制流中
- 对象的反序列化(Deserialize) :从二进制流中恢复该 Java 对象(反序列化时必须存在对象的字节码对象)
- 支持序列化机制的对象的类必须实现 Serializable 接口(标识接口,无抽象方法)
- 序列化时,会跳过 transient 或 static 关键字修饰的字段
- 为了在反序列化时确保序列化版本的兼容性,最好在每个要序列化的类中定义一个 private static final long serialVersionUID 字段,用于标识该 Java 类的序列化版本号(具体数值可以自己定义),如果不显式定义 serialVersionUID 类变量的值,该类变量的值将由 JVM 根据类的相关信息计算
打印流
- 都是输出流
- PrintStream 构造器PrintStream(File file, String csn)PrintStream(String fileName, String csn)PrintStream(OutputStream out)
- PrintWriter 构造器PrintWriter(File file, String csn)PrintWriter(String fileName, String csn)PrintWriter(Writer out, boolean autoFlush)如果 autoFlush 为 true,则在调用 println、printf 或 format 的其中一个方法时将刷新输出缓冲区,否则只会在调用 flush、close 方法时才会写出所有缓冲的输出字符
- 实例方法
void print(Object o)
:打印任意类型数据值或对象的字符串值void println(Object o)
:打印任意类型数据或对象的字符串值,然后换行PrintStream printf(String format, Object… args)
:使用指定格式字符串和参数将格式化的字符串写入此输出流 - 标准字节打印流,输出到显示器:System.out标准错误字节打印流:System.err
- System 类中重定向标准输入/输出的类方法
void setIn(InputStream in)
:将 SyStem.in 的输入重定向到其它输入流void setOut(PrintStream out)
:将 SyStem.out 的输出重定向到其它打印流void setErr(PrintStream err)
:将 SyStem.err 的输出重定向到其它打印流
数据流
- DataInputStream:从二进制流中读取字节,并根据所有基本类型数据进行重构
- DataOutputStream:将数据从任意基本类型转换为一系列字节,并将这些字节写入二进制流
- 注意:writeXxx 和 readXxx 必须要对应起来,writeByte 写出的数据,此时只能使用 readByte 读取回来
java4的NIO
NIO与传统IO的比较
- NIO 的三大重要组件:Channel(通道)、Buffer(缓冲区)和 Selector(选择器)
- 通道(Channel)与传统 IO 中的流(Stream)类似,表示打开到 IO 设备(例如:文件、套接字)的连接,但通道是双向的,而 Stream 是单向的,输入流只负责输入,输出流只负责输出
- 在 NIO 中,有 FileChannel、SocketChannel、ServerSocketChannel 和 DatagramChannel 4 种 Channel 对象,第一种是针对文件的,后三种是针对网络编程的
- 唯一能与通道交互的组件是缓冲区(Buffer),通过通道,可以从缓冲区中读写数据
- Buffer 对象用来缓存读写的内容,在 NIO 中,通过 Channel 对象来进行读写操作,通过 Buffer 对象缓存读写的内容
- 选择器(Selector)可以同时监控多个 SelectableChannel 的 IO 状况,是非阻塞 IO 的核心
- FileChannel 不能与 Selector —起使用,因为 FileChannel 不能切换到非阻塞模式
NIO(New I/O)是Java中的一种新输入输出库,它提供了一种更高效的文件读写方式。与传统的IO相比,NIO有以下区别:
-
非阻塞性:传统的IO是阻塞性的,即在执行I/O操作时,程序会等待直到操作完成。而NIO是非阻塞性的,它可以在等待I/O操作完成的同时执行其他任务。这使得NIO在处理大量并发请求时具有更高的性能。
-
缓冲区:传统的IO使用字节流进行读写,每次读写都会涉及到数据的复制。而NIO使用缓冲区(Buffer)进行读写,可以减少数据复制的次数,提高读写效率。
-
通道(Channel):传统的IO使用字节流进行读写,需要通过Socket、FileInputStream等对象进行封装。而NIO使用通道(Channel)进行读写,可以更方便地管理多个连接和数据传输。
-
选择器(Selector):传统的IO使用多线程或多进程来处理多个连接,这会导致系统资源的浪费。而NIO使用选择器(Selector)来管理多个通道,可以有效地减少系统资源的消耗。
下面是一个简单的例子,说明Java中NIO与传统IO的区别:
// 传统IO示例
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class TraditionalIOExample {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8080);
while (true) {
Socket socket = serverSocket.accept();
new Thread(() -> {
try {
InputStream inputStream = socket.getInputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = inputStream.read(buffer)) != -1) {
System.out.println("接收到的数据:" + new String(buffer, 0, len));
}
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
}
}
// NIO示例
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
public class NIOExample {
public static void main(String[] args) throws IOException {
Selector selector = Selector.open();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.socket().bind(new InetSocketAddress(8080));
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
selector.select();
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectedKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
if (key.isAcceptable()) {
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {
SocketChannel socketChannel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int bytesRead = socketChannel.read(buffer);
if (bytesRead > 0) {
buffer.flip();
System.out.println("接收到的数据:" + new String(buffer.array(), 0, bytesRead));
}
}
iterator.remove();
}
}
}
}
在这个例子中,我们分别使用了传统IO和NIO来实现一个简单的服务器。可以看到,NIO在处理多个连接时具有更高的性能,因为它使用了非阻塞性和缓冲区。
Java 7 的 NIO.2
Path 接口、Files 和 Paths 工具类
- Paths 中的类方法
Path get(String first, String… more)
- Files 中的用于文件拷贝的类方法
Path copy(Path source, Path target, CopyOption… options)``long copy(InputStream in, Path target, CopyOption… options)``long copy(Path source, OutputStream out)
- Files 中其它的类方法:isHidden、readAllLines、size、write、list、walk、find
使用 FileVisitor 遍历文件和目录
- Files 类遍历文件的类方法
Path walkFileTree(Path start, FileVisitor<? super Path> visitor)
:遍历 start 路径下的所有文件和子目录Path walkFileTree(Path start, Set<FileVisitOption> options, int maxDepth, FileVisitor<? super Path> visitor)
:与上一个方法的功能类似,该方法最多遍历 maxDepth 深度的文件 - 通过继承 SimpleFileVisitor(FileVisitor的实现类)来实现自己的 “ 文件访问器”