File
概念
java.io.File用于表示文件(目录),可以通过File类在程序中操作硬盘上的文件和目录。File类只能用于描述文件(目录)的属性信息(名称、大小等),不能对文件内容进行访问(即不具有从文件读取信息和向文件写入信息的功能)。
文件路径
文件路径尽量使用相对路径,"."表示当前路径(当前类所在项目的根目录)。文件路径分隔符尽量使用File.separator常量代替,它会根据不同操作系统返回不同的文件路径分隔符(windows返回\,Linux返回/)。
源码
public class File implements Serializable, Comparable<File>{ //...... }
构造方法
File(String pathname) 通过将给定路径名字符串转换为抽象路径名来创建一个新的File实例 File(URI uri) 通过将给定的URI转换为一个抽象路径名来创建一个新的File实例 File(String parent, String child) 根据parent路径名字字符串和child路径名字符串创建一个新的File实例 File(File parent, String child) 根据parent抽象路径名和child路径名字符串创建一个新的File实例。 使用
/** * 根据给定抽象路径创建对象,仅仅只是创建文件对象而不表示创建文件 * 文件路径尽量使用相对路径 */ File file = new File("." + File.separator + "1.txt");
常用方法
boolean exists() 测试此抽象路径所表示文件或者文件夹是否存在。
true表示存在;false表示不存在。
long length() 返回此抽象路径所表示的文件的长度 long lastModified() 返回此抽象路径所表示的文件最后一次被修改的时间毫秒 String getName() 返回此抽象路径所表示的文件或文件夹的名称 String getPath() 返回此抽象路径所表示的文件或文件夹的抽象路径 boolean isFile()
boolean isDirectory()
测试试此抽象路径所表示文件是否是一个标准的文件
测试试此抽象路径所表示文件是否是一个标准的文件夹
boolean createNewFile() 创建此抽象路径所表示的文件。
返回true表示创建成功;返回false表示创建失败。
boolean delete() 删除此抽象路径所表示文件或文件夹
返回true表示删除成功;返回false表示删除失败。
注意事项:若此抽象路径表示文件夹其该文件夹下存在子文件或者文件夹,则需要将子文件或子文件夹删除之后才能删除当前文件夹。
boolean mkdir()
创建此抽象路径名指定的文件夹(只能创建一层目录结构,对于多层目录结构需要使用mkdirs()方法)。
返回true表示创建成功;返回false表示创建失败。
boolean mkdirs() 创建此抽象路径下指定的文件夹,包括所有必需但不存在的父目录。建议使用此方法。 String[] list() 返回此抽象路径下所有子文件和子文件夹的名称数组 File[] listFiles() 返回此抽象路径下所有子文件和子文件夹数组 File[] listFiles(FileFilter fileter) 返回此抽象路径下经过文件过滤规则筛选过的子文件和子文件夹。
FileFilter是一个接口,需要自定义一个类实现它。
class MyFileFilter implements FileFilter{
//文件筛选的过滤逻辑。返回true则保留当前文件或文件夹;返回false则不保留。
public boolean accept(File pathname){
String fileName = pathname.getName(); //获取当前文件名//例如判断文件名以.txt结尾则返回true,否则返回false
return fileName.endWith(".txt");
}
}或者使用匿名内部类方式:
FIle[] subFiles = file.listFiles(new FileFilter(){
public boolean accept(File pathname){
String fileName = pathname.getName();return fileName.endWith(".txt");
}});
IO流
IO流概念
IO流指的是输入流(Input)和输出流(Output)。站在内存的角度作为参考,外部文件到内存叫做输入,内存到外部文件叫输出。
分类
- 字节流与字符流
按照数据传输方式或是运输方式角度来看,可以将IO流分为:字节流和字符流。两种区别如下:
1.字节流读取单个字节;字符流读取单个字符(一个字符根据编码的不同对应的字节也不同。如UTF-8编码是3个字节,中文编码是2个字节)。 2.字节流用来处理二进制文件(如图片、音频、视频文件等非文本文件);字符流只能用来处理纯文本文件(可以看做是特殊的二进制文件,使用了某种编码,人可以阅读)。字节流也可以处理文本文件,故字节流使用较多。 3.形象比喻,字节是给计算机看的,字符是给用户看的。
- 节点流与处理流
按照流的功能可以将IO流分为:分为处理流和节点流。
处理流用于包装节点流,对其功能进行加强,处理流单独存在没有意义。 处理流(高级流)
1.BufferedInputStream, BufferedOutputStream
使用如:BufferedInputStream bfInput = new BufferedInputStream(new FileInputStream(参数));
2.ObjectInputStream, ObjectOutputStream
3.OutputStreamWriter, InputStreamReader
4.BufferedReader, BufferedWriter, PrintWriter
节点流(低级流)
1.FileInputStream, FileOutputStream
- 规律
以InputStream结尾的流是字节出入流;以OutputStream结尾的流是字节输出流;以Reader结尾的流是字符输入流;以Writer结尾的流是字符输出流 体系结构
常用的类:File开头的文件最基本的读写类;Buffered开头的文件读写带缓存区的类;Object开头的对象序列化和反序列化相关的类。
从上图可以看见,IO的最基本的4个抽象类为:InputStream、OutputStream、Reader、Writer。
注意事项
带缓冲的流需要调用close()方法关闭流。文件写入丢失数据现象的原因:对于带缓冲的输出流需要等待缓冲数组写满时才会一起被写入文件,因此流使用完一定要调用close()方法。close()方法内部会调用flush()方法,将缓存数组中剩余的数据强制刷新到文件中。
字节流
InputStream
InputStream是所有字节输入流的父类,是抽象类(不能直接new)。其源码如下所示:
public abstract class InputStream implements Closeable { //MAX_SKIP_BUFFER_SIZE is used to determine the maximum buffer size to use when skipping. private static final int MAX_SKIP_BUFFER_SIZE = 2048; public abstract int read() throws IOException; public int read(byte b[]) throws IOException { return read(b, 0, b.length); } public int read(byte b[], int off, int len) throws IOException { if (b == null) { throw new NullPointerException(); } else if (off < 0 || len < 0 || len > b.length - off) { throw new IndexOutOfBoundsException(); } else if (len == 0) { return 0; } int c = read(); if (c == -1) { return -1; } b[off] = (byte)c; int i = 1; try { for (; i < len ; i++) { c = read(); if (c == -1) { break; } b[off + i] = (byte)c; } } catch (IOException ee) { } return i; } public long skip(long n) throws IOException { long remaining = n; int nr; if (n <= 0) { return 0; } int size = (int)Math.min(MAX_SKIP_BUFFER_SIZE, remaining); byte[] skipBuffer = new byte[size]; while (remaining > 0) { nr = read(skipBuffer, 0, (int)Math.min(size, remaining)); if (nr < 0) { break; } remaining -= nr; } return n - remaining; } public int available() throws IOException { return 0; } public void close() throws IOException {} public synchronized void mark(int readlimit) {} public synchronized void reset() throws IOException { throw new IOException("mark/reset not supported"); } public boolean markSupported() { return false; } }
- 常用方法
int read() 从此输入流中一次读取一个字节,返回int值,低8位有效,高24位全部补0。
返回-1表示已经读取到文件的末尾。
int read(byte[] b) 从此输入流中将最多b.length个字节读入字节数组b中。
返回值代表实际读取的字节数个数,如果因为已经到达文件末尾而没有更多的数据,则返回
-1
。解码:将字节数组安按照给的字符集编码还原成为字符串,若不指定编码集,则按照系统默认的编码集。
String s = new String(b, "编码格式(UTF-8、GBK等)");
注意:编码和解码使用的编码集要一致。
int read(byte[] b, int offset, int len) 从此输入流中将最多 len
个字节的数据读入一个 byte 数组中。void close() 关闭此输入流并释放与该流关联的所有系统资源。 long skip(long n) 跳过和丢弃此输入流中数据的 n
个字节。OutputStream
OutputStream是所有字节输出流的父类,是抽象类(不能直接new)。其源码如下所示:
public abstract class OutputStream implements Closeable, Flushable { public abstract void write(int b) throws IOException; public void write(byte b[]) throws IOException { write(b, 0, b.length); } public void write(byte b[], int off, int len) throws IOException { if (b == null) { throw new NullPointerException(); } else if ((off < 0) || (off > b.length) || (len < 0) || ((off + len) > b.length) || ((off + len) < 0)) { throw new IndexOutOfBoundsException(); } else if (len == 0) { return; } for (int i = 0 ; i < len ; i++) { write(b[off + i]); } } public void flush() throws IOException {} public void close() throws IOException {} }
- 常用方法
void write(int b) 一次写出一个字节,将int的低8位写出。 void wtite(byte[] b)
一次写出一个字节数组
注String的方法:byte[] getBytes(String charsetName)
将字符串按照给的的字符集编码转换成对应字节数组。
编码:将Unicode形式的字节数组转换成指定编码集的字节数组。若不指定编码集,则按照系统默认的编码集编码。Windows默认编码集为GBK,Linux默认编码集为UTF-8。
byte[] b = s.getBytes("编码格式(UTF-8、GBK等)");
void write(byte[]b, int offset, int len) 从字节数组的offset位置开始连续写出len个字节到输出流中。 void flush() 刷新此输出流并强制写出所有的缓冲的输出字节。 void close() 关闭此输出流并释放与此流有关的所有系统资源。 关闭流
关闭此文件输入流并释放与此流有关的所有系统资源都调用其close()方法。
注意事项
1.关闭处理流内部会自动关闭其包装的节点流,故只需要关闭处理流即可。 2.文件写入丢失数据现象的原因:对于带缓冲的输出流需要等待缓冲数组写满时才会一起被写入文件,因此流使用完一定要调用close()方法。close()方法内部会调用flush()方法,将缓存数组中剩余的数据强制刷新到文件中。 比如ObjectOutputStream的close()方法源码如下所示,调用了flush()方法:
public class ObjectOutputStream extends OutputStream implements ObjectOutput, ObjectStreamConstants{ //...... public void close() throws IOException { flush(); clear(); bout.close(); } }
InputStream和OutputStream的子类
FileInputStream
FileInputStream是文件输入流,用于从文件系统中的某个文件中获得输入字节。以字节为单位,一次读取一个字节。用于读取诸如图像数据之类的原始字节流,要读取字符流,使用
FileReader
。
- 源码
public class FileOutputStream extends OutputStream{ //...... }
- 构造方法
FileInputStream(File file) 通过打开一个到实际文件的连接来创建一个 FileInputStream
,该文件通过文件系统中的File
对象file
指定。FileInputStream(String name) 通过打开一个到实际文件的连接来创建一个 FileInputStream
,该文件通过文件系统中的路径名name
指定。FileInputStream(FileDescriptor fdObj) 通过使用文件描述符 fdObj
创建一个FileInputStream
,该文件描述符表示到文件系统中某个实际文件的现有连接。FileOutputStream
FileOutputStream是文件输出流,以字节为单位,一次向文件写出一个字节。用于写入诸如图像数据之类的原始字节的流。要写入字符流,使用
FileWriter
。
- 源码
public class FileInputStream extends InputStream{ //...... }
- 构造方法
FileOutputStream(File file) 创建一个向指定 File
对象表示的文件中写入数据的文件输出流。默认以覆盖的方式FileOutputStream(String name) 创建一个向具有指定名称的文件中写入数据的输出文件流。默认以覆盖的方式 FileOutputStream(FileDescriptor fdObj) 创建一个向指定文件描述符处写入数据的输出文件流,该文件描述符表示一个到文件系统中的某个实际文件的现有连接。 FileOutputStream(File file, boolean append) 创建一个向指定 File
对象表示的文件中写入数据的文件输出流。以追加的方式。FileOutputStream(String name, boolean append) 创建一个向具有指定名称的文件中写入数据的输出文件流。以追加的方式。 BufferedInputStream
在创建
BufferedInputStream
时,会创建一个内部缓冲区数组。在读取或跳过流中的字节时,可根据需要从包含的输入流再次填充该内部缓冲区,一次填充多个字节。mark
操作记录输入流中的某个点,reset
操作使得在从包含的输入流中获取新字节之前,再次读取自最后一次mark
操作后读取的所有字节。将数据读取到内存中,从内存中获取数据比从文件中获取效率高。
- 源码
public class BufferedInputStream extends FilterInputStream { private static int DEFAULT_BUFFER_SIZE = 8192; private static int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8; protected volatile byte buf[]; private static final AtomicReferenceFieldUpdater<BufferedInputStream, byte[]> bufUpdater = AtomicReferenceFieldUpdater.newUpdater (BufferedInputStream.class, byte[].class, "buf"); protected int count; protected int pos; protected int markpos = -1; protected int marklimit; private InputStream getInIfOpen() throws IOException { InputStream input = in; if (input == null) throw new IOException("Stream closed"); return input; } private byte[] getBufIfOpen() throws IOException { byte[] buffer = buf; if (buffer == null) throw new IOException("Stream closed"); return buffer; } public BufferedInputStream(InputStream in) { this(in, DEFAULT_BUFFER_SIZE); } public BufferedInputStream(InputStream in, int size) { super(in); if (size <= 0) { throw new IllegalArgumentException("Buffer size <= 0"); } buf = new byte[size]; } private void fill() throws IOException { byte[] buffer = getBufIfOpen(); if (markpos < 0) pos = 0; /* no mark: throw away the buffer */ else if (pos >= buffer.length) /* no room left in buffer */ if (markpos > 0) { /* can throw away early part of the buffer */ int sz = pos - markpos; System.arraycopy(buffer, markpos, buffer, 0, sz); pos = sz; markpos = 0; } else if (buffer.length >= marklimit) { markpos = -1; /* buffer got too big, invalidate mark */ pos = 0; /* drop buffer contents */ } else if (buffer.length >= MAX_BUFFER_SIZE) { throw new OutOfMemoryError("Required array size too large"); } else { /* grow buffer */ int nsz = (pos <= MAX_BUFFER_SIZE - pos) ? pos * 2 : MAX_BUFFER_SIZE; if (nsz > marklimit) nsz = marklimit; byte nbuf[] = new byte[nsz]; System.arraycopy(buffer, 0, nbuf, 0, pos); if (!bufUpdater.compareAndSet(this, buffer, nbuf)) { // Can't replace buf if there was an async close. // Note: This would need to be changed if fill() // is ever made accessible to multiple threads. // But for now, the only way CAS can fail is via close. // assert buf == null; throw new IOException("Stream closed"); } buffer = nbuf; } count = pos; int n = getInIfOpen().read(buffer, pos, buffer.length - pos); if (n > 0) count = n + pos; } public synchronized int read() throws IOException { if (pos >= count) { fill(); if (pos >= count) return -1; } return getBufIfOpen()[pos++] & 0xff; } private int read1(byte[] b, int off, int len) throws IOException { int avail = count - pos; if (avail <= 0) { /* If the requested length is at least as large as the buffer, and if there is no mark/reset activity, do not bother to copy the bytes into the local buffer. In this way buffered streams will cascade harmlessly. */ if (len >= getBufIfOpen().length && markpos < 0) { return getInIfOpen().read(b, off, len); } fill(); avail = count - pos; if (avail <= 0) return -1; } int cnt = (avail < len) ? avail : len; System.arraycopy(getBufIfOpen(), pos, b, off, cnt); pos += cnt; return cnt; } public synchronized int read(byte b[], int off, int len) throws IOException { getBufIfOpen(); // Check for closed stream if ((off | len | (off + len) | (b.length - (off + len))) < 0) { throw new IndexOutOfBoundsException(); } else if (len == 0) { return 0; } int n = 0; for (;;) { int nread = read1(b, off + n, len - n); if (nread <= 0) return (n == 0) ? nread : n; n += nread; if (n >= len) return n; // if not closed but no bytes available, return InputStream input = in; if (input != null && input.available() <= 0) return n; } } public synchronized long skip(long n) throws IOException { getBufIfOpen(); // Check for closed stream if (n <= 0) { return 0; } long avail = count - pos; if (avail <= 0) { // If no mark position set then don't keep in buffer if (markpos <0) return getInIfOpen().skip(n); // Fill in buffer to save bytes for reset fill(); avail = count - pos; if (avail <= 0) return 0; } long skipped = (avail < n) ? avail : n; pos += skipped; return skipped; } public synchronized int available() throws IOException { int n = count - pos; int avail = getInIfOpen().available(); return n > (Integer.MAX_VALUE - avail) ? Integer.MAX_VALUE : n + avail; } public synchronized void mark(int readlimit) { marklimit = readlimit; markpos = pos; } public synchronized void reset() throws IOException { getBufIfOpen(); // Cause exception if closed if (markpos < 0) throw new IOException("Resetting to invalid mark"); pos = markpos; } public boolean markSupported() { return true; } public void close() throws IOException { byte[] buffer; while ( (buffer = buf) != null) { if (bufUpdater.compareAndSet(this, buffer, null)) { InputStream input = in; in = null; if (input != null) input.close(); return; } // Else retry in case a new buf was CASed in fill() } } }
其中FilterInputStream类继承自InputStream
- 构造方法
BufferedInputStream(InputStream in) 创建一个 BufferedInputStream
BufferedInputStream(InputStream in, int size) 创建具有指定缓冲区大小的 BufferedInputStream
- 常用方法
见InputStream类
BufferedOutputStream
该类实现缓冲的输出流。通过设置这种输出流,应用程序就可以将各个字节写入底层输出流中,而不必针对每次字节写入调用底层系统。
- 源码
public class BufferedOutputStream extends FilterOutputStream { protected byte buf[]; protected int count; public BufferedOutputStream(OutputStream out) { this(out, 8192); } public BufferedOutputStream(OutputStream out, int size) { super(out); if (size <= 0) { throw new IllegalArgumentException("Buffer size <= 0"); } buf = new byte[size]; } /** Flush the internal buffer */ private void flushBuffer() throws IOException { if (count > 0) { out.write(buf, 0, count); count = 0; } } public synchronized void write(int b) throws IOException { if (count >= buf.length) { flushBuffer(); } buf[count++] = (byte)b; } public synchronized void write(byte b[], int off, int len) throws IOException{ if (len >= buf.length) { /* If the request length exceeds the size of the output buffer, flush the output buffer and then write the data directly. In this way buffered streams will cascade harmlessly. */ flushBuffer(); out.write(b, off, len); return; } if (len > buf.length - count) { flushBuffer(); } System.arraycopy(b, off, buf, count, len); count += len; } public synchronized void flush() throws IOException { flushBuffer(); out.flush(); } }
其中FilterOutputStream类继承自OutputStream
- 构造方法
BufferedOutputStream(OutputStream out) 创建一个新的缓冲输出流,以将数据写入指定的底层输出流。 BufferedOutputStream(OutputStream out, int size) 创建一个新的缓冲输出流,以将具有指定缓冲区大小的数据写入指定的底层输出流。
- 常用方法
见OutputStream类
ObjectInputStream
- 概念
ObjectInputStream表示对象输入流。对使用 ObjectOutputStream 写入的基本数据和对象进行反序列化。
- 源码
public class ObjectInputStreamextends InputStream implements ObjectInput, ObjectStreamConstants{ //...... }
- 构造方法
ObjectInputStream(InputStream in) 创建写入指定 InputStream 的 ObjectInputStream。
- 常用方法
Object readObjectj) 从输出流读取对象。 ObjectOutputStream
- 概念
ObjectOutputStream表示对象输出流,将 Java 对象的基本数据类型写入 OutputStream流,通过在流中使用文件可以实现对象的持久存储。
- 源码
public class ObjectOutputStream extends OutputStream implements ObjectOutput, ObjectStreamConstants{ //...... }
- 构造方法
ObjectOutputStream(OutputStream out) 创建写入指定 OutputStream 的 ObjectOutputStream。
- 常用方法
void writeObject(Object obj) 将对象后出到输出流中。
字符流
Reader
Reader是所有字符输入流的父类。
- 源码
- 常用方法
int read() 读取一个字符,返回的int值低16位有效 Writer
Writer是所有字符输出流的父类。
- 源码
- 常用方法
void write(int c) 写出一个int值低16位表示的字符 void write(char[] chs) 将给的字符数组中所有的字符写出 void write(String str) 将给的字符串写出 void write(char[] chs, int offset, int len)
Reader子类
InputStreamReader
字符输入流,使用该字符流可以设置字符集,
BufferedReader
- 源码
public class BufferedReader extends Reader { private Reader in; private char cb[]; //...... }
可以看到,定义了一个字符缓冲数组cb。
- 常用方法
String readLine() 一次读取一行数据。通过下列字符之一即可认为某行已终止:换行 ('\n')、回车 ('\r') 或回车后直接跟着换行。
如果已到达流末尾,则返回 null
Writer子类
OutputStreamWriter
BufferedWriter
PrintWriter
PrintWriter是具有自动行刷新的缓存字符输出流。
- 源码
- 构造方法
PrintWriter(File file) 使用指定文件创建不具有自动行刷新的新 PrintWriter。 PrintWriter(File file, String csn) 创建具有指定文件和字符集且不带自动刷行新的新 PrintWriter。 PrintWriter(OutputStream out) 根据现有的 OutputStream 创建不带自动行刷新的新 PrintWriter。 PrintWriter(OutputStream out, boolean autoFlush) 通过现有的 OutputStream 创建是否带自动行刷新的 PrintWriter。
自动行刷新:指每调用一次println()方法,会强制刷新缓冲。会增加写出次数,从而降低IO性能。因此,对于及时性要求不高的系统不推荐使用
PrintWriter(String fileName) 创建具有指定文件名称且不带自动行刷新的新 PrintWriter。 PrintWriter(String fileName, String csn) 创建具有指定文件名称和字符集且不带自动行刷新的新 PrintWriter。 PrintWriter(Writer out) 创建不带自动行刷新的新 PrintWriter。 PrintWriter(Writer out, boolean autoFlush) 创建新 PrintWriter。
- 常用方法
Properties
概念
Properties类是Map集合的实现类,表示了一个持久的属性集,具备键值对的特征,位于java.util包下。Properties类常常用于读取和加载属性配置文件(以.properties结尾的文件)。如下所示:db.properties
url=jdbc:mysql://localhost:3306/test driver=com.mysql.cj.jdbc.driver username=root password=root
注意事项:
1.一行只能书写一个配置,每个配置包含key-value键值对,以等号或者空格进行分隔。 2.不支持中文,如果输入了中文,则会将中文自动转换为对应的Unicode码。 3.注释使用# 源码
public class Properties extends Hashtable<Object,Object> { private static final long serialVersionUID = 4112578634029874840L; protected Properties defaults; public Properties() { this(null); } public Properties(Properties defaults) { this.defaults = defaults; } public synchronized Object setProperty(String key, String value) { return put(key, value); } //...... }
构造方法
Properties() 创建一个无默认值的空属性列表。 Properties(Properties defaults) 创建一个带有指定默认值的空属性列表。 常用方法
void load(InputStream in)
void load(Reader reader)
从输入流中读取属性列表(键和元素对)。
按简单的面向行的格式从输入字符流中读取属性列表(键和元素对)。
String getProperty(String key) 用指定的键在此属性列表中搜索属性。如果不存在对应的属性值,则返回null String getProperty(String key, String defaultValue) 用指定的键在此属性列表中搜索属性。如果不存在对应的属性值,则返回defaultValue 示例
读取上面的db.properties文件代码如下所示:
public class PropertiesDemo { public static void main(String[] args) throws IOException { Properties properties = new Properties(); //相当于类路径(src下)下去查询文件并返回该文件的资源输入流 InputStream in = PropertiesDemo.class.getClassLoader().getResourceAsStream("db.properties"); properties.load(in); System.out.println(properties.getProperty("url")); System.out.println(properties.getProperty("driver")); System.out.println(properties.getProperty("username")); System.out.println(properties); } } 输出结果: jdbc:mysql://localhost:3306/test com.mysql.cj.jdbc.driver root root