Java之IO

IO(Input Output)流,用来处理设备之间的数据传输

IO

IO概述

Java对数据的操作是通过流的方式
Java用于操作流的对象都在IO包中
流按操作数据分为两种:字节流与字符流
流按流向分为:输入流,输出流

最开始只有字节流,这符合计算机底层的存储原理,后来为了文本的操作方便开始引入字符流

IO流常用基类

字节流的抽象基类:

  • InputStream
  • OutputStream

字符流的抽象基类:

  • Reader
  • Writer

注:由这四个类派生出来的子类名称都是以其父类名作为子类名的后缀。如:InputStream的子类FileInputStream,Reader的子类FileReader,后缀名是父类名。 前缀名是该流对象的功能

IO程序的书写

  • 导入IO包中的类
  • 进行IO异常处理
  • 在finally中对流进行关闭

字符流

字符流——创建文件(FileWriter)

FileWriter构造方法
FileWriter(File file) 根据给定的 File 对象构造一个 FileWriter 对象
FileWriter**(File file, boolean append) 根据给定的 File 对象构造一个 FileWriter 对象
FileWriter(FileDescriptor fd) 构造与某个文件描述符相关联的 FileWriter 对象
FileWriter(String fileName) 根据给定的文件名构造一个 FileWriter 对象
FileWriter(String fileName, boolean append) 根据给定的文件名以及指示是否附加写入数据的 boolean 值来构造 FileWriter 对象
  • 创建流对象,建立数据存放文件
    FileWriter fw = new FileWriter("Test.txt");
  • 调用流对象的写入方法,将数据写入流
    fw.write("text");
  • 关闭流资源,并将流中的数据清空到文件中
    fw.close();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
FileWriter fw = null;
try{
fw = new FileWriter("Test.txt");
fw.write("text");
} catch (IOException e){
System.out.println(e.toString());
} finally{
If(fw != null)
try{
fw.close();
} catch (IOException e){
System.out.println(e.toString());
}
}

当需要在已有文件追加内容时,使用FileWriter(filename, true)来写入文件
注:

  • 创建一个FileWriter对象,该对象一被初始化就必须要明确被操作的文件,而且该文件会被创建到指定目录下
  • 如果该目录下已有同名文件,将被覆盖
  • 关闭流资源,但是关闭之前会刷新一次内部的缓冲中的数据,将数据刷到目的地中
  • close和flush区别:flush刷新后,流可以继续使用,close刷新后,会将流关闭

字符流——读取文件(FileReader)

FileReader构造方法
FileReader(File file) 在给定从中读取数据的 File 的情况下创建一个新 FileReader
FileReader(FileDescriptor fd) 在给定从中读取数据的 FileDescriptor 的情况下创建一个新 FileReader
FileReader(String fileName) 在给定从中读取数据的文件名的情况下创建一个新 FileReader
  • read()方法用于读取一个字符,每次读完会自动后移一位,当文件读取完成,返回-1
  • read(buf)方法用于将数组读入数组,返回读到的字符长度,如果一次没读取完,下一次再调用继续读取后面的字符,当返回-1代表文件读取完毕

  • 建立一个流对象,将已存在的一个文件加载进流
    FileReader fr = new FileReader(“Test.txt”);

  • 创建一个临时存放数据的数组
    char[] ch = new char[1024];
  • 调用流对象的读取方法将流中的数据读入到数组中
    fr.read(ch);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
FileReader fr = null;
try{
fr = new FileReader("c:\\test.txt");
char[] buf = new char[1024];
int len= 0;
while((len = fr.read(buf)) != -1){
System.out.print(new String(buf,0,len));
}
} catch (IOException e){
System.out.println("read-Exception :" + e.toString());
} finally{
if(fr != null){
try{
fr.close();
} catch (IOException e){
System.out.println("close-Exception :" + e.toString());
}
}
}

文件复制的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import java.io.*;
class Text {
public static void main(String[] args) throws IOException {
MyCopy();
}

public static void MyCopy() {
FileWriter fw = null;
FileReader fr = null;
try {
fw = new FileWriter("Demo_copy.txt");
fr = new FileReader("Demo.java");

char[] buf = new char[1024];

int len = 0;
while((len=fr.read(buf))!=-1) {
fw.write(buf,0,len);
}
} catch (IOException e) {
throw new RuntimeException("读写失败");
} finally {
if(fr != null)
try {
fr.close();
} catch (IOException e) {
}
if(fw != null)
try {
fw.close();
} catch (IOException e) {
}
}
}
}

