1. I/O流概念入门
概念: 大多数的应用程序都需要与外部设备(磁盘、硬盘和网络等)进行数据交换,I/O流指的就是应用程序和这些设备之间的各种各样的数据交换方式,我们可以将他们理解为一根根功能各异的"管道"。
- 流的分类:
- 按方向:输入流/输出流,都是站在程序代码的角度来辨别方向。
- 按处理数据方式:字节流/字符流,字节流以二进制方式进行数据传输,字符流是以字符为单位进行数据传输。
- 按功能:节点流/处理流,节点流是对数据直传输,处理流会在数据传输过程中对数据进行二次处理后再传输。
- java中有很多负责I/O数据交换的类,都在
java.io包中:- File:表示文件本身的类。
- RandomAccessFile:表示允许随机位置访问的文件本身的类。
- InputStream:表示输入字节流的抽象类。
- OutputStream:表示输出字节流的抽象类。
- Reader:表示输入字符流的抽象类。
- Writer:表示输出字符流的抽象类。
带有中文的纯文本内容推荐使用字符流,图片,影音等二进制内容推荐使用字节流。
2. File
概念: File类是I/O包中唯一代表硬盘文件本身的类,通过它可以对文件进行创建,删除,重命名,设置读写权限等操作。
- 构造器:
new File(String filename):filename是路径和文件名的整体字符串。new File(String parent, String child):parent是目录字符串,child是文件字符串。new File(File parent, String child):parent是目录对应的文件,child是文件字符串。- java种的路径支持
UNIX和WINDOWS/DOS约定的路径分隔符/和\\这两种写法,建议直接使用File.separator来动态获取。
- 常用API方法:
boolean exists():判断文件/目录是否存在。boolean mkdir():创建单层目录。boolean mkdirs():创建多层目录。void delete():删除文件/目录,彻底删除,不进回收站,删除文件夹的前提是,文件夹是空文件夹。void deleteOnExit():在JVM终止时删除文件,只有在JVM正常终止时,才会尝试执行删除操作。boolean createNewFile():创建一个文件。String getName():得到文件/目录的名称。String getPath():得到文件/目录的路径。long length():得到文件字节数。String getParent():返回父目录的路径名,没有父目录返回null。long lastModified():返回文件最后一次被修改的时间戳,文件不存在或发生异常都会返回0L。boolean canRead():判断文件是否可读(能查看但是不能操作和存储)。boolean canWrite():判断文件是否可写(能写入但是看不见)。boolean isFile():判断文件是否是文件。boolean isDirectory():判断文件是否是目录。boolean isHidden():返回文件是否是一个隐藏文件。boolean isAbsolute:返回文件是否是绝对路径(从盘符出发就是绝对路径)。String getAbsolutePath():返回文件的绝对路径。boolean renameTo(File file):重命名为新的file实例的名字注意参数中的file实例不要创建出来。boolean equals(File file):判断文件/目录是否是同一个。
源码: /javase-advanced/
- src:
c.y.io.FileTest
/**
* @author yap
*/
public class FileTest {
private File file;
@Before
public void before() throws IOException {
String dirPath = "D:" + File.separator + "java-io";
File dirs = new File(dirPath);
if (!dirs.exists()) {
System.out.println(dirs.mkdirs() ? "目录创建成功" : "目录创建失败");
}
String filePath = "start.txt";
this.file = new File(dirPath, filePath);
if (this.file.exists()) {
System.out.println(this.file.delete() ? "文件删除成功" : "文件删除失败");
}
System.out.println(this.file.createNewFile() ? "文件创建成功" : "文件创建失败");
}
@Test
public void fileApi() {
System.out.println("文件名:" + file.getName());
System.out.println("文件路径:" + file.getPath());
System.out.println("文件大小:" + file.length());
System.out.println("父目录:" + file.getParent());
System.out.println("最后修改日期:" + new Date(file.lastModified()));
System.out.println(file.canRead() ? "可读" : "不可读");
System.out.println(file.canWrite() ? "可写" : "不可写");
System.out.println(file.isFile() ? "是文件" : "不是文件");
System.out.println(file.isDirectory() ? "是目录" : "不是目录");
System.out.println(file.isHidden() ? "隐藏文件" : "非隐藏文件");
System.out.println(file.isAbsolute() ? "是绝对路径" : "不是绝对路径");
System.out.println("文件绝对路径:" + file.getAbsolutePath());
File newFile = new File("D:\\java\\io\\build-1.txt");
System.out.println(file.renameTo(newFile) ? "修改成功" : "修改失败");
System.out.println(file.equals(newFile) ? "是同一个" : "非同一个");
}
}
3. RandomAccessFile
概念: RandomAccessFile可以从指定的位置开始访问一个文件,它具有一个位置指示器,负责指向当前读写处的位置,默认位置是文件的开头,我们可以自己设定指示器的初始位置,读写n个字节后,指示器指向n个字节后的下一个字节处。
- 构造器:
new RandomAccessFile(String name, String mode)- p1:文件路径,如果文件不存在会帮忙创建,但是如果目录不存在直接报错。
- p2:文件读写模式,
r表示只读,rw表示读写。
- 常用API方法:
final void writeUTF(String str):写入一个字符串。- UTF-8编码下一个字符占3个字节,GBK编码下一个字符占2个字节。
- writeUTF()会首先把指定字符串的总字节数写进前2个字节,故6个字节的数据其实占了8字节的空间。
final void writeInt(int v):写入一个int类型值。void seek(long pos):设置位置指示器的位置。void skipBytes(int n):位置指示器向后移动n个字节(跳过n个字节)。final String readUTF():读取一个字符串。final int readInt():读取一个int类型值。void close():关闭RandomAccessFile,节省内存资源。
RandomAccessFile仅限于操作文件,不能访问其他IO设备,如网络,内存映像等。
源码: /javase-advanced/
- src:
c.y.io.RandomAccessFileTest
/**
* 使用同一个RandomAccessFile对象对一个文件完成
* 三个员工信息(姓名和年龄)的按顺序写和按顺序读,且在输出过程种,跳过赵四的年龄。
*
* @author yap
*/
public class RandomAccessFileTest {
private RandomAccessFile randomAccessFile;
@Before
public void before() throws IOException {
randomAccessFile = new RandomAccessFile("D:\\java\\io\\emp.txt", "rw");
randomAccessFile.writeUTF("赵四");
randomAccessFile.writeInt(18);
randomAccessFile.writeUTF("刘能");
randomAccessFile.writeInt(28);
randomAccessFile.writeUTF("广坤");
randomAccessFile.writeInt(38);
}
@After
public void after() throws IOException {
randomAccessFile.close();
}
@Test
public void write() throws IOException {
// 位置指示器重置于0号位
randomAccessFile.seek(0);
System.out.print(randomAccessFile.readUTF() + "\t");
// 跳过赵四的年龄
randomAccessFile.skipBytes(4);
System.out.print(randomAccessFile.readUTF() + "\t");
System.out.print(randomAccessFile.readInt() + "\t");
System.out.print(randomAccessFile.readUTF() + "\t");
System.out.print(randomAccessFile.readInt() + "\n");
}
@Test
public void work01() throws IOException {
randomAccessFile.seek(24);
System.out.print(randomAccessFile.readUTF() + "\t");
System.out.print(randomAccessFile.readInt() + "\t");
randomAccessFile.seek(12);
System.out.print(randomAccessFile.readUTF() + "\t");
System.out.print(randomAccessFile.readInt() + "\t");
randomAccessFile.seek(0);
System.out.print(randomAccessFile.readUTF() + "\t");
System.out.print(randomAccessFile.readInt() + "\n");
}
}
java.io.EOFException 异常在某个文件已经到末尾了,你仍然在读取时爆发。
4. 文件节点流
概念: 文件节点流是对硬盘文件上的数据的直传输,中间不涉及任何数据的处理。
4.1 FileInputStream
概念: FileInputStream是InputStream的一个子类,可以以字节的方式读取文件内容。
- 构造器:
FileInputStream(String name):- p1:文件路径和文件名的组合字符串。
FileInputStream(File file):- p1:文件对象。
- 常用API方法:
int available():查看可见(还剩下的)总字节数。int read():从此输入流中读取一个字节的数据,如果文件已经读完,返回-1。
源码: /javase-advanced/
- src:
c.y.io.FileSeriesTest.fileInputStream()
/**
* @author yap
*/
public class FileSeriesTest {
private String filePath = "D:" + File.separator + "java-io" + File.separator + "HelloWorld.java";
/**
* 将 `HelloWorld.java` 内容输入到程序中并打印在控制台,并计算总字节数
*/
@Test
public void fileInputStream() {
try (FileInputStream fis = new FileInputStream(filePath)) {
System.out.println("total bytes:" + fis.available());
// temp var
int b;
while ((b = fis.read()) != -1) {
System.out.print((char) b);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
4.2 FileOutputStream
概念: FileOutputStream是OutputStream的一个子类,可以以字节的方式将内容写入指定文件。
- 构造器:
FileOutputStream(String name):- p1:文件路径和文件名的组合字符串。
FileOutputStream(File file):- p1:文件对象。
FileOutputStream(String name, boolean append):- p1:文件路径和文件名的组合字符串。
- p2:是否追加内容,默认false。
FileOutputStream(File file, boolean append):- p1:文件对象。
- p2:是否追加内容,默认false。
- 要输出的文件路径如果没有I/O会帮忙创建(目录不存在会报错)。
- 常用API方法:
void write(int b):将指定的字节b写入此文件输出流。void flush():刷新此输出流并强制任何缓冲的输出字节被写出来。
源码: /javase-advanced/
- src:
c.y.io.FileSeriesTest.fileOutputStream()
/**
* @author yap
*/
public class FileSeriesTest {
private String filePath = "D:" + File.separator + "java-io" + File.separator + "HelloWorld.java";
/**
* 将 `HelloWorld.java` 内容copy到 `HelloWorld.txt`
*/
@Test
public void fileOutputStream() {
String destPath = "D:" + File.separator + "java-io" + File.separator + "HelloWorld.txt";
try (FileInputStream fis = new FileInputStream(filePath);
FileOutputStream fos = new FileOutputStream(destPath, true)) {
int b;
while ((b = fis.read()) != -1) {
fos.write(b);
}
fos.flush();
System.out.println("copy over...");
} catch (IOException e) {
e.printStackTrace();
}
}
}
4.3 FileReader
概念: FileReader是Reader的一个子类,可以以字符的方式将内容写入指定文件。
- 构造器:
FileReader(String name):- p1:文件路径和文件名的组合字符串。
FileReader(File file):- p1:文件对象。
- 常用API方法:
String getEncoding():获取当前输入流的编码。
源码: /javase-advanced/
- src:
c.y.io.FileSeriesTest.fileReader()
/**
* @author yap
*/
public class FileSeriesTest {
private String filePath = "D:" + File.separator + "java-io" + File.separator + "HelloWorld.java";
/**
* 将包含中文的 `HelloWorld.java` 输入到程序中并打印在控制台
*/
@Test
public void fileReader() {
try (FileReader fr = new FileReader(filePath)) {
System.out.println(fr.getEncoding());
int b;
while ((b = fr.read()) != -1) {
System.out.print((char) b);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
4.4 FileWriter
概念: FileWriter是Writer的一个子类,可以以字符的方式将内容写入指定文件。
- 构造器:
FileWriter(String fileName):- p1:文件路径和文件名的组合字符串。
FileWriter(File file):- p1:文件对象。
FileWriter(String fileName, boolean append):- p1:文件路径和文件名的组合字符串。
- p2:是否追加内容,默认false。
FileWriter(File file, boolean append):- p1:文件对象。
- p2:是否追加内容,默认false。
- 常用API方法:
void write(int c):向输出流中写入一个字符。
源码: /javase-advanced/
- src:
c.jy.io.FileSeriesTest.fileWriter()
/**
* @author yap
*/
public class FileSeriesTest {
/**
* 输出一个文件 `Unicode.dat`,包含65536个Unicode字符
*/
@Test
public void fileWriter() {
String destPath = "D:" + File.separator + "java-io" + File.separator + "Unicode.dat";
try (FileWriter fw = new FileWriter(destPath)) {
int unicodeMaxLength = 65535;
for (int i = 0; i < unicodeMaxLength; i++) {
fw.write(i);
}
fw.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
5. 字节数组节点流
概念: 字节数组节点流是对内存上的数据的直传输,中间不涉及任何数据的处理。
ByteArrayInputStream:字节数组输入字节流,是InputStream的子类。- 构造器:
ByteArrayInputStream(byte buf[]):仅支持byte[]格式的参数。
- 构造器:
ByteArrayOutputStream:字节数组输出字节流,是OutputSteam的子类。- 构造器:
ByteArrayOutputStream():默认在内存中生成一个无名的字节数组。ByteArrayOutputStream(int size):size可以指定初始字节数组大小,默认32。
- 常用API方法:
byte toByteArray()[]:将流中的数据转换成为一个字节数组类型并返回。int size():返回字节数组的连续内存空间长度。
- 构造器:
源码: /javase-advanced/
- src:
c.y.io.ByteArraySeriesTest.byteArrayStream()
/**
* @author yap
*/
public class ByteArraySeriesTest {
/**
* 在内存中创造一个虚拟的字节数组,向字节数组中练习写入 `100`, `101`, `102`,计算长度,并读出所有数。
*/
@Test
public void byteArrayStream() {
ByteArrayOutputStream baos = null;
ByteArrayInputStream bais = null;
try {
baos = new ByteArrayOutputStream();
baos.write(100);
baos.write(101);
baos.write(102);
System.out.println("the length of baosData:" + baos.size());
baos.flush();
byte[] byteArray = baos.toByteArray();
bais = new ByteArrayInputStream(byteArray);
System.out.println(bais.read());
System.out.println(bais.read());
System.out.println(bais.read());
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (baos != null) {
baos.close();
}
if (bais != null) {
bais.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}