数据是如何存储的?
变量、数组、集合 弊端:不能永久化存储,程序关闭数据就没了。
计算机中,有没有一个硬件实现永久化存储。硬盘可以实现数据永久化存储。
IO就可以对硬盘中的文件进行读写操作
File表示要读写的文件在哪里,也可以对文件进行操作
File:他是文件和目录路径名的抽象表示
文件和目录可以通过File封装成对象
File封装的对象仅仅是一个路径名
方法名说明File(String pathname)可通过给定字符串转化为路径名创建File对象File(String pathname,String child)父路径名和子路径名创建出一个新的File实例File(File parent,String child)父文件对象名和子路径名字符串创建处一个行的File实例案例:
public class File001 { public static void main(String[] args) throws IOException { //File(String pathname)可通过给定字符串转化为路径名创建File对象 File file1 = new File("D:\\User\\a.txt"); //File(String pathname,String child)父路径名和子路径名创建出一个新的File实例 String s1 = "D:\\User\\A"; String s2 = "a.txt"; File file2 = new File(s1, s2); //File(File parent,String child)父文件对象名和子路径名字符串创建处一个行的File实例 File file3 = new File("D:\\MM\\AA"); String s3="a.txt"; File file4 = new File(file3, s3); } }相对路径:相对当前项目路径下File file1 = new File("模块名\\a.txt");
绝对路径:从盘符开始 File file1 = new File("D:\\User\\a.txt");
注意事项:
createNewFile():只创建一个空的文件,如果在一个不存在的文件夹中创建会报错。mkdir():只创建单级文件夹,如果有多级文件夹路径,会报错。mkdirs():可创建单级文件夹和多级文件夹,可代替mkdir()方法。delete():只能删除单个文件或者空的文件夹,如果文件夹嵌套很深,可结合listFiles()方法用递归的方法逐层删除。**案例一:**删除文件夹中的多层文件
public class DeleteFileTest01 { public static void main(String[] args) { File file = new File("F:\\GitHub"); DeleteFile(file); } private static void DeleteFile(File file) { File[] files = file.listFiles(); //获取当前文件夹所有文件和文件夹 for (File file1 : files) { //循环遍历每个文件 if (file1.isFile()){ //判断是否是文件 file1.delete(); //是文件做删除操作 }else { DeleteFile(file1); //重点:不是文件做递归操作重复执行上述流程,传入的参数是新的文件夹名称 } file.delete(); //最后所有文件夹删除后,在删除自己(根文件夹) } } }**案例二:**统计文件夹每种文件的次数
public class IOTest { public static void main(String[] args) throws IOException { //统计文件夹每种文件的次数 //获取文件路径,获取Map集合根据集合键唯一值不唯一特性来获取文件次数 File file = new File("D:\\IDEAworkspace\\basic_Code");//要查看的文件夹路径 HashMap<String, Integer> hashMap = new HashMap<>(); //定义一个集合用于存储文件名和出现的次数 HashMap<String, Integer> count = getCount(file, hashMap); //定义一个方法 System.out.println(count); //最后输出集合 } private static HashMap<String, Integer> getCount(File file, HashMap<String, Integer> hashMap) { if (file.exists()){ //判断文件夹是否存在 if (file.isFile()){ //判断是否是文件 String name = file.getName(); //如果是文件获取文件名 String[] split = name.split("\\.");//分割文件名 .有时做通配符使用 \\.是为了去掉.的通配效果变成一个普通的点 if (split.length==2){ //排除不规范的文件名如:a.a.txt String s = split[1]; //并截取文件名后缀名 if (hashMap.containsKey(s)){ //判断该文件名是否在集合中存在 Integer count = hashMap.get(s); //存在的话获取到他的值(出现的次数) count++; //并做自增操作 hashMap.put(s,count); //放入集合(键会覆盖,值会重新赋值为自增后的次数) }else { hashMap.put(s,1); //不存在的话存入文件名为键,次数为1 } } }else { File[] files = file.listFiles(); //获取文件清单 for (File file1 : files) { //循环遍历每个文件 getCount(file1,hashMap); //递归上述操作 } } } return hashMap; } }使用场景:
想要进行文件拷贝,一律使用字节流或字节缓冲流。
文件拷贝是在不同文件之间进行数据搬运。
按流向分:分为输入流和输出流。
按数据类型分:分为字节流(能操作所有类型文件)和字符流(只能操作纯文本文件)。
以内存为参照物
I:表示input,是数据从硬盘进内存的过程,称为读。
O:表示output,是数据从内存进硬盘的过程,称为写。
写入数据步骤:
创建文件对象写数据释放资源 FileOutputStream fos = new FileOutputStream("D:\\basic_Code");1、FileOutputStream第二个参数是一个续写开关:为true时源文件不会被清空,会接着原有文件写入文件。
FileOutputStream fos = new FileOutputStream("D:\\basic_Code",true);2、换行
字节流写数据如何实现换行呢?
写完数据后,加上换行符。
Windows:\r\n
linux:\n
max:\r
public class test { public static void main(String[] args) throws IOException { FileOutputStream fos = new FileOutputStream(new File("D:\\Temp\\TxGameDownload")); fos.write(96); fos.write("\r\n".getBytes());//文件在计算机中是以字节传输所以我们需要对换行符进行装换 fos.write(97); fos.write("\r\n".getBytes()); fos.write(98); fos.close(); } }读入数据步骤:
创建文件对象读数据释放资源 FileInputStream fis = new FileInputStream("D:\\basic_Code\\aaa\\a.txt");文件在计算机中以字节进行传输,返回值就是本次读到的字节,也就是字符在码表中的数字。如:a对象的字节就是96. 如果我们想要看到字符数据可对其进行强转
System.out.println((char) b);
通过定义一个字节数组每次传递多个字节并写入,已达到高效拷贝的目的。
public class test { public static void main(String[] args) throws IOException { FileInputStream fis = new FileInputStream(new File("D:\\Temp\\TxGameDownload\\a.txt")); FileOutputStream fos = new FileOutputStream(new File("D:\\Temp\\TxGameDownload\\a.txt")); byte[] bytes = new byte[1024]; //定义一个字节数组,用于一次读取这么多长度的字节 int len; //定义一个长度,用于定义读取到的实际字节长度 while ((len=fis.read(bytes))!=-1){//循环判断一次读取到bytes的长度是否到了-1(-1就意味着数据没有了读取到了最后) fos.write(bytes,0,len); //写入读取到的文件,从0索引开始,写入的长度为len } fos.close(); //释放资源 fis.close(); } }BufferedInputStream:字节缓冲输入流
BufferedOutputStream:字节缓冲输出流
构造方法:
BufferedOutputStream(OutputStream)
BufferedInputStream(InputStream)
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:\\Temp\\TxGameDownload\\a.txt")); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("E:\\Temp\\TxGameDownload\\a.txt"));缓冲流如何高效实现数据传输:对文件实现传输的对象是字节流。当我们定义一个数组为1024时字节流可以每次从内存中获取到1024大小的字节,进行传输,效率能得到提高。
当定义一个缓冲流时他的底层会定义一个默认长度是8192的数组,用于数据传输,所以缓冲流会帮我们从内存中获取到8192个大小的字节进行传输。
public class StreamTest001 { public static void main(String[] args) throws IOException { //缓冲流底层会默认创建一个长度为8192的数组,用于数据传输 BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:\\basic_Code\\aaa\\a.txt")); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("D:\\basic_Code")); int b; while ((b=bis.read())!=-1){ bos.write(b); } bos.close(); bis.close(); } }每次我们都要对字节流进行关流操作,如果我们在创建字节流后程序出现错误,则程序报错,无法执行关流代码
对字节流写数据我们都要加上try…catch…finally操作
finally无论怎么操作左后都会执行
//标准关流操作 public class IOTry_Catch_Finaly { public static void main(String[] args) { FileInputStream fis = null; FileOutputStream fos = null; try { fis = new FileInputStream("F:\\GitHub\\123.jpg"); fos = new FileOutputStream("F:\\GitHub\\a\\123.jpg"); int b; // byte[] bytes = new byte[1024]; // while ((b=fis.read(bytes))!=-1){ // fos.write(b); // } while ((b=fis.read())!=-1){ fos.write(b); } }catch (IOException e){ e.printStackTrace(); }finally { if (fis!=null){ try { fos.close(); }catch (IOException e){ e.printStackTrace(); } } if (fis!=null){ try { fis.close(); }catch (IOException e){ e.printStackTrace(); } } } } }使用场景:
想把文件数据读到内存中,请使用字符输入流。
想把内存数据写入到文件中,请使用字符输出流。
字节流可以操作任意类型的文件,为什么还要字符流呢?
因为字节流读取文件时按每一个字节进行读取,而中文在GBK码表中占两个字节,在UTF-8码表中占三个字节。所以用字节流读取文件时,读取一个字节就去对应的编码表中找字符了,以至于找不到字符造成乱码状态。同理把中文写入文本文件时,也可能出现乱码。
字符串中的编码解码问题:
编码:
byte[] getBytes():使用平台默认的字符集将String编码为一系列字节,将结果存储到新的字节数组中。byte[] getBytes(String charsetName):使用指定的字符集将String编码为一系列字节,将结果存储到新的字节数组中。解码:
String(byte[] bytes):使用平台默认字符集解码指定的字节数组来构造新的String。
String(byte[] bytes,String charsetName):使用指定的字符集解码指定的字节数组来构造新的String。
public class 编码和解码 { public static void main(String[] args) throws UnsupportedEncodingException { // - byte[] getBytes():使用平台**默认的字符集**将String编码为一系列字节,将结果存储到新的字节数组中。 String s = "张三"; byte[] bytes = s.getBytes();//默认平台UTF-8 System.out.println(Arrays.toString(bytes));//[-27, -68, -96, -28, -72, -119] // - byte[] getBytes(String charsetName):使用**指定的字符集**将String编码为一系列字节,将结果存储到新的字节数组中。 String s1 = "张三"; byte[] bytes1 = s1.getBytes("GBK");//GBK一个字符两个字节 System.out.println(Arrays.toString(bytes1));//[-43, -59, -56, -3] // - String(byte[] bytes):使用平台**默认字符集**解码指定的字节数组来构造新的String。 String Str = new String(bytes); System.out.println(Str);//张三 // - String(byte[] bytes,String charsetName):使用**指定的字符集**解码指定的字节数组来构造新的String。 String str1 = new String(bytes1, "gbk"); System.out.println(str1);//张三 } }字符流=字节流+编码表
不管在哪张码表中,中文的第一个字节一定是个负数。
所以读取时读到了一个负数的字节时就知道这是个中文,所以就会按编码表去读取字节,如果是GBK编码表就会一次性读取两个字节再去编码表中找对应的字符。
FileReader
步骤:
创建字符输入流对象
写数据
释放资源
public class Test002 { public static void main(String[] args) throws IOException { FileReader fr = new FileReader("a.txt"); int b; while ((b=fr.read())!=-1){ System.out.println(b); } fr.close(); } }FileWriter
步骤:
创建字符输出流对象
写数据
释放资源
public class Test002 { public static void main(String[] args) throws IOException { FileWriter fw = new FileWriter("a.txt"); fw.write("写入数据"); fw.flush(); //刷新流 fw.close(); }原理同字节流
BufferedReader
BufferedWriter
方法说明void newLine()分隔符相当于回车换行public String readLine()一次读取一行字符 public class BufferedReaderTest { public static void main(String[] args) throws IOException { BufferedWriter bw = new BufferedWriter(new FileWriter("c.txt")); bw.write("97"); bw.newLine(); //回车换行 bw.write("98"); bw.newLine(); bw.write("99"); bw.newLine(); bw.write("100"); bw.close(); BufferedReader br = new BufferedReader(new FileReader("c.txt")); br.readLine();//一次读一行 br.close(); } }案例:
public class Test002 { public static void main(String[] args) throws IOException { //练习将a.txt文件中的数据1 3 7 5 6 3 2 9 8排序好写入到b.txt文件中 //思路:先读取文件数据,把空格分割,将每一个数字保存到数组中 //利用数组Arrays的sort方法排序 //在写入到b.txt文件中 BufferedReader br = new BufferedReader(new FileReader("a.txt")); String s = br.readLine(); //整行读取 System.out.println(s); br.close(); String[] split = s.split(" ");//以空格进行分割 int[] ints = new int[split.length]; //定义数组用于保存分割后的数字 for (int i = 0; i < split.length; i++) { //循环遍历分割数组,把每一个数字保存到数组中 String s1 = split[i]; int i1 = Integer.parseInt(s1); //因为分割下来的是字符串所以要对其进行转换成int ints[i] = i1; } Arrays.sort(ints); //数组排序 System.out.println(Arrays.toString(ints)); BufferedWriter bw = new BufferedWriter(new FileWriter("b.txt")); for (int i = 0; i < ints.length; i++) { //将数组中每个数据循环写入文件 bw.write(ints[i]+" "); bw.flush(); //刷新操作 } bw.close(); //释放资源 } }**字符串的排列规则:**不是比较整体大小而是取每个字符对比相等再取第二个字符比较
字符流拷贝文件纯文本文件:读取字符将字符转换成二进制,传输到目标文件,在根据二进制数字到编码表中查对应的字符写入文件
字符流拷贝数字:拷贝数字时直接写入数字会被转化成字符,可以在数字后+""转成字符串
FileInputStream
FileOutputStream
作用:
可以指定字符集,当文件是以其他字符集保存的,我们进行读写时,需要按相同的字符集进行操作
可以将字节与字符进行转换
public class InputStreamReaderTest { public static void main(String[] args) throws Exception { InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt"),"gbk");//文件是gbk编码格式所以用gbk编码读取文件 int b; while ((b=isr.read())!=-1){ System.out.println((char) b); } isr.close(); OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("a.txt"), "utf-8");//utf-8格式将数据写入文件 osw.write("重写写入数据"); osw.close(); } }在JDK11后,字符流推出了一个构造,也可以指定编码表,可以不使用转换流
FileReader fr = new FileReader("D:\\IDEAworkspace\\basic_Code", Charset.forName("utf-8"));主要用于实现对象序列化和反序列化。
就是把对象中保存的数据以字节的形式写入文件中和从文件读取出来。保护数据提高安全性
序列化写文件的数据我们是看不懂的提高了安全性,所以需要反序列化读取出来我们才能看懂
ObjectInputStream:对象输入流
ObjectOutputStream:对象输出流
//对象操作流,可实现序列化对对象进行数据传输保存到本地文件 public class ObjectStreamTest { public static void main(String[] args) throws IOException, ClassNotFoundException { // method1(); // method2(); } //写入文件 private static void method1() throws IOException { ArrayList<Student> list = new ArrayList<>(); //创建一个集合用于保存对象中的数据 Student s1 = new Student("战三", 12); //创建对象封装数据 Student s2 = new Student("五四", 23); Student s3 = new Student("赵柳", 24); Student s4 = new Student("前妻", 41); list.add(s1); list.add(s2); list.add(s3); list.add(s4); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("a.txt")); //创建对象输出流 oos.writeObject(list); //将集合中的数据写入对象中 } //读取文件 private static void method2() throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream("a.txt")); //创建对象输入流 ArrayList<Student> o = (ArrayList<Student>) ois.readObject(); //读取文件中的数据 for (Student student : o) { System.out.println(student); //循环变量文件中读取到的数据 } } }是一个Map体系的集合类
Properties中有跟IO相关的方法
只存字符串
具备集合的增删改查方法:put,get,remove
还具备自己的特有功能:
方法名说明Object setProperties(String key,String value)设置集合中的键,都是String类型相当于Map集合中putString getProperties(String key)根据键获取值相当于Map集合中getSet stringPropertyName()返回一个键集相当于Map集合中keySet结合IO流使用的方法:
方法名说明void load(Reader reader)从输入字符流中获取属性列表(键与值)void store(Writer writer,String comments)将属性列表写入Properties表中案例:
public class PropertiesTest { public static void main(String[] args) throws IOException { Properties pro = new Properties();//创建集合 pro.setProperty("张三","123"); //添加键值对 pro.setProperty("李四","465"); pro.setProperty("王五","789"); FileWriter fw = new FileWriter("b.txt"); pro.store(fw,null); //将集合写入文件 fw.close(); FileReader fr = new FileReader("b.txt"); pro.load(fr); //读取文件 System.out.println(pro); fr.close(); } }文件中以键值对的方式存在
应用场景:常用于加载配置文件获取配置文件中的数据