IO流常用基类
IO流按操作数据分为:字节流+字符流
- 字节流的抽象基类:InputStream,OutputStream
- 字符流的抽象基类:Reader,Writer
- 由这四个类派生出来的子类名称都是以其父类名作为子类名的后缀。如:InputStream的子类FileInputStream,Reader的子类FileReader。
- 写的时候用输出流,读的时候用输入流。
FileWriter/FileReader
1.文件写入
字符流创建文件过程:
1.创建流对象,建立数据存放文件(用try-catch包住或者在声明主函数的时候抛出异常)
FileWriter fw = new FileWriter("d:/aa.txt");
2.调用流对象的写入方法,将数据写入流
fw.write("Hello world");
write方法:
public void write(int c) 写入单个字符
public void write(char[] cbuf) 把字符数组写入
public abstract void write(char[] cbuf,int off,int len) 写入字符数组的某一部分
public void write(String str) 写入字符串
public void write(String str,int off,int len) 写入字符串的某一部分
3.关闭流资源,并将流中的数据清空到文件中
fw.close();
package IO;
import java.io.FileWriter;
import java.io.IOException;
public class App {
public static void main(String[] args) {
try {
FileWriter fw = new FileWriter("d:/aa.txt");
fw.write("Hello world");
fw.close();
System.out.println("Over");
} catch (IOException e) {
e.printStackTrace();
}
}
}
考虑健壮性
package IO;
import java.io.FileWriter;
import java.io.IOException;
public class App {
public static void main(String[] args) {
FileWriter fw = null;
try {
fw = new FileWriter("d:/aa.txt");
fw.write("Hello world");
System.out.println("Over");
} catch (IOException e) {
e.printStackTrace();
}
finally{
//释放资源
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
package IO;
import java.io.FileWriter;
import java.io.IOException;
public class App {
public static void main(String[] args) {
FileWriter fw = null;
try {
//默认是覆盖模式(false),改为true变为追加
fw = new FileWriter("d:/aa.txt,",true);
fw.write("Hello world\r\n");
fw.write("How are you\r\n"); // 回车换行
fw.write("我懂了\r\n");
System.out.println("Over");
} catch (IOException e) {
e.printStackTrace();
} finally {
// 释放资源
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
2.文件读取
字符流读取过程:
1.建立一个流对象,将已经存在的文件加载进流
FileReader fr = new FileReader("aa.txt") //路径+文件名
文件路径可以用"/"或者"\\"表示
2.创建一个临时存放数据的数组(也叫缓冲区)
char[] ch = new char[1024];
3.调用流对象的读取方法将流中的数据读入到数组中
fr.read(ch);
read() 读取一个单一字符,返回所读取的字符,如果流已经到达了文件尾,则返回-1。
read(char[] cbuf) 将字符读入数组,cbuf为目标缓冲区,返回的是读取的字符数
package IO;
import java.io.FileReader;
public class FileReaderDemo {
public static void main(String[] args) throws Exception {
//一个一个字符读
FileReader fr = new FileReader("d:/aa.txt");
int i =0;
while( (i = fr.read()) != -1){ //read()读取一个单一字符,返回所读取的字符,如果流已经到达了文件尾,则返回-1。
System.out.print((char)i);
}
fr.close();
//使用char[]缓冲区读取文件
System.out.println("--------------------------");
int len = 0;
fr = new FileReader("d:/aa.txt"); //第九行赋值的fr其指针已经到了尾端,因此需要重新new一下
char[] buf = new char[10];
while((len = fr.read(buf)) != -1){
System.out.print(new String(buf,0,len));
}
fr.close();
}
}
mark (int readAheadLimit) 标记流中的当前位置。对 reset() 的后续调用将尝试将该流重新定位到此点。并不是所有的字符输入 流都支持 mark() 操作。
reset () 重置该流。如果已标记该流,则尝试在该标记处重新定位该流。并不是所有的字符输入流都支持 reset() 操作.
skip (long n) 跳过字符。
但FileReader 不支持mark和reset
3. 利实现文件的复制
package IO;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class CopyFileDemo {
public static void main(String[] args) {
String srcfile = "d:/aa.txt";
String targfile = "d:/bb.txt";
FileReader reader = null;
FileWriter writer = null;
try {
reader = new FileReader(srcfile);
writer = new FileWriter(targfile);
//定义字符缓冲区
char[] buf = new char[1024];
int len = 0;
while((len = reader.read(buf)) != -1){
writer.write(buf, 0, len);
}
System.out.println("over");
}
catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
finally{
try {
reader.close();
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
BufferedWriter/BufferedReader
字符流缓冲区的出现提高了对数据的读写效率,对应类有BufferedWriter和BufferedReader,缓冲区要结合流才可以使用。
BufferedWriter/BufferedReader 支持 mark() 和 reset()
1.BufferedWriter
FileWriter没有缓冲区功能,本身没有新增任何功能,继承OutputStreamWriter。
等级树结构: Object ——> Writer ——> OutputStreamWriter ——> FileWriter
BufferedWriter对Writer进行包装,里面定义缓冲区,提供写入单个字符、char[]、String的效率
等级树结构:Object ——> Writer ——> BufferedWriter
flushBuffer() 清理缓冲区,将缓冲区数据写入到目的地。
BufferedWriter 的 close() 方法中包含 flushBuffer() 方法。
package IO;
import java.io.BufferedWriter;
import java.io.FileWriter;
public class BufferedWriterDemo {
public static void main(String[] args) throws Exception {
//非缓冲
FileWriter writer = new FileWriter("d:/a.txt");
//缓冲
BufferedWriter bufWriter = new BufferedWriter(new FileWriter("d:/b.txt")); //将FileWriter包装
/***********************非缓冲区writer操作************************/
//最大次数
int max = 1000000;
//开始时间
long startTime = System.currentTimeMillis();
for(int i =0 ; i<max ; i++){
writer.write("a");
}
writer.close();
//结束时间
long endTime = System.currentTimeMillis();
System.out.println("writer over:花了" + (endTime-startTime) + "毫秒");
/********************缓冲区writer操作************************/
startTime = System.currentTimeMillis();
for(int i =0 ; i<max ; i++){
bufWriter.write("a");
}
bufWriter.close();
//结束时间
endTime = System.currentTimeMillis();
System.out.println("bufWriter over:花了" + (endTime-startTime) + "毫秒");
}
}
但需要注意的是,并不是有缓冲区速度就快,写入速度和缓冲区容量以及要写入的大小有很大关系。
2.BufferedReader
FileReader
等级树结构:Object ——> Reader ——> InputStreamReader ——> FileReader
BufferedReader
等级树结构:Object ——> Reader ——> BufferedReader ——> LineNumberReader
BufferedReader 方法:
1.readLine() 读取文本行,无论何时读取行结束符,当前行号都将加 1。返回包含行内容的字符串,不包括任何行结束符,如果已 到达流的末尾,则返回 null;
package IO;
import java.io.BufferedReader;
import java.io.FileReader;
public class BufferedReaderDemo {
public static void main(String[] args) throws Exception {
BufferedReader bufReader = new BufferedReader(new FileReader("d:/a.txt"));
String line = null;
while((line = bufReader.readLine()) != null){
System.out.println(line);
}
System.out.println("over");
bufReader.close();
}
}
3.LineNumberReader
继承了BufferedReader,中有输出行号的方法 getLineNumber() ;
package IO;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.LineNumberReader;
public class BufferedReaderDemo {
public static void main(String[] args) throws Exception {
BufferedReader bufReader = new BufferedReader(new FileReader("d:/a.txt"));
String line = null;
while((line = bufReader.readLine()) != null){
System.out.println(line);
}
System.out.println("over");
bufReader.close();
/*
* 使用LineNumberReader读取文件行号
* 答应输出 行号+内容
*/
LineNumberReader lineNumReader = new LineNumberReader(new FileReader("d:/a.txt"));
while((line = lineNumReader.readLine()) != null){
int lineNo = lineNumReader.getLineNumber();
System.out.println(lineNo + "." + line);
}
lineNumReader.close();
}
}
FileInputStream/FileOutputStream
OutputStream:
此抽象类是表示输出字节流的所有类的超类。输出流接受输出字节并将这些字节发送到某个接收器。
OutputStream方法:
1.write(byte[] b) 将 b.length 个字节从指定的byte数组写入此输出流。
2.write(byte[] b,int off,int len) 将指定 byte 数组中从偏移量 off
开始的 len
个字节写入此输出流
3.write(int b) 将指定的字节写入此输出流
4.flush() 刷新此输出流并强制写出所有缓冲的输出字节
5.close() 关闭此输出流并释放与此流有关的所有系统资源
FileOutputStream继承了OutputStream
FileOutputStream的构造方法中有
FileOutputStream(String name, boolean append)
创建一个向具有指定 name
的文件中写入数据的输出文件流。如果第二个参数为 true
,则将字节写入文件末尾处,而不是写入文件开始处。
package IO;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
public class FileOutputStreamDemo {
public static void main(String[] args) throws Exception {
/*
* 利用FileOutputStream和FileInputStream实现图片的复制
*/
FileInputStream fis = new FileInputStream("d:/hmbb.ICO");
FileOutputStream fos = new FileOutputStream("d:/hmbb2.ICO");
byte[] bytes = new byte[1024*100];
int len = 0;
while((len = fis.read(bytes)) != -1){
fos.write(bytes,0,len);
}
fis.close();
fos.close();
System.out.println("over");
}
}
BufferedInputStream/BufferedOutputStream
Object ---> OutputStream ---> FileOutputStream ---> BufferedOutputStream
Object ---> IntputStream ---> FileInputStream ---> BufferedInputStream
package IO;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
public class BufferedInputStreamDemo {
public static void main(String[] args) throws Exception {
FileInputStream fis = new FileInputStream("d:/aa.txt");
BufferedInputStream bis = new BufferedInputStream(fis);
int i = 0;
while((i = bis.read()) != -1){
char c = (char)i;
System.out.print(c);
}
bis.close(); //先关闭外面
fis.close();
}
}
ByteArrayOutputStream(字节数组输出流)/ByteArrayInputStream
ByteArrayOutputStream 此类实现了一个输出流,其中的数据被写入一个 byte 数组。缓冲区会随着数据的不断写入而自动增长。可使用 toByteArray()
和 toString()
获取数据。也就是在内存中开辟空间存取数据。
package IO;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
public class ByteArrayOutputStreamDemo {
public static void main(String[] args) throws Exception {
/**
* 写入
*/
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write(10);
baos.write(127);
baos.write(255);
baos.write(256); //256二进制为 1 0000 0000 但范围是0~255,所以只写入低八位,因此读出0
byte[] bytes = baos.toByteArray();
baos.close();
/**
* 读取
*/
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
int i = 0 ;
while((i = bais.read()) != -1){
System.out.println(i);
}
}
}
package IO;
public class IntAndByte {
public static void main(String[] args) {
int i =255 ;
byte[] bytes = intByteArray(i);
System.out.println(bytes);
int i2 = byteArrayInt(bytes);
System.out.println(i2);
}
/**
* 将int转换成byte
*/
public static byte[] intByteArray(int i){
byte[] bytes = new byte[4];
//处理低位开始第一个byte
bytes[0] = (byte)i;
//处理低位开始第二个byte
bytes[1] = (byte)(i >> 8);
//处理低位开始第三个byte
bytes[1] = (byte)(i >> 16);
//处理低位开始第四个byte
bytes[1] = (byte)(i >> 24);
return bytes;
}
/**
* 将byte转换成int
*/
public static int byteArrayInt(byte[] arr){
int i3 = arr[3] << 24;
int i2 = (arr[2] & 0xFF) << 16;
int i1 = (arr[1] & 0xFF) << 8;
int i0 = arr[0] & 0xFF;
return i3 | i2 | i1 | i0 ;
}
}
InputStreamReader/OutputStreamWriter转换流
InputStreamReader 是字节流通向字符流的桥梁:它使用指定的
读取字节并将其解码为字符。它使用的字符集可以由名称指定或显式给定,或者可以接受平台默认的字符集。为了达到最高效率,可要考虑在 BufferedReader 内包装 InputStreamReadercharset
package IO;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
public class InputStreamReaderDemo {
public static void main(String[] args) throws Exception {
FileInputStream fis = new FileInputStream("d:/aa.txt");
InputStreamReader isr = new InputStreamReader(fis,"gbk");//指定字符集
BufferedReader br = new BufferedReader(isr); //为了达到最高效率,在 BufferedReader 内包装 InputStreamReader
int len = 0;
char[] buf = new char[1024];
while((len = br.read(buf)) != -1){
System.out.print(new String(buf,0,len));
}
br.close();
isr.close();
fis.close();
}
}
OutputStreamWriter 是字符流通向字节流的桥梁,可使用指定的
将要写入流中的字符编码成字节。它使用的字符集可以由名称指定或显式给定,否则将接受平台默认的字符集。为了获得最高效率,可考虑将 OutputStreamWriter 包装到 BufferedWriter 中,以避免频繁调用转换器。charset
package IO;
import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
public class OutputStreamWriterDemo {
public static void main(String[] args) throws Exception {
FileOutputStream fos = new FileOutputStream("d:/c.txt",true); //true为追加而不是覆盖
OutputStreamWriter osw = new OutputStreamWriter(fos);
BufferedWriter bw = new BufferedWriter(osw); //为了获得最高效率,可考虑将 OutputStreamWriter 包装到 BufferedWriter 中,以避免频繁调用转换器。
bw.write("我懂了,I see");
bw.close();
osw.close();
fos.close();
//以上代码可简写为
BufferedWriter bw2 = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("d:/c.txt",true)));
bw2.write("合成代码");
bw2.close();
}
}
标准IO流
package IO;
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class StandardIO {
/*
* 标准IO
*/
public static void main(String[] args) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String line = "";
while((line = br.readLine()) != null){ //Buffered中有readLine()这个方法
if(line.equals("exit")){
System.exit(-1);
}
System.out.println(line);
}
br.close();
}
}
File
File类:用来将文件或者文件夹封装成对象,分别对文件与文件夹的属性信息进行操作,File对象可以作为参数传递给流的构造函 数。
目录:文件所在文件夹的路径(不包括文件)
路径:精准定位文件的地址
File基本操作及常用方法:
1.renameTo(File dest) 重新命名此抽象路径名表示的文件(其实相当于移动文件)。返回:当且仅当重命名成功时,返回 true
; 否则返回 false
File fi = new File("d:/a.txt");
fi.renameTo(new File("e:/b.txt")); //结果是D盘的a.txt被移动到E盘并重命名为b.txt
2.getAbsolutePath() 返回此抽象路径名的绝对路径名字符串
3.getName() 返回文件名
4.getParent() 返回此抽象路径名父目录的路径名字符串
package IO;
import java.io.File;
import java.io.FileOutputStream;
public class FileDemo {
public static void main(String[] args) throws Exception {
File f = new File("d:/aa.txt");
System.out.println(f.exists());
System.out.println(f.getAbsolutePath()); //返回绝对路径
System.out.println(f.getName()); //返回文件名
System.out.println(f.getParent()); //返回目录
f = new File("d:/f.txt");
if(!f.exists()){
//创建空文件
f.createNewFile();
}
if(f.exists()){
//删除文件
f.delete();
}
//目录
f = new File("d:/a/a1");
//创建目录
f.mkdirs();
//通过目录和文件名构造File对象
f = new File(f,"a.txt");
f.createNewFile();
FileOutputStream fos = new FileOutputStream(f);
fos.write("abcdefg".getBytes());
fos.close();
//list返回一个字符串数组,这些字符串指定此抽象路径名表示的目录中的文件和目录
f = new File("d:/Downloads");
String[] names = f.list();
for(String str : names){
System.out.println(str);
}
}
}
RandomAccessFile (随机访问文件)
构造方法:
1.RandomAccessFile(File file, String mode)
创建从中读取和向其中写入(可选)的随机访问文件流,该文件 由 File
参数指定。mode如下:
"r" 以只读方式打开。调用结果对象的任何 write 方法都将导致抛出 IOException
。"rw" 打开以便读取和写入。如果该文件尚不存在,则尝试创建该文件。 "rws" 打开以便读取和写入,对于 "rw",还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备。 "rwd" 打开以便读取和写入,对于 "rw",还要求对文件内容的每个更新都同步写入到底层存储设备。
2.RandomAccessFile(String name, String mode)
创建从中读取和向其中写入(可选)的随机访问文件流,该文件具有指定名称。
方法:
1.void seek(long pos)
设置到此文件开头测量到的文件指针偏移量,在该位置发生下一个读取或写入操作。
pos
:从文件开头以字节为单位测量的偏移量位置,在该位置设置文件指针。
2. int skipBytes(int n) (从当前位置)
尝试跳过输入的 n
个字节以丢弃跳过的字节。返回实际跳过的字节数。
3. void setLength(long newLength)
设置此文件的长度。(相当于给文档分配空间)。
4. long getFilePointer()
返回此文件中的当前偏移量。
package IO;
import java.io.RandomAccessFile;
public class RandomAccessFileDemo {
public static void main(String[] args) throws Exception {
RandomAccessFile raf = new RandomAccessFile("d:/aa.txt", "rw");
raf.writeBytes("mememem");//默认从头开始覆盖
raf.seek(raf.length()); //跳到文档尾,此时再写入就成了追加。
raf.writeBytes("bbbb");
raf.close();
}
}
文件切割:
package IO;
import java.io.File;
import java.io.FileOutputStream;
import java.io.RandomAccessFile;
public class FileSplitter {
public static void main(String[] args) throws Exception {
//源文件
File srcFile = new File("d:/aa.txt");
//随机访问
RandomAccessFile raf = new RandomAccessFile(srcFile, "r");
//文件总长度
long totalLength = srcFile.length();
//文件份数
int number = 3;
//计算平均每个文件长度,最后一份特殊
int fileLength = (int)totalLength/number;
//循环份数,读写文件
for(int i = 0 ; i < number ; i++){
System.out.println("循环" + i);
//开始指针
int startIndex = i * fileLength ;
//处理结束指针
int endIndex = 0;
if (i == (number-1)) {
endIndex = (int)totalLength -1 ;
}
else {
endIndex = (i+1) * fileLength - 1;
}
FileOutputStream fos = new FileOutputStream(srcFile.getAbsolutePath() + "_" + i);
raf.seek(startIndex);
while(true){
int itmp = raf.read();//一个字节一个字节读
fos.write(itmp);
if(raf.getFilePointer() > endIndex){
break;
}
}
fos.close();
System.out.println("结束");
}
}
}
加入缓冲区,加快速度
package IO;
import java.io.File;
import java.io.FileOutputStream;
import java.io.RandomAccessFile;
public class FileSplitter {
public static void main(String[] args) throws Exception {
//源文件
File srcFile = new File("d:/iphone.exe");
//随机访问
RandomAccessFile raf = new RandomAccessFile(srcFile, "r");
//文件总长度
long totalLength = srcFile.length();
//文件份数
int number = 3;
//计算平均每个文件长度,最后一份特殊
int fileLength = (int)totalLength/number;
//循环份数,读写文件
for(int i = 0 ; i < number ; i++){
System.out.println("循环" + i);
//开始指针
int startIndex = i * fileLength ;
//处理结束指针
int endIndex = 0;
if (i == (number-1)) {
endIndex = (int)totalLength -1 ;
}
else {
endIndex = (i+1) * fileLength - 1;
}
//创建文件输出流
FileOutputStream fos = new FileOutputStream(srcFile.getAbsolutePath() + "_" + i);
raf.seek(startIndex);
//定义缓冲区
byte[] buf = new byte[1024];
while(true){
//得到当前文件指针
int currPointer = (int)raf.getFilePointer();
int remain = endIndex - currPointer + 1;
if(remain >= buf.length){
raf.read(buf);
fos.write(buf);
}
else{
raf.read(buf, 0, remain);
fos.write(buf, 0, remain);
}
if(raf.getFilePointer() > endIndex){
break;
}
}
fos.close();
System.out.println("结束");
}
}
}
ObjectInputStream/ObjtceOutputStream
对对象进行输入和输出,将java对象变成字节数组流byte[] 。
package IO;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class ObjectStreamDemo {
public static void main(String[] args) throws Exception {
FileOutputStream fos = new FileOutputStream("d:/data.txt");
//对象输出流
ObjectOutputStream oos = new ObjectOutputStream(fos); //包装
oos.writeObject(new String("Hello world"));//写入的是一个对象,但此时如果打开data.txt会发现有乱码
oos.close();
fos.close();
System.out.println("over");
//读取文件
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:/data.txt"));
String str = (String) ois.readObject();
System.out.println(str);
ois.close();
}
}
输入时将对象转化成了串行的二进制流,因此打开data.txt文件出现乱码,输出时将二进制流逆转化为对象,所以输出的和输入的相同。
自己定义的JavaBean,要想实现 串行化需要实现Serializable接口,如下:
package IO;
import java.io.Serializable;
public class Cat implements Serializable{
private String name ;
private int age ;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Cat(String name, int age) {
super();
this.name = name;
this.age = age;
}
}
package IO;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;
public class SerializableDemo {
public static void main(String[] args) throws Exception {
List<Cat> list = new ArrayList<Cat>();
for(int i = 0 ; i < 100 ; i++){
list.add(new Cat("tom" + i,i));
}
/*****************写入(串行)********************/
//字节数组输出流
ByteArrayOutputStream baos = new ByteArrayOutputStream(); //选它是因为要把流写入内存中,FileOutputStream(“”)是把流写入到指定文件中
//对象输出流
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(list);
oos.close();
baos.close();
/*****************读取(反串行)***********************/
byte[] bytes = baos.toByteArray();
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bais);
list = (List<Cat>) ois.readObject();
ois.close();
bais.close();
for(Cat c : list){
System.out.println(c.getName() + "-" + c.getAge());
}
}
}
transient的作用及使用方法
我们都知道一个对象只要实现了Serilizable接口,这个对象就可以被序列化,java的这种序列化模式为开发者提供了很多便利,我们可以不必关系具体序列化的过程,只要这个类实现了Serilizable接口,这个类的所有属性和方法都会自动序列化。然而在实际开发过程中,我们常常会遇到这样的问题,这个类的有些属性需要序列化,而其他属性不需要被序列化,打个比方,如果一个用户有一些敏感信息(如密码,银行卡号等),为了安全起见,不希望在网络操作(主要涉及到序列化操作,本地序列化缓存也适用)中被传输,这些信息对应的变量就可以加上transient关键字。换句话说,这个字段的生命周期仅存于调用者的内存中而不会写到磁盘里持久化。总之,java 的transient关键字为我们提供了便利,你只需要实现Serilizable接口,将不需要序列化的属性前添加关键字transient,序列化对象的时候,这个属性就不会序列化到指定的目的地中。
DataInputStream/DataOutputStream(数据输入/输出流)
数据输入流允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型。应用程序可以使用数据输出流写入稍后由数据输入流读取的数据。