注意事项

  • 定义文件路径时,可以用”/“或者”\\
  • 在创建一个文件时,如果目录下有同名文件将被覆盖
  • 在读取文件时,必须保证该文件已存在,否则出异常

字符流的缓冲区

  • 缓冲区的出现提高了对数据的读写效率
  • 缓冲区要结合流才可以使用
  • 在流的基础上对流的功能进行了增强
  • 关闭缓冲区,就是在关闭缓冲区中的流对象

具备缓冲区的字符流

  • BufferedWriter
    用到缓冲区,就要使用flush()刷新
    该缓冲区中提供了一个跨平台的换行符:newLine();
  • BufferedReader
    该缓冲区提供了一个一次读一行的方法 readLine,方便于对文本数据的获取,当返回null时,表示读到文件末尾
    readLine方法返回的时候只返回回车符之前的数据内容,并不返回回车符
  • LineNumberReader
    继承自BufferedReader,可以实现行号的输出getLineNumber(),还可以设置行号setLineNumber()

使用缓冲区复制文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import java.io.*;

class Text {
public static void main(String[] args) {
BufferedReader bufr = null;
BufferedWriter bufw = null;
try {
bufr = new BufferedReader(new FileReader("Demo.java"));
bufw = new BufferedWriter(new FileWriter("Demo_copy.txt"));

String line = null;
while((line = bufr.readLine()) != null) {
bufw.write(line);
bufw.newLine();
bufw.flush();

}
} catch (IOException e) {
throw new RuntimeException("读写失败");
} finally {
try {
if(bufr != null)
bufr.close();
} catch (IOException e) {
throw new RuntimeException("读取关闭失败");
}
try {
if(bufw != null)
bufw.close();
} catch (IOException e) {
throw new RuntimeException("写入关闭失败");
}
}
}
}

字节流

基本操作与字符流类相同
但它不仅可以操作字符,还可以操作其他媒体文件
而字符流不可以操作媒体文件

  • InputStream
  • OutputStream

字节流示例

e.g.(复制图片)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import java.io.*;
class Test {
public static void main(String[] args) {
FileOutputStream fos = null;
FileInputStream fis = null;
try {
fos = new FileOutputStream("c:\\2.bmp");
fis = new FileInputStream("c:\\1.bmp");
byte[] buf = new byte[1024];
int len = 0;
while((len = fis.read(buf)) != -1) {
fos.write(buf,0,len);
}
} catch (IOException e) {
throw new RuntimeException("复制文件失败");
} finally {
try {
if(fis != null)
fis.close();
} catch (IOException e) {
throw new RuntimeException("读取关闭失败");
}
try {
if(fos!=null)
fos.close();
} catch (IOException e) {
throw new RuntimeException("写入关闭失败");
}
}
}
}

字节流的缓冲区

同样是提高了字节流的读写效率

  • BufferedInputStream:类似于BufferedReader
  • BufferedOutputStream:类似于BufferedWrite
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public static void copy()throws IOException {
    BufferedInputStream bufis = new BufferedInputStream(new FileInputStream("c:\\0.mp3"));
    BufferedOutputStream bufos = new BufferedOutputStream(new FileOutputStream("c:\\1.mp3"));
    int by = 0;
    while((by = bufis.read()) != -1) {
    bufos.write(by);
    }
    bufos.close();
    bufis.close();
    }

转换流

转换流

  • InputStreamReader:字节流通向字符流的桥梁
  • OutputStreamWriter:字符流通向字节流的桥梁
    转换流的由来
  • 字符流与字节流之间的桥梁
  • 方便了字符流与字节流之间的操作

转换流的应用

  • 字节流中的数据都是字符时,转成字符流操作更高效

标准输入输出流

System类中的字段:in,out
它们各代表了系统标准的输入和输出设备
默认输入设备是键盘,输出设备是显示器
System.in的类型是InputStream
System.out的类型是PrintStream是OutputStream的子类FilterOutputStream的子类

