字节流与字符流
在java.io包中操作文件内容的主要有两大类:字节流、字符流,两类都分为输入和输出操作。在字节流中输出数据主要是使用OutputStream完成,输入使的是InputStream,在字符流中输出主要是使用Writer类完成,输入流主要使用Reader类完成。(这四个都是抽象类)
处理流的用法:
按照流是否直接与特定的地方(如磁盘、内存、设备等)相连,分为节点流和处理流两类。
节点流:可以从或向一个特定的地方(节点)读写数据。如FileReader
处理流:是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写。
如BufferedReader。处理流的构造方法总是要带一个其他的流对象做参数。一个流对象经过其他流的多次包装,称为流的链接。
常用的节点流
父 类 InputStream OutputStream Reader Writer
文 件 *FileInputStream FileOutputStrean FileReader FileWriter 文件进行处理的节点流
数 组 *ByteArrayInputStream ByteArrayOutputStream CharArrayReader CharArrayWriter
对数组进行处理的节点流(对应的不再是文件,而是内存中的一个数组)
字符串 *无 无 StringReader StringWriter 对字符串进行处理的节点流
管 道 *PipedInputStream PipedOutputStream PipedReader PipedWriter 对管道进行处理的节点流
常用处理流(关闭处理流使用关闭里面的节点流)
父 类 InputStream OutputStream Reader Writer
缓冲流 *BufferedImputStrean BufferedOutputStream BufferedReader BufferedWriter ----需要父类作为参数构造,增加缓冲功能,避免频繁读写硬盘,可以初始化缓冲数据的大小,由于带了缓冲功能,所以就写数据的时候需要使用flush方法。
转换流 *InputStreamReader OutputStreamWriter- 要inputStream 或OutputStream作为参数,实现从字节流到字符流的转换 数据流 *DataInputStream DataOutputStream -提供将基础数据类型写入到文件中,或者读取出来,为什么要有这个流呢?
看这样的分析,如果没有这种流的话,有一个long,本身只占8个字节,如果我要写入到文件,需要转成字符串,然后在转成字符数组,那空间会占用很多,但是有了这种流之后就很方便了,直接将这8个字节写到文件就完了。。是不是既节约了内存空间有让程序写起来更加方便简单了呐。写倒是很简单,但是读取的时候就注意了,根据读取的数据类型,指针会往下移,所以你写的顺序必须要和读的顺序一致才能完成你正确的需求。
因此,我们使用处理流时的典型思路是:使用处理流来包装字节流,程序通过处理流来执行输入/输出功能,让字节流与底层的I/O设备、文件进行交互。
输入输出流体系:
Java的输入输出流提供了近40个类:看似毫无规律,但我们可以按功能划分:如下图:
通常来说,字节流的功能比字符流的功能强大,因为计算机里的所有数据都是二进制的,你懂得!
但我还是喜欢字符流,直接明了。
转换流
两个转换流:
1.将字节转换成字符流
InputStreamReader:将字节输入流转换成字符输入流,OutPutStreamWriter:将字节输出流转换成字符输出流
2.从字符流到字节流:可以从字符流中获取char[]数组,转换为String,然后调用String的API函数getBytes() 获取到byte[],然后就可以通过ByteArrayInputStream、ByteArrayOutputStream来实现到字节流的转换。
package com.haixu.io; import java.io.BufferedReader; import java.io.InputStreamReader; public class KeyinTest { public static void main(String[] args) { try { //将system.in对象转换成Reader对象 System.out.println("请输入:"); InputStreamReader reader = new InputStreamReader(System.in); //将Reader对象转换成BufferedReader对象 BufferedReader br = new BufferedReader(reader); String buffer = null; //进行逐行的读取 while((buffer = br.readLine()) != null){ //如果读取的内容为exit,则退出程序 if(buffer.equals("exit")){ System.exit(1); } System.out.println("输入内容为:" + buffer); } } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } finally{reader.close();br.close()}//不要忘记关闭流 我忘记了,后加入的! } }
弄点高大尚的:
推回输入流
在输入输出流中,有两个特殊的流: PushbackInputStream 和 PushbackReader
Pushback用于输入流允许字节被读取然后返回(即“推回”)到流。PushbackInputStream类实现了这个想法。它提供了一种机制来“窥视”在没有受到破坏的情况下输入流生成了什么。
package com.haixu.io; import java.io.FileReader; import java.io.PushbackReader; public class PushBackTest { /** * 推回输入流练习 * * */ public static void main(String[] args) { try { /* * 创建推回输入流的对象,并设定缓存的大小为:64字节 * */ PushbackReader pr = new PushbackReader( new FileReader("E://Java编程//Java06//src//com//haixu//io//PushbackTest.java") , 64); char [] buf = new char[64]; //用于保存上次读取的字符串内容 String lastContent = ""; int hasRead = 0; //循环读取文件内容 while((hasRead = pr.read(buf)) > 0 ){ String content = new String(buf , 0 , hasRead); int targetIndex = 0 ; //将上次读取的内容与本次读取的内容拼接起来 //查看是否包含目标字符串,如果包含目标字符串 if((targetIndex = (lastContent + content).indexOf("new PushbackReader"))>0){ //将本次内容与上次内容一起推回缓冲区 pr.unread((lastContent + content).toCharArray()); int len = targetIndex >32 ? 32 : targetIndex; //指定读取指定长度的内容 pr.read(buf , 0 , len); //打印输出 System.out.println(new String(buf ,0 , len)); System.exit(0); }else{ System.out.println(lastContent); //将本次内容设定上次读取的内容 lastContent = content; } } } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } } }<span style="color:#ff0000;"> </span>
Java的标准输入/输出分别通过System.in和System.out来代表,在默认的情况下分别代表键盘和显示器,当程序通过System.in来获得输入时,实际上是通过键盘获得输入。当程序通过System.out执行输出时,程序总是输出到屏幕。
在System类中提供了三个重定向标准输入/输出的方法
static void setErr(PrintStream err) 重定向“标准”错误输出流
static void setIn(InputStream in) 重定向“标准”输入流
static void setOut(PrintStream out)重定向“标准”输出流
package com.haixu.io; import java.io.FileOutputStream; import java.io.PrintStream; public class RedirectOut { public static void main(String[] args) { try { /* * 一次性创建PrintStream输出流 */ PrintStream ps = new PrintStream(new FileOutputStream("out.text")); //将标准输入重定向到ps输入流中 System.setOut(ps); //向标准输入一个字符串 System.out.println("普通字符串"); //向标准输出输出一个对象 System.out.println((new RedirectOut())); } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } } }
package com.haixu.io; import java.io.FileInputStream; import java.util.Scanner; public class RedirectIN { public static void main(String[] args) { try { //创建FileInputStreamd对象 FileInputStream fis = new FileInputStream("E://Java编程//Java06//src//com//haixu//io//RedirectIN.java"); //标准的输入重定向到fis输入流 System.setIn(fis); //使用System.in创建Scanner对象,用于标准的输入 Scanner sc = new Scanner(System.in); //回车 sc.useDelimiter("/n"); while(sc.hasNext()){ System.out.println("键盘输入的内容" + sc.next()); } } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } } }
RandomAccessFile
RandomAccessFile是用来访问那些保存数据记录的文件的,你就可以用seek( )方法来访问记录,并进行读写了。这些记录的大小不必相同;但是其大小和位置必须是可知的。但是该类仅限于操作文件。
RandomAccessFile不属于InputStream和OutputStream类系的。实际上,除了实现DataInput和DataOutput接口之外(DataInputStream和DataOutputStream也实现了这两个接口),它和这两个类系毫不相干,甚至不使用InputStream和OutputStream类中已经存在的任何功能;它是一个完全独立的类,所有方法(绝大多数都只属于它自己)都是从零开始写的。这可能是因为RandomAccessFile能在文件里面前后移动,所以它的行为与其它的I/O类有些根本性的不同。总而言之,它是一个直接继承Object的,独立的类。
基本上,RandomAccessFile的工作方式是,把DataInputStream和DataOutputStream结合起来,再加上它自己的一些方法,比如定位用的getFilePointer( ),在文件里移动用的seek( ),以及判断文件大小的length( )、skipBytes()跳过多少字节数。此外,它的构造函数还要一个表示以只读方式("r"),还是以读写方式("rw")打开文件的参数 (和C的fopen( )一模一样)。它不支持只写文件。
只有RandomAccessFile才有seek搜寻方法,而这个方法也只适用于文件。BufferedInputStream有一个mark( )方法,你可以用它来设定标记(把结果保存在一个内部变量里),然后再调用reset( )返回这个位置,但是它的功能太弱了,而且也不怎么实用。
内存映射文件能让你创建和修改那些因为太大而无法放入内存的文件。有了内存映射文件,你就可以认为文件已经全部读进了内存,然后把它当成一个非常大的数组来访问。这种解决办法能大大简化修改文件的代码。
fileChannel.map(FileChannel.MapMode mode, long position, long size)将此通道的文件区域直接映射到内存中。注意,你必须指明,它是从文件的哪个位置开始映射的,映射的范围又有多大;也就是说,它还可以映射一个大文件的某个小片断。
MappedByteBuffer是ByteBuffer的子类,因此它具备了ByteBuffer的所有方法,但新添了force()将缓冲区的内容强制刷新到存储设备中去、load()将存储设备中的数据加载到内存中、isLoaded()位置内存中的数据是否与存储设置上同步。这里只简单地演示了一下put()和get()方法,除此之外,你还可以使用asCharBuffer( )之类的方法得到相应基本类型数据的缓冲视图后,可以方便的读写基本类型数据。
/* * 程序功能:演示了RandomAccessFile类的操作,同时实现了一个文件复制操作。 */ package com.lwj.demo; import java.io.*; public class RandomAccessFileDemo { public static void main(String[] args) throws Exception { RandomAccessFile file = new RandomAccessFile("file", "rw"); // 以下向file文件中写数据 file.writeInt(20);// 占4个字节 file.writeDouble(8.236598);// 占8个字节 file.writeUTF("这是一个UTF字符串");// 这个长度写在当前文件指针的前两个字节处,可用readShort()读取 file.writeBoolean(true);// 占1个字节 file.writeShort(395);// 占2个字节 file.writeLong(2325451l);// 占8个字节 file.writeUTF("又是一个UTF字符串"); file.writeFloat(35.5f);// 占4个字节 file.writeChar('a');// 占2个字节 file.seek(0);// 把文件指针位置设置到文件起始处 // 以下从file文件中读数据,要注意文件指针的位置 System.out.println("——————从file文件指定位置读数据——————"); System.out.println(file.readInt()); System.out.println(file.readDouble()); System.out.println(file.readUTF()); file.skipBytes(3);// 将文件指针跳过3个字节,本例中即跳过了一个boolean值和short值。 System.out.println(file.readLong()); file.skipBytes(file.readShort()); // 跳过文件中“又是一个UTF字符串”所占字节,注意readShort()方法会移动文件指针,所以不用加2。 System.out.println(file.readFloat()); //以下演示文件复制操作 System.out.println("——————文件复制(从file到fileCopy)——————"); file.seek(0); RandomAccessFile fileCopy=new RandomAccessFile("fileCopy","rw"); int len=(int)file.length();//取得文件长度(字节数) byte[] b=new byte[len]; file.readFully(b); fileCopy.write(b); System.out.println("复制完成!"); } }
/** * * @param skip 跳过多少过字节进行插入数据 * @param str 要插入的字符串 * @param fileName 文件路径 */ public static void beiju(long skip, String str, String fileName){ try { RandomAccessFile raf = new RandomAccessFile(fileName,"rw"); if(skip < 0 || skip > raf.length()){ System.out.println("跳过字节数无效"); return; } byte[] b = str.getBytes(); raf.setLength(raf.length() + b.length); for(long i = raf.length() - 1; i > b.length + skip - 1; i--){ raf.seek(i - b.length); byte temp = raf.readByte(); raf.seek(i); raf.writeByte(temp); } raf.seek(skip); raf.write(b); raf.close(); } catch (Exception e) { e.printStackTrace(); } }