-
可以从中读取一个字节序列的对象称做输入流;而可以向其中写入一个字节序列的对象称做输出流。
这些字节序列的来源和目的地可以是文件,而且通常都是文件,但也可以是网络,甚至是内存块。 -
抽象类InputStream 和 OutputStream 构成了有层次结构的输入/输出(IO)类的基础。因为面向字节的流不便处理以Unicode形式(Unicode 中每个字符都使用了多个字节来表示)存储的信息。所以从抽象类Reader和Writer中继承
出来的专门用于处理Unicode字符的类构成了一个单独的层次结构,这些类拥有的读入和写出操作都是基于两字节的Unicode码元的,而不是基于单字节的字符。 -
InputStream 的一个抽象方法:
public abstract int read() throws IOException;
这个方法将从输入流中读入一个字节,并返回读入的字节。在设计具体输入流类时,必须覆盖这个方法。 类似OutputStream 类定义了如下抽象方法:public abstract void write(int b) throws IOException;
想输出流写入一个字节。 read 和 write 方法在
执行的过程中是阻塞的,直至字节确实被读入或写出,即线程有可能会被阻塞。 -
当完成对流的读写操作时,应该通过调用close方法来关闭它,这个方法会释放掉资源。关闭一个输出流的同时 也就是在清空用于该输出流的缓冲区:所有被临时置于缓冲区中,以便以更大的包的形式传递的字符在关闭输出流时都将被送出。即使流提供了原始的read 和 write功能,程序员使用的不多,因为大家感兴趣的可能包含数字、字符串和对象,而不是原生字节。Java提供了众多从基本的InputStream 和 OutputStream 类导出的类,这些类使我们可以处理那些以常用格式表示的数据,而不只是在字节级别上表示的数据。
-
DataInputStream 和 DataOutputStream 可以以二进制格式读取所有的 基本Java类型
-
ZipInputStream 和 ZipInputStream 可以以常见的Zip压缩格式读写文件。
-
FileInputStream 和 FileOutputStream 可以提供附着在一个磁盘文件上的输入流和输出流。与InputStream 和 OutputStream一样,支持字节级别上的读写,也就是说只能从中读取字节和字节数组。(DataInputStream也没有任何从文件中获取数据的方法,Java使用了一种灵巧的机制来分离这两种职责,组合流过滤器,可以看一下 FilterInputStream,装饰器模式)
//组合输入流过滤器
val fin = FileInputStream("a.txt")
val din = DataInputStream(fin)
val res = din.readInt()
所有在Java.io中的类都将相对路径名解释为以用户工作目录开始。 工作目录可以通过这个来查看:
System.getProperty("user.dir")
-
BufferedInputStream 和 BufferedOutputStream 提供读写缓冲区。流在默认情况下,输入流在默认情况下是不被缓冲区缓存的,也就是说每对read进行调用都会请求操作系统再分发一个字节,相比之下请求一个数据块并将其置于缓冲区会显得更高效。(磁盘操作比内存操作慢得多)
-
字符编码方式:输入和输出流都是用于字节序列的,但是在很多情况下,我们希望操作的是文本,即字符序列。于是字符如何编码成字节就成了问题。
Java针对字符使用的是Unicode标准。每个字符或者【编码点】都具有一个21位的整数,也就是说Unicode总共能表示200多万字符。有多种不同的字符编码方式,也就是说,将这些21位数字包装成字节的方法有很多种。UTF-8 它会将每个Unicode编码点编码为1到4个字节的序列,包含英语中用到的所有ASCII字符集中的字符都只会占用一个字节。UTF-16 它会将每个Unicode编码点编码为1个或2个 16位值,这是Java字符串中使用的编码方式。
不存在任何可靠的方式可以自动地探测字节流中所使用的字符编码方式,某些API方法让我们使用【默认字符集】,即计算机的操作系统首选的字符编码方式。我们应该总是明确指定编码方式。
-
DataInput 和 DataOutput 定义了以二进制格式对数组、字符、boolean 和 字符串 进行处理的方法。
-
Jar文件只是带有一个特殊项的Zip文件,这个项称作清单。
//这个抽象类是所有实现了【基于字节】输入流的超类
public abstract class InputStream implements Closeable {
/**
从输入流中读取下一字节数据,返回数据是int类型,区间值为 0 到 255。-1 代表到了输入流的结尾。这个方法将会被阻塞直到输入数据是可用,或者到了流的结尾,又或者抛出一个异常。
*/
public abstract int read() throws IOException;
}
//这个抽象类是所有实现了【基于字节】输出流的超类
public abstract class OutputStream implements Closeable, Flushable {
//将指定的字节写入输出流。参数是个int类型,取低8位,剩下的24位忽略
public abstract void write(int b) throws IOException;
}
//基于从文件系统中读取文件的输入流。这个类从文件读取最原始的【字节】数组流,比如一个图片。如果从文件中读取【字符】流,可以考虑使用 FileReader
class FileInputStream extends InputStream{
//从输入中读取一个字节的数据 (-1 到 255)
public int read() throws IOException {
return read0();
}
private native int read0() throws IOException;
}
// 组合过滤输入流。它通常包含其他的输入流作为数据基础来源。可能进行数据的转化或者提供额外的功能。以DataInputStream为例。这是装饰器模式的应用。
class FilterInputStream extends InputStream {
protected volatile InputStream in;
protected FilterInputStream(InputStream in) {
this.in = in;
}
}
/** DataInputStream 可以让我们从它依赖的输入流中读取原始Java数据类型(机器无关)。所以我们知道 DataInputStream 的使用是要依赖于其它 输入流的。
*/
class DataInputStream extends FilterInputStream implements DataInput {
//基本类型:byte、short、char、int、long、float、String(readUTF)
//构造器
public DataInputStream(InputStream in)
//读取Int。依次从依赖的输入流中读取四个字节,然后组合成int
public final int readInt() throws IOException {
int ch1 = in.read();
int ch2 = in.read();
int ch3 = in.read();
int ch4 = in.read();
if ((ch1 | ch2 | ch3 | ch4) < 0)
throw new EOFException();
return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0));
}
}
// BufferedInputStream 给一个输入流提供更多的功能。 例如:缓存输入,支持mark、reset等。当BufferedInputStream被创建,内部会同时创建一个缓存数组。当执行read、skip方法,缓存可能需要被重新填充。mark 方法会在输入流中记录一个位置,reset 方法执行时,会让字节重新回到最近一次被mark的位置。
class BufferedInputStream extends FilterInputStream {}
//这是一个读取【基于字符】流的抽象类
public abstract class Reader implements Readable, Closeable {
//从流中读取一个字符数组。这个方法会阻塞线程,直到输入是可用的,或者到了流的末尾,又或者抛出一个IO错误。
abstract public int read(char cbuf[], int off, int len) throws IOException;
}
// InputStreamReader 是一个从 【字节流】到 【字符流】的桥梁。它读取【字节】然后按照指定的编码方式将字节解码成【字符】
public class InputStreamReader extends Reader {
//读取一个字符。这个方法会从它所依赖的 【字符流】中读取一个或更多字节,然后转化成字符。
public int read() throws IOException {}
}
// 方便的文件【字符】读取工具。实现很简单,其实就是构建了一个FileInputStream 传给 InputStreamReader
public class FileReader extends InputStreamReader {
//构造器
public FileReader(String fileName) throws FileNotFoundException {
super(new FileInputStream(fileName));
}
}
//写出到【字符】流的抽象类。
public abstract class Writer implements Appendable, Closeable, Flushable {
...
}
/**OutputStreamWriter 提供了一个桥梁:从【字符流】到【字节流】(因为最终需要编码成字节进行传递,这里的作用和 InputStreamReader作用相反。)字符最终通过特定的Charset编码成字节。 每当write()方法执行,将会触发编码转换器将字符转换成字节,同时会将字节缓存起来(不是立即写入它所依赖的outputStream)。考虑到效率,可以考虑将 OutputStreamWriter 包裹在一个 BufferedWriter中,从而避免频繁触发编码转换器。例如:
Writer out = new BufferedWriter(new OutputStreamWriter(System.out));
*/
public class OutputStreamWriter extends Writer {
// 写入一个字符
public void write(int c) throws IOException {
se.write(c);
}
}
//文件写入字符流。其实就是 OuputStreamWriter 和 FileOuputStream 的组合。
public class FileWriter extends OutputStreamWriter {}
/**将文本写入字符输出流。通过缓存字符可以提高写入的效率。可以指定缓存区大小,也可以接收默认大小,对大多数用途,默认值足够大。提供了newLine()方法,使用平台自己系统属性中定义的分隔符概念。 通常,Writer会立即将其输出发送到基础字符或字节流,除非需要立即输出,否则建议将其包装在BufferedWriter里。例如:FileWriter 、OutputStreamWriter。
PrintWriter out = new PrintWriter(new BufferedWriter(FileWriter("a.txt")))
*/
public class BufferedWriter extends Writer {
private char cb[];
private static int defaultCharBufferSize = 8192;
}
//将格式化的对象表示形式打印到文本输出流。 print、println、format、append
public class PrintWriter extends Writer {}
// PrintStream 给其它输出流添加额外功能。由PrintStream打印的所有字符都使用平台的默认字符编码转换为字节。 在需要写入字符而不是字节的情况下,应使用PrintWriter类。
public class PrintStream extends FilterOutputStream
implements Appendable, Closeable{}
其实没分清楚 PrintWriter 和 PrintStream的区别,只是PrintWriter处理文本能力相对强一点。PrintStream也可以处理文本,也可以处理原生字节。
读取读取文本输入可以使用 BufferedReader。例如:
BufferedReader reader = new BufferReader(InputStreamReader(inputStream,Charsets.UTF_8))。 还有另外一个方式Scanner,可以从任何输入流中构建Scanner对象。 Scanner 可以按照某种解码方式读取字符、文本,还有读取数字等基本Java基本类型的方法。
网友评论