标准输入输出流示例

例:获取键盘录入数据,然后将数据流向显示器,那么显示器就是目的地

  • 通过System类的setIn,setOut方法对默认设备进行改变
    System.setIn(new FileInputStream(“1.txt”));//将源改成文件1.txt
    System.setOut(new FileOutputStream(“2.txt”));//将目的改成文件2.txt
  • 因为是字节流处理的是文本数据,可以转换成字符流,操作更方便
    BfferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
    BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));

转换流示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import java.io.*;

class Test {
public static void main(String[] args) throws IOException {
//获取键盘录入对象。
//InputStream in = System.in;
//将字节流对象转成字符流对象,使用转换流。InputStreamReader
//InputStreamReader isr = new InputStreamReader(in);
//为了提高效率,将字符串进行缓冲区技术高效操作。使用BufferedReader
//BufferedReader bufr = new BufferedReader(isr);

//键盘的最常见写法。
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));
String line = null;
while((line = bufr.readLine()) != null) {
if("over".equals(line))
break;
bufw.write(line);
bufw.newLine();
bufw.flush();
}
bufr.close();
}
}

字符流与字节流的使用明确

  • 明确源和目的
    源:输入流。InputStream Reader
    目的:输出流。OutputStream Writer

  • 操作的数据是否是纯文本
    是:字符流
    不是:字节流

File类

  • 用来将文件或者文件夹封装成对象
  • 方便对文件与文件夹的属性信息进行操作
  • File对象可以作为参数传递给流的构造函数

构造方法

  • File(File parent, String child)
    根据 parent 抽象路径名和 child 路径名字符串创建一个新 File 实例
  • File(String pathname)
    通过将给定路径名字符串转换为抽象路径名来创建一个新 File 实例
  • File(String parent, String child)
    根据 parent 路径名字符串和 child 路径名字符串创建一个新 File 实例
  • File(URI uri)
    通过将给定的 file: URI 转换为一个抽象路径名来创建一个新的 File 实例

常用方法

  • 创建

    1
    2
    3
    boolean createNewFile(); //在指定位置创建文件,如果该文件已经存在,则不创建,返回false
    boolean mkdir(); //创建文件夹
    boolean mkdirs(); //创建多级文件夹
  • 删除

    1
    2
    boolean delete(); //删除失败返回false。如果文件正在被使用,则删除不了返回falsel。
    void deleteOnExit(); //在程序退出时删除指定文件。
  • 判断

    1
    2
    3
    4
    5
    boolean exists(); //文件是否存在
    isFile();
    isDirectory();
    isHidden();
    isAbsolute();
  • 获取信息

    1
    2
    3
    4
    5
    6
    getName();
    getPath();
    getParent();
    getAbsolutePath();
    long lastModified();
    long length();

FILE的递归操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.io.*;
class Test {
public static void main(String[] args) {
File dir = new File("d:\\testdir");
removeDir(dir);
}

public static void removeDir(File dir) {
File[] files = dir.listFiles();
for(int x = 0; x < files.length; x++) {
if(files[x].isDirectory())
removeDir(files[x]);
else
System.out.println(files[x].toString() + ":-file-:" + files[x].delete());
}
System.out.println(dir + "::dir::" + dir.delete());
}
}

打印流

该流提供了打印方法,可以将各种数据类型的数据都原样打印

  • 字节打印流:PrintStream
    构造函数可以接收的参数类型:
    1,file对象。File
    2,字符串路径。String
    3,字节输出流。OutputStream

  • 字符打印流:PrintWriter
    构造函数可以接收的参数类型:
    1,file对象。File
    2,字符串路径。String
    3,字节输出流。OutputStream
    4,字符输出流,Writer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.io.*;

class Test {
public static void main(String[] args) throws IOException {
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
PrintWriter out = new PrintWriter(new FileWriter("a.txt"), true);
String line = null;
while((line = bufr.readLine()) != null) {
if("over".equals(line))
break;
out.println(line);
//out.flush();
}
out.close();
bufr.close();
}
}

序列流(合并流)

