预备知识
1. 文件分隔符
Windows:D:\Soft\QQ\Plugin
Linux:D:/Soft/QQ/Plugin
2. 换行
Windows:换行 = 回车+换行,即\r\n
Linux:换行 = 换行,即\n
3. IDEA默认的当前路径
工程Project的根就是IDEA的默认当前路径
4. IDEA文件分隔符格式
Windows: File file = new File(D:\test\test.txt);
Linux:File file = new File(D://test//test.txt);
统一:File testFile = new File(“D:” + File.separator + “test” + File.separator + fileName);
创建文件
public class FileTest {public static void main(String[] args) throws IOException {// 创建D:\filepath\test\test.txtFile testFile = new File("D:\filepath\test\test.txt");// 获取test.txt文件的上级路径File fileParent = testFile.getParentFile();// 创建多级目录if (!fileParent.exists()) {fileParent.mkdirs();}// 创建文件if (!testFile.exists())testFile.createNewFile();System.out.println("path:"+testFile.getPath());System.out.println("absolutePath:"+testFile.getAbsolutePath());System.out.println("getFileName:"+testFile.getName());}
}path:D:\filepath\test\test.txt
absolutePath:D:\filepath\test\test.txt
getFileName:test.txt
文件的常用方法
public class FileGet {public static void main(String[] args) {File f = new File("d:/aaa/bbb.java"); //文件绝对路径:d:\aaa\bbb.java System.out.println("文件绝对路径:"+f.getAbsolutePath());//文件构造路径:d:\aaa\bbb.javaSystem.out.println("文件构造路径:"+f.getPath());//文件名称:bbb.javaSystem.out.println("文件名称:"+f.getName());//文件长度:2116字节System.out.println("文件长度:"+f.length()+"字节");//是否存在:trueSystem.out.println(f.exists());}
}
1. InputStream
构造方法
// 1. 使用File对象创建流对象
FileInputStream fin = new FileInputStream(new File("G:\\b.txt"));// 2. 使用文件名称创建流对象
FileInputStream fin = new FileInputStream("G:\\b.txt");
读取数据
read():从输入流读取数据的下一个字节
read(byte[] b):从输入流读取一些字节数,并将它们存储到缓冲区b
read(byte[] b, int off, int len):从输入流读取最多 len字节的数据到一个字节数组。
public class FISRead {public static void main(String[] args) throws IOException{FileInputStream fis = new FileInputStream("read.txt");int b;while ((b = fis.read())!=-1) {System.out.println((char)b);}fis.close();}
}
错误读取
public class FISRead {public static void main(String[] args) throws IOException{FileInputStream fis = new FileInputStream("read.txt"); // read.txt文件中内容为abcdeint len; byte[] b = new byte[2];while (( len= fis.read(b))!=-1) {System.out.println(new String(b));}fis.close();}
}
输出结果:
ab
cd
ed
分析: 由于read.txt文件中内容为abcde,而错误数据d,是由于最后一次读取时,只读取一个字节e,数组中,上次读取的数据没有被完全替换【注意是替换,看下图】,所以要通过len ,获取有效的字节
public class FISRead {public static void main(String[] args) throws IOException{// 使用文件名称创建流对象.FileInputStream fis = new FileInputStream("read.txt"); // 文件中为abcde// 定义变量,作为有效个数int len ;// 定义字节数组,作为装字节数据的容器 byte[] b = new byte[2];// 循环读取while (( len= fis.read(b))!=-1) {// 每次读取后,把数组的有效字节部分,变成字符串打印System.out.println(new String(b,0,len));// len 每次读取的有效字节个数}// 关闭资源fis.close();}
}输出结果:
ab
cd
e
2. OuputStream
1. 构造方法
// 1. 使用File对象创建流对象
FileOutputStream fos = new FileOutputStream(new File("G:\\b.txt"));// 2. 使用文件名称创建流对象
FileOutputStream fos = new FileOutputStream("G:\\b.txt");// 3. 使用File对象创建流对象,并开启数据追加功能
FileOutputStream fos = new FileOutputStream(new File("G:\\b.txt"), true);// 4. 使用文件名创建流对象,并开启数据追加功能
FileOutputStream fos = new FileOutputStream("G:\\b.txt", true)
2. 输出数据
public class IoWrite {public static void main(String[] args) throws IOException {FileOutputStream fos = new FileOutputStream("fos.txt"); // 1. write(int b)fos.write(97); //2. write(byte[] b)byte[] b = "麻麻我想吃烤山药".getBytes();fos.write(b);//3. write(byte[] b, int off, int len)byte[] b = "abcde".getBytes();fos.write(b,2,2);fos.close();}
}
3. Reader
1. 构造器
// 1. 使用File对象创建流对象
FileReader fr = new FileReader(new File("G:\\b.txt"));// 2. 使用文件名称创建流对象
FileInputStream fos = new FileInputStream("G:\\b.txt");
2. 读取字符
public class FRRead {public static void main(String[] args) throws IOException {FileReader fr = new FileReader("G:\\b.txt");int b;while ((b = fr.read())!=-1) {System.out.println((char)b);}fr.close();}
}
4. Writer
1. 构造方法
// 1. 使用File对象创建流对象
FileWriter fw = new FileWriter(new File("G:\\b.txt"));// 2. 使用文件名称创建流对象
FileWriter fw = new FileWriter("G:\\b.txt");// 3. 使用File对象创建流对象,并开启数据追加功能
FileWriter fw = new FileWriter(new File("G:\\b.txt"), true);// 4. 使用文件名创建流对象,并开启数据追加功能
FileWriter fw = new FileWriter("G:\\b.txt", true)
2. 写出数据
flush :刷新缓冲区,流对象可以继续使用。
close:刷新缓冲区,然后通知系统释放资源。流对象不可以再被使用了。
public class FWWrite {public static void main(String[] args) throws IOException {// 使用文件名称创建流对象FileWriter fw = new FileWriter("fw.txt"); // 写出数据fw.write(97); // 写出第1个字符fw.write('b'); // 写出第2个字符fw.write('C'); // 写出第3个字符fw.close();}
}
输出结果:
abC
原理
创建缓冲流对象时,会创建一个内置的默认8kb的缓冲区数组。
当读数据时,一次I/O就会读取8kb数据,后面read都是读取缓冲区数据,减少系统IO次数,从而提高读的效率。
当写数据时,先把8kb缓冲区填满,然后再调用write把缓冲区8kb数据一次输出,减少系统IO次数,从而提高写的效率。
实例
/*** 使用缓冲字节输入输出流*/
public static void method3() throws IOException {BufferedInputStream bis = new BufferedInputStream(new FileInputStream("/Users/liyihua/IdeaProjects/Study/src/view/study/demo33/test.jpg"));BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("/Users/liyihua/IdeaProjects/Study/src/view/study/demo33/copy3.jpg"));byte[] bytes = new byte[1024];int len = 0;while ((len = bis.read(bytes)) != -1) {bos.write(bytes, 0, len);}bos.close();bis.close();
}
InputStreamReader
字节流到字符流的桥梁,OutputStreamWriter
字符流到字节流的桥梁。
序列化 与 反序列化
可序列化的两个条件
- 该类必须实现java.io.Serializable 接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出NotSerializableException 。
- 该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的,使用transient 关键字修饰。
public class Employee implements java.io.Serializable {public String name;public String address;public transient int age; // transient瞬态修饰成员,不会被序列化public void addressCheck() {System.out.println("Address check : " + name + " -- " + address);}
}
public class SerializeDemo{public static void main(String [] args) {Employee e = new Employee();e.name = "zhangsan";e.address = "beiqinglu";e.age = 20; try {ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("employee.txt"));out.writeObject(e);out.close();fileOut.close();} catch(IOException i) {i.printStackTrace();}}
}
1. 非阻塞IO
原始的IO并没有缓存,所以线程调用read或 write方法时,必须等待读取或写入所有字节,这时候线程不能干其他事情,所以线程是阻塞的。
非阻塞NIO增加了缓存,线程调用read或 write方法时,不需要等待读取或写入所有字节,可以去做其他的事情。如果一个线程有多个缓存区,那么可以先去缓存区1执行read,然后到缓存区2执行read,这时候返回缓存区1读取数据,这样就避免了在缓存区1中等待。
2. 组件
NIO主要有三大核心部分:Channel(通道),Buffer(缓冲区), Selector(选择器)。数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(选择区)用于监听多个通道的事件。因此,单个线程可以监听多个数据通道。
1. Channel
通道可以同时进行读写
2. Buffer
当向buffer写入数据时,buffer会记录下写了多少数据,一旦要读取数据,需要通过flip()方法将Buffer从写模式切换到读模式,在读模式下可以读取之前写入到buffer的所有数据,一旦读完了所有的数据,就需要清空缓冲区,让它可以再次被写入。
Buffer对象包含三个重要的属性
capacity:Buffer的最大值
position:position表示当前的位置。
limit:在写模式下,表示最多能往Buffer里写多少数据或者最多能读到多少数据。
3. Selector
当你调用Selector的select()或者 selectNow() 方法它只会返回有数据读取的SelectableChannel的实例