输入/输出流
输入流:可以从其中读入一个字节序列的对象;
输出流:可以向其中写入一个字节序列的对象。
这些字节序列的来源地和目的地可以是文件,而通常是文件,但也可以是网络连接,甚至是内存块。
输入与输出
一、InputStream 和 OutputStream
抽象类 InputStream 和 OutputStream 构成了输入/输出(I/O) 类层次结构的基础。
1.InputStream抽象类
抽象方法:
(在设置具体的输入流类时,必须覆盖这个方法)
(读入一个字节)
abstract int read ( )
将读入一个字节,并返回读入的字节,或者在遇到输入源结尾是返回 -1。
非抽象方法:
(读入字节数组)
int read(byte[] b)
将读入一个字节数组,并返回实际读入的字节数,或者再碰到输入流的结尾是返回 -1.(这个 read 方法最多读入 b.length 个字节)
int read(byte[] b,int off,int len)
int readNBytes(byte[] b,int off,int len)
如果未阻塞(read),则读入有 len 指定数量的字节,或者阻塞值所有的值都被读入 (readNBytes)。读入的值将置于b中从off开始的位置。返回实际读入的字节数,或者在碰到输入流的结尾时返回 -1。
(产生一个数组)
byte[] readAllBytes()
产生一个数组,包含可以从当前流中读入的所有字节。
(输入流传送至输出流)
long transferTo(OutputStream out)
将当前输入流的所有字节传送到给定的输出流,返回传递的字节数。这两个流都不应该处于关闭状态。
(跳过字节)
long skip(long n)
在输入流中跳过n个字节,并返回实际跳过的字节数(如果碰到输入流的结尾,则可能小于n)。
(可获取的字节数)
int available()
返回在不阻塞的情况下可获取的字节数(阻塞意味着当前线程将失去它对资源的占用)。
(关闭输入流)
void close()
关闭这个输入流
(打标记)
void mark(int readlimit)
在输入流的当前位置打一个标记(并非所有的流度支持这个特性)。如果从输入流中已经读入的字节多于readlimit个,则这个流允许忽略这个标记。
(返回到最后一个标记)
void reset()
返回到最后一个标记,随后对read的调用将重新读入这些字节。如果当前没有任何标记,则这个流不被重置。
(是否支持打标记)
boolean markSupported()
如果这个流支持打标记,则返回true。
2.OutputStream抽象类
抽象方法:
(在设置具体的输入流类时,必须覆盖这个方法)
(写出一个字节)
abstract void write(int n)
写出一个字节的数据。
非抽象方法:
(写出字节到数组)
void write(byte[] b)
void write(byte[] b,int off,int len)
写出所有字节或某个范围的字节到数组b中。
(冲刷关闭输出流)
void close()
冲刷并关闭输出流
(冲刷输出流)
void flush()
冲刷输出流,也就是将所有缓冲的数据发送到目的地。
二、Reader 和 Writer
面向字节的流不便处理以Unicode形式存储的信息(Unicode 中每个字符都使用了多个字节来表示)
从抽象类Reader和Writer中继承出来了一个专门用于处理Unicode字符的单独的类层次结构。
这些类拥有的读入和写出操作都是基于两个字节的Char值的(即Unicode码元),而不是基于byte值的。
Reader 和 Writer 类的基本方法与 InputStream 和 OutputStream中的方法类似
抽象方法:
abstract int read()
abstract void write(int c)
read方法返回一个Unicode码元(一个在0~65535之间的整数),或者在碰到文件结尾时返回 -1。
write方法在被调用时,需要传递一个Unicode码元。
-------------------------------
4+1个附加接口
Closeable、Flushable、Readable 和 Appendable
Closeable:(InputStream、OutputStream、Reader 和 Writer)
(关闭)
void close() throws IOException
关闭这个Closeable,这个方法可能会抛出IOException。
Flushable: (OutputStream 和 Writer)
(冲刷)
viod flush()
冲刷这个Flushable。
Readable:
(向cb读入char值)
int read(CharBuffer cb)
尝试着向cb读入其可持有数量的char值。返回读入的char值的数量,或者当从这个Readable中无法在获得更多的值是返回 -1 。
CharBuffer类拥有按顺序和随机的进行读写访问的方法,它表示一个内存中的缓冲区或者一个内存映像的文件。
Appendable:(Writer)
(流追加码元)
Appendable append(char c)
Appendable append(CharSequence cs)
向这个Appendable中追加给定的码元或者给定的序列中的码元,返回this。
CharSequence: (String、CharBuffer、StringBuilder 和 StringBuffer)
(索引处码元)
char charAt(int index)
返回给定索引处的码元。
(序列码元数量)
int length()
返回在这个序列中的码元的数量。
(原型截断)
CharSequence subSequence(int startIndex,int endIndex)
返回由存储在startIndex到endIndex-1处的所有码元构成的CharSequence。
(码元字符串)
String toString()
返回这个序列中所有码元构成的字符串。
-------------------------------
InputStream子类 和OutputStream子类
三、组合输入/输出流过滤器
即使某个输入/输出流类提供了使用原生的read和write功能的某些具体方法,应用系统的程序员还是很少使用它们,因为大家感兴趣的数据可能包含数字、字符串和对象,而不是原生字节。
我们可以使用众多的构建与基本的InputStream和OutputStream类之上的某个输入/输出类,而不是直接使用字节。
1.FileInputStream 和 FileOutputStream
FileInputStream 和 FileOutputStream 可以提供附着在一个磁盘文件上的输入流和输出流,你只需向其构造器提供文件名或文件的完整路径名。
与抽象类InputStream 和 OutputStream 一样,这些类只支持在字节级别上的读写。而如DataInputStream只能读入数值类型,却没有任何从文件中获取数据的方法。
某些输入流可以从文件和其他更外部的位置上获取字节,而其他的输入流可以将字节组装到更有用的数据类型中。Java程序员必须对二者进行结合。
FileInputStream:
(创建文件输入流)
FileInputStream(String name)
FileInputStream(File file)
使用由name字符串或file对象指定路径名的文件创建一个新的文件输入流(File类后续文章会描述)。非绝对的路径名将按照相对于VM启动时所设置的工作目录来解析。
FileOutputStream:
(创建文件输出流)
FileOutputStream(String name)
FileOutputStream(String name,boolean append)
FileOutputStream(File file)
FileOutputStream(File file,boolean append)
使用由name字符串或file对象指定路径名的文件创建一个新的文件输出流(File类在后续文章中描述)。如果append参数为ture,那么数据将被添加到文件尾,而具有相同名字的已有文件不会被删除;否则,这个方法会删除所有具有相同名字的已有文件。
2.BufferedInputStream 和 BufferedOutputStream
BufferedInputStream:
(创建带缓冲区的输入流)
BufferedInputStream(InputStream in)
创建一个带缓冲区的输入流。带缓冲区的输入流在从流中读入字符时,不会每次都访问设备。当缓冲区为空时,会向缓冲区中读入一个新的数据块。
BufferedInputStream:
(创建带缓冲区的输出流)
BufferedInputStream(OutputStream out)
创建一个带缓冲区的输出流。带缓冲区的输出流在手机上要写出的自复式,不会每次都访问设备。当缓冲区填满或当流被冲刷时,数据就被写出。
3.PushbackInputStream
PushbackInputStream:
(创建预览、回推缓冲输入流)
PushbackInputStream(InputStream in)
PushbackInputStream(InputStream in,int size)
构建一个可以预览一个字节或者具有指定尺寸的回推缓冲区的输入流。
(回推、可再次获取)
void unread(int b)
回推一个字节,它可以在下次调用read时被再次获取。
四、文本输入与输出
在保存数据时,可以选择二进制格式或文本格式,在这里首先讨论文本格式的I/O。
OutputStreamWriter 和 InputStreamReader
在存储文本字符串时,需要考虑字符编码方式。
OutputStreamWriter类将使用选定的字符编码方式,把Unicode码元的输出流转换为字节流。
InputStreamReader类将包含字节(用某种字符编码方式表示的字符)的输入流转换为可以产生Unicode码元的读入器。(除System.in使用主机系统索使用的默认字符编码方式外,在桌面操作系统中,总应在InputStreamReader构造器中选择一种具体的编码方式)
五、如何写出文本输出
对于文本输出,可以使用PrintWriter。
PrintWriter
这个类拥有以文本格式打印字符串和数字的方法。(注意:自动冲刷模式默认禁用)
(创建PW)
PrintWriter(Writer out)
PrintWriter(Writer writer)
创建一个 给定的写出器写出的 新的 PrintWriter。
PrintWriter(String filename,String encoding)
PrintWriter(File file,String encoding)
创建一个使用给定的编码方式向给定的文件写出的新的PrintWriter。
(打印)
void print(Object obj)
通过打印从toString产生的字符串来打印一个对象。(Arrays.toString返回包含a中元素的一个字符串,这些元素用中括号包围,并用逗号分割(大略仅限于基础类型元素))
void print(String s)
打印一个包含Unicode码元的字符串。
void println(String s)
打印一个字符串,后面紧跟一个行终止符。如果这个流处于自动冲刷模式,那么就会冲刷这个流。(可以通过使用PrintWriter(Writer writer,boolean autoFlush)来启用或禁用自动冲刷模式)
void print(char[] s)
打印在给定的字符串中的所有Unicode码元。
void print(char c)
打印一个Unicode码元。
void print(int i)
void print(long l)
void print(float f)
void print(double d)
void print(boolean b)
以文本格式打印给定的值
void prinf(String format,Object... args)
按照格式字符串指定的方式打印给定的值。
boolean checkError()
如果产生格式化或输出错误,则返回true。一旦这个流碰到了错误,它就受到了污染,并且所有对checkError的调用都将返回true。
六、如何读入文本输入
(1)最简单的处理任意文本的方式就是我们广泛使用的Scanner类。我们可以从任何输入流中构建Scanner对象。
(2)可以将短小的文本文件向下面这样读入到一个字符串中:
var content = (Files.read String path,charset);
(3)想要一行一行地读入,可以调用:
List<String> lines = Files.readAllLines(path,charset);
(4)文件太大,可以将行惰性处理为一个Stream对象:
try(Stream<String> lines = Files.lines(path,charset)){...}
(5)使用扫描器来读入符号,即由分隔符分割的字符串,默认的分割符是字符。可以将分割符修改为任意的正则表达式。
Scanner in = ... ; in.useDelimiter("\\PL+");
将接受任何非Unicode字母作为分隔符。之后这个扫描器将值接受Unicode字母。
调用next方法可以产生下一个符号:
while(in.hasNext()) { String word = in.next(); }
或者,向这样获取一个包含所有符号的流:
Stream<String> words = in.tokens();
结:以文本格式存储对象(示例程序)
记录集样本:
Harry Hacker|35500|1989-10-01
Carl Cracker|75000|1987-12-15
Tony Tester|38000|1990-03-15
1.写出记录
要写出记录到文本文件中,我们使用PrintWriter类。
public static void writerEmployee(PrintWriter out,Employee e)
{
out.println(e.getName()+"|"+e.getSalary()+"|"+e.getHireDay());
}
2.读入记录
我们每次读入一行,然后分离所有字段。我们使用扫描器来读入每一行,然后用String.split方法将这一行断开成一组标记。
public static Employee readEmployee(Scanner in)
{
String line = in.nextLine(); //按行读入
String[] tokens = line.split("\\|"); //分割
//从tokens字符串数组中分别读入
String name = tokens[0];
double salary = Double.parseDouble(token[1]);
LocalDate hirDate = LocalDate.parse(token[2]);
//从日期对象得到年月日
......
}
附:字符编码方式
输入和输出流都是用于字节序列的,但是在许多情况下,我们希望操作的是文本,即字符序列。于是,字符如何编码成字节就成了问题。
Java针对字符使用的是Unicode标准。每个字符或“编码点”都具有一个21位的整数。有多种不同的字符编码方式,也就是说,将这些21位数字包装成字节的方法有多种。
StandarCharsets类具有类型为Charset的静态变量,用于表示每种Java虚拟机都必须支持的字符编码方式:
StandarCharsets.UTF_8
StandarCharsets.UTF_16
StandarCharsets.UTF_16BE
StandarCharsets.UTF_16LE
StandarCharsets.ISO_8859
StandarCharsets.US_ASCII
为获取另一种编码方式的Charset,可以使用静态的forName方法:
Charset shiftJIS = Charset.forName("Shift-JIS");
在读入或写出文本时,应该使用Chaeset对象。
例:我们可以这样将一个字节数组转换为字符串:
var str = new String(bytes,StandardCharsets.UTF_8);