java编程基础——IO(输入输出)
一、File 类型
java.io.File 用与表示文件或目录,可通过File类在程序中操作硬盘上的文件和目录,能新建、删除、重命名文件或目录,也能获取文件或目录的信息(大小,修改时间等),但不能访问文件里的内容。如果需要访问文件里的内容,则需要使用输入/输出流。
File 类用于封装一个路径,这个路径可以是从系统盘符开始的绝对路径,如 D:\file\a.txt,也可以是相对于当前目录而言的相对路径,如src\Hello.java。File类内部封装的路径可以指向一个文件,也可以指向一个目录。在File类中提供了针对这些文件或目录的一些常规操作。
Windows系统的根为盘符:如D: F: Linux系统的根为"/"
方法声明 | 功能描述 |
File(String pathname) | 通过指定的一个字符串类型文件路径来创建一个新的File 对象 |
File(File parent,String child) | 根据指定的一个字符串类型的父路径和一个字符串类型的字路径(包括文件名称)创建一个File 对象 |
File(String parent, String child) | 根据指定的 File 类的父路径和字符串类型的自路径(包括文件名称) 创建一个File对象 |
通常来讲,如果程序只处理一个目录或文件,并且知道该目录或文件的路径,使用第一个构造方法比较方便。如果程序处理的是一个公共目录中的若干字母或文件,那么使用第二个或者第三个构造方法会更方便。
File 类中提供了一系列方法,用于操作其内部封装的路径指向的文件或者目录,例如 判断文件 / 目录 是否存在、创建、删除文件/目录等。
方法声明 | 功能描述 |
boolean exists() | 判断File对象对应的文件或目录是否存在,若存在则返回true,否则返回false |
boolean delete() | 删除 File 对相对应的文件或目录,若成功删除则返回 true,否则返回 false |
boolean createNewFile() | 当 File 对相对应的文件不存在时,该方法将新建一个此 File 对象所指定的的新文件,若创建成功则返回 true,否则返回 false。 |
boolean canRead() | 判断 File 对象对应的文件或目录是否可读,若可读则返回true 否则返回false |
boolean canWrite() | 判断 File 对象对应的文件或目录是否可写,若可读则返回true 否则返回false |
boolean isFile() | 判断 File 对象对应的是否是文件(不是目录),若是文件则返回true 否则返回false |
booolean isDirectory() | 判断 File 对象对应的是否是目录(不是文件),若是目录则返回true 否则返回false |
boolean isAbsolute() | 判断 File对象对象对应的文件或目录是否是绝对路径 |
String getName() | 返回 File对象表示的文件或文件夹的名称 |
String getPath() | 返回 File对象对应的路径转换为路径名字符串 |
String getAbsolutePath() | 返回 File对象路径名对应的绝对路径命的字符串(在UNIX/Linux 等系统上,如果路径是以 / 开始,则这个路径视觉是绝对路径;在Windows 等系统上,如果路径是从盘符开始,则这个路径是绝对路径) |
String getParent() | 返回 File对象对应目录的父目录(即返回的目录不包含最后一级子目录),如果此路径名未指定父目录,则返回null。 |
String []list() | 返回一个字符串数组,命名由此抽象路径表示的目录文件和目录 |
long length() | 返回由此路径名表示的文件的长度 |
long lastModified() | 返回此抽象路径名表示的文件上次修改的时间 |
File[] listFiles() | 返回一个抽象路径名数组,表示由该抽象路径表示的目录中的文件。 |
递归算法:
如果想删除一个非空目录,则需要使用递归算法来实现递归的定义,递归是指在函数的定义中使用函数自身的方法。
直接递归:自己调用自己。间接递归:A调B,B调用A
使用递归方法删除非空目录:
import java.io.File;
public static void delDir(String pathname) {
File file = new File(pathname); //创建File对象
if(file.exists()) { //测试此抽象路径名表示的文件或目录是否存在
if(file.isDirectory()) { //测试此抽象路径名表示的文件是否为目录
File[] fs = file.listFiles();//得到File数组
if(fs.length!=0) {
for(File f:fs) { //遍历所有的字目录和文件
delDir(f.getAbsolutePath());
}
}
}
file.delete(); //如果是文件,直接删除
}else {
System.out.println("路径不存在!");
}
}
二:基本I/O流
在Java中将信息的输入与输出过程抽象为I/O流(Input/Output)
- 输入:是指数据流入程序,通常我们读取外界数据时使用,所以输入是用来读取数据的。
- 输出:是指数据从程序流出,通常我们需要写出数据到外界时使用,所以输出是用来写出数据的。
一个流就是一个从数据源向目的地的数据序列。I/O流类一旦被创建就会自动打开。通过调用close方法,可以显式关闭任何一个流,如果流对象不再被引用,Java的垃圾回收机制也会隐式地关闭它不论数据从哪来,到哪去,也不论数据本身是何类型,读写数据的方法大体上都是一样的
IO 流有很多种,按照操作数据的不同,可以分为字节流(8位的字节),字符流(16位的字节),按照数据传输的流向不同又可分为输入流和输出流,程序从输入流中读取数据,像输出流中写入数据。在 IO 包中,自己留的输入输出流分别用 java.io.InputStream 和 java.io.OutputStream 表示,字符流的输入输出流分别用 java.io.Reader 和 java.io.Writer 表示。
按照流的功能分类
- 节点流:可以从一个特定的IO设备上读/写数据的流。也称之为低级流
- 处理流:是对一个已经存在的流的连接和封装,通过所封装的流的功能调用实现数据读/写操作。通常处理流的构造器上都会带有一个其他流的参数。也称之为高级流或者过滤流
字节流:
IO 流中针对字节的输入输出提供一系列的流,统称为字节流。字节流是程序中最常用的流。根据数据的传输方向可将其分为字节输入流和字节输出流。在JDK中,提供了两个抽象类 InputSream和OutputStream,他们是字节流的顶级父类。
1. InputStream 和 OutputStream
InputStream 和 OutputStream为各种输入输出字节流的基类,所有字节流都继承这两个基类。
方法声明 | 功能描述 |
int read() | 从输入流中读取一个字节,把它转换为0-255之间的整数,并返回这一整数,如果返回-1,说明读到文件末尾(EOF) |
int read(byte[] b) | 从输入流中读取若干个字节,把它们保存到缓冲区b中,返回的整数表示读取的字节数,如果遇到输入流的结尾,返回-1 |
int read(byte[] b, int off, int len) | 从输入流读取最多 len字节的数据到一个字节数组。从指定下标off开始存 返回的整数表示实际读取的字节数。如果遇到输入流的结尾,返回-1 |
void close() | 关闭输入流 |
int available() | 返回可以从输入流中读取的字节数目 |
long skip(long n) | 从输入流中跳过参数n指定数目的字节 |
boolean markSupported() | 测试这个输入流是否支持 mark和 reset方法。 |
void mark(int readLimit) | 标记此输入流中的当前位置。 |
void reset() | 将此流重新定位到上次在此输入流上调用 mark方法时的位置。 |
方法名称 | 方法描述 |
void write(int b) | 向输出流写出一个字节 |
void write(byte[] b) | 将 b.length字节从指定的字节数组写入此输出流 |
void write(byte[] b,int off, int len) | 从指定的字节数组写入 len个字节,从偏移 off开始输出到此输出流 |
void close() | 关闭输出流 |
void flush() | OutputStream类本身的flush方法不执行任何操作,它的一些带缓冲区的子类覆盖了flush方法,该方法强制把缓冲区内的数据写到输出流中 |
2. FileInputStream 和 FileOutputStream 文件字节缓冲流
这两个从字面意思很容易理解,是对文件的字节流操作,也会最常见的IO操作流。
FileInputStream:是文件的字节输入流,该流以字节为单位从文件中读取数据。
功能描述 | |
FileInputStream(File file) | 创建一个从指定File对象表示的文件中读取数据的文件输入流 |
FileInputStream(String name) | 创建一个从指定路径名所指定的文件中读取数据的文件输入流 |
FileOutputStream:是文件的字节输入流,该流以字节为单位从文件中读取数据。
功能描述 | |
FileOutputStream(File file) | 创建一个向指定file对象表示的文件中写出数据的文件输出流 |
FileOutputStream(String filename) | 创建一个向具体指定名称的文件中写出数据的文件输出流以上两个构造器,若指定的文件已经包含内容,那么当使用该流进行写出数据时,会将原有内容全部清除追加模式构造方法 |
FileOutputStream(File file,boolean append) | 创建一个向指定file对象表示的文件中写出数据的文件输出流 |
FileOutputStream(String filename,boolean append) | 创建一个向具体指定名称的文件中写出数据的文件输出流,以上两个构造器,当append为true时,通过该流写出的数据就会追加在文件末尾。 |
若指定目录中的指定文件不存在,任何输出流都会自动将该文件创建出来,
3.DataInputStream和DataOutputStream
DataInputStream 是数据输入流,它继承于FilterInputStream。该流提供了一些可以直接读取基本数据类型的方法
No. |
方法 |
类型 |
描述 |
1 |
public DataInputStream(InputStream in) |
构造 |
实例化对象 |
2 |
public final int readInt() throws IOException |
普通 |
从输入流中读取整数 |
3 |
public final float readFloat() throws IOException |
普通 |
从输入流中读取小数 |
4 |
public final char readChar() throws IOException |
普通 |
从输入流中读取一个字符 |
DataOutputStream 是数据输出流,它继承于FilterOutputStream。扩展了一些功能,提供了一些可以直接写出基本数据类型的方法。
No |
方法 |
类型 |
描述 |
1 |
public DataOutputStream(OutputStream out) |
构造 |
实例化对象 |
2 |
public final void writeInt(int v) throws IOException |
普通 |
将一个int值以4-byte值形式写入基础输出流中 |
3 |
public final void writeDouble(double v) throws IOException |
普通 |
写入一个double类型,该值以8-byte值形式写入基础输出流中 |
4 |
public final void writeChars(String s) throws IOException |
普通 |
将一个字符串写入到输出流之中 |
5 |
public final void writeChar(int v) throws IOException |
普通 |
将一个字符写入到输出流之中 |
将定单数据写入到文件order.txt之中
package org.lxh.demo12.datademo;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
public class DataOutputStreamDemo {
public static void main(String[] args) throws Exception {
DataOutputStream dos = null ; // 声明数据输出流对象
File f = new File("D:" + File.separator + "order.txt");// 指定文件的保存路径
dos = new DataOutputStream(new FileOutputStream(f)) ;// 实例化数据输出流对象
String names[] = { "衬衣", "手套", "围巾" }; // 商品名称
float prices[] = { 98.3f, 30.3f, 50.5f }; // 商品价格
int nums[] = { 3, 2, 1 }; // 商品数量
for (int i = 0; i < names.length; i++) { // 循环写入
dos.writeChars(names[i]) ; // 写入字符串
dos.writeChar('\t') ; // 加入分隔符
dos.writeFloat(prices[i]) ; // 写入小数
dos.writeChar('\t') ; // 加入分隔符
dos.writeInt(nums[i]) ; // 写入整数
dos.writeChar('\n') ; // 换行
}
dos.close() ; // 关闭输出流
}
}
二者配合使用,“允许应用程序以与机器无关方式从底层输入流中读写基本 Java 数据类型”。
4.BufferedInputStream 和 BufferedOutputStream 字节缓冲流
在IO包中提供两个带缓冲的字节流,分别是BufferedInputStream和BufferOutputStream。它们的构造法方法中分别接收InputStream 和 OutputStream 类型的参数作为被包装对象,再读写数据中提供缓冲功能。
应用程序时通过缓冲流来完成数据读写的,二缓冲流又是通过底层被包装的字节流与设备进行关联的。
BufferedInputStream是带缓冲区的输入流,它继承于FilterInputStream。默认缓冲区大小是8k,能够减少访问磁盘的次数,提高文件读取性能。
读取数据时因为以字节为单位,往往会因为读取次数过于频繁而大大降低读取效率,因此我们可以通过提高一次读取的字节数量来减少读取次数,从而提高读取的效率该缓冲输入流,内部维护着一个缓冲区。使用该流读取数据时,该流会尽可能多的一次性读取数据存入缓冲区,直到该缓冲区中的数据被全部读取完毕,会再次读取数据存入该缓冲区,反复进行。这样就减少了读取次数,从而提高效率。
- BufferedInputStream(InputStream in) 以指定节点流in作为参数,创建一个缓冲输入流
- BufferedInputStream(InputStream in, int size) 以指定节点流in和缓冲区大小作为参数,创建一个缓冲输入流。
BufferedOutputStream是带缓冲区的输出流,它继承于FilterOutputStream,能够提高文件的写入效率。
它们提供的“缓冲功能”本质上是通过一个内部缓冲区数组实现的。例如,在新建某输入流对应的BufferedInputStream后,当我们通过read()读取输入流的数据时,BufferedInputStream会将该输入流的数据分批的填入到缓冲区中。每当我们向该流写数据时,都会先将数据存储缓冲区,当缓冲区已满时,缓冲流会将数据一次性全部写出。使用该流虽然可以提高写出效率,但是缺乏即时性,此时我们可以使用flush方法,清空缓冲区,强制写出。
- BufferedOutputStream(OutputStream out) 创建一个新的缓冲输出流,以将数据写入指定的底层输出流。
- BufferedOutputStream(OutputStream out, int size) 创建一个新的缓冲输出流,以便以指定的缓冲区大小将数据写入指定的底层输出流。
PS:任何输出流,在关闭操作前,都会将流内的数据写出。当一次性写出的字节数组的长度>=缓冲区,就不使用缓冲区而是直接写出。
字符流
一、字符流 Reader / Writer
Reader/Writer字符输入/输出流,都是字符流的两个抽象顶级父类。
Writer
- 处理单位:按照字符处理的,只不过底层依然是字节流
- 原理:即底层将字符按照相应的字符集转成对应的自己数据。
-
writer类的常用方法 No.
方法或常量
描述
1
public abstract void close() throws IOException
关闭输出流
2
public void write(String str) throws IOException
将字符串输出
3
public void write(char[] cbuf) throws IOException
将字符数组输出
4
public abstract void flush() throws IOException
强制性清空缓存
Reader
- 处理单位:字符,底层是字节流
- 原理:字符流将字节流读取的字节数据按照指定字符集进行再一次处理
字符流是以字符(char)为单位读写数据的, 一次处理一个unicode。字符流的底层仍然是基本的字节流;
字符流只能对纯文本文件进行操作。
No. |
方法或常量 |
描述 |
1 |
public abstract void close() throws IOException |
关闭输出流 |
2 |
public int read() throws IOException |
读取单个字符 |
3 |
public int read(char[] cbuf) throws IOException |
将内容读到字符数组之中,返回读入的长度 |
二、转换流 OutputStreamWriter / InputStreamReader
可以设置字符集
常用构造器:OutputStreamWriter(OutputStream out)
OutputStreamWriter(OutputStream out,String charsetName)
InputStreamReader(InputStream in)
InputStreamReader(InputStream in,String charsetName)
public class InputStreamReaderDemo01 {
public static void main(String[] args) {
Reader reader=null;
try {
reader=new InputStreamReader(new BufferedInputStream(new FileInputStream("ch.txt")));
char[] chs=new char[100];
int length=reader.read(chs);
String info=new String(chs);
System.out.println(length);
System.out.println(info);
} catch (Exception e) {
e.printStackTrace();
}finally{
try {
reader.close();
} catch (Exception e2) {
e2.printStackTrace();
}
}
}
}
三、缓冲字符流 PrintWriter / BufferedReader
(1)PrintWriter 缓冲字符输出流
PrintWriter是具有自动行刷新的缓冲字符输出流,其提供了比较丰富的构造方法,通常比BufferedWriter更实用。
常用构造方法 PrintWirter(File file)
PrintWriter(String filename)
PrintWriter(OutputStream out)
PrintWriter(OutputStream out,boolean autoFlush)
PrintWriter(Writer writer)
PrintWriter(Writer writer,boolean autoFlush)
常用方法:除了write方法,PrintWriter提供了丰富的重载print()和println()方法。
println方法在输出目标数据后自动输出一个系统支持的换行符。若该流设置了自动行刷新,那么每当通过println方法 写出的内容都会被实际写出。而不是进行缓存。
(2) BufferedReader 缓冲字符输入流
No. |
方法或常量 |
类型 |
描述 |
1 |
public BufferedReader(Reader in) |
构造 |
接收一个Reader类的实例 |
2 |
public String readLine() throws IOException |
普通 |
一次性从缓冲区中将内容全部读取进来 |
package org.lxh.demo12.buffdemo;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class BufferedReaderDemo01 {
public static void main(String[] args) {
BufferedReader buf = null;
buf = new BufferedReader(new InputStreamReader(System.in));
String str = null;
System.out.print("请输入内容:");
try {
str = buf.readLine(); // 读取内容
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("输入的内容为:" + str);
}
}
四、文件字符流 FileWriter / FileReader
(1) FileWriter
相当于OutputStreamWriter和FileOutputStream合起来的功能。内部也维护着一个缓存区,需要手动调用flush方法进行刷新。
在使用字符流操作的时候,也可以实现文件的追加功能,直接使用FileWriter类中的以下构造即可实现追加:
public FileWriter(File file,boolean append) throws IOException 将append的值设置成true,就表示追加。
构造方法 FileWriter(File file)
FileWriter(File file,boolean append)
FileWriter(String filepath)
FileWriter(String filepath,boolean append)
//追加文件内容
import java.io.File;
import java.io.FileWriter;
import java.io.Writer;
public class WriterDemo02 {
public static void main(String[] args) throws Exception { // 异常抛出,不处理
// 第1步、使用File类找到一个文件
File f = new File("d:" + File.separator + "test.txt"); // 声明File对象
// 第2步:通过子类实例化父类对象
Writer out = null; // 准备好一个输出的对象
out = new FileWriter(f,true); // 通过对象多态性,进行实例化
// 第3步:进行写操作
String str = "\r\nLIXINGHUA\r\nHello World!!!" ; // 准备一个字符串
out.write(str); // 将内容输出
// 第4步:关闭输出流
out.close(); // 关闭输出流
}
}
(2) FileReader
相当于InputStreamReader和FileInputStream合起来的功能,但是不能设置字符集。内部也维护着一个缓存区。
FileReader的构造方法定义如下: public FileReader(File file) throws FileNotFoundException
构造方法 FileReader(File file)
FileReader(String filepath)
字节流与字符流的区别
字节流在操作的时候本身是不会用到缓冲区(内存)的,是与文件本身直接操作的,而字符流在操作的时候是使用到缓冲区的。
对象流
对象是存在于内存中的,有的时候我们需要将对象保存到硬盘上,又有时我们需要将对象传输到另一台计算机上等等这些的操作。此时,我们需要将对象转换成一个字节序列,这个过程我们称之为序列化。相反,我们将一个字节序列转换成对应的对象,这个过程我们称之为反序列化。我们可以通过ObjectOutputStream流的方法WriteObject(Object o)实现对象序列化,通过ObjectInputStream流的方法readObject()实现对象反序列化。
对象序列化,就是把一个对象变为二进制的数据流的一种方法,通过对象序列化可以方便的实现对象的传输或存储。
对象是存在于内存中的,有的候需要将对象保存到硬盘上,或者将对象传输到另一台计算机上的等等操作。想要使用对象流,我们首先要知道Serializable这个接口
Serializable接口
ObjectOutputStream在对对象进行序列化时有一个要求,就是需要对象所属的类型必须实现Serializable接口。此接口内什么都没有,只是作为可序列化的标识。
//定义一个可序列化的类
import java.io.Serializable;
public class Person implements Serializable { // 此类的对象可以被序列化
private String name; // 声明name属性
private int age; // 声明age属性
public Person(String name, int age) { // 通过构造方法设置属性内容
this.name = name;
this.age = age;
}
public String toString() { // 覆写toString()方法
return "姓名:" + this.name + ";年龄:" + this.age;
}
}
SerailVersionUID
通常实现序列化接口的类需要提供一个常量serialVersionUID,表明该类的版本。若不显示的声明,在该对象序列化时也会根据当前类的各个方面计算该类的默认serialVersionUID。但是不同平台的编译器实现有所不同,所以想要跨平台,都应该显示的声明版本号。如果类的对象序列化存入硬盘上面,之后随着需求的变化更改了类的属性(增加或减少或改名等),那么当反序列化时,就会出现异常(InvalidClassException),这样就造成了不兼容性的问题。但当serialVersionUID相同时,就会将不一样的field以type的预设值反序列化,避免不兼容问题。
序列化时为了保持版本的兼容性,即在版本升级时反序列化仍保持对象的唯一性。有两种生成方式:
- 默认的1L,比如:private static final long serialVersionUID = 1L;
- 根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段,比如: private static final long serialVersionUID = xxxxL;
Transient关键字
对象在序列化后得到的字节序列往往比较大,有时候我们在对一个对象序列化操作时,可以忽略某些不必要的属性,从而对序列化后得到的字节序列“瘦身”。使用transient关键字修饰的属性在序列化时其值将被忽略。
首先创建一个类person 然后实现Serializable接口,然后为了使其可以进行返序列化操作,我们就要显式声明一个UID
//Person中的name属性不希望被序列化
import java.io.Serializable;
public class Person implements Serializable { // 此类的对象可以被序列化
private transient String name; // 此属性将不被序列化
private int age; // 此属性将被序列化
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String toString() { // 覆盖toString(),输出信息
return "姓名:" + this.name + ";年龄:" + this.age;
}
}
System类
System类是一些与系统相关的属性和方法的集合,而且在System类中所有的属性都是静态的,要想引用这些属性和方法,直接使用System类调用即可。
No. |
方法定义 |
类型 |
描述 |
1 |
public static void exit(int status) |
普通 |
系统退出 |
2 |
public static void gc() |
普通 |
运行垃圾收集机制,调用的是Runtime类中的gc方法 |
3 |
public static long currentTimeMillis() |
普通 |
返回以毫秒为单位的当前时间 |
4 |
public static void arraycopy(Object src,int srcPos,Object dest,int destPos,int length) |
普通 |
数组拷贝操作 |
5 |
public static Properties getProperties() |
普通 |
取得当前的系统全部属性 |
6 |
public static String getProperty(String key) |
普通 |
根据键值取得属性的具体内容 |
System.in实际上是一个键盘的输入流,其本身是InputStream类型的对象。那么,此时就可以利用此方式完成从键盘读取数据的功能。
其他流
System.out:为PrintStream类型,代表标准输出流,默认的数据输出是控制台out:是System类的一个静态成员变量
No. |
System类的常量 |
描述 |
1 |
public static final PrintStream out |
对应系统标准输出,一般是显示器 |
2 |
public static final PrintStream.err |
错误信息输出 |
3 |
public static final InputStream in |
对应着标准输入,一般是键盘 |