字节流和字符流
字节流和字符流的操作几乎完全一样,不同的是字节流操作的数据单元是字节,而字符流操作的是字符。
InputStream和Reader
InputStream和Reader是所有输入流的抽象基类。
在InputStream里包含的三个方法:
-
int read()
:从输入流中读取单个字节,返回所读取的字节数据。 -
int read(byte[] b)
:从输入流中最多读取b.length个字节的数据,并将其存储在字节数组中,返回实际读取的字节数; -
int read(byte[] b,int off,int len)
:从输入流中最多读取len个字节的数据,并将其存储在字节数组中,并不是从数组起点开始,而是从off位置开始,返回实际读取的字节数;
public class FileInputStreamdemo {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("FileInputStreamdemo.java");
//创建byte数组,长度为1024
byte[] bytes = new byte[1024];
//用于保存实际读取的字节数
int hasRead=0;
while ((hasRead=fis.read(bytes))>0){
System.out.println(new String(bytes,0,hasRead));
}
//关闭文件输入流
fis.close();
}
在Reader里包含三个方法:
-
int read()
:从输入流中读取单个字符,返回所读取的字符数据。 -
int read(char[] c)
:从输入流中最多读取c.length个字符的数据,并将其存储在字符数组中,返回实际读取的字符数; -
int read(char[] b,int off,int len)
:从输入流中最多读取len个字符的数据,并将其存储在字符数组中,并不是从数组起点开始,而是从off位置开始,返回实际读取的字符数;
除此之外,InputStream和Reader还支持几个方法来移动记录指针:
-
void mark(int readAheadLimit)
:在记录指针当前位置记录一个标记; -
boolean markSupported()
:判断此流是否支持mark()操作,是否支持记录标记; -
void reset()
:将此流的记录指针重新定位到上一次记录标记的位置; -
long skip(long n)
:记录指针向前移动n个字节/字符。
OutputStream和Writer
都提供了以下方法:
-
void write(int c)
:将指定字节/字符输出到输出流中,c既可以是字节,也可以是字符。 -
void write(byte[]/char[] buf)
:将字节数组/字符数组的数据输出到指定输出流中。 -
void write(byte[]/char[] buf,int off,int len)
:将字节数组/字符数组中从off开始,长度为len的数据输出到指定输出流中。
除此之外Writer还包含了两个方法:
-
void write(String str)
将str字符串包含的字符输出到指定输出流中。 -
void write(String str,int off,int len)
:将str字符串里从off位置开始,长度为len的字符输出到指定输出流中。
实例:复制文件
public static void main(String[] args) throws IOException {
FileInputStream in = new FileInputStream("FileInputStreamdemo.java");
FileOutputStream out = new FileOutputStream("javafile.txt");
byte[] bytes = new byte[1024];
int len=0;
while ((len=in.read(bytes))!=-1){
out.write(bytes);
}
in.close();
out.close();
}
如果想直接输出字符串内容,则使用Writer更好。
public static void main(String[] args) throws IOException {
//true表示是否追加内容,如果为false则覆盖之前的内容
FileWriter fw = new FileWriter("abcd.txt",true);
fw.write("床前明月光,疑是地上霜。\r\n");
fw.write("举头望明月,低头思故乡. \r\n");
fw.close();
}
输入/输出流体系
处理流的用法
处理流可以隐藏底层设备上节点流的差异,并对外提供更加方便的输入/输出方法,让程序员只需关心高级流的操作。
实际识别处理流非常简单,只要流的构造器参数不是一个物理节点,而是已经存在的流,那么这个流就是处理流。
public static void main(String[] args) {
try (
FileOutputStream fos = new FileOutputStream("test.txt");
PrintStream ps = new PrintStream(fos);
){
ps.println("普通字符串");
ps.println(new PrintStreamdemo());
}
catch (IOException ioe){
ioe.printStackTrace();
}
}
PrintStream ps = new PrintStream(fos);像这种构造器参数为已存在流,而不是物理节点的流,就是处理流。
输入/输出流体系
字符串流
public static void main(String[] args) {
String str="我想要一座房子 \n"+
"面朝大海 \n"+
"那里海风呼啸 \n"+
"没有烦恼 \n";
char[] buffer = new char[32];
int hasRead=0;
try (StringReader sr = new StringReader(str)){
while ((hasRead=sr.read(buffer))>0){
System.out.println(new String(buffer,0,hasRead));
}
} catch (IOException e) {
e.printStackTrace();
}
//这里实际上是以一个StringBuffer作为输出节点
try (StringWriter sw = new StringWriter()){
sw.write("等爱的人很多,\n");
sw.write("不预设你会在乎我\n");
System.out.println(sw.toString());
} catch (IOException e) {
e.printStackTrace();
}
}
以字符串作为物理节点。
转换流
输入/输出流体系中还提供了两个转换流,这两个转换流用于实现将字节流传换成字符流。
实例:获取键盘输入
Java使用System.in代表标准输入,但这个标准输入是InputStream类的实例,使用不太方便,而且键盘输入内容都是文本内容,所以可以使用InputStreamReader将其转化成字符输入流,可以将普通的Reader再次包装成BufferedReader,利用它的readLine()方法可以一次性获取一行。
public static void main(String[] args) {
try (
//将System.in转换成Reader对象
InputStreamReader reader = new InputStreamReader(System.in);
//将普通Reader包装成BufferedReader
BufferedReader br = new BufferedReader(reader);
){
String line=null;
while ((line=br.readLine())!=null){
//如果读取的字符串是exit则程序退出
if(line.equals("exit")){
System.exit(1);
}
System.out.println("输入的内容为:"+line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
BufferedReader具有缓冲功能,他可以一次读取一行文本,以换行符为标志。
推回输入流
在输入/输出流体系中,有两个特殊的流与众不同,就是PushbackInputStream和PushbackReader,他们都提供了如下三个方法:
-
void unread(byte[]/char[] buf)
:将一个字节/字符数组内容推回到推回缓冲区里,从而允许重复读取刚刚读取的内容。 -
void unread(byte[]/char[] buf,int off,int len)
:将一个字节/字符数组里从off开始,长度为len字节/字符推回到推回缓冲区里,从而允许重复读取刚刚读取的内容。 -
void unread(int b)
:将一个字节/字符推回到推回缓冲区里,从而允许重复读取刚刚读取的内容。
这两个推回输入流都提供了一个推回缓冲区当程序调用这两个推回输入流的unread()方法时,系统将会把指定数组的内容推回到该缓冲区里,而推回输入流每次调用read()方法时总是先从推回缓冲区读取,只有完全读取了推回缓冲区的内容后,但还没有装满read()所需数组时才会从原输入流中读取。
当程序创建一个推回缓冲流时需要指定推回缓冲区的大小,默认为1,如果推回内容大于推回缓冲区大小时,则会引发Pashback buffer overflow 的IOException异常。
实例:
试图找出程序中“new PushbackReader”字符串,找到后,程序只是打印出目标字符串之前的内容。
package org.westos.demo7;
import java.io.*;
public class PushbaskTest {
public static void main(String[] args) {
try (
//创建一个PushbackReader,指定推回缓冲区大小为64
PushbackReader pr = new PushbackReader(new FileReader("PushbaskTest.java"), 64);
){
char[] buf = new char[32];
//用以保存上次读取的字符串内容
String lastContent="";
int hasRead=0;
while ((hasRead=pr.read(buf))>0){
//将读取的内容转换为字符串
String content = new String(buf, 0, hasRead);
//将上次读取的内容和本次读取的字符串拼起来
//查看是否包含目标字符串
int targetIndex=0;
if ((targetIndex=(lastContent+content).indexOf("new PushbackReader"))>0){
//将两次的内容推回到推回缓冲区
pr.unread((lastContent+content).toCharArray());
//重新定义一个长度为targetIndex的char数组
if(targetIndex>32){
buf=new char[targetIndex];
}
//再次读取指定长度的内容,
pr.read(buf,0,targetIndex);
System.out.println(new String(buf,0,targetIndex));
System.exit(0);
}else {
System.out.println(lastContent);
//将本次内容设为上次读取的内容
lastContent=content;
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
重定向标准输入/输出
在java中默认的标准输出和标准输入分别是屏幕和键盘。
在System类里提供了三个重定向标准输入、输出的方法:
-
static void setErr(PrintStream err)
:重定向“标准”错误输出流; -
static void setIn(InputStream in)
:重定向“标准”输入流; -
static void setOut(PrintStream out)
:重定向“标准”输出流;
实例:
将System.out的输出重定向到文件输出,而不是在屏幕输出;
package org.westos.demo7;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
public class RedirectOut {
public static void main(String[] args) {
try(
//一次性创建PrintStream输出流
PrintStream ps = new PrintStream(new FileOutputStream("out.txt"));
) {
//将标准输出流重定向到ps输出流
System.setOut(ps);
//下面的输出将输出到out.txt文件中
System.out.println("普通字符串");
System.out.println(new RedirectOut());
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
实例:
重定向标准输入,从而可以将System.in重定向到指定文件,而不是键盘输入。
package org.westos.demo7;
import java.io.*;
import java.util.Scanner;
public class RedirectIn {
public static void main(String[] args) {
try (
//一次性创建PrintStream输出流
FileInputStream fis = new FileInputStream("out.txt");
) {
//将标准输入流重定向到ps输出流
System.setIn(fis);
//获取标准输入
Scanner sc = new Scanner(System.in);
//判断是否还有下一行
while (sc.hasNext()) {
System.out.println("键盘输入的内容是:" + sc.next());
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
Java虚拟机读写其他进程的数据
使用Runtime对象的exec()方法可以运行平台上的其他程序,该方法产生一个Process对象,Process对象代表由该Java程序启动的子进程。
Process类提供了如下三个方法用于让程序和其他子进程进行通信。
-
InputStream getErrorStream()
:获取子进程的错误流; -
InputStream getInputStream()
:获取子进程的输入流; -
OutputStream getOutputStream()
:获取子进程的输出流;
实例:
读取其他进程的输出信息
public static void main(String[] args) throws IOException {
//运行javac命令,返回运行该命令的子进程
Process p = Runtime.getRuntime().exec("javac");
try(
//以p进程的错误流创建BufferedReader对象
//这个错误流对本程序是输入流,对p进程则是输出流
BufferedReader br = new BufferedReader(new InputStreamReader(p.getErrorStream()));
)
{
String buff=null;
while ((buff=br.readLine())!=null){
System.out.println(buff);
}
}
}
不仅如此,也可以通过Process的getOutputStream()方法获取向进程输入数据的流,该流对程序来说是输出流,对子进程来说是输入流。
RandomAccessFile
RandomAccessFile是java输入/输出流体系中功能最丰富的文件内容访问类,它提供了众多的方法来访问文件内容。与普通的输入/输出流不同的是RandomAccessFile支持“随机访问”的方式,程序可以直接跳转到文件的任意地方来访问数据。
RandomAccessFile允许自由定位文件记录指针,可以不从开始的地方开始输出。包含的指针用以标识当前读写处的位置,当程序新创建一个RandomAccessFile对象时,该对象文件记录指针位于文件头,当读/写了n个字节后,文件记录指针将会向后移动n个字节。除此之外,RandomAccessFile还可以自由移动记录指针。
RandomAccessFile包含了如下两个方法来操作文件记录指针:
-
long getFilePointer()
:返回文件记录指针的当前位置; -
void seek(long pos)
:将文件记录指针定到pos位置。
RandomAccessFile可以读写文件,所以既包含类似于InputStream的三个read()方法,也包含类似于OutputStream的三个write()方法。
RandomAccessFile类有两个构造器,一个用String参数来指定文件名,一个使用File参数来指定文件本身。创建RandomAccessFile对象时还需要一个参数,该参数具有4个值:
- “r”:以只读的方式打开指定文件。如果试图向其中写入数据则引发IO异常;
- “rw”:以读写的方式打开指定文件。
- “rws”:以读写的方式打开指定文件,与上面的参数不同的是,还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备。
- “rwd”:同上;
实例:
用RandomAccessFile来访问指定的中间部分数据:
package org.westos.demo7;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
public class RandomAccessFileTest {
public static void main(String[] args) {
try(
RandomAccessFile r = new RandomAccessFile("RandomAccessFileTest.java", "r");
) {
//获取RandomAccessFile对象文件指针位置,初始位置是0
System.out.println("文件指针的初始位置:"+r.getFilePointer());
//移动r的文件记录指针位置
r.seek(300);
byte[] buf = new byte[1024];
int hasRead=0;
while ((hasRead=r.read(buf))>0){
System.out.println(new String(buf,0,hasRead));
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
实例:
向指定文件后追加内容,程序先将指针移动到最后位置,然后开始向文件中输出内容。
package org.westos.demo7;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
public class AppendContent {
public static void main(String[] args) {
try (
RandomAccessFile raf = new RandomAccessFile("out.txt","rw");
)
{
//将记录指针移动到文件最后位置
raf.seek(raf.length());
raf.write("追加的内容 \r\n".getBytes());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
RandomAccessFile依然不能向文件的指定位置插入内容,如果直接将文件记录指针移动到中间某位置后开始输入,则新的输出内容会覆盖文件中原有的内容,如果需要向文件中间插入内容,则需要将插入点后面的内容读入缓冲区,等把需要插入的内容插入到文件后,再将缓冲区的内容读取出来追加到文件后面。
实例:
实现向指定文件、位置插入内容的功能:
package org.westos.demo7;
import java.io.*;
public class InsertContent {
public static void insert(String filename,long pos,String insertContent) throws IOException {
File tmp = File.createTempFile("tmp", null);
//在程序退出时文件被删除
tmp.deleteOnExit();
try (
RandomAccessFile raf = new RandomAccessFile(filename, "rw");
//使用临时文件来保存插入点后的数据
FileOutputStream tmpOut = new FileOutputStream(tmp);
FileInputStream tmpIn = new FileInputStream(tmp);
){
raf.seek(pos);
//下面代码将插入后的内容读入临时文件中保存
byte[] tmpBuf = new byte[64];
int hasRead=0;
while ((hasRead=raf.read(tmpBuf))>0){
tmpOut.write(tmpBuf,0,hasRead);
}
//插入内容
raf.seek(pos);
//需要插入的内容
raf.write(insertContent.getBytes());
//追加临时文件中的内容
while ((hasRead=tmpIn.read(tmpBuf))>0){
raf.write(tmpBuf,0,hasRead);
}
}
}
public static void main(String[] args) throws IOException {
insert("InsertContent.java",45,"插入的内容! \r\n");
}
}
实现:断点复制文件,正在复制的文件由于出现故障中断了复制,再次复制的时候,从上一次断点位置开始复制。
package org.westos.demo7;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
public class InterruptCopy {
public static void main(String[] args) {
try (
RandomAccessFile file = new RandomAccessFile("yinyue.mp3", "rw");
RandomAccessFile file2 = new RandomAccessFile("yinyue2.mp3", "rw");
){
//将指针移到两个文件上次中断的位置
file.seek(file2.length());
file2.seek(file2.length());
byte[] buf = new byte[1024];
int hasRead=0;
System.out.println(file.read(buf));
while ((hasRead=file.read(buf))>0){
file2.write(buf,0,hasRead);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}