将多个流合并成一个流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.io.*;
import java.util.*;
class Test {
public static void main(String[] args) throws IOException {
Vector<FileInputStream> v = new Vector<FileInputStream>();
v.add(new FileInputStream("c:\\1.txt"));
v.add(new FileInputStream("c:\\2.txt"));
v.add(new FileInputStream("c:\\3.txt"));
Enumeration<FileInputStream> en = v.elements();
SequenceInputStream sis = new SequenceInputStream(en);
FileOutputStream fos = new FileOutputStream("c:\\4.txt");
byte[] buf = new byte[1024];
int len = 0;
while((len = sis.read(buf)) != -1) {
fos.write(buf, 0, len);
}
fos.close();
sis.close();
}
}

Properties

Properties概述

  • Properties是hashtable的子类,也就是说它具备map集合的特点。而且它里面存储的键值对都是字符串
  • 是集合中和IO技术相结合的集合容器
  • 该对象的特点:可以用于键值对形式的配置文件
  • 那么在加载数据时,需要数据有固定格式:键 = 值

Properties使用示例

从文本中加载键值对

1
2
3
4
5
6
7
8
9
BufferedReader bufr = new BufferedReader(new FileReader("info.txt"));
Properties prop = new Properties();
String line = null;
while((line = bufr.readLine()) != null) {
String[] arr = line.split("=");
prop.setProperty(arr[0],arr[1]);
}
bufr.close();
System.out.println(prop);

Properties可以直接从流中获取数据,修改并存储

1
2
3
4
5
6
7
8
9
Properties prop = new Properties();
FileInputStream fis = new FileInputStream("info.txt");
prop.load(fis); //将流中的数据加载进集合
prop.setProperty("testKey","testValue");
FileOutputStream fos = new FileOutputStream("info.txt");
prop.store(fos,"note");
prop.list(System.out);
fos.close();
fis.close();

打印对象

ObjectInputStream与ObjectOutputStream
被操作的对象需要实现Serializable(标记接口)
静态属不能被序列化
要使改变过后的类还能使用以前的序列化,就需要自定义序列ID
不想序列化的成员加上transient关键字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.io.*;
class Person implements Serializable {

public static final long serialVersionUID = 42L;
private String name;
transient int age;
static String country = "cn";

Person(String name, int age, String country) {
this.name = name;
this.age = age;
this.country = country;
}

public String toString() {
return name + ":" + age + ":" + country;
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.io.*;
class Test {

public static void main(String[] args) throws Exception {
//writeObj();
readObj();
}

public static void readObj()throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("obj.object"));
Person p = (Person)ois.readObject();
System.out.println(p);
ois.close();
}

public static void writeObj()throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("obj.object"));
oos.writeObject(new Person("name",0,"cn"));
oos.close();
}
}

管道流

PipedInputStream和PipedOutputStream
输入输出可以直接进行连接,通过结合线程使用
使用单线程时,容易造成死锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import java.io.*;

class Read implements Runnable {
private PipedInputStream in;
Read(PipedInputStream in) {
this.in = in;
}
public void run() {
try {
byte[] buf = new byte[1024];
int len = in.read(buf);
String s= new String(buf, 0, len);
System.out.println(s);
in.close();
} catch (IOException e) {
throw new RuntimeException("管道读取流失败");
}
}
}

class Write implements Runnable {
private PipedOutputStream out;
Write(PipedOutputStream out) {
this.out = out;
}
public void run() {
try {
out.write("Test Datas".getBytes());
out.close();
} catch (Exception e) {
throw new RuntimeException("管道输出流失败");
}
}
}

class Test {
public static void main(String[] args) throws IOException {

PipedInputStream in = new PipedInputStream();
PipedOutputStream out = new PipedOutputStream();
in.connect(out); //连接管道
Read r = new Read(in);
Write w = new Write(out);
new Thread(r).start();
new Thread(w).start();
}
}

RandomAccessFile

该类不是算是IO体系中子类
而是直接继承自Object

但是它是IO包中成员。因为它具备读和写功能
内部封装了一个数组,而且通过指针对数组的元素进行操作
可以通过getFilePointer获取指针位置,同时可以通过seek改变指针的位置

其实完成读写的原理就是内部封装了字节输入流和输出流

通过构造函数可以看出,该类只能操作文件
而且操作文件还有模式:只读r,读写rw等

