IO流技术:
用于处理设备上数据。
流:可以理解数据的流动,就是一个数据流。IO流最终要以对象来体现,对象都存在IO包中。
流的分类(四个基类)
输入流(读)和输出流(写),因为处理的数据不同,分为字节流和字符流,在这四个系统中,它们的子类,都有一个共性特点:子类名后缀都是父类名,前缀名都是这个子类的功能名称。
字节流两个基类:InputStrean OutputStrean
字符流两个基类:Reader Writer
字节流:处理字节数据的流对象。设备上的数据无论是图片或者dvd,文字,它们都以二进制存储的。二进制的最终都是以一个8位为数据单元进行体现,所以计算机中的最小数据单元就是字节。意味着,字节流可以处理设备上的所有数据,所以字节流一样可以处理字符数据。
字符流:因为字符每个国家都不一样,所以涉及到了字符编码问题,那么GBK编码的中文用unicode编码解析是有问题的,所以需要获取中文字节数据的同时+ 指定的编码表才可以解析正确数据。为了方便于文字的解析,所以将字节流和编码表封装成对象,这个对象就是字符流。只要操作字符数据,优先考虑使用字符流体系。
注意:流的操作只有两种:读和写。
流的体系因为功能不同,但是有共性内容,不断抽取,形成继承体系。该体系一共有四个基类,而且都是抽象类。
常用的流操作对象有:FileInputStream FileOutputStream BufferedInputStream BfferedOutputStream
FileReader FileWriter BufferedReader BufferedWriter
如何使用流操作数据呢?例:
import java.io.*; class ioDemo { public static void main(String[] args) throws IOException { FileWriter fw = null; //创建一个fw对象指向为空,目的是为了在处理异常关闭资源的时候,可以调用fw来调用close方法 try { /* 创建一个FileWriter对象,该对象一被初始化就必须要明确被操作的文件 而且该文件会被创建到指定目录下,如果该目录已有相同文件,将被覆盖, 其实该步就是在明确数据要存放的目的地 */ fw = new FileWriter("demo.txt",true); //调用write方法,将字符串写入到流中。 fw.write("\r\nxueru"); // \r\n在win里识别为回车 /* 刷新流对象中的缓冲区的数据 将数据刷到目的地中 */ //fw.flush(); } catch (IOException i) { System.out.println(i.toString()); } finally { try { /* 关闭资源,但是关闭之前会刷新一次内部的缓冲区中的数据 将数据刷到目的地中 和flush区别:flush刷新后,流可以继续使用,close刷新后,会将流关闭 */ fw.close(); } catch (IOException i) { System.out.println(i.toString()); } } reader(); } public static void reader() throws IOException //读取文件函数 { //第一种方式: //创建一个文件读取流对象,和指定名称的文件相关联 //要保证该文件时已经存在的,如果不存在,会发生异常FileNotFoundException FileReader fr = new FileReader("demo1.txt"); //调用读取流单个字符的read 方法,每读完一个会自动读下一个,当返回-1,说明已经读完 /* int ch = 0; while((ch=fr.read())!=-1) { System.out.println("ch="+(char)ch); } */ //第二种方式: //调用读取流数组的read方法,每读完一次,会返回所读的个数,当返回-1,说明已读完 char[] buf = new char[1024]; int num = 0; while((num=fr.read(buf))!=-1) { System.out.println(new String(buf,0,num)); } fr.close(); } }
close()和flush()的区别:
flush():将缓冲区的数据刷到目的地中后,流可以使用。
close():将缓冲区的数据刷到目的地中后,流就关闭了,该方法主要用于结束调用的底层资源。这个动作一定做。
io异常的处理方式:io一定要写finally,因为操作了资源,所以要关闭资源,finalla为一定会执行到的语句;
IO中的使用到了一个设计模式:装饰设计模式。
装饰设计模式解决:对一组类进行功能的增强。
包装:写一个类(包装类)对被包装对象进行包装;
* 1、包装类和被包装对象要实现同样的接口;
* 2、包装类要持有一个被包装对象;
* 3、包装类在实现接口时,大部分方法是靠调用被包装对象来实现的,对于需要修改的方法我们自己实现;
字符流:
Reader:用于读取字符流的抽象类。子类必须实现的方法只有 read(char[], int, int) 和 close()。
|---BufferedReader:从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。 可以指定缓冲区的大小,或者可使用默认的大小。大多数情况下,默认值就足够大了。
|---LineNumberReader:跟踪行号的缓冲字符输入流。此类定义了方法 setLineNumber(int) 和 getLineNumber(),它们可分别用于设置和获取当前行号。
|---InputStreamReader:是字节流通向字符流的桥梁:它使用指定的 charset 读取字节并将其解码为字符。它使用的字符集可以由名称指定或显式给定,或者可以接受平台默认的字符集。
|---FileReader:用来读取字符文件的便捷类。此类的构造方法假定默认字符编码和默认字节缓冲区大小都是适当的。要自己指定这些值,可以先在 FileInputStream 上构造一个 InputStreamReader。
|---CharArrayReader:
|---StringReader:
Writer:写入字符流的抽象类。子类必须实现的方法仅有 write(char[], int, int)、flush() 和 close()。
|---BufferedWriter:将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。
|---OutputStreamWriter:是字符流通向字节流的桥梁:可使用指定的 charset 将要写入流中的字符编码成字节。它使用的字符集可以由名称指定或显式给定,否则将接受平台默认的字符集。
|---FileWriter:用来写入字符文件的便捷类。此类的构造方法假定默认字符编码和默认字节缓冲区大小都是可接受的。要自己指定这些值,可以先在 FileOutputStream 上构造一个 OutputStreamWriter。
|---PrintWriter:为其他输出流添加了功能,使它们能够方便地打印各种数据值表示形式
|---CharArrayWriter:
|---StringWriter:
字节流:
InputStream:是表示字节输入流的所有类的超类。
|--- FileInputStream:从文件系统中的某个文件中获得输入字节。哪些文件可用取决于主机环境。FileInputStream 用于读取诸如图像数据之类的原始字节流。要读取字符流,请考虑使用 FileReader。
|--- FilterInputStream:包含其他一些输入流,它将这些流用作其基本数据源,它可以直接传输数据或提供一些额外的功能。
|--- BufferedInputStream:该类实现缓冲的输入流。
|--- Stream:
|--- ObjectInputStream:
|--- PipedInputStream:
|--- ByteArrayInputStream:在构造的时候,需要接收数据源,而且数据时一个字节数组;
OutputStream:此抽象类是表示输出字节流的所有类的超类。
|--- FileOutputStream:文件输出流是用于将数据写入 File 或 FileDescriptor 的输出流。
|--- FilterOutputStream:此类是过滤输出流的所有类的超类。
|--- BufferedOutputStream:该类实现缓冲的输出流。
|--- PrintStream:
|--- DataOutputStream:
|--- ObjectOutputStream:
|--- PipedOutputStream:
|--- ByteArrayOutputStream:在构造的时候不用定义数据目的,因为该对象已经内部封装了可变长度的字节数组,这就是数据目的地(内存)
例:使用Buffered缓冲区提高效率.
<span style="font-family:宋体;">/* 缓冲区的出现时为了提高流的操作的效率而出现的 所以在创建缓冲区之前,必须要有流对象 BufferedWriter:该缓冲区中提供了一个跨平台的换行符:newLine(); BufferedReader:该缓冲区提供了一个一次读一行的方法,方便与对文本数据的获取:readLine(),当返回null时,说明已到文件末尾; 并且该方法往回返的数据是回车符之前的数据,不包含回车符 */ import java.io.*; class BuffereDemo { public static void main(String[] args) throws IOException { writer(); reader(); } public static void writer() throws IOException { //创建一个字符写入流对象 FileWriter fw = new FileWriter("buf.txt"); //为了提高字符写入流的效率,加入了缓冲技术 //只要将需要被提高效率的流对象作为参数传递给缓冲区的构造函数即可 BufferedWriter bufw = new BufferedWriter(fw); for (int x= 0;x<5 ;x++ ) { bufw.write("abcd"+x); bufw.newLine(); //newLine:写入一个换行分隔符,可跨平台 bufw.flush(); } //其实关闭缓冲区,就是在关闭缓冲区中的流对象,因为其实最终操作的是流对象,只是为了提高效率 bufw.close(); } public static void reader() throws IOException { //创建一个读取流对象和文件相关联 FileReader fr = new FileReader("buf.txt"); //为了提高效率,加入缓冲技术,将字符流对象作为参数传递给缓冲的构造函数 BufferedReader bufr = new BufferedReader(fr); String line = null; while((line=bufr.readLine())!=null) //readLine():读取一行的字符串数据,当读取完数据会返回null; { System.out.println(line); } bufr.close(); } } </span>
流对象其实很简单,就是读取和写入,但是因为功能的不同,流的体系中提供N多的对象。那么开始时,到底该用哪个对象更为合适呢?这就需要明确流的操作规律,通过三个明确来完成:
1.明确源和目的
源:输入流。 InputStream Reader
目的:输出流: OutputStream Writer
2.操作的数据是否为纯文本
是:字符流
不是:字节流
3.当体系明确后,在明确要使用哪个具体的对象
通过设备来进行区分;
源设备:内存 硬盘 键盘
目的设备:内存 硬盘 控制台
例: 将一个文本文件中的数据存储到另一个文件中区,复制文件:
源:因为是源,所以要使用读取流, InputStream, Reader
是不是可以操作文本文件
是!这时就可以选择Reader
这样体系就明确了
接下来明确要使用该体系中的哪个对象
明确设备:硬盘,上一个文件
Reader体系中可以操作文件的对象是 FileReader
是否需要提高效率:是!加入 Reader 体系中缓冲区 BufferedReader
FileReader fr = new FileReader("a.txt");
BufferedReader bufr = new BufferedReader(fr);
目的: 写入流: OutputStream Writer
是否是纯文本
是!用 Writer
设备:硬盘,一个文件
Writer 体系中可以操作文件的对象是 FileWriter
是否需要提高效率:是!加入 Reader 体系中缓冲区 BufferedWriter
FileWriter fw = new FileWriter("a.txt");
BufferedWriter bufr = new BufferedWriter(fw);
转换流特有功能:转换流可以将字节转成字符,原因在于,将获取到的字节通过查编码表获取到指定对应字符。
转换流的最强功能就是基于 字节流 + 编码表 。没有转换,就没有字符流。
转换流有一个子类就是操作文件的字符流对象:
InputStreamReader
|--FileReader
OutputStreamWriter
|--FileWrier
想要操作文本文件,必须要进行编码转换,而编码转换动作转换流都完成了。所以操作文件的流对象只要继承自转换流就可以读取一个字符了。
但是子类有一个局限性,就是子类中使用的编码是固定的,是本机默认的编码表,对于简体中文版的系统默认码表是GBK。
FileReader fr = new FileReader("a.txt");
InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt"),"gbk");
以上两句代码功能一致,
如果仅仅使用平台默认码表,就使用FileReader fr = new FileReader("a.txt"); //因为简化。
如果需要制定码表,必须用转换流。
转换流 = 字节流+编码表。
转换流的子类File = 字节流 + 默认编码表。
凡是操作设备上的文本数据,涉及编码转换,必须使用转换流。
例:通过键盘录入将字节转换成字符
import java.io.*; class zhuanhuan { public static void main(String[] args) throws IOException { //获取键盘录入对象 InputStream in = System.in;//文件源对象是键盘录入,也可以定义成文件 //将字节流转换成字符流对象,使用转换流 InputStreamReader isr = new InputStreamReader(in); BufferedReader buf = new BufferedReader(isr); /* String line = null; while((line=buf.readLine())!=null) { if("over".equals(line)) break; System.out.println(line); } */ //字符流转成字节流 OutputStream out = System.out; //把文件写到控制台上也就是dos窗口 OutputStreamWriter osw = new OutputStreamWriter(out); BufferedWriter bufw = new BufferedWriter(osw); String lines = null; while((lines=buf.readLine())!=null) { if("over".equals(lines)) break; bufw.write(lines); bufw.newLine(); //跨平台换行 bufw.flush(); } } }
File类:将文件系统中的文件和文件夹封装成了对象。提供了更多的属性和行为可以对这些文件和文件夹进行操作。这些是流对象办不到的,因为流只操作数据。
File常用方法:
1,创建
boolean createNewFile();在指定位置创建文件,如果该文件已存在,则返回flase
和输出流不一样,输出流对象一建立就创建文件。
boolean mkdir();根目录创建文件夹
boolean mkdirs(); 多目录创建文件夹
2,删除
boolean delete();删除失败返回flase
void deleteOnExit();在程序退出时删除指定的文件
3,判断
boolean exists() ;文件是否存在
boolean isDirectory();是否为文件夹
boolean isFile();是否为文件
boolean isHidden();测试此抽象路径名指定的文件是否是一个隐藏文件。
boolean isAbsolute();测试此抽象路径名是否为绝对路径名。
4,获取信息
String getAbsolutePath();返回此抽象路径名的绝对路径名字符串。
String getName() ;返回由此抽象路径名表示的文件或目录的名称。
String getPath() ;将此抽象路径名转换为一个路径名字符串
String getParent() ;返回此抽象路径名父目录的路径名字符串;如果此路径名没有指定父目录,则返回 null。
long lastModified() ;返回此抽象路径名表示的文件最后一次被修改的时间
long length() ; 返回由此抽象路径名表示的文件的长度
boolean renameTo(File dest) ;重新命名此抽象路径名表示的文件。
static File[] listRoots() ;获取可用的系统根
String[] list() ; 返回一个字符串数组,这些字符串指定此抽象路径名表示的目录中的文件和目录。
File[] listFiles();返回当前目录所有的文件及文件夹
例,File常见方法的使用:
import java.io.*; class FileDemo { public static void main(String[] args) throws IOException { //consmethod_2(); File fi = new File("d:\\DCIM"); //showDir(fi); } public static void consmethod_2() { File[] files = File.listRoots();//获取可用系统根 for(File f : files) { System.out.println(f); } /* File f = new File("c:\\"); String[] names = f.list(); //list():列出所有文件,调用list()方法必须是封装了一个目录,而且必须存在 for(String name : names) { System.out.println(name); } */ //list()过滤方法 File dir = new File("d:\\java0507"); String[] arr = dir.list(new FilenameFilter() { public boolean accept(File dir,String name) { return name.endsWith(".txt"); } }); for(String s : arr) { System.out.println(s); } //listFiles() 遍历当前路径所有文件及文件夹 File fa = new File("d:\\"); File[] fb = fa.listFiles(); for(File f : fb) { System.out.println(f); } } public static void consmethod_1() throws IOException { File f = new File("File.txt"); f.deleteOnExit(); //在程序退出时删除文件,不管有没发生异常 System.out.println(f.createNewFile()); System.out.println(f.delete()); } public static void consmethod() { //创建对象 File f1 = new File("d:\\buf.txt"); File f5 = new File("f:\\hah.txt"); System.out.println("renameTo():"+f1.renameTo(f5));//重新命名此抽象路径名表示的文件 System.out.println(f1.exists()); //判断文件是否存在 System.out.println(f1.isDirectory()); //判断文件是否是文件夹 System.out.println(f1.isFile()); //判断文件是否是文件 System.out.println(f1.canExecute()); //判断文件是否可执行 System.out.println(f1.exists()); //判断文件是否存在 //跨平台分隔符 File f2 = new File("d:"+File.separator+"java0507","b.txt"); File a = new File("d:\\java0507"); File f3 = new File(a,"c.txt"); System.out.println(f1+"\r\n"+f2+"\r\n"+f3); } }<strong> </strong>
递归的概念:就是函数自身调用自身。
什么时候用递归呢?
当一个功能被重复使用,而每一次使用该功能时的参数不确定,都由上次的功能元素结果来确定。
简单说:功能内部又用到该功能,但是传递的参数值不确定。(每次功能参与运算的未知内容不确定)。
例,列出指定目录下的文件或者文件夹,包括目录中的内容:
/*列出指定目录下的文件或者文件夹,包括目录中的内容 因为目录中还有目录,只要使用同一个列出目录功能的函数完成即可 在列出过程中出现的还是目录的话,还可以再次调用本功能 也就是函数自身调用自身 这种表现形式,或者编程手法,称为递归 递归要注意: 1,限定条件 2,注意次数,避免内存溢出 */ public static void showDir(File dir) { System.out.println(dir); File[] files = dir.listFiles(); for (int x= 0;x<files.length ; x++) { if(files[x].isDirectory())<span> </span>//如果遍历到的File是一个文件夹,则继续调用自身的方法 showDir(files[x]); else System.out.println(files[x]);<span> </span>//如果遍历到的是文件,那么输出打印 } }
递归的注意事项:
1:一定要定义递归的条件。
2:递归的次数不要过多。容易出现 StackOverflowError 栈内存溢出错误。
其实递归就是在栈内存中不断的加载同一个函数。
Properties:一个可以将键值进行持久化存储的对象。Map--Hashtable的子类,也就是说它具备map集合的特点,而且它里面存储的键值对都是字符串,是集合中和IO技术相结合的集合容器
Map
|--Hashtable
|--Properties:用于属性配置文件,键和值都是字符串类型。
该对象特点:
可以用于键值对形式配置文件
可以加载文件类型的键值对,也可以将键值对保存到指定的文件中,但是加载的文件必须是有固定格式的:键=值;
例,用于记录应用程序运行的次数,如果使用次数已到,那么给出注册提示:
import java.io.*; import java.util.*; class RunCount { public static void main(String[] args) throws IOException { Properties prop = new Properties(); File file = new File("d:\\count.ini"); if(!file.exists()) file.createNewFile(); FileInputStream fis = new FileInputStream(file); prop.load(fis); //将流中的键值加载进集合 int count = 0; String value = prop.getProperty("time");<span> </span>//程序启动就加载配置文件,读取使用次数 if (value!=null) { count = Integer.parseInt(value); if(count>=5) { System.out.println("您好,试用期已过,请注册"); return; } } count++; prop.setProperty("time",count+"");<span> </span>//用户每使用一次都会自增一次 FileOutputStream fos = new FileOutputStream("d:\\count.ini");<span> </span>//将自增后的次数重新写入到配置文件 prop.store(fos,""); fos.close(); fis.close(); // count(); } }
PrintStream:打印流
1:提供了更多的功能,比如打印方法。可以直接打印任意类型的数据。
2:它有一个自动刷新机制,创建该对象,指定参数,对于指定方法可以自动刷新。
3:它使用的本机默认的字符编码.
4:该流的print方法不抛出IOException。
例:
字节打印流:PrintSream 构造函数可以接收的参数类型: 1.file对象。File 2.字符串路径。String 3.字节输出流。OutputStream 字符打印流:PrintWriter 1.file对象。File 2.字符串路径。String 3.字节输出流。OutputStream 4.字符输出流。Writer */ class { public static void main(String[] args) { BufferedReader bufr = //读取键盘录入 new BufferedReader(new InputStreamReader(System.in)); PrintWriter out = new PrintWriter(System.out,true); //当设为true时,println,printf,format方法自动刷新 String line = null; while((line = bufr.readLine()!=null)) { if("over".equals(line)) break; out.println(line.toUpperCase()); //将键盘录入的数据打印在控制台 } out.close(); bufr.close(); } }
SequenceInputStream:序列流,作用就是将多个读取流合并成一个读取流,实现数据合并。
表示其他输入流的逻辑串联。它从输入流的有序集合开始,并从第一个输入流开始读取,直到到达文件末尾,接着从第二个输入流读取,依次类推,直到到达包含的最后一个输入流的文件末尾为止。
这样做,可以更方便的操作多个读取流,其实这个序列流内部会有一个有序的集合容器,用于存储多个读取流对象。
该对象的构造函数参数是枚举,想要获取枚举,需要有Vector集合,但不高效。需用ArrayList,但ArrayList中没有枚举,只有自己去创建枚举对象。
但是方法怎么实现呢?因为枚举操作的是具体集合中的元素,所以无法具体实现,但是枚举和迭代器是功能一样的,所以,可以用迭代替代枚举。
合并原理:多个读取流对应一个输出流。
切割原理:一个读取流对应多个输出流。
例:
import java.io.*; import java.util.*; class SequenceDemo { public static void main(String[] args) throws IOException { //splitFile(); Texts(); } //合并文件 public static void hebingFile()throws IOException { Vector<FileInputStream> v = new Vector<FileInputStream>(); v.add(new FileInputStream("d:\\1.txt")); v.add(new FileInputStream("d:\\2.txt")); v.add(new FileInputStream("d:\\3.txt")); Enumeration<FileInputStream> e = v.elements(); //返回此向量的组件的枚举 SequenceInputStream sis = new SequenceInputStream(e); FileOutputStream fos = new FileOutputStream("d:\\4.txt"); //创建合并的文件对象 byte[] buf = new byte[1024]; //读取SequenceInputStream中的三个文件,再写入一个文件 int len = 0; while((len=sis.read(buf))!=-1) { fos.write(buf,0,len); } fos.close(); sis.close(); } //分割文件 public static void splitFile() throws IOException { FileInputStream fis = new FileInputStream("d:\\Bandari - 款款柔情.mp3"); FileOutputStream fos = null; byte[] by = new byte[1024*1024]; //按照每个文件1M的大小进行分割 int len = 0; int count = 1; while((len=fis.read(by))!=-1) { fos = new FileOutputStream("d:\\"+(count++)+".part"); //分割的文件名随着count的自增而改变 fos.write(by,0,len); fos.close(); } fis.close(); } //练习,把分割的文件合并回去 public static void Texts()throws IOException { Vector<FileInputStream> v = new Vector<FileInputStream>(); v.add(new FileInputStream("d:\\1.part")); v.add(new FileInputStream("d:\\2.part")); v.add(new FileInputStream("d:\\3.part")); v.add(new FileInputStream("d:\\4.part")); v.add(new FileInputStream("d:\\5.part")); Enumeration<FileInputStream> e = v.elements(); //返回此向量的组件的枚举 SequenceInputStream sis = new SequenceInputStream(e); FileOutputStream fos = new FileOutputStream("d:\\合并的音乐.mp3"); byte[] buf = new byte[1024*1024]; int len = 0; while((len=sis.read(buf))!=-1) { fos.write(buf,0,len); } fos.close(); sis.close(); } }
ObjectOutputStream: 将一个类封装成一个文件
ObjectInputStream: 将封装好的一个文件读取出来
该对象可以将对象写入到一个文件中,但是被写入的对象必须实现一个序列化接口,如果一个对象被写进了文件中,但是后面又修改了文件的话,那么是读取不到此文件的,因为这样已经把UID改了,有什么方法解决呢?
这时,可以在对象加入一行代码:
public static final long serialVarsionUID = 42L;值可以由自己设定。
Serializable:用于启动对象的序列化功能,可以强制让指定类具备序列化功能,该接口中没有成员,这是一个标记接口。这个标记接口用于给序列化类提供UID。这个uid是依据类中的成员的数字签名进行运行获取的。如果不需要自动获取一个uid,可以在类中,手动指定一个名称为serialVersionUID id号。依据编译器的不同,或者对信息的高度敏感性。最好每一个序列化的类都进行手动显示的UID的指定。
注意:静态数据不能被序列化,因为静态数据不在堆内存中,是存储在静态方法区中。
如何将非静态的数据不进行序列化?用transient 关键字修饰此变量即可。
例:
import java.io.*; class ObjectStreamDemo { public static void main(String[] args) throws Exception { //writeObj(); //封装对象成一个文件 readObj(); //读取一个文件 } public static void writeObj() throws Exception { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("d:\\obj.txt")); //一般被封装的类的文件不要写成txt的扩展名,因为里面是看不懂的东西,没必要打开它 oos.writeObject(new Person("zhangsan",20)); oos.close(); } public static void readObj() throws Exception { ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:\\obj.txt")); Person p = (Person)ois.readObject(); System.out.println(p); } } class Person implements Serializable //要实现Serializable接口才可以被序列化 { public static final long serialVarsionUID = 42L; //给与固定UID序列号,即使里面的内容有更改,也会读取里面的内容, //如果没有固定的UID序列号,系统会计算出序列号,当内容有所改变,将不能读取,必须重新加载成一个新文件 static String name; //静态修饰的成员不会被序列化的 int age; Person(String name,int age) { this.name = name; this.age = age; } public String toString() { return name+"-"+age; } }
RandomAccessFile:此类的实例支持对随机访问文件的读取和写入
该方法之对文件有效,该对象的构造函数要操作的文件不存在就自动创建,如果存在,则不会覆盖
如果模式为只读:r,不会创建文件,回去读取一个已存在的文件,如果该文件不存在,则会出现异常
如果模式为rw的话,如果文件不存在会自动创建
"r" 以只读方式打开。调用结果对象的任何 write 方法都将导致抛出 IOException。
"rw" 打开以便读取和写入。如果该文件尚不存在,则尝试创建该文件。
"rws" 打开以便读取和写入,对于 "rw",还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备。
"rwd" 打开以便读取和写入,对于 "rw",还要求对文件内容的每个更新都同步写入到底层存储设备。
特点:
1:该对象即可读取,又可写入。
2:该对象中的定义了一个大型的byte数组,通过定义指针来操作这个数组。
3:可以通过该对象的getFilePointer()获取指针的位置,通过seek()方法设置指针的位置。
4:该对象操作的源和目的必须是文件。
5:其实该对象内部封装了字节读取流和字节写入流。
注意:实现随机访问,最好是数据有规律。
class RandomAccessFileDemo { public static void main(String[] args) throws Exception { //存 RandomAccessFile rf = new RandomAccessFile("d:\\rf.txt","rw"); //rf1.seek(8*0); //可以指定添加位置,调整指针 rf.write("李四".getBytes()); rf.writeInt(97); //因为97超过了八位,而一个字节是八位,为了防止数据丢失,使用此方法 rf.write("王五".getBytes()); rf.write(20); rf.close(); //读 RandomAccessFile rf1 = new RandomAccessFile("d:\\rf.txt","rw"); //rf1.seek(8*0); //调整读取对象时的指针 rf.skipBytes(8); //跳过指定的字节数,但是只能往前跳,不能往回跳 byte[] by = new byte[4]; rf1.read(by); String name = new String(by); int age = rf1.read(); System.out.println(name+"-"+age); rf1.close(); } }
管道流:管道读取流和管道写入流可以像管道一样对接上,管道读取流就可以读取管道写入流写入的数据。
注意:需要加入多线程技术,因为单线程,先执行read,会发生死锁,因为read方法是阻塞式的,没有数据的read方法会让线程等待。
import java.io.*; class Read implements Runnable //管道读取流线程 { private PipedInputStream in; Read(PipedInputStream in) { this.in = in; } public void run() { try { System.out.println("读取文件中。。。。阻塞"); byte[] by = new byte[1024]; int len = in.read(by); String s = new String(by,0,len); System.out.println("读取文件中成功"); System.out.println(s); in.close(); } catch (Exception i) { } } } class Writ implements Runnable //管道写入流线程 { private PipedOutputStream ou ; Writ(PipedOutputStream ou) { this.ou = ou; } public void run() { try { System.out.println("正在载入文件。。。。"); Thread.sleep(6000); ou.write("piped lai la".getBytes()); System.out.println("载入文件成功"); ou.close(); } catch (Exception e) { } } } class pipedDemo { public static void main(String[] args) throws IOException { PipedOutputStream ou = new PipedOutputStream(); //创建管道的输入输出流 PipedInputStream in = new PipedInputStream(); in.connect(ou); //将两个流对接 Read r = new Read(in); Writ w = new Writ(ou); new Thread(r).start(); new Thread(w).start(); } }
DataOutputStream、DataInputStream:专门用于操作基本数据类型数据的对象
import java.io.*; class DataStreamDemo { public static void main(String[] args) throws IOException { //存,直接存储基本数据类型 DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.txt")); dos.writeInt(234); dos.writeBoolean(true); dos.writeDouble(9887.32); dos.close(); //取,直接返回基本数据类型 DataInputStream dis = new DataInputStream(new FileInputStream("data.txt")); int num = dis.readInt(); //注意去的时候要按顺序取,否则会乱码 boolean b = dis.readBoolean(); double d = dis.readDouble(); System.out.println(num+"-"+b+"-"+d); dis.close(); } }
ByteArrayInputStream:源:内存
ByteArrayOutputStream:目的:内存。
这两个流对象不涉及底层资源调用,操作的都是内存中数组,所以不需要关闭。
直接操作字节数组就可以了,为什么还要把数组封装到流对象中呢?因为数组本身没有方法,只有一个length属性。为了便于数组的操作,将数组进行封装,对外提供方法操作数组中的元素
import java.io.*; class ByteArrayStream { public static void main(String[] args) { //数据源 ByteArrayInputStream bis = new ByteArrayInputStream("ABCDE".getBytes()); //数据目的 ByteArrayOutputStream bos = new ByteArrayOutputStream(); int by = 0; while((by=bis.read())!=-1) { bos.write(by); } System.out.println(bos.size()); System.out.println(bos.toString()); } }