Java基础之IO流
Java IO
一、什么是IO?
Java中I/O操作主要是指使用Java进行输入,输出操作.。Java所有的I/O机制都是基于数据流进行输入输出,这些数据流表示了字符或者字节数据的流动序列。Java的I/O流提供了读写数据的标准方法。任何Java中表示数据源的对象都会提供以数据流的方式读写它的数据的方法。
IO又分为流IO(java.io)和块IO(java.nio)
流IO的好处是简单易用,缺点是效率较低;块IO效率很高,但编程比较复杂。
二、流IO
在电脑上的数据有三种存储方式,一种是外存,一种是内存,一种是缓存。比如电脑上的硬盘,磁盘,U盘等都是外存,在电脑上有内存条,缓存是在CPU里面的。外存的存储量最大,其次是内存,最后是缓存,但是外存的数据的读取最慢,其次是内存,缓存最快。这里总结从外存读取数据到内存以及将数据从内存写到外存中。对于内存和外存的理解,我们可以简单的理解为容器,即外存是一个容器,内存又是另外一个容器。那又怎样把放在外存这个容器内的数据读取到内存这个容器以及怎么把内存这个容器里的数据存到外存中呢?
1、IO的分类
1)根据流向分为输入流和输出流:
输出:把程序(内存)中的内容输出到磁盘、光盘等存储设备中;
输入:读取外部数据(磁盘、光盘等存储设备的数据)到程序(内存)中。
2)根据传输数据单位分为字节流和字符流:
字节流:数据流中最小的数据单元是字节 ;
字符流:数据流中最小的数据单元是字符, Java中的字符是Unicode编码,一个字符占用两个字节(无论中文还是英文都是两个字节)。
3)根据功能分为节点流和包装流:
节点流:节点流:可以从或向一个特定的地方(节点)读写数据。如FileReader;
处理流:是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写。如BufferedReader.处理流的构造方法总是要带一个其他的流对象做参数。一个流对象经过其他流的多次包装,称为流的链接。
2、常见的IO流
1)字节流FileInputStream和FileOutputStream
public class FileInputStream extends InputStream
(1)FileInputStream
从文件系统中的某个文件中获得输入字节。哪些文件可用取决于主机环境。
(2)FileInputStream
用于读取诸如图像数据之类的原始字节流。
public class FileOutputStream extends OutputStream
(1)文件输出流是用于将数据写入 File
或 FileDescriptor
的输出流。文件是否可用或能否可以被创建取决于基础平台。特别是某些平台一次只允许一个 FileOutputStream(或其他文件写入对象)打开文件进行写入。在这种情况下,如果所涉及的文件已经打开,则此类中的构造方法将失败。
(2) FileOutputStream
用于写入诸如图像数据之类的原始字节的流。
2)字符流FileReader和FileWriter
FileWriter功能说明:
●其内部是通过一个字符数组缓冲区写入文件。
●FileWriter是使用默认码表写出文件。
●FileWriter是用于写入字符流。要编写原始字节流,请考虑使用FileOutputStream。
3)字节缓冲流BufferedInputStream和BufferedOutputStream
public class BufferedInputStream extends FilterInputStream
BufferedInputStream
为另一个输入流添加一些功能,即缓冲输入以及支持 mark
和 reset
方法的能力。在创建 BufferedInputStream
时,会创建一个内部缓冲区数组。在读取或跳过流中的字节时,可根据需要从包含的输入流再次填充该内部缓冲区,一次填充多个字节。mark
操作记录输入流中的某个点,reset
操作使得在从包含的输入流中获取新字节之前,再次读取自最后一次 mark
操作后读取的所有字节。
public class BufferedOutputStream extends FilterOutputStream
该类实现缓冲的输出流。通过设置这种输出流,应用程序就可以将各个字节写入底层输出流中,而不必针对每次字节写入调用底层系统。
4)字符缓冲流BufferedReader和BufferedWriter
字符缓冲流具备文本特有的表现形式,行操作 ;
public class BufferedReader extends Reader ;
public class BufferedWriter extends Writer ;
5)转换流:InputStreamReader和OutputStreamWriter
InputStreamReader和OutputStreamWriter是字符和字节的桥梁,也可称之为字符转换流。原理:字节流+编码。FileReader和FileWriter作为子类,仅作为操作字符文件的便捷类存在。当操作的字符文件,使用的是默认编码表时可以不用父类,而直接使用子类完成操作,简化代码。一旦要指定其他编码时,不能使用子类,必须使用字符转换流。
public class InputStreamReader extends Reader
●InputStreamReader 是字节流通向字符流的桥梁:它使用指定的 charset
读取字节并将其解码为字符。它使用的字符集可以由名称指定或显式给定,或者可以接受平台默认的字符集。
●每次调用 InputStreamReader 中的一个 read() 方法都会导致从底层输入流读取一个或多个字节。要启用从字节到字符的有效转换,可以提前从底层流读取更多的字节,使其超过满足当前读取操作所需的字节。
●为了达到最高效率,可以考虑在 BufferedReader 内包装 InputStreamReader。
public class OutputStreamWriter extends Writer
●OutputStreamWriter 是字符流通向字节流的桥梁:可使用指定的 charset
将要写入流中的字符编码成字节。它使用的字符集可以由名称指定或显式给定,否则将接受平台默认的字符集。
●每次调用 write() 方法都会导致在给定字符(或字符集)上调用编码转换器。在写入底层输出流之前,得到的这些字节将在缓冲区中累积。可以指定此缓冲区的大小,不过,默认的缓冲区对多数用途来说已足够大。注意,传递给 write() 方法的字符没有缓冲。
●为了获得最高效率,可考虑将 OutputStreamWriter 包装到 BufferedWriter 中,以避免频繁调用转换器。
6)打印流PrintWriter和PrintStream
public class PrintWriter extends Writer
●向文本输出流打印对象的格式化表示形式。此类实现在 PrintStream
中的所有 print 方法。不能输出字节,但是可以输出其他任意类型。
●与 PrintStream
类不同,如果启用了自动刷新,则只有在调用 println、printf 或 format 的其中一个方法时才可能完成此操作,而不是每当正好输出换行符时才完成。这些方法使用平台自有的行分隔符概念,而不是换行符。
●此类中的方法不会抛出 I/O 异常,尽管其某些构造方法可能抛出异常。客户端可能会查询调用 checkError()
是否出现错误。
public class PrintStream extends FilterOutputStreamimplements Appendable, Closeable
● PrintStream
为其他输出流添加了功能(增加了很多打印方法),使它们能够方便地打印各种数据值表示形式(例如:希望写一个整数,到目的地整数的表现形式不变。字节流的write方法只将一个整数的最低字节写入到目的地,比如写fos.write(97),到目的地(记事本打开文件)会变成字符'a',需要手动转换:fos.write(Integer.toString(97).getBytes());而采用PrintStream:ps.print(97),则可以保证数据的表现形式)。
● 与其他输出流不同,PrintStream
永远不会抛出 IOException
;而是,异常情况仅设置可通过 checkError
方法测试的内部标志。
另外,为了自动刷新,可以创建一个 PrintStream
;这意味着可在写入 byte 数组之后自动调用 flush
方法,可调用其中一个 println
方法,或写入一个换行符或字节 ('\n'
)。 ● PrintStream
打印的所有字符都使用平台的默认字符编码转换为字节。在需要写入字符而不是写入字节的情况下,应该使用
类。 PrintWriter
7)、对象操作流ObjectInputStream和ObjectOutputStream
public class ObjectOutputStream extends OutputStream implements ObjectOutput, ObjectStreamConstants
● ObjectOutputStream 将 Java 对象的基本数据类型和图形写入 OutputStream。只能使用 ObjectInputStream 读取(重构)对象。
● 只能将支持 java.io.Serializable 接口的对象写入流中。
● writeObject 方法用于将对象写入流中。所有对象(包括 String 和数组)都可以通过 writeObject 写入。可将多个对象或基元写入流中。必须使用与写入对象时相同的类型和顺序从相应 ObjectInputstream 中读回对象。
构造方法:ObjectOutputStream(OutputStream out)
创建写入指定 OutputStream 的 ObjectOutputStream。
public class ObjectInputStream extends InputStream implements ObjectInput, ObjectStreamConstants
● ObjectInputStream 对以前使用 ObjectOutputStream 写入的基本数据和对象进行反序列化。
● 只有支持 java.io.Serializable 或 java.io.Externalizable 接口的对象才能从流读取。
● readObject
方法用于从流读取对象。应该使用 Java 的安全强制转换来获取所需的类型。在 Java 中,字符串和数组都是对象,所以在序列化期间将其视为对象。读取时,需要将其强制转换为期望的类型。
3、File类
文件和文件夹都是用File代表。
1 <1>创建一个文件对象 2 public static void main(String[] args) { 3 // 绝对路径 4 File f1 = new File("d:/LOLFolder"); 5 System.out.println("f1的绝对路径:" + f1.getAbsolutePath()); 6 // 相对路径,相对于工作目录,如果在eclipse中,就是项目目录 7 File f2 = new File("LOL.exe"); 8 System.out.println("f2的绝对路径:" + f2.getAbsolutePath()); 9 // 把f1作为父目录创建文件对象 10 File f3 = new File(f1, "LOL.exe"); 11 System.out.println("f3的绝对路径:" + f3.getAbsolutePath()); 12 } 13 <2>文件常用方法 14 public static void main(String[] args) { 15 File f = new File("d:/LOLFolder/LOL.exe"); 16 System.out.println("当前文件是:" +f); 17 //文件是否存在 18 System.out.println("判断是否存在:"+f.exists()); 19 //是否是文件夹 20 System.out.println("判断是否是文件夹:"+f.isDirectory()); 21 //是否是文件(非文件夹) 22 System.out.println("判断是否是文件:"+f.isFile()); 23 //文件长度 24 System.out.println("获取文件的长度:"+f.length()); 25 //文件最后修改时间 26 long time = f.lastModified(); 27 Date d = new Date(time); 28 System.out.println("获取文件的最后修改时间:"+d); 29 //设置文件修改时间为1970.1.1 08:00:00 30 f.setLastModified(0); 31 //文件重命名 32 File f2 =new File("d:/LOLFolder/DOTA.exe"); 33 f.renameTo(f2); 34 System.out.println("把LOL.exe改名成了DOTA.exe"); 35 System.out.println("注意: 需要在D:\\LOLFolder确实存在一个LOL.exe,\r\n才可以看到对应的文件长度、修改时间等信息"); 36 } 37 ----------------------------------------------------------------- 38 public static void main(String[] args) throws IOException { 39 File f = new File("d:/LOLFolder/skin/garen.ski"); 40 // 以字符串数组的形式,返回当前文件夹下的所有文件(不包含子文件及子文件夹) 41 f.list(); 42 // 以文件数组的形式,返回当前文件夹下的所有文件(不包含子文件及子文件夹) 43 File[] fs= f.listFiles(); 44 // 以字符串形式返回获取所在文件夹 45 f.getParent(); 46 // 以文件形式返回获取所在文件夹 47 f.getParentFile(); 48 // 创建文件夹,如果父文件夹skin不存在,创建就无效 49 f.mkdir(); 50 // 创建文件夹,如果父文件夹skin不存在,就会创建父文件夹 51 f.mkdirs(); 52 // 创建一个空文件,如果父文件夹skin不存在,就会抛出异常 53 f.createNewFile(); 54 // 所以创建一个空文件之前,通常都会创建父目录 55 f.getParentFile().mkdirs(); 56 // 列出所有的盘符c: d: e: 等等 57 f.listRoots(); 58 // 刪除文件 59 f.delete(); 60 // JVM结束的时候,刪除文件,常用于临时文件的删除 61 f.deleteOnExit(); 62 }
4、序列化与反序列化(对象流)
1)、什么是序列化与反序列化?
序列化:指把堆内存中的 Java 对象数据,通过某种方式把对象存储到磁盘文件中或者传递给其他网络节点(在网络上传输)。这个过程称为序列化。通俗来说就是将数据结构或对象转换成二进制串的过程
反序列化:把磁盘文件中的对象数据或者把网络节点上的对象数据,恢复成Java对象模型的过程。也就是将在序列化过程中所生成的二进制串转换成数据结构或者对象的过程
2)、为什么要做序列化?
①、在分布式系统中,此时需要把对象在网络上传输,就得把对象数据转换为二进制形式,需要共享的数据的 JavaBean 对象,都得做序列化。
②、服务器钝化:如果服务器发现某些对象好久没活动了,那么服务器就会把这些内存中的对象持久化在本地磁盘文件中(Java对象转换为二进制文件);如果服务器发现某些对象需要活动时,先去内存中寻找,找不到再去磁盘文件中反序列化我们的对象数据,恢复成 Java 对象。这样能节省服务器内存。
3)、Java 怎么进行序列化?
①、需要做序列化的对象的类,必须实现序列化接口:Java.lang.Serializable 接口(这是一个标志接口,没有任何抽象方法),Java 中大多数类都实现了该接口,比如:String,Integer
②、底层会判断,如果当前对象是 Serializable 的实例,才允许做序列化,Java对象 instanceof Serializable 来判断。
③、在 Java 中使用对象流来完成序列化和反序列化
ObjectOutputStream:通过 writeObject()方法做序列化操作
ObjectInputStream:通过 readObject() 方法做反序列化操作