IO
IO流概述
可以将数据传输操作,看作一种数据的流动,按照流动的方向分为输入(Input)和输出(Output) java中的IO操作主要指的是java.io包下的一些常用类的使用,通过这些常用类对数据进行读取(输入Input)和写出(输出Output)
IO流的分类
按照流的方向来分,可以分为:输入流和输出流 按照流动的数据类型来分,可以分为:字节流和字符流 计算机中的任何数据(文本、图片、视频、音乐等等)都是以二进制的形式存储的 在数据传输时也都是以二进制的形式存储的 后续的任何流,在传输时底层都是二进制 流在写完以后一定要关闭
字节流:一切皆字节
输入流:InputStream
常用方法: int read (byte[] b): 从输入流中读取一些字节数并将它们存储到缓冲区数组 b 。 int read (byte[] b, int off, int len): 从输入流 len最多 len字节的数据读入一个字节数组。 abstract int read():从输入流中读取下一个数据字节。 byte[] readAllBytes(): 从输入流中读取所有剩余字节。 int readNBytes (byte[] b, int off, int len): 从输入流中读取请求的字节数到给定的字节数组中。 byte[] readNBytes (int len): 从输入流中读取指定的字节数。 void mark (int readlimit) 标记此输入流中的当前位置。 void reset(): 将此流重新定位到上次在此输入流上调用 mark方法时的位置。 long transferTo (OutputStream out) 从该输入流中读取所有字节,并按读取顺序将字节写入给定的输出流。 void close(): 关闭此输入流并释放与该流关联的所有系统资源。
常用子类:FileInputStream(实体类)文件输入流
构造方法: FileInputStream(File file): 通过打开与实际文件的连接来创建 FileInputStream ,该文件由文件系统中的 File对象 file命名。 FileInputStream (String name): 通过打开与实际文件的连接来创建 FileInputStream ,该文件由文件系统中的路径名 name命名。 常用方法: int available(): 返回可以从此输入流中读取(或跳过)的剩余字节数的估计值,而不会被下一次调用此输入流的方法阻塞。 void close(): 关闭此文件输入流并释放与该流关联的所有系统资源。 FileChannel getChannel(): 返回与此文件输入流关联的唯一FileChannel对象。 FileDescriptor getFD(): 返回 FileDescriptor对象,该对象表示与此 FileInputStream正在使用的文件系统中的实际文件的连接。 int read(): 从此输入流中读取一个字节的数据。 int read (byte[] b): 从此输入流 b.length最多 b.length字节的数据读 b.length字节数组。 int read (byte[] b, int off, int len): 从此输入流 len最多 len字节的数据读入一个字节数组。 long skip (long n): 跳过并从输入流中丢弃 n字节的数据。 注意:通过while (inputStream.read() != -1)这种方式来循环输出文件内容是不可行的,隔一个输出一个 通过一组字节数组来获取文件内容,文件内容是进行覆盖操作,abcdefg,用一个长度为三的字节数组输出, 输出结果为 abc def gef 最后一个是因为只有一个元素g了,覆盖了上一数组 def 就变成了 gef 解决方案: byte[] bytes = new byte[3]; int len = fis.read(bytes); sout(new String(bytes,0,len)); 使用重复输出时,数据读完后len == -1 常用一组获得的方法
输出流:OutputStream(抽象类)
常用方法: void close():关闭此输出流并释放与此流关系的所有系统资源 void flush():刷新此输出流并强制写出任何缓冲的输出字节 static OutputStream nullOutputStream():返回一个新的OutputStream,它丢弃所有字节 void write(byte[] b):将b.length字节从指定的字节数组写入此输出流 void write(byte[] b,int off ,int len):将从偏移量off开始的指定字节数组汇总的len字节写入此输出流 abstract void write(int b):将指定的字节写入此输出流
常用子类:FileOutputStream(实体类)文件输出流
构造方法: FileOutputStream (File file): 创建文件输出流以写入由指定的 File对象表示的文件。 FileOutputStream (File file, boolean append): 创建文件输出流以写入由指定的 File对象表示的文件。 FileOutputStream (String name): 创建文件输出流以写入具有指定名称的文件。 FileOutputStream (String name, boolean append): 创建文件输出流以写入具有指定名称的文件。 append参数表示是否是续写 新建对象时参数写的是true\false 常用方法: void close():关闭此文件输出流并释放与此流关联的所有系统资源。 void write (byte[] b):将指定字节数组中的 b.length字节写入此文件输出流。 void write (byte[] b, int off, int len):将从偏移量 off开始的指定字节数组中的 len字节写入此文件输出流。 off是开始的下标。len是写出的长度 void write (int b):将指定的字节写入此文件输出流。 注意:流的所有操作都要在关闭(close())之前执行,在关闭后执行会报异常,而且关闭要尽可能早的关闭 使用字节流输出一串字符串时,要先将字符串转换成byte,xx.getBytes();
字符流:
以字符为单位,解决了字节流字符乱码的问题
输入流:Reader
构造方法: protected Reader(): 创建一个新的字符流阅读器,其关键部分将在阅读器本身上同步。 protected Reader (Object lock): 创建一个新的字符流阅读器,其关键部分将在给定对象上同步。 常用方法: abstract void close(): 关闭流并释放与其关联的所有系统资源。 void mark (int readAheadLimit): 标记流中的当前位置。 boolean markSupported(): 判断此流是否支持mark()操作。 static Reader nullReader(): 返回不读取任何字符的新 Reader 。 int read(): 读一个字符。 int read (char[] cbuf): 将字符读入数组。 abstract int read (char[] cbuf, int off, int len): 将字符读入数组的一部分。 int read (CharBuffer target): 尝试将字符读入指定的字符缓冲区。 boolean ready(): 判断此流是否可以读取。 void reset(): 重置流。 long skip (long n): 跳过字符。 long transferTo (Writer out): 读取此阅读器中的所有字符,并按照读取的顺序将字符写入给定的编写器。
输出流:Writer
构造方法: protected Writer(): 创建一个新的字符流编写器,其关键部分将在编写器本身上同步。 protected Writer (Object lock): 创建一个新的字符流编写器,其关键部分将在给定对象上同步。 常用方法: Writer append (char c): 将指定的字符追加到此writer。 Writer append (CharSequence csq): 将指定的字符序列追加到此writer。 Writer append (CharSequence csq, int start, int end): 将指定字符序列的子序列追加到此writer。 abstract void close(): 关闭流,先冲洗它。 abstract void flush(): 刷新流。 static Writer nullWriter(): 返回一个新的 Writer ,它丢弃所有字符。 void write (char[] cbuf): 写一个字符数组。 abstract void write (char[] cbuf, int off, int len): 写一个字符数组的一部分。 void write (int c): 写一个字符。 void write (String str): 写一个字符串。 void write (String str, int off, int len): 写一个字符串的一部分。 append()方法返回的是调用append()方法的对象,所以append()方法可以套娃,xx.append().append()....;
转换流
将字节流 转换(装饰)为 字符流:使用了装饰者设计模式
InputStreamReader
构造方法: InputStreamReader(InputStream(要转换的字节输入流))
OutputStreamWriter
构造方法: OutputStreamWriter(OutputStream(要转换的字节输出流))
###
Print与BufferedReader
一种更好的字节流转换成字符流的方法
PrintStream:字节流 PrintWriter:字符流 记得最后要刷新流(flush) 或者 关闭(close) 操作方法: new PrintStream("地址") new PrintWriter("地址") FileOutputStream fos = new FileOutputStream("地址") new PrintStream(fos) FileWriter fw = new FileWriter("地址") new PrintWriter(fw)
BufferReader
缓存读取流,将字符输入流,转换为带有缓存 可以一次读取一行的缓存字符读取流 操作方法: FileReader fr = new FileReader("地址") new BufferReader(fr);
收集异常日志
try{ String s =null; s.toString(); }catch(Exception e){ e.printStackTrace(); } try{ String s =null; s.toString(); }catch(Exception e){ PrintWriter pw = new PrintWriter("地址"); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss") pw.println(sdf); e.printStackTrace(pw); pw.close(); }
Properties
是使用键值对方式进行存储的 构造方法: Properties(): 创建一个没有默认值的空属性列表。 常用方法: String getProperty (String key): 在此属性列表中搜索具有指定键的属性。 String getProperty (String key, String defaultValue): 在此属性列表中搜索具有指定键的属性。 void list (PrintStream out): 将此属性列表打印到指定的输出流。 void list (PrintWriter out): 将此属性列表打印到指定的输出流。 void load (InputStream inStream): 从输入字节流中读取属性列表(键和元素对)。 void load (Reader reader): 以简单的面向行的格式从输入字符流中读取属性列表(键和元素对)。 void loadFromXML (InputStream in): 将指定输入流上的XML文档表示的所有属性加载到此属性表中。 Object setProperty (String key, String value): 调用 Hashtable方法 put 。 void store (OutputStream out, String comments): 将此 Properties表中的此属性列表(键和元素对)以适合使用 load(InputStream)方法加载到 Properties表的格式写入输出流。 void store (Writer writer, String comments): 将此 Properties表中的此属性列表(键和元素对)以适合使用 load(Reader)方法的格式写入输出字符流。 void storeToXML (OutputStream os, String comment): 发出表示此表中包含的所有属性的XML文档。 void storeToXML (OutputStream os, String comment, String encoding): 使用指定的编码发出表示此表中包含的所有属性的XML文档。 void storeToXML (OutputStream os, String comment, Charset charset): 使用指定的编码发出表示此表中包含的所有属性的XML文档。 comment:输出到文件时的备注 Set<String> stringPropertyNames(): 从此属性列表返回一组不可修改的键,其中键及其对应的值是字符串,如果尚未从主属性列表中找到相同名称的键,则包括默认属性列表中的不同键。
序列化与反序列化
能用,但不建议用,因为java官方说近几个版本会删除 使用序列化时,类和属性都必须实现序列化接口 存进去的一个ArrayList取出来也得是要个集合,不能
对象的序列化和反序列化实现
public class Student implements Serializable { private String stuNum; private String stuName; private List<String> teacherList; } 定义序列化和反序列化工具类 public class MySerializeUtil { //序列化 public static void mySerialize(Object obj , String fileName) throws IOException { FileOutputStream ops = new FileOutputStream(fileName); ObjectOutputStream oos = new ObjectOutputStream(ops); oos.writeObject(obj); oos.close(); } //反序列化 public static Object myDeserialize (String fileName) throws IOException, ClassNotFoundException { FileInputStream fis = new FileInputStream(fileName); ObjectInputStream ois = new ObjectInputStream(fis); Object object = ois.readObject(); return object; } } main方法测试 public static void main( String[] args ) throws IOException { ArrayList<String> teacher = new ArrayList<>(); teacher.add("bdyjy"); teacher.add("ssyy"); Student student = new Student("20181271","szh",teacher); System.out.println("原始对象:" + student); String fileName = "stu.txt"; try { MySerializeUtil.mySerialize(student,fileName); System.out.println("对象序列化完成"); Object obj = MySerializeUtil.myDeserialize(fileName); System.out.println("对象反序列化完成"); if (obj instanceof Student){ Student NewStu = (Student) obj; System.out.println("反序列化后的对象:" + NewStu); } } catch (ClassNotFoundException e) { e.printStackTrace(); } } 执行结果: 原始对象:Student{stuNum='20181271', stuName='szh', teacherList=[bdyjy, ssyy]} 对象序列化完成 对象反序列化完成 反序列化后的对象:Student{stuNum='20181271', stuName='szh', teacherList=[bdyjy, ssyy]} 同时 stu.txt 文件里面也有内容写入
部分属性的序列化
1.使用transient修饰符 : 使用该修饰符修饰的属性将不会被序列化 2.使用static修饰符 :static修饰符修饰的属性也不会参与序列化和反序列化。修改实体类,将实体类中不想序列化的属性添加static修饰词。 3.默认方法writeObject和readObject。 private void writeObject(ObjectOutputStream objOut) throws IOException { System.out.println("writeObject-----------"); objOut.writeObject(stuNum); objOut.writeObject(stuName); } private void readObject(ObjectInputStream objIn)throws IOException,ClassNotFoundException { System.out.println("readObject-----------"); stuNum= (String) objIn.readObject(); stuName= (String) objIn.readObject(); } 我们在添加的方法中只对stuNum和stuName属性做了序列化和反序列化的操作,因此只有这个两个属性可以被序列化和反序列化。 注意:添加的两个方法必须是private void,否则不生效。
Externalizable实现Java序列化
刚刚我们说实现部分属性序列化的方式有多种,最后一种来啦!就是通过实现Eexternalizable接口。 Externalizable继承自Serializable,使用Externalizable接口需要实现readExternal方法和 writeExternal方法来实现序列化和反序列化。 需要实现序列化和反序列化的类实现Externalizable接口,并重写里面的两个方法 @Override public void writeExternal(ObjectOutput out) throws IOException { out.writeObject(stuNum); out.writeObject(stuName); //out.writeObject(teacherList); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { stuNum= (String) in.readObject(); stuName= (String) in.readObject(); //teacherList= (List<String>) in.readObject(); } Externalizable接口继承了Serializable接口,所以实现Externalizable接口也能实现序列化和反序列化。 Externalizable接口中定义了writeExternal和readExternal两个抽象方法,这两个方法其实对应 Serializable接口的writeObject和readObject方法。可以这样理解:Externalizable接口被设计出来的 目的就是为了抽象出writeObject和readObject这两个方法,但是目前这个接口使用的并不多。
序列化
Java序列化是指把Java对象转换为字节序列的过程,Java反序列化是指把字节序列恢复成Java对象的过程。通过序列化和反序列化实现网络传输、本地存储的目的。
Serializable实现Java序列化
要实现Java对象的序列化,只要将类实现标识接口--Serializable接口即可,不需要我们重写任何方法就可以实现序列化
Serializable VS Externalizable
区别 | Serializable | Externalizable |
---|---|---|
实现复杂度 | 实现简单,Java对其有内建支持 | 实现复杂,由开发人员自己完成 |
执行效率 | 所有对象由Java统一保存,性能较低 | 开发人员决定哪个对象保存,可能造成速度提升 |
保存信息 | 保存时占用空间大 | 部分存储,可能造成空间减少 |
使用频率 | 高 | 偏低 |
try-with-resources
JDK1.7之前 : 太麻烦,为了获取一个字符,写了一大段代码 FileReader fr = null; try{ fr = new FileReader("地址"); int c = fr.read(); sout((char)c); }catch(IOExceprion e){ e.printStackTrace(); }finally{ try{ fr.close(); }catch(IOException e){ e.printStackTrace(); } } JDK1.7时 : 耦合性太强,假如fr在前面就已经被定义,或者在后面还需调用的话,无法使用此方法 try(FileReader fr = new FileReader("地址")){ int c = fr.read(); sout((char)c); }catch(IOException e){ e.printStackTrace(); } JDK9进行优化 可自动关闭流。也不妨碍前后调用。需要关闭多个流,各个流之间用;隔开 FileReader fr = new FileREader("地址"); FileWriter fw = new FileWriter("地址"); try(fr;fw){ int c = fr.read(); sout((char)c); }catch(IOException e){ e.printStackTrace(); }