Java IO
-
基本概念:Java 输入 / 输出系统
-
输入和输出:从外边读数据是输入(InputStream/Reader), 程序向外边写数据就用输出
-
思维导图
-
字节流和字符流:
- 字节流主要用来处理字节或二进制对象。
- 字符流(一个字符占两个字节)设计的,主要用来处理字符或字符串。
- 字符流处理的单元为2个字节的Unicode字符,分别操作字符、字符数组或字符串,而字节流处理单元为1个字节,操作字节和字节数组。所以字符流是由Java虚拟机将字节转化为2个字节的Unicode字符为单位的字符而成的,所以它对多国语言支持性比较好!如果是音频文件、图片、歌曲,就用字节流好点,如果是关系到中文(文本)的,用字符流好
-
注意:在关闭流的时候,应该先关闭外层的包装流,即“后打开的先关闭”
FileOutputStream fos = new FileOutputStream("f:\\qs"); OutputStreamWriter osw = newOutputStreamWriter(fos); BufferedWriter bw = newBufferedWriter(osw); bw.write("hello qs!"); bw.close(); osw.close(); fos.close();
一、流式部分
上面思维导图显示了各种流的大概关系,下面具体做一些说明
1.1 文件流
- FileInputStream/FileOutputStrean 文件字节流,通过字节方式读取文件,适用于所有类型文件,但是不能很好的处理Unicode字符
- FileReader/FileWriter 文件字符流,用于文本文件Unicode编码
1.2 缓冲流
Java缓冲流本身并不具有IO流的读取与写入功能,只是在别的流(节点流或其他处理流)上加上缓冲功能提高效率,就像是把别的流包装起来一样,因此缓冲流是一种处理流(包装流) ,缓存区的大小默认是8192字节,也可以使用其它的构造方法自己指定大小
- BufferedInputStream/BufferedOutputStream 这两个流是缓冲字节流,通过内部缓存数组来提高操作流的效率。
- BufferedReader/BufferedWriter 缓冲字符流,增加了缓存机制,提高了读写文本文件的效率,同时,提供了readLine()方法方便按行读取。注意写入一行后使用 newLine()方法换行。
1.3 数据流
用来装饰其它输入流,数据输入流允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型。
- DataInputStream/DataOutputStream
DataInputStream dis = new DataInputStream(InputStream in);//构造函数
//write方法
writeBytes(String str);
writeChars(String str);
writeUTF(String str);//写入数据的同时也写入长度
//特殊的读方法
ReadUTF();//知道长度才能读
readBoolean();
readByte();
readChar();
readDouble();
readFloat();
readFully(byte[] dst);
readFully(byte[] dst, int offset, int byteCount);
readInt();
readLine();
readLong();
readShort();
readUTF(DataInput in);
readUTF();
readUnsignedByte();
readUnsignedShort();
skipBytes(int count);
//以上根据名字可以知道函数作用
由于java中的字符是Unicode编码,是双字节的,writeByte() 只是将字符中的每一个字符的低字节内容写入目标设备中。而writeChars() 将字符串中的,每一个字符的两个字节的内容都写到目标设备中;writeUTF() 将字符串按UTF编码后的字节长度写入目标设备中,然后才是每一个字节的UTF编码。
DataInputStream只提供了一个ReadUTF() 方法返回字符串。这是因为要在一个连续的字节流读取一个字符串,如果没有特殊标记作为一个字符串的结尾,并且不知道这个字符串的长度,就无法知道读取到什么位置才是这个字符串的结束。DataOutputStream类中只有writeUTF() 方法向目
标设备中写入字符串的长度,所以也能准确的读回写入字符串。
1.4 回退流
在JAVA IO中所有的数据都是采用顺序的读取方式,即对于一个输入流来讲都是采用从头到尾的顺序读取的,如果在输入流中某个不需要的内容被读取进来,则只能通过程序将这些不需要的内容处理掉,为了解决这样的处理问题,在JAVA中提供了一种回退输入流(PushbackInputStream、PushbackReader),可以把读取进来的某些数据重新回退到输入流的缓冲区之中。
-
PushbackInputStream/PushbackOutputStream 字节回退流
public PushbackInputStream(InputStream in);//构造方法 将输入流放入到回退流之中。 public int read() throws IOException;//读取数据。 public int read(byte[] b,int off,int len) throws IOException;//读取指定范围的数据。 public void unread(int b) throws IOException;//回退一个数据到缓冲区前面。 public void unread(byte[] b) throws IOException//回退一组数据到缓冲区前面。 public void unread(byte[] b,int off,int len) throws IOException//回退指定范围的一组数据到缓冲区前面。
回退流与输入流的操作相对应
-
PushbackReader/PushbackWriter 字符回退流
1.5 对象流
- ObjectInputStream是将对象的原始数据序列化
- ObjectOutputStream将序列化的数据反序列化
只有对象支持java.io.Serializable或java.io.Externalizable接口的才能够被从流中读取。
方法readObject() 是被用来从流中读取对象,java的安全性应该被用来获取想要的类型,在Java中,string和arrays在序列化时是被当做对象,读取时把它们转换成期望的类型。
readObject() 方法负责阅读和使用相应的writeObject() 方法流写入的数据恢复其特定的类的对象的状态。该方法不需要担心其属于超类或者子类的状态,状态的恢复是从ObjectInputStream 中读取的字段和对象,读取原始数据类型可以被DataInput接口支持。
1.6 管道流
PipedOutputStream/**PipedInputStream **是字节管道流。
作用是让多线程可以通过管道进行线程间的通讯。在使用管道通信时,必须将PipedOutputStream和PipedInputStream配套使用。
使用流程:在线程A中向PipedOutputStream中写入数据,这些数据会自动的发送到与PipedOutputStream 对应的PipedInputStream 中,进而存储在 PipedInputStream 的缓冲中;此时,线程B通过读取PipedInputStream中的数据。就可以实现,线程A和线程B的通信。
注意:不要在一个线程中同时使用PipeInpuStream和PipeOutputStream,这会造成死锁
PipedOutputStream in = new PipedOutputStream();
PipedOutputStream out = new PipedOutputStream();
in.connect(out);//将“管道输入流”和“管道输出流”关联起来,等价于out.connect(in),只需要调用一次。
1.7 序列流
- SequenceInputStream/SequenceOutputStream 字节序列流,首先读取第一个 InputStream 中的所有字节,然后读取第二个 InputStream 中的所有字节。 这就是它被称为 SequenceInputStream 的原因,因为 InputStream 实例是按顺序读取的。
两种方法初始化
SequenceInputStream sis = new SequenceInputStream(InputStream s1, InputStream s2);//通过两个参数初始化新创建的 SequenceInputStream(将按顺序读取这两个参数,先读取 s1,然后读取 s2)
SequenceInputStream(Enumeration<? extends InputStream> e);//通过枚举对象来初始化新创建的 SequenceInputStream,该参数必须是生成运行时类型为 InputStream 对象的 Enumeration 型参数
//组合多个流
InputStream input1 = new FileInputStream("1.txt");
InputStream input2 = new FileInputStream("2.txt");
InputStream input3 = new FileInputStream("3.txt");
Vector<InputStream> streams = new Vector<>();
streams.add(input1);
streams.add(input2);
streams.add(input3);
//利用 Vector 对象的 elements() 方法返回 enumeration 对象
SequenceInputStream sequenceInputStream = new SequenceInputStream(streams.elements());
1.8 数组流
数据被写入到字节数组中, 缓冲区在数据写入时会自动增长,关闭该流无效,关闭此流后调用方法不会有异常
-
ByteArrayInputStream/**ByteArrayOutputStream **字节数组流,在内存中创建一个字节数组缓冲区,从输入流读取的数据保存在该字节数组缓冲区中。
write(int b);//写入指定的字节到此字节输出流中 write(byte b[], int off, int len);//从指定数组的下标off开始写入len个字节到该输出流中 writeTo(OutputStream out);//将此字节输出流的内容写入到指定的输出流中 reset();//重置此字节输出流,废弃此前存储的数据 toByteArray();//对输出流的数据进行检索
-
CharArrayReader/CharArrayWriter 字符数组流,操作以字符为单位
write(int oneChar);//的作用将int类型的oneChar换成char类型,然后写入到CharArrayWriter中。 write(char[] buffer, int offset, int len);//是将字符数组buffer写入到输出流中,offset是从buffer中读取数据的起始偏移位置,len是读取的长度。 write(String str, int offset, int count);//是将字符串str写入到输出流中,offset是从str中读取数据的起始位置,count是读取的长度。 append(char c);//的作用将char类型的c写入到CharArrayWriter中,然后返回CharArrayWriter对象。注意:append(char c)与write(int c)都是将单个字符写入到CharArrayWriter中。它们的区别是,append(char c)会返回CharArrayWriter对象,但是write(int c)返回void。 ppend(CharSequence csq, int start, int end);//的作用将csq从start开始(包括)到end结束(不包括)的数据,写入到CharArrayWriter中。注意:该函数返回CharArrayWriter对象! append(CharSequence csq);//的作用将csq写入到CharArrayWriter中。注意:该函数返回CharArrayWriter对象! writeTo(OutputStream out);//将该“字符数组输出流”的数据全部写入到“输出流out”中。
1.9 转换流
InputStreamReader/OutputStreamWriter
Java IO流中提供了两种用于将字节流转换为字符流的转换流。
InputStreamReader 用于将字节输入流转换为字符输入流,其中OutputStreamWriter用于将字节输出流转换为字符输出流。使用转换流可以在一定程度上避免乱码,还可以指定输入输出所使用的字符集
OutputStreamWriter 将字节输出流转换为字符输出流。创建使用指定字符集的 OutputStreamWriter,如果不指定字符集就使用默认字符集创建OutputStreamWriter。转换之后可以不用关闭OutputStream
//输出流中的方法
flsh();//刷新该流的缓冲
close();//关闭此流,关闭前需要刷新
getEncoding();//获取此流使用的字符编码的名称。
write(char[] ,int offset ,int length);//写入字符数组的某一部分
write(String ,int offset ,int length);//写入字符串的某一部分
write(String );//写入单个字符
二、非流式部分
2.1 SerializablePermission
用于可序列化权限。可序列化执行包含一个名称(也称为"目标名称"),但没有操作列表。
2.2 File
Java文件类以抽象的方式代表文件名和目录路径名。该类主要用于文件和目录的创建、文件的查找和文件的删除等。
序号 | 方法描述 |
---|---|
1 | public String getName() 返回由此抽象路径名表示的文件或目录的名称。 |
2 | public String getParent() 返回此抽象路径名的父路径名的路径名字符串,如果此路径名没有指定父目录,则返回 null 。 |
3 | public File getParentFile() 返回此抽象路径名的父路径名的抽象路径名,如果此路径名没有指定父目录,则返回 null 。 |
4 | public String getPath() 将此抽象路径名转换为一个路径名字符串。 |
5 | public boolean isAbsolute() 测试此抽象路径名是否为绝对路径名。 |
6 | public String getAbsolutePath() 返回抽象路径名的绝对路径名字符串。 |
7 | public boolean canRead() 测试应用程序是否可以读取此抽象路径名表示的文件。 |
8 | public boolean canWrite() 测试应用程序是否可以修改此抽象路径名表示的文件。 |
9 | public boolean exists() 测试此抽象路径名表示的文件或目录是否存在。 |
10 | public boolean isDirectory() 测试此抽象路径名表示的文件是否是一个目录。 |
11 | public boolean isFile() 测试此抽象路径名表示的文件是否是一个标准文件。 |
12 | public long lastModified() 返回此抽象路径名表示的文件最后一次被修改的时间。 |
13 | public long length() 返回由此抽象路径名表示的文件的长度。 |
14 | public boolean createNewFile() throws IOException 当且仅当不存在具有此抽象路径名指定的名称的文件时,原子地创建由此抽象路径名指定的一个新的空文件。 |
15 | public boolean delete() 删除此抽象路径名表示的文件或目录。 |
16 | public void deleteOnExit() 在虚拟机终止时,请求删除此抽象路径名表示的文件或目录。 |
17 | public String[] list() 返回由此抽象路径名所表示的目录中的文件和目录的名称所组成字符串数组。 |
18 | public String[] list(FilenameFilter filter) 返回由包含在目录中的文件和目录的名称所组成的字符串数组,这一目录是通过满足指定过滤器的抽象路径名来表示的。 |
19 | public File[] listFiles() 返回一个抽象路径名数组,这些路径名表示此抽象路径名所表示目录中的文件。 |
20 | public File[] listFiles(FileFilter filter) 返回表示此抽象路径名所表示目录中的文件和目录的抽象路径名数组,这些路径名满足特定过滤器。 |
21 | public boolean mkdir() 创建此抽象路径名指定的目录。 |
22 | public boolean mkdirs() 创建此抽象路径名指定的目录,包括创建必需但不存在的父目录。 |
23 | public boolean renameTo(File dest) 重新命名此抽象路径名表示的文件。 |
24 | public boolean setLastModified(long time) 设置由此抽象路径名所指定的文件或目录的最后一次修改时间。 |
25 | public boolean setReadOnly() 标记此抽象路径名指定的文件或目录,以便只可对其进行读操作。 |
26 | public static File createTempFile(String prefix, String suffix, File directory) throws IOException 在指定目录中创建一个新的空文件,使用给定的前缀和后缀字符串生成其名称。 |
27 | public static File createTempFile(String prefix, String suffix) throws IOException 在默认临时文件目录中创建一个空文件,使用给定前缀和后缀生成其名称。 |
28 | public int compareTo(File pathname) 按字母顺序比较两个抽象路径名。 |
29 | public int compareTo(Object o) 按字母顺序比较抽象路径名与给定对象。 |
30 | public boolean equals(Object obj) 测试此抽象路径名与给定对象是否相等。 |
31 | public String toString() 返回此抽象路径名的路径名字符串。 |
2.3 RandomAccessFile
RandomAccessFile实现了大部分文件输入输出流的方法,但是底层实现中他实现的是DataInput和DataOutput接口,并非是FileInputStream和FileOutputStream。RandomAccessFile使用很多native方法实现了对文件的操作,并且很多native方法跟inputstream都有重叠
2.4 FileDescriptor
FileDescriptor可以被用来表示开放的文件,开放的套接字等。
当FileDescriptor表示文件来说,当FIleDescriptor表示某文件时,我们可以通俗的将FIleDescriptor看成该文件。但是,我们不能直接通过FIleDescriptor对该文件进行操作;若需要通过FIleDescriptor对该文件进行操作,则需要创建FileDescriptor对应的FileOutputStream,再对文件进行操作。
三、补充
-
在文件中,换行为 \r\n
-
路径分隔符, linux文件路径分隔符为 / ,windows的文件路径分隔符为 \ 。System.getProperty(“file.separator”)
-
字节流和字符流的区别:
- 读写单位不同:字节流以字节(8 bit)为单位,字符流以字符为单位,根据码表映射字符,一次可能读多个字节。
- 处理对象不同:字节流能处理所有类型的数据(如图片、avi 等),而字符流只能处理字符类型的数据。
- 字节流没有缓冲区,是直接输出的,而字符流是输出到缓冲区的。因此在输出时,字节流不调用 colse() 方法时,信息已经输出了,而字符流只有在调用 close() 方法关闭缓冲区时,信息才输出。要想字符流在未关闭时输出信息,则需要手动调用 flush() 方法。
-
节点流和处理流:Java io 分类方式有很多,根据是否直接处理数据,Java io又分为节点流和处理流,节点流是真正直接处理数据的;处理流是装饰加工节点流的。
-
什么叫对象序列化,什么是反序列化,实现对象序列化需要做哪些工作?
- 对象序列化:将对象以二进制的形式保存在硬盘上;
- 反序列化:将二进制的文件转化为对象读取;
- 实现 serializable 接口可以实现对象序列化,其中没有需要实现的方法,implements Serializable 只是为了标注该对象是可被序列化的
-
什么是 Filter 流有哪些?
FilterStream 是一种 IO 流,主要作用是用来对存在的流增加一些额外的功能,像给目标文件增加源文件中不存在的行数,或者增加拷贝的性能等。在 java.io 包中主要由 4 个可用的 filter Stream。两个字节 filter stream,两个字符 filter stream。FilterStream 是抽象类不能被是咯话,其子类有:DataInputStream、BufferedInputStream、PushbackInputStream
四、NIO
NIO和传统IO(一下简称IO)之间第一个最大的区别是,IO是面向流的,NIO是面向缓冲区。 Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区。NIO的缓冲导向方法略有不同。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。但是,还需要检查是否该缓冲区中包含所有您需要处理的数据。而且,需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的数据。
IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。 NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。而不是保持线程阻塞,所以直至数据变得可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。 线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。