将数据输入到计算机内存的过程叫数据输入。
将数据输出到外部存储的过程叫数据输出。
IO
流常用类大概有40
个,按照数据输入输出的单位可分为字节流
和字符流
两个大类
- 字节流 InputStream、OutputStream
- 字符流 Reader、Writer
输入流常用方法
read()
:返回输入流中下一个字节的数据。返回的值介于 0 到 255
之间。如果未读取任何字节,则代码返回 -1
,表示文件结束。read(byte b[ ])
: 从输入流中读取数据到数组b中,读取b数组的长度,这一点我们可以通过源码印证public int read(byte b[]) throws IOException {
return read(b, 0, b.length);
}
read(byte b[], int off, int len)
:读取数据到b数组中,从off开始,读取len个。skip(long n)
:忽略输入流中的 n 个字节 ,返回实际忽略的字节数。available()
:返回输入流中可以读取的字节数。close()
:关闭输入流释放相关的系统资源。使用示例
/**
* 注意中文有可能出现乱码问题
*/
@Test
public void readTest() {
//input.txt内容:JAVAIO_STUDY
try (InputStream fis = new FileInputStream("input.txt")) {
int available = fis.available();
System.out.println("skip前输入流中可以读取的字节数" + available);//skip前输入流中可以读取的字节数12
long skip = fis.skip(2);
System.out.println("跳过的字节数 " + skip);//跳过的字节数 2
available = fis.available();
System.out.println("skip后输入流中可以读取的字节数" + available);//skip后输入流中可以读取的字节数10
int content;
System.out.println("读取input.txt");
while ((content = fis.read()) != -1) {
System.out.print((char) content);//VAIO_STUDY
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
输出流常用方法
write(int b)
:将特定字节写入输出流。write(byte b[ ])
: 将b数组的内容写到输出流中。write(byte[] b, int off, int len)
: 将b数组中从off到off+len的内容写到输出流中。flush()
:刷新此输出流并强制写出所有缓冲的输出字节。close()
:关闭输出流释放相关的系统资源。输出流使用示例
将FileOutputStream
写到out.txt
中
/**
* 输出流
*
* @throws Exception
*/
@Test
public void outputStreamTest() throws Exception {
try (FileOutputStream fis = new FileOutputStream("out.txt")) {
fis.write("FileOutputStream".getBytes());
}
}
DataInputStream
常用于文本文件有特定格式的场景读取使用,例如我们有一个文本文件内容格式依次是布尔、整形、短整型、浮点型。我们就可以使用DataInputStream
,注意格式必须严格遵守,就像下面这样
/**
* 注意读写顺序必须保持一致
*
* @throws Exception
*/
@Test
public void dataInputStreamTest() throws Exception {
FileOutputStream fileOutputStream = new FileOutputStream("abc.txt");
DataOutputStream dataOutputStream = new DataOutputStream(fileOutputStream);
dataOutputStream.writeBoolean(true);
dataOutputStream.writeInt(55);
dataOutputStream.writeShort(111);
dataOutputStream.writeDouble(3.14);
FileInputStream fileInputStream = new FileInputStream("abc.txt");
DataInputStream dataInputStream = new DataInputStream(fileInputStream);
System.out.println(dataInputStream.readBoolean());
System.out.println(dataInputStream.readInt());
System.out.println(dataInputStream.readShort());
System.out.println(dataInputStream.readDouble());
}
ObjectInputStream
常用于将文件内容反序列化
/**
* 对象流
*
* @throws Exception
*/
@Test
public void objTest() throws Exception {
Student student = new Student();
student.setId(1);
student.setName("小往");
System.out.println(student);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("obj.data"));
oos.writeObject(student);
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("obj.data"))) {
Student student1 = (Student) ois.readObject();
System.out.println(student1);
}
}
以字符的形式读取或者写入数据,从而避免一些编码问题。我们常见的编码有:
utf8
:英文一个字节,一个中文占3个字节。unicode
:无论中文英文都占2个字节。gbk
:一个英文1个字节,中文2个字节。常用方法
read()
: 从输入流读取一个字符。read(char[] cbuf)
: 从输入流中读取一些字符,并将它们存储到字符数组 cbuf
中,等价于 read(cbuf, 0, cbuf.length)
。read(char[] cbuf, int off, int len)
:在read(char[] cbuf) 方法的基础上增加了 off 参数(偏移量)和 len 参数(要读取的最大字节数)。skip(long n)
:忽略输入流中的 n 个字符 ,返回实际忽略的字符数。close()
: 关闭输入流并释放相关的系统资源。使用示例
@Test
public void fileReaderTest() throws Exception {
//input.txt内容JAVAIO_STUDY
try (FileReader fis = new FileReader("input.txt")) {
int content;
fis.skip(2);
while ((content = fis.read()) != -1) {
System.out.print((char) content);
}
}
}
write(int c)
: 写入单个字符。write(char[] cbuf)
:将cbuf内容全部写到输出流。write(char[] cbuf, int off, int len)
:将cbuf的内容从off到off+len写到cbuf中。write(String str)
:写入字符串,等价于 write(str, 0, str.length())
。write(String str, int off, int len)
:在write(String str)
方法的基础上增加了 off 参数(偏移量)
和 len 参数(要读取的最大字节数)
。append(CharSequence csq)
:将指定的字符序列附加到指定的 Writer
对象并返回该 Writer
对象。append(char c)
:将指定的字符附加到指定的 Writer
对象并返回该 Writer
对象。flush()
:刷新此输出流并强制写出所有缓冲的输出字符。close()
:关闭输出流释放相关的系统资源。使用示例
@Test
public void writerTest() {
try (FileWriter writer = new FileWriter("writer.txt")) {
writer.write("LLJAVA", 2, "LLJAVA".length()-2);//将JAVA写到writer.txt
} catch (Exception e) {
e.printStackTrace();
}
}
try
块中。finally
中关闭流,注意判空。当然java8
的之后我们完全可以使用try-with-resource
@Test
public void writerTest() {
FileWriter fw = null;
try {
fw = new FileWriter("demo.txt");
fw.write("abcdefg");
} catch (IOException e) {
System.out.println("catch:" + e.toString());
} finally {
try {
if (fw != null)
fw.close();
} catch (IOException e) {
System.out.println(e.toString());
}
}
}
//传递一个true参数,代表不覆盖已有的文件。并在已有文件的末尾处进行数据续写。
FileWriter fw = new FileWriter("demo.txt",true);
BufferedInputStream
会将读取到的字节流存到缓冲区中,只有当缓冲区满了,再将数据刷到内存中,注意缓冲区默认大小为8192
字节,如有需要我们可以手动调整一下。如下源码所示
public class BufferedInputStream extends FilterInputStream {
// 内部缓冲区数组
protected volatile byte buf[];
// 缓冲区的默认大小
private static int DEFAULT_BUFFER_SIZE = 8192;
// 使用默认的缓冲区大小
public BufferedInputStream(InputStream in) {
this(in, DEFAULT_BUFFER_SIZE);
}
// 自定义缓冲区大小
public BufferedInputStream(InputStream in, int size) {
super(in);
if (size <= 0) {
throw new IllegalArgumentException("Buffer size <= 0");
}
buf = new byte[size];
}
public synchronized int read() throws IOException {
if (pos >= count) {
//缓冲空间不足情况下,该函数会通过CAS对缓冲区进行扩容,将count重新计算一下实际大小
fill();
if (pos >= count)
return -1;
}
return getBufIfOpen()[pos++] & 0xff;
}
}
同样的BufferedOutputStream
会将写入的数据优先存放到缓冲区,当前缓冲区空间不足时,才将缓冲区数据写到输出流中,如下源码所示
public synchronized void write(int b) throws IOException {
if (count >= buf.length) {
flushBuffer();
}
buf[count++] = (byte)b;
}
缓冲流与常规性能测试代码
可以看到走了缓冲之后,避免了没必要的IO
,程序性能一下子提高不少。
@Test
public void readTimeTest() throws Exception {
long start = System.currentTimeMillis();
int content;
try (FileInputStream fis = new FileInputStream("斗破苍穹.txt");
FileOutputStream fos = new FileOutputStream("fos斗破苍穹.txt")) {
/**
* public int read() throws IOException {
* return read0();
* }
*/
while ((content = fis.read()) != -1) {
fos.write(content);
}
}
long end = System.currentTimeMillis();
System.out.println("FileOutputStream:" + (end - start));
start = System.currentTimeMillis();
try (BufferedInputStream fis = new BufferedInputStream(new FileInputStream("斗破苍穹.txt"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("fos斗破苍穹.txt"))) {
/**
* public synchronized int read() throws IOException {
* if (pos >= count) {
* fill();
* if (pos >= count)
* return -1;
* }
* return getBufIfOpen()[pos++] & 0xff;
* }
*/
while ((content = fis.read()) != -1) {
/**
* 可以看到超过了buf才执行写入
* public synchronized void write(int b) throws IOException {
* if (count >= buf.length) {
* flushBuffer();
* }
* buf[count++] = (byte)b;
* }
*/
bos.write(content);
}
}
end = System.currentTimeMillis();
System.out.println("BufferedOutputStream:" + (end - start));
}
输出结果
/**
* 输出结果
* FileOutputStream:26886
* BufferedOutputStream:156
*/
和字节缓冲流差不多,这里就不多介绍了。通过源码我们就知道,字符流是以字符为单位将数据存到数组中,如下源码所示:
public int read() throws IOException {
synchronized (lock) {
ensureOpen();
for (;;) {
if (nextChar >= nChars) {
fill();
if (nextChar >= nChars)
return -1;
}
if (skipLF) {
skipLF = false;
if (cb[nextChar] == '\n') {
nextChar++;
continue;
}
}
//读入缓冲区数组中
return cb[nextChar++];
}
}
}
手动实现一个bufferReader
,实际上readLine
底层就是调用单字符读取的read
方法,一旦遇到\n
停止指针偏移,输出读取到的字符串,如下图:
代码示例
public class MyBufferReader implements Closeable {
private Reader reader;
public MyBufferReader(Reader reader) {
this.reader = reader;
}
public MyBufferReader() {
}
public String readLine() throws IOException {
StringBuilder stringBuilder = new StringBuilder();
int readCode;
while ((readCode = reader.read()) != -1) {
char c = (char) readCode;
if (c == '\n') {
break;
}
if (c == '\r') {
continue;
}
stringBuilder.append(c);
}
return stringBuilder.toString();
}
@Override
public void close() throws IOException {
reader.close();
}
}
测试代码
@Test
public void myReaderTest(){
/**
* 文本内容
* JAVAIO_STUDY
* 123
*/
try (MyBufferReader reader = new MyBufferReader(new FileReader("input.txt"))) {
System.out.println(reader.readLine());
System.out.println(reader.readLine());
/**
* 输出结果
* JAVAIO_STUDY
* 123
*/
} catch (Exception e) {
e.printStackTrace();
}
}
如果有输入则要用到InputStream
或者Reader
如果有输出则要用到OutputStream
或Writer
如果是文本文件则是字符流,反之其他媒体文件或者对格式没有要求的文件都用字节流
如果需要考虑性能就使用缓冲流包装一下
按照题目所说因为有源,所以要使用InputStream Reader
读取的是字符所以用Reader
原设备是硬盘的文本文件,所以用FileReader
我们需要提高效率所以要用BufferedReader
所以代码为
FileReader fr = new FileReader(“a.txt”);
BufferedReader bufr = new BufferedReader(fr);
输出流同理得出
FileWriter fw = new FileWriter(“b.txt”);
BufferedWriter bufw = new BufferedWriter(fw);
将键盘录入的数据保存到一个文件中。
源是键盘,但是要转成文本所以用System.in转换成Reader
不需要提高效率所以InputStreamReader足矣
输出到文本且要效率所以的BufferedWriter套FileWriter
随机访问流支持操作偏移量灵活改动或者读取数据如下所示
@Test
public void randomAccessFile() throws Exception {
RandomAccessFile randomAccessFile = new RandomAccessFile("input.txt", "rw");
System.out.println("读取前的偏移量:" + randomAccessFile.getFilePointer() + " 读取到的字符: " + (char) randomAccessFile.read() + " 读取后的偏移量: " + randomAccessFile.getFilePointer());
System.out.println("读取前的偏移量:" + randomAccessFile.getFilePointer() + " 读取到的字符: " + (char) randomAccessFile.read() + " 读取后的偏移量: " + randomAccessFile.getFilePointer());
System.out.println("读取前的偏移量:" + randomAccessFile.getFilePointer() + " 读取到的字符: " + (char) randomAccessFile.read() + " 读取后的偏移量: " + randomAccessFile.getFilePointer());
randomAccessFile.seek(1);
System.out.println("读取前的偏移量:" + randomAccessFile.getFilePointer() + " 读取到的字符: " + (char) randomAccessFile.read() + " 读取后的偏移量: " + randomAccessFile.getFilePointer());
randomAccessFile.seek(0);
// 覆盖已有字符串
randomAccessFile.write(new byte[]{'J', 'A', 'V', 'A'});
}