如果模式为只读r:不会创建文件,会去读取一个已存在文件,如果该文件不存在,则会出现异常
如果模式rw:操作的文件不存在,会自动创建,如果存在则不会覆盖

构造方法

RandomAccessFile(File file, String mode)创建从中读取和向其中写入(可选)的随机访问文件流,该文件由 File 参数指定
RandomAccessFile(String name, String mode)创建从中读取和向其中写入(可选)的随机访问文件流,该文件具有指定名称

含意
“r”以只读方式打开。调用结果对象的任何 write 方法都将导致抛出 IOException
“rw”打开以便读取和写入。如果该文件尚不存在,则尝试创建该文件
“rws”打开以便读取和写入,对于 “rw”,还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备
“rwd”打开以便读取和写入,对于 “rw”,还要求对文件内容的每个更新都同步写入到底层存储设备

可以用来实现多线程的下载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import java.io.*;
class Test {
public static void main(String[] args) throws IOException {
writeFile();
//readFile();
}

public static void readFile()throws IOException {
RandomAccessFile raf = new RandomAccessFile("ran.txt","r");

//调整对象中指针。
//raf.seek(8*1);

//跳过指定的字节数
raf.skipBytes(8);

byte[] buf = new byte[4];
raf.read(buf);
String name = new String(buf);
int age = raf.readInt();
System.out.println("name=" + name);
System.out.println("age=" + age);
raf.close();
}

//实现在指定位置写入
public static void writeFile_2()throws IOException {
RandomAccessFile raf = new RandomAccessFile("ran.txt","rw");
raf.seek(8 * 3);
raf.write("王五".getBytes());
raf.writeInt(21);
raf.close();
}

public static void writeFile()throws IOException {
RandomAccessFile raf = new RandomAccessFile("ran.txt","rw");
raf.write("张三".getBytes());
raf.writeInt(20);
raf.write("李四".getBytes());
raf.writeInt(22);
raf.close();
}
}

常见数据类型的操作流

  • 操作基本数据类型
    DataInputStreamDataOutputStream
    存入与读取顺序要相对应

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    import java.io.*;
    class Test {

    public static void main(String[] args) throws IOException {
    //writeData();
    //readData();
    }

    public static void readData() throws IOException {
    DataInputStream dis = new DataInputStream(new FileInputStream("data.data"));
    int num = dis.readInt();
    boolean b = dis.readBoolean();
    double d = dis.readDouble();
    System.out.println("num = " + num);
    System.out.println("b = " + b);
    System.out.println("d = " + d);
    dis.close();
    }

    public static void writeData() throws IOException {
    DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.data"));
    dos.writeInt(123);
    dos.writeBoolean(true);
    dos.writeDouble(876.543);
    dos.close();
    }
    }
  • 操作字节数组
    ByteArrayInputStreamByteArrayOutputStream
    ByteArrayInputStream包含一个内部缓冲区,该缓冲区包含从流中读取的字节。内部计数器跟踪read方法要提供的下一个字节
    ByteArrayOutputStream实现了一个输出流,其中的数据被写入一个byte数组。缓冲区会随着数据的不断写入而自动增长。可使用toByteArray()toString()获取数据
    关闭ByteArrayInputStreamByteArrayOutputStream无效。此类中的方法在关闭此流后仍可被调用,而不会产生任何IOException

ByteArrayInputStream:在构造的时候,需要接收数据源,而且数据源是一个字节数组
ByteArrayOutputStream:在构造的时候,不用定义数据目的,因为该对象中已经内部封装了可变长度的字节数组,
这就是数据目的地。

因为这两个流对象都操作的数组,并没有使用系统资源,所以,不用进行close关闭

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.io.*;
class Test {
public static void main(String[] args) {
//数据源
ByteArrayInputStream bis = new ByteArrayInputStream("ABCDEF".getBytes());
//数据目的
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int by = 0;
while((by = bis.read()) != -1) {
bos.write(by);
}
System.out.println(bos.size());
System.out.println(bos.toString());
}
}
  • 操作字符数组
    CharArrayReaderCharArrayWrite
    与操作字节数组类似

  • 操作字符串
    StringReaderStringWriter
    与操作字节数组类似

Donate comment here