字节流类为处理字节式输入输出提供了丰富的环境。
InputStream/OutputStream
InputStream 是一个定义了 Java 字节流输入模式的抽象类。 该类的所有方法在出错条件下都将引发一个 IOException 异常。其声明格式如下:
public abstract class InputStream extends Object implements Closeable
InputStream 类继承了 Object 类, 实现了 Closeable 接口,该接口是 Java 5.0 新增的接口,定义了一个 close() 方法,通过调用该方法,可以释放流所占用的资源。
InputStream 的方法
方法 | 描述 |
int available() | 返回当前可读的输入字节数 |
void close() | 关闭输入源。关闭之后的读取会产生 IOException 异常 |
void mark(int readlimit) | 在输入流的当前点放置一个 标记。该流在读取 readlimit 个字节前都保持有效 |
boolean markSupported() | 如果调用的流支持 mark()/reset() 就返回 true |
int read() | 如果下一个字节可读则返回一个整型,遇到文件尾时返回 -1 |
int read(byte b[]) | 试图读取 b.length 个字节到 b 中,并返回实际成功读取的字节数。遇到文件尾时返回 -1 |
int read(byte b[], int offset, int len) | 试图读取 len 字节到 b 中,从 offset 开始存放,返回实际读取的字节数。遇到文件尾时返回 -1 |
void reset() | 重新设置输入指针到先前设置的标志处 |
long skip(long n) | 跳过 n 个输入字节,返回实际跳过的字节数 |
OutputStream 是定了字节流输出模式的抽象类。该类的所有方法都返回一个 void值,并且在出错情况下引发一个 IOException 异常。其声明格式如下:
public abstract class OutputStream extends Object implements Closeable, Flushable
OutputStream 继承了 Object 方法,实现了 Closeable 和 Flushable 接口。 Flushable 接口中定义了一个方法 flush(), 调用该方法会输出缓冲区中的数据。
OutputStream 的方法
方法 | 描述 |
void close() | 关闭输出流,关闭后的写操作会产生 IOException 异常 |
void flush() | 刷新缓冲区 |
void write(int b) | 向输出流写入单个字节。注意参数是一个整型数,它允许不必把参数转换成字节型就可以调用 write(), 但是输出有效值为 b 的低 8 位,高 24 位被舍弃 |
void write(byte b[]) | 向一个输出流写一个完整的字节数组 |
void write(byte b[],int offset, int len) | 输出数组 b 以 b[offset] 为起点的 len 个字节区域内的内容 |
标准输入输出流
标准输入输出指在字符方式下,程序与系统进行交互的方式,分为 3 种:
- 标准输入 stdin , 对象是键盘。
- 标准输出 stdout , 对象是屏幕。
- 标准错误输出 stderr, 对象也是屏幕。
Java 通过系统类 System 实现标准输入输出的功能,定义了 三个流变量: in、out 和 err。这些成员在 System 中时被定义成 pulbic 和 static 型的,这意味着他们可以直接通过 Systetm 类进行调用。其声明格式如下:
public static PrintStream err
public static PrintStream out
public static InputStream in
标准输入
System.in 作为字节输入流类 InputStream 的对象实现标准输入,通过 read() 方法从键盘接收数据。
int read();
int read(byte b[]);
int read(byte b[], int offset, int len);
从标准输入读取数据
import java.io.IOException;
public class FileDemo {
public static void main(String[] args) throws IOException {
System.out.println("input: ");
byte b[] = new byte[512];
int count = System.in.read(b);
System.out.println("Output: ");
for (int i = 0; i < count; i++){
System.out.print( b[i] + " ");
}
System.out.println(" ");
for(int i = 0; i < count; i++){
System.out.print((byte)b[i] + " ");
}
System.out.println("cout = " + count);
}
}
// input:
// abcd
// Output:
// 97 98 99 100 10
// 97 98 99 100 10 cout = 5
标准输出
System.out 作为打印流类 PrintStream 的对象实现标准输出。其中定义了 print 和 println 方法,支持 Java 任意基本类型作为参数。
public void print(int i);
public void println(int i);
两种区别在于, print ln 在输出时加一个回车换行符。
在使用 PrintStream 的方法时,不会抛出 IOException,可以使用 checkError() 检查流的状态,在使用其输出字符时,被转换成字符。
Java 5.0 对 PrintStream 类进行了扩充,支持数据的格式化输出, 增加了 printf() 方法。
public PrintStream printf(String format, Object... args);
public PrintStream printf(Locale l, String format, Object...args);
这两种方法都使用了 Java 5.0 中增加的功能, 支持可变参数,即方法的参数个数是可变的。 format 参数指明输出个数,args 是输出参数列表, l 是地区设置。
format 字符串的格式是:
%[argument_index $][flags][width][.precision]conversion
argument_index 用十进制整数表示参数列表中的位置,第一个参数用 1$表示,第二个参数用 2$ 表示,以此类推; flags 是调整输出格式的字符集合; width 是一个非负整数,表示输出的最小字符数, precision 是一个非负整数,用于限制字符个数; conversion 是一个字符,表示参数被如何格式化。
conversion 被分成 6 类:
- General ,应用于任何参数类型。
- Character,应用于可以表示 Unicode 字符的基本数据类型,如 char, byte, short,int
等。还有他们的包装器类型。 - Number 又可以分为 Integer 和 Float Point ,应用于 Java 的数字类型。
- Data/Time,应用于日期,long ,Calendar、Date 是表示日期的类型。
- Percent ,在数字上增加一个 %。
- Line Separator ,产生一个平台相关的行分隔符。
具体的转化字符(conversion)表示如下。大部分转化字符有 大写和 小写 两种形式。
字符 | 类型 | 描述 |
‘b’,’B’ | general | 如果参数 arg 是 null,则输出结果是 false,如果 arg 是 boolean 或者 Boolean,则输入结果是 String.valueOf(),否则结果是 true |
‘h’,’H’ | general | 如果 arg 是 null, 则结果是 null,否则结果等于 Integer.toHexString |
’s’,’S’ | general | 如果 arg 是 null,则结果是 null;如果 arg 实现了 Formattable,那么结果相当于 arg.formatTo,否则结果相当于 arg.toString() |
‘c’,’C’ | character | 结果是 Unicode 字符 |
‘d’ | integral | 十进制整数 |
‘o’ | integral | 八进制整数 |
‘x’,’X’ | integral | 十六进制整数 |
‘e’,’E’ | floating point | 用科学计数法表示 |
‘f’ | floating point | 十进制数 |
‘g’,’G’ | floating point | 用科学记数法 或十进制整数表示,与数字的精度,舍入有关系 |
‘a’,’A’ | floating point | 带有指数的十六进制浮点数 |
‘t’,’T’ | date/time | 使用时间格式字符的前缀 |
‘%’ | percent | 转化成百分数 |
‘n’ | line separator | 平台相关的行分隔符 |
日期/时间 转化符如下, 在使用时需要加前缀 t 或 者 T。
字符 | 描述 |
‘H’ | 24 小时制,用两位字符表示,如 00~23 |
‘I’ | 12 小时制,用两位字符表示,如 00~12 |
‘k’ | 24 小时制,如 0~23 |
‘l’ | 12 小时制,如 1~12 |
‘M’ | 用两位数字表示的分,如 00~59 |
‘S’ | 用两位数字表示的秒,如 00~60 |
‘L’ | 3位数字表示的毫秒,如 000~999 |
‘N’ | 9位数字表示的纳秒,如 000 000 000 ~999 999 999 |
‘p’ | 用字母表示 上午 或 下午,如 am 或 pm 。使用 T 前缀转化为大写,即 AM 或 PM |
‘z’ | 时区,如-0800 |
‘Z’ | 时区的简化字符串表示 |
’s’ | 从 1970-1-1 00:00:00 UTC 开始的秒数,如 Long.MIN_VALUE/1000 to Long.MAX_VALUE/1000 |
‘Q’ | 从 1970-1-1 00:00:00 UTC 开始的毫秒数,如 Long.MIN_VALUE/1000 to Long.MAX_VALUE/1000 |
标识符和表示符支持的参数类型如下图,y 表示 支持
符号 | General | Character | Integral | Floating Point | Date/Time | 描述 |
‘-‘ | y | y | y | y | y | 左对齐 |
‘#’ | y | - | y | y | - | 依赖于转化符的格式 |
‘+’ | - | - | y | y | - | 总是显示符号 |
” | - | - | y | y | - | 对整数加前导空格 |
‘0’ | - | - | y | y | - | 结果用 0 填补 |
‘,’ | - | - | y | y | - | 本地化千位分隔符 |
‘(‘ | - | - | y | y | - | 负数显示在括号中 |
格式化输出
import java.util.Calendar;
import java.util.Date;
public class FileDemo {
public static void main(String[] args) {
Calendar c = Calendar.getInstance();
Date d = new Date();
double d1 = -234.567;
c.add(Calendar.DATE,2);
// 格式化日期
System.out.printf("%1$Tc is after %n %2$TF %2$Tr %n",c,d);
//格式化数字
System.out.printf("%1$ (4.9e\n",d1);
System.out.printf("%1$+4.9G",d1);
}
}
// THU JUL 12 23:57:02 CST 2018 is after
// 2018-07-10 11:57:02 PM
// (2.345670000e+02)
// -234.567000
文件字节流
InputStream 和 OutputStream 都是抽象类,不能实例化,因此在实际应用中都使用的是它们的子类,这些子类在实现其超类方法的同时又定义了特有的功能,用于不同的场合。
文件数据流类 FileInputStream 和 FileOutputStream 用于进行文件的输入输出处理,其数据源和接收器都是文件。
FileInputStream
FileInputStream 用于顺序访问本地文件,从超类继承 read,close 等方法,对文件进行操作,不支持 mark 方法 和 reset 方法。它的两个常用的构造函数如下:
FileInputStream(String filePath);
FileInputStream(File fileObj);
它们都能引发 FileNotFoundException 异常。这里,filePath 是文件的全称路径, fileObj 是描述该文件的 File 对象。 可以用下面的代码构造文件输入流:
FileInputStream f1 = new FileInputStream("Test.java");
File f = new File("Test.java");
FileInputStream f2 = new FileInputStream(f);
FileInputStream 重写了抽象类 InputStream 的读取数据的方法:
public int read() throws IOException
public int read(byte[] b) throws IOException
public int read(byte[] b, int off, int len) throws IOException
这些方法在读取数据时,输入流结束则返回 -1
FileOutputStream
FileOutputStream 用于向一个文本文件写数据。 它从超类中继承 write,close 等方法。它常用的构造函数如下:
FileOutputStream(String filePath);
FileOutputStream(File fileObj);
FileOutputStream(String filePath, boolean append);
FileOutputStream(File fileObj, boolean append);
它们可以引发 IOException 或 SecurityException 异常。这里 filePath是文件的全称路径,fileObj 是描述该文件的 File对象。如果 append 为 true, 则文件以追加的方式打开,不覆盖已有文件的内容,如果为 false,则覆盖原文的内容。
FileOutputStream 的创建不依赖于文件是否存在。如果 filePath表示的文件不存在,则 FileOutputStream 在打开之前创建它;如果文件已经存在,则打开它,准备写。 若 试图打开一个只读文件,会引发一个 IOException 异常。
FileOutputStream 重写了抽象类 OutputStream 的写数据的方法:
public void write(byte[] b) throws IOException
public void write(byte[] b,int off, int len) throws IOException
public void write(int b) throws IOException
b 是 int 类型时,占用 4 个字节, 只有最低的一个字节被写入输出流,忽略其余字节。
文件复制程序
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileDemo {
public static void main(String[] args) throws IOException {
int size;
FileInputStream f = new FileInputStream("D:\\JAVA\\Java File\\FileDemo.class");
FileOutputStream fout = new FileOutputStream("copy-of-file.txt");
System.out.println("Total Available Bytes:" + (size = f.available()));
int n = size/30;
System.out.println("First " + n + "bytes of the file one read() at a time");
for (int i = 0; i < n; i++){
fout.write(f.read());
}
System.out.println("Still Available: " + f.available());
System.out.println("Reading the next " + n + "with one read(b[])");
byte b[] = new byte[n];
if(f.read(b) != n){
System.out.println("couldn't read " + n + "bytes");
}
fout.write(b);
System.out.println("Still Available:" + f.available());
System.out.println("Reading the rest bytes with read(b[],offset,len)");
int count = 0;
while((count = f.read(b, 0, n)) != -1){
fout.write(b,0,count);
}
System.out.println("Still Available:" + f.available());
f.close();
fout.flush();
fout.close();
}
}
// Total Available Bytes:2465
// First 82bytes of the file one read() at a time
// Still Available: 2383
// Reading the next 82with one read(b[])
// Still Available:2301
// Reading the rest bytes with read(b[],offset,len)
// Still Available:0
过滤流
过滤流在读/写数据的同时可以对数据进行处理,它提供了同步机制,使得在某一时刻只有一个线程可以访问 I/O 流,以防止多个线程同时对一个 I/O 流进行操作所带来的意想不到的结果。
这些过滤字节流是 FilterInputStream 和 FilterOutputStream。它们的构造函数如下:
FilterOutputStream(OutputStream os);
FilterInputStream(InputStream is);
为了使用一个过滤流,必须首先把过滤流连接到某个输入输出流,过滤在构造方法的参数中指定所要连接的输入输出流来实现。
过滤流扩展了输入输出流的功能,典型的扩展是缓冲,字符字节转换和数据转换。为了提高数据的传输效率,为一个流配备缓冲区(Buffer),称为缓冲流。
当向缓冲流写入数据是,系统将数据发送到缓冲区,而不是直接发送到外部设备,缓冲区自动记录数据。当缓冲区满时,系统将数据全部发送到设备。
当从一个缓冲流中读取数据时,系统实际是从缓冲区中读取数据的。当缓冲区空时,系统会自动从相关设备读取数据,并读取尽可能多的数据充满缓冲区。
因为有缓冲区可用, 缓冲流支持跳过(skip)、标记(mark)、和重新设置(reset)等方法。
常用的缓冲输入流有 BufferedInputStream、 DataInputStream
PushbackInputStream、常用的缓冲输出流有 BufferedOutputStream, DataOutputStream, PrintStream。
1. BufferedInputStream / BufferedOutputStream
缓冲输入输出是一个非常普通的性能优化。 Java 的 BufferedInputStream 类允许把任何 InputStream 类“包装” 成缓冲流并使它的性能提高 。BufferedInputStream 有两个构造函数:
BufferedInputStream(InputStream inputStream);
BufferedInputStream(InputStream inputStream, int bufSize);
第一种 形式生成了 一个默认缓冲区长度的缓冲流 。
第二种形式缓冲区 大小时由 bufSize 传入的。使用内存页 或磁盘块等的若干倍的缓冲区大小可以给执行性能带来很大的正面影响。但这是依赖于执行情况的。 最理想的缓冲长度一般与主机操作系统,可用内存空间及机器配置有关。合理利用缓冲不需要特别复杂的操作,一般缓冲大小为 8192 字节。用这样的方法,低级系统可以从磁盘或网络读取数据块并在缓冲区中存储结果。
BufferedInputStream.markSupported() 返回 true。BufferedInputStream 支持 mark() 和 reset() 方法。
BufferdOutputStream 用一个 flush() 方法来保证数据缓冲区被写入到实际的输出设备。因为 BufferedOutputStream 是通过减小系统写数据的时间而提高性能的,可以调用 flush() 方法输出缓冲区中待写的数据。
下面是两个可用的构造函数:
BufferedOutputStream(OutputStream outputStream);
BufferedOutputStream(OutputStream outputStream, int bufSize);
第一种形式创建了一个使用 512 字节缓冲区的缓冲流。 第二种形式,缓冲区的大小由 bufSize 参数传入。
一般使用缓冲流来实现文件的复制。
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileDemo {
public static void main(String[] args) throws IOException {
int size;
FileInputStream f = new FileInputStream("D:\\JAVA\\Java File\\FileDemo.class");
FileOutputStream fout = new FileOutputStream("D:\\JAVA\\Java File\\copy-of-file.txt");
BufferedInputStream bis = new BufferedInputStream(f);
BufferedOutputStream bos = new BufferedOutputStream(fout);
System.out.println("Total Avaiable Bytes:"+(size = bis.available()));
int n = size / 30;
System.out.println("First " + n + "bytes of the file one read() at a time");
for(int i = 0; i< n; i++){
bos.write(bis.read());
}
System.out.println("Still Available: " + bis.available());
System.out.println("Reading the next " + n +" with one read(b[])");
byte b[] = new byte[n];
if(bis.read(b) != n){
System.err.println("couldn't read " + n +"bytes");
}
bos.write(b);
System.out.println("Still Available: " + bis.available());
System.out.println("Reading the rest bytes with read(b[], offset, len)");
int count = 0;
while ((count = bis.read(b,0,n)) != -1){
bos.write(b, 0, count);
}
System.out.println("Still Available: " + bis.available());
bis.close();
bos.flush();
bos.close();
f.close();
fout.flush();
fout.close();
}
}
// Total Avaiable Bytes:2465
// First 82bytes of the file one read() at a time
// Still Available: 2383
// Reading the next 82 with one read(b[])
// Still Available: 2301
// Reading the rest bytes with read(b[], offset, len)
// Still Available: 0
2.DataInputStream / DataOutputStream
DataInputStream 和 DataOutputStream 不仅能使用一般的 read() 方法读取数据流,一般的write() 方法写数据流, 而且能直接读/写各种各样的 java 语言的基本数据类型: 如 boolean、int、float、double 等。这些基本数据类型文件中的表示方式和他们在内存中的一样,无须多余的转换工程。
DataInputStream 的构造函数如下:
// 需要使用其他输入流作为参数,这里可用 FileInputStream。
public DataInputStream(InputStream in)
DataOutputStream 的构造函数如下:
// 需要使用其他流作为参数,这里可用 FileOutputStream。
public DataOutputStream(OutputStream out);
随机存取文件
对于 InputStream 和 OutputStream 它们都是顺序访问流,从头至尾访问流,并且输入流只能读,不能写,输出流只能写不能读,即对一个文件不能同时进行读写。
RandomAccessFile 类提供了一个称为 “随机访问文件”的方式,可以:对一个文件同时进行读写操作。
可以在文件的任意位置进行读写操作。
1:声明和构造函数
RandomAccessFile 的声明如下:
public calss RandomAccessFile extends Objects implements DataInput、DataOutput、Closeable
RandomAccessFile 包装了一个随机访问的文件,它不是派生于InputStream 和 OutputStream 而是直接继承于 Object 实现定义了基本输入输出方法的 DataInput 和 DataOutput 接口。 可以在文件内部放置文件指针。它有两个构造函数:
RandomAccessFile(String name, String mode) throws FileNotFoundException;
RandomAccessFile(File file, String mode) throws FileNotFoundException;
其中 ,file 识别文件路径,mode 指定参数访问模式; r 表示读,w 表示写, rw 表示读写。当文件不存在是,构造方法将抛出 FileNotFoundException。
主要的方法:
方法 | 描述 |
public long length() | 返回文件的长度。 |
void setLength(long newLength) | 设置文件的新长度。 |
public void seek(long pos) | 改变文件指针位置 |
public final int readInt() | 读入一个整数类型,因为其实现了DataInput接口,在读取数据的能力上 和 DataInputStream 相同,可以使用 readDouble() 等 |
public final void writeInt(int v) | 写入一个整数,因其实现了 DataOutput 接口,写数据的能力 和 DataOutputStream相同 |
public long getFilePointer() | 获取文件指针位置 |
public int skipBytes(int n) | 跳过 n 个字节 |
close() | 关闭文件 |
import java.io.*;
public class FileDemo {
public static void main(String[] args) {
String filename = "raf.txt";
RandomAccessFile raf = null;
String str1 = "this is a file";
String str3 = "中华人民共和国";
long length;
long pos;
try{
raf = new RandomAccessFile(filename,"rw");
raf.writeChars(str1);
pos = raf.getFilePointer();
length = str1.length();
System.out.println("第一个字符串的长度为:" + length);
// 一个字符用两个字节表示,内存中的表示和文件中的表示一致
System.out.println("写入第一个字符串后,文件指针:" + pos);
System.out.println("第二个字符串:");
pos = raf.getFilePointer();
raf.writeChars(str3);
raf.seek(pos);
for (int i = 0; i < str3.length(); i++){
System.out.print(raf.readChar());
}
pos = raf.getFilePointer();
System.out.println("\n写入" + str3.length() +"字符后,文件指针:" + pos);
System.out.println("文件测试成功");
}catch(FileNotFoundException e){
System.out.println("文件不存在");
}catch(IOException e){
}
}
}
// 第一个字符串的长度为:14
// 写入第一个字符串后,文件指针:28
// 第二个字符串:
// 中华人民共和国
// 写入7字符后,文件指针:42
// 文件测试成功
其它字节流
1:ByteArrayInputStream / ByteArrayOutputStream
ByteArrayInputStream 是吧字节 数组当成源的输入流。 该类有两个构造函数,每个构造函数需要一个字节数组提供数据源:
ByteArrayInputStream(byte b[]);
ByteArrayInputStream(byte[] b, int offset, int len);
这里, b 是输入源。 第二个构造函数从字节数组的子集构造输入流, 以 offset 指定索引的字符为起点,长度有 len 决定 。
ByteArrayInputStream 实现了 mark() 和 reset() 方法。
ByteArrayOutputStream 把字节数组当做输出流。ByteArrayOutputStream 有两个构造函数:
ByteArrayOutputStream();
ByteArrayOutputStream(int n);
第一个构造函数生成了一个 32 位字节的缓冲区。 第二个构造函数生成了一个大小为 n 的缓冲区 。缓冲区保存 在 ByteArrayOutputStream 的受保护的 buf 成员中 。缓冲区的大小在需要的情况下会自动增加 。缓冲区保存的字节数是由 ByteArrayOutputStream 的受保护的 count 域保存的。
2.SequenceInputStream
SequenceInputStream 类允许连接多个 InputStream 流。SequenceInputStream 的构造不同于任何其他的 InputStream。SequenceInputStream 构造函数要么使用一对 InputStream,要么用 InputStream 的一个 Enumeration ,显示如下:
SequenceInputStream(InputStream first, InputStream second);
SequenceInputStream(Enumeration streanEnum);
从操作上来说,该类满足读取完第一个 InputStream 后转去读取第二个 流的要求。在使用 Enumeration 的情况下,它将继续读取所有 Inputstream 流,直到最后一个被读完。
3.PushbackInputStream
缓冲流的一个新颖的用法是实现退回(Pushback),PushbackInputStream 允许字节被读取然后再退回到流。
PushbackInputStream 构造函数:
PushbackInputStream(InputStream in);
PushbackInputStream(InputStream in ,int size);
这种能够把字节退回到流的功能在统计一个文件的单词个数时非常有用。