Java之IO流
1 IO流
Java中I/O操作主要是指使用Java进行输入,输出操作,即设备之间的数据传输。
1.1 File类
File类是java.io包下的,对文件和目录操作,包括文件的创建,删除,修改等操作。但 File 不能访问文件内容本身,如果需要访问文件内容本身,则需要使用输入/输出流。
想要在Java程序中表示一个真实存在的文件或目录,那么必须有一个File对象,但是Java程序中的一个File对象,可能没有一个真实存在的文件或目录。 File对象可以作为参数传递给流的构造器 。
File 类常用构造器如下:
构造器 | 描述 |
public File(String pathname) | 以pathname为路径创建File对象,可以是绝对路径或者相对路径,如果pathname是相对路径,则默认的当前路径在系统属性user.dir中存储。 |
public File(String parent,String child) | 以parent为父路径,child为子路径创建File对象。 |
public File(File parent,String child) | 根据一个父File对象和子文件路径创建File对象 |
常用API如下:
方法 | 描述 |
String getName() | 返回文件名或目录名 |
String getPath() | 返回路径名字符串 |
File getAbsoluteFile() | 返回绝对路径名的File对象 |
String getAbsolutepath() | 返回绝对路径名的字符串 |
String getParent() | 返回上一次路径名字符串 |
boolean renameTo(File newName) | 重命名次路径表示的文件(调用的对象必须要存在,并且在同一磁盘下) |
boolean exists() | 判断文件或目录 是否存在 |
boolean canWrite() | 是否可写 |
boolean canRead() | 是否可读 |
boolean isFile() | 是否是一个文件 |
boolean isDirectory() | 是否是一个目录 |
long lastModified() | 最后的修改时间 |
long length() | 文件的大小 |
boolean createNewFile() | 创建新文件 |
boolean delete() | 删除文件 |
boolean mkDir() | 创建目录,上级目录 必须存在 |
boolean mkDirs() | 创建目录,上级目录 可以不存在 |
String[] list() | 遍历当前目录下的文件返回文件的名称 |
File[] listFiles() | 遍历当前目录 下的文件返回的文件对象 |
注意:路径分隔符和系统有关,windows和DOS系统默认使用“\”来表示,UNIX和URL使用“/”来表示。为了解决这个隐患,File类提供了一个常量:public static final String separator。根据操作系统,动态的提供分隔符。 如:File file = new File("d:" + File.separator + "study" + File.separator + "hello.txt");
1.2 IO流原理
I/O技术是非常实用的技术,用于处理设备之间的数据传输。如读/写文件,网络通讯等。 所有的I/O机制都是基于数据流进行输入输出,这些数据流表示了字符或者字节数据的流动序列。IO又分为流IO(java.io)和块IO(java.nio)。
注意:磁盘到内存(是输入操作input),内存到磁盘(是输出操作output)。
1.3 IO流分类
(1)按操作数据单位分:字节流8bit(一般操作非文本文件),1byte = 8bit,字符流16bit(一般操作文本文件),1char = 2byte = 16bit;
(2)按流向分:输入流(磁盘到内存),输出流(内存到磁盘);
(3)按角色不同分:节点流(4),处理流(N)。
节点流:直接从数据源或目的地读写数据。处理流:不能直接连接到数据源或目的地,而是连接已经存在的流(可以是节点流或处理流)之上,通过对数据的处理为程度提供更强大的读写能力。
常用的几个IO流如下
抽象父类 | 节点类 | 缓冲流 |
InputStream(二进制格式操作) | FileInputStream | BufferedInputStream |
OutputStream(二进制格式操作) | FileOutputStream | BufferedOutputStream |
Reader(文件格式操作) | FileReader | BufferedReader |
Writer(文件格式操作) | FileWriter | BufferedWriter |
1.4 节点流
节点流:可以从或向一个特定的地方(节点)读写数据。
(1)FileInputStream与OutputStream。
InputStream常用方法
int read() | 从输入流中读取数据的下一个字节。返回 0 到 255 范围内的 int 字节值。如果因为已经到达流末尾而没有可用的字节,则返回值 -1 |
int read(byte[] b) | 从此输入流中将最多 b.length 个字节的数据读入一个 byte 数组中。如果因为已经到达流末尾而没有可用的字节,则返回值 -1。否则以整数形式返回实际读取的字节数。 |
int read(byte[] b, int off,int len) | 将输入流中最多 len 个数据字节读入 byte 数组。尝试读取 len 个字节,但读取的字节也可能小于该值。以整数形式返回实际读取的字节数。如果因为流位于文件末尾而没有可用的字节,则返回值 -1 |
close() | 关闭此输入流并释放与该流关联的所有系统资源。 |
OutputStream常用方法
void write(int c) | 将指定的字节写入此输出流。write 的常规协定是:向输出流写入一个字节。要写入的字节是参数 b 的八个低位。b 的 24 个高位将被忽略。即写入0~255范围的。 |
void write(byte[] b) | 将b.length 个字节从指定的 byte 数组写入此输出流。write(b) 的常规协定是:应该与调用 write(b, 0, b.length) 的效果完全相同。 |
void write(byte[] b,int off,int len) | 将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此输出流。 |
void flush() | 刷新此输出流并强制写出所有缓冲的输出字节,调用此方法指示应将这些字节立即写入它们预期的目标。 |
close() | 关闭此输出流并释放与该流关联的所有系统资源。 |
//文件的复制
@Test
public void copy() {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
//创建输入流
fis = new FileInputStream(new File("jdbc.properties"));
//创建输出流
fos = new FileOutputStream("copy.txt");
//创建字节数组
byte[] buffer = new byte[1024];
int len;
while((len = fis.read(buffer))!=-1) {
fos.write(buffer, 0, len);
}
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
if(fos!=null) {
}
if(fis!=null) {
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
(2)FileReader和FileWriter是先文件的复制
Reader常用方法
int read() | 读取单个字符。作为整数读取的字符,范围在 0 到 65535 之间 (0x00-0xffff)(2个字节的Unicode码),如果已到达流的末尾,则返回 -1 |
int read(char[] cbuf) | 将字符读入数组。如果已到达流的末尾,则返回 -1。否则返回本次读取的字符数。 |
int read(char[] cbuf,int off,int len) | 将字符读入数组的某一部分。存到数组cbuf中,从off处开始存储,最多读len个字符。如果已到达流的末尾,则返回 -1。否则返回本次读取的字符数。 |
close() | 关闭此输入流并释放与该流关联的所有系统资源。 |
Writer 常用方法
void write(int c) | 写入单个字符。要写入的字符包含在给定整数值的 16 个低位中,16 高位被忽略。 即 写入0 到 65535 之间的Unicode码。 |
void write(char[] cbuf) | 写入字符数组。 |
void write(char[] cbuf,int off,int len) | 写入字符数组的某一部分。从off开始,写入len个字符 |
void write(String str) | 写入字符串。 |
void write(String str,int off,int len) | 写入字符串的某一部分。 |
void flush() | 刷新该流的缓冲,则立即将它们写入预期目标。 |
close() | 关闭此输出流并释放与该流关联的所有系统资源。 |
@Test
public void copy2() {
FileReader fr = null;
FileWriter fw = null;
try {
//创建输入流
fr = new FileReader(new File("jdbc.properties"));
//创建输出流
fw = new FileWriter("copy.txt");
//创建字符数组
char[] buffer = new char[1024];
int len;
while((len = fr.read(buffer))!=-1) {
fw.write(buffer, 0, len);
}
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
if(fw!=null) {
fw.close();
}
if(fr!=null) {
fr.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
1.5 缓冲流
缓冲流是处理流的一种,建立在相应的节点流之上,对读写的数据提供了缓冲的功能,提高了效率,还增加了一些新的方法。BufferedReader新增了readline()用于此意读取一行字符串,以\r或\n认为一行结束,BufferedWriter新增了newLine()用于写出一个行分隔符。
注意:①对于缓冲输出流,写出的数据会先缓存在内存缓冲区中,关闭流前要用flush()将缓冲区的数据立刻写出。②关闭流的顺序和打开流的顺序相反。只要关闭最外层流即可,关闭最外层流也会相应关闭内层节点流③如果是带缓冲区的流对象的close()方法,不但会关闭流,还会在关闭流之前刷新缓冲区,关闭后不能再写出
//缓冲流操作字节
@Test
public void copy3() {
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
bis = new BufferedInputStream(new FileInputStream(new File("copy.txt")));
bos = new BufferedOutputStream(new FileOutputStream(new File("copy.txt2")));
byte[] buffer = new byte[1024];
int len;
while((len=bis.read(buffer))!=-1) {
bos.write(buffer,0,len);
//刷新缓冲
bos.flush();
}
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
if(bos!=null) {
bos.close();
}
if(bis!=null) {
bis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
//缓冲流操作字符
@Test
public void copy4() {
BufferedReader br = null;
BufferedWriter bw = null;
try {
br = new BufferedReader(new FileReader(new File("copy.txt")));
bw = new BufferedWriter(new FileWriter(new File("copy.txt1")));
String len;
while((len=br.readLine())!=null) {
bw.write(len);
//换行
bw.newLine();
//刷新缓冲
bw.flush();
}
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
if(bw!=null) {
bw.close();
}
if(br!=null) {
br.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
1.6 转换流
InputStreamReader:将字节输入流转换为字符输入流(字节流转向字符流),可以读取不同编码格式的文件 需要使用到 FileInputStream类。
OutputStreamWriter:将字符输出流转换为字节输出流(字符流转向字节流),字符流转向字节流 可以使用不同编码格式写入 需要使用到FileOutStream类。
使用标准输入输出流实现Scanner,标准的输入流:System.in,标准的输出流:System.out。
很多时候使用转换流来处理文件乱码问题,实现编码和解码的功能。
public class TestMyScanner {
public static void main(String[] args) {
//接收键盘输入字符信息,并且打印
System.out.println("请输入信息");
MyScanner mc = new MyScanner(System.in);
System.out.println(mc.nextString());
}
}
class MyScanner {
InputStream is;
public MyScanner(InputStream is) {
this.is= is;
}
//接收输入字符串
public String nextString() {
//将输入的字节流装换成字符流
InputStreamReader isr = new InputStreamReader(is);
//将字符流装换成缓冲流
BufferedReader br = new BufferedReader(isr);
String len =null;
try {
len = br.readLine();
} catch (IOException e) {
e.printStackTrace();
}
return len;
}
//接收int
public int nextInt() {
int result = Integer.parseInt(nextString());
return result;
}
}
1.7 对象流
对象流的主要作用就是将Java对象序列化(持久化),并保存到本地磁盘文件,或者将磁盘文件反序列化成Java对象。它的强大之处就是可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来。
对象流:ObjectInputStrem和ObjectOutputStrem
序列化:对象信息存储到文件中这个过程叫序列化;
反序列化:从文件中将信息返回给对象的过程叫反序列化。
注意:(1)所保存的对象必须实现Serializable接口,所保存的对象的属性也必须实现Serializable接口;
(2)最好要给该对象提供一个版本号,private static final long serialVersionId;
(3)Static , transient修饰的属性不能序列化的。
public class TestObjectIO {
//序列化操作
@Test
public void serialization() throws FileNotFoundException, IOException {
Person2 p = new Person2("小明",18,new Account(100.0));
Person2 p1 = new Person2("小红",18,new Account(200.0));
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("E:\\test\\object.obj")));
oos.writeObject(p);
oos.writeObject(p1);
}
//反序列化操作
@Test
public void deserialization() throws FileNotFoundException, IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("E:\\test\\object.obj")));
Person2 p = (Person2) ois.readObject();
System.out.println(p);
Person2 p1 = (Person2) ois.readObject();
System.out.println(p1);
}
}
class Person2 implements Serializable{
private static final long serialVersionUID = -4043673773479922618L;
private String name;
private Integer age;
private Account ac;
public Person2(String name, Integer age, Account ac) {
super();
this.name = name;
this.age = age;
this.ac = ac;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + ", ac=" + ac + "]";
}
}
class Account implements Serializable {
private static final long serialVersionUID = -4043673773479922617L;
private double balance;
public Account(double balance) {
super();
this.balance = balance;
}
@Override
public String toString() {
return "Account [balance=" + balance + "]";
}
}
1.8 数据操作流
java中提供了两个与平台无关的数据操作流:(1)DataInputStream(继承自FillterInputStream类,同时实现DataOutput接口):数据输入流(2)DataOutputStream(继承自FillterOutputStream类,同时实现DataOutput接口):数据输出流。数据操作流通常按照一定格式将数据输出,再按照一定格式将数据输入。
注意:DataOutput接口和DataInput接口。两个数据操作流的方法都是继承两个接口的方法,这两个接口的操作,彼此对应,而且以后还要使用。
DataInputStream:boolean readBoolean() ,byte readByte(),char readChar() ,float readFloat(),double readDouble() ,short readShort(),long readLong() ,int readInt(),String readUTF() 读字符串 ,void readFully(byte[] b) 读字节数组 。
public class TestDataIO {
@Test
public void dataOutputStream() throws Exception {
DataOutputStream dos = new DataOutputStream(new FileOutputStream(new File("E:\\test\\a.txt")));
try {
dos.writeUTF("中国");
dos.writeInt(5);
dos.writeChar('a');
dos.writeBoolean(true);
} catch (IOException e) {
e.printStackTrace();
}
}
@Test
public void dataInputStream() throws FileNotFoundException {
DataInputStream dis = new DataInputStream(new FileInputStream(new File("E:\\test\\a.txt")));
try {
System.out.println(dis.readUTF());
System.out.println(dis.readInt());
System.out.println(dis.readChar());
System.out.println(dis.readBoolean());
} catch (IOException e) {
e.printStackTrace();
}
}
}
1.9 打印流
在整个IO包中,打印流是输出信息最方便的类,打印流提供了非常方便的打印功能,主要包含字节打印流PrintStream和字符打印流PrintWriter,可以打印任何类型的数据。
常用构造方法PrintStream(File file),PrintStream(OutputStream out)等,PrintStream常用方法如下:
方法 | 描述 |
PrintStream printf(Locale l, String format, Object... args) | 根据指定的locale进行格式化输出 |
PrintStream printf(String format, Object... args) | 根据本地环境格式化输出 |
void print(Object obj) | 输出任意类型 |
void println(Object obj) | 输出任意类型后换行 |
使用打印流有时会比较方便,比如不用像OutputStream那样输出字符串的时候还需要将String转成字节数组再输出。创建打印流的时候通过直接接收OutputStream的构造方法,将OutputStream重新包装了一下,使得输出更加方便。
格式化输出的需要指出其数据类型,如下表。
字符 | 描述 |
%s | 表示内容为字符串 |
%d | 表示内容为整型 |
%f | 表示内容为小数 |
%c | 表示内容为字符 |
public class TestPrintStream {
public static void main(String[] args) {
PrintStream ps =null;
try {
ps = new PrintStream(new BufferedOutputStream(new FileOutputStream(new File("printStream.txt"))));
ps.println("打印流");
ps.println(66.666);
String name = "refuel";
int age = 18;
double income = 100000.0;
char gender = 'M';
ps.printf("姓名:%s;年龄:%d;收入:%f;性别:%c",name,age,income,gender);
} catch (FileNotFoundException e) {
e.printStackTrace();
}finally {
if(ps!=null) {
ps.close();
}
}
}
}
/*
* 打印流
* 66.666
* 6姓名:refuel;年龄:18;收入:100000.000000;性别:M
*/
总结:PrintStream 可以方便的完成输出的功能,所以输出都可以用它,PrintStream 属于装饰设计模式。
1.10 随机输入输出流
随机输入输出流RandomAccessFile:(1)该类可以作为输入,也可以输出,可以实现修改内容(替换、插入)。(2)与普通的输入输出流不一样的是RamdomAccessFile可以任意的访问文件的任何地方。
RandomAccessFile的对象包含一个记录指针,用于标识当前流的读写位置,这个位置可以向前移动,也可以向后移动。有两个方法操作这个指针:(1)long getFilePoint():记录文件指针的当前位置。(2)void seek(long pos):将文件记录指针定位到pos位置。
RandomAccessFile操作文件的4种模式的mode的值代表读写方式,如下:
值 | 描述 |
r | 以只读方式打开文件,如果写入会报IO异常 |
ws | 以读写方式打开指定文件,不存在就创建新文件 |
rws | 除了‘rw‘功能以外,文件内容或者元数据更新时一同写入 |
rwd | 除了‘rw‘功能以外,文件内容更新时一同写入 |
public class TestRandomAccess {
//abcxydef 插入
@Test
public void test3() throws Exception {
RandomAccessFile raf = new RandomAccessFile(new File("RandomAccess.txt"), "rw");
raf.seek(3); //将光标移3位置
String str=raf.readLine(); //光标放最后 def
raf.seek(3);
raf.write("xy".getBytes()); //替换abcxy f
raf.write(str.getBytes()); //abcxydef
raf.close();
}
//abcdef 替换
@Test
public void test2() throws Exception {
RandomAccessFile raf = new RandomAccessFile(new File("RandomAccess.txt"), "rw");
raf.seek(3); //将光标移3位置
raf.write("xy".getBytes()); //替换 abcxyf
raf.close();
}
//通过随机访问流实现文件的复制
@Test
public void test1() throws Exception {
RandomAccessFile raf1 = new RandomAccessFile(new File("RandomAccess1.txt"), "r");
RandomAccessFile raf2 = new RandomAccessFile(new File("RandomAccess2.txt"), "rw");
byte[] b =new byte[5];
int len;
while((len=raf1.read(b))!=-1) {
raf2.write(b, 0, len);
}
raf2.close();
raf1.close();
}
}