赞
踩
在Java程序中,对于数据的输入输出操作以流Stream方式进行,JavaSE提供各种各样的类用于使用相同的方法获取不同类型的数据,程序中通过标准的方法输入或者输出数据
流是处理输入/输出的一个洁净的方法,它不需要代码理解键盘和网络的不同。Java中流的实现是基于java.io包定义的类层次结构的
从Java不同版本上来说,流可以分为BIO、NIO和AIO三大类。Java中的BIO、NIO和AIO理解为是Java语言对操作系统的各种Il0模型的封装。程序员在使用这些API的时候,不需要关心操作系统层面的知识,也不需要根据不同操作系统编写不同的代码。只需要使用ava的API就可以了。
BIO即同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成
NIO即同步非阻塞,一个线程不断的轮询每个输入输出的状态改变,如果有状态发生了改变,则进行下一步的操作
AIO即异步非阻塞I/O模型,无需一个线程去轮询所有IO操作的状态改变,在相应的状态改变后,系统会通知对应的线程来处理。
按流向分为输入流和输出流,可以从输入流读取数据但不能写,要从输入流读取数据,则必须有一个与这个流相关的字符源
按传输单位分为字节流和字符流
Java具备平台无关性,这里的字节是指8位,字符是16位
字节流从InputStream/OutputStream派生出来,以字节为基本处理单位,一般用于操作二进制数据,字节次序是有意义的
字符流从Reader/Writer派生出来的,以16位的Unicode码表示字符为基本处理单位,一般用于操作字符数据
使用桥接流可以实现两个流之间的转换
按功能还可以分为节点流和过滤流
节点流:负责数据源和程序之间建立连接,结点流对特定的地方读写
过滤流:用于给节点增加功能,过滤流使用结点流进行输入/输出并添加附加功能
过滤流的构造方式是以其他流位参数构造(这样的设计模式称为装饰模式)。Java的IO流使用装饰器模式,将IO流分成底层节点流和上层处理流。其中节点流用于和底层的物理存储节点直接关联。过滤流是连接在已存在的流之上,通过对数据的处理为程序提供更为强大的读写功能。
注意:IO流是一类很宝贵的资源,使用完后必须调用close()方法关闭流并释放资源。在关闭流时只用关闭最外层的流
字符流就是字节流读取文字字节数据后,不直接操作而是先查指定的编码表以获取对应的文字。简单的说:字符流=字节流+编码表
java.io.File用于封装和平台无关的文件夹和文件对象
例如获取一个文件的字节数
File ff=new File("dd.txt");
//1ength():1ong用于获取文件的字节数,如果文件不存在则返回01/一个汉字=2B
System.out.println(ff.length()+"B");
这个写法是针对windows平台的,如果使用mac或者linux平台,则需要使用
·File(String pathName)以path为路径创建File对象,如果pathname是相对路径,则默认的当前路径在系统属性user.dir中存储
new File("dd.txt");使用的就是相对路径,这个路径将从项目的根目录开始算起
使用的分隔符是/,不是\,因为\在java种表示转移字符,如果需要使用\,则必须写成\\
new File("d:/data/dd.txt")使用绝对路径,这里从d:开始,当然是windows系统
String ss=system.getProperties().getProperty("user.dir");
System.out.println(ss);//D: \workspace\eclipse-2020\2022-01-04
File(String parent,String child)这里文件对象的路径为相对于parent路径的child路径,相当于parent+" /""+child
Separator存储当前系统的路径分隔符
注意:\在字符串中为转义字符,如果需要使用\则必须写成\l。一般推荐使用/
带参数的listFiles方法支持对于子文件进行过滤,只获取需要满足条件的文件对象
FilenameFilter接口中包含一个accept(File dir,String name)方法,该方法用于对指定File的所有子目录或者文件进行迭代,如果方法返回true则list方法会获取该目录或者文件
匿名内部类的写法:推荐写法
1、函数式接口
@FunctionInterface
public interface FilenameFilter{
2、具体的表达式写法
File[] fs=f.1istFiles((dir,name)->{
return name !=nu11 &&name.endswith(" .ini");
});
简化写法
string[] nameList=file.list((dir,name)->name.endwith(".java")||new
File(name).isDirectory());
代表一个平台无关的平台路径Files提供工具方法操作文件
Paths提供创建Path的静态工厂方法Path相关方法
复制文件Files.copy(Paths.get(“T1.java”), new FileOutputStream(“a.txt”))
一次性读取文件的所有行List lines=Files.readAllLines()
典型题目:例如获取c:\windows文件夹的大小【后代文件大小之和】和其中的文件个数等 – 递归
public class Test1 { public static long number = 0; // 统计文件个数 public static long size = 0;// 统计所有文件大小之和 public static void main(String[] args) { // File.pathSeparator 根据操作系统不同可以有:(Linux)和;(windows)两个,用于分割 多个路径 // File.separator用于分割路径的不同部分。路径中所使用分隔符有\\和/两种写法 File file=new File("c:/windows"); list(file); System.out.println("总共文件个数为:"+number); System.out.println(file.getAbsolutePath()+"总大小为:"+size); } public static void list(File file) { if (file != null) { if (file.isFile()) { //如果是文件 number++; size += file.length(); }else if(file.isDirectory()){ //如果是文件夹 File[] children=file.listFiles(); if(children!=null && children.length>0) for(File tmp:children) list(tmp); } } } }
流是用于隐藏数据传输细节的操作模型,是数据传输的抽象表达,Java程序可以通过流来完成输入/输出 操作
Decorate允许向一个现有对象动态添加新功能,同时不改变结构,属于JavaSE 23种设计模式中的结构 型设计模型
要求:抽象
编码实现:4种角色
被装饰方抽象角色
public interface IShape{
void draw(); //public abstract,不是默认package
}
具体的装饰方实现,可以有多个不同实现类
public class Circle implemenets IShape{
public void draw(){
System.out.println("画了一个圆圈");
}
}
装饰抽象角色
public abstract class DecorateShape implements IShape{
private IShape target;//被装饰方对象,就是需要动态追加功能的对象
public DecodateShape(IShape target){
this.target=target;
}
public void draw(){
target.draw(); //具体的操作需要通过被装饰方提供实现
}
}
具体装饰角色
public class RedDecorateShape extends DecorateShape{
public RedDecorateShape(IShape target){
super(target);
}
public void draw(){
System.out.println("使用红色");
super.draw(); //调用父类中的被覆盖的方法
System.out.println("恢复默认的颜色");
}
}
测试类
//调用时进行组装
IShape circle=new Circle(); //节点流
IShape decorate=new RedDecorateShape(circle); //过滤流
decorate.draw();
//也允许这样使用
IShape decorate2=new BlueDecorateShape(decorate);
使用统一接口进行操作,具体实现细节无关
InputStream和OutputStream,都实现了Closeable接口,所以支持try-resources InputStream操作用于实现数据的读取操作
OutputStream操作方法用于实现数据的写出操作
样例:使用字节流进行文件的拷贝
public class Test4 { public static void main(String[] args) { InputStream is = null; OutputStream os = null; try { is = new FileInputStream("data/a1.txt"); File file = new File("out/"); if(!file.exists()) file.mkdirs(); os=new FileOutputStream("out/a1.bak");//自动创建文件,如果文件已存在 则采用覆盖 int data=0; while((data=is.read())!=-1) { os.write(data); } } catch (Exception e) { System.out.println(e); } finally { try { if (is != null) is.close(); } catch (IOException e) { e.printStackTrace(); } try { if (os != null) os.close(); } catch (IOException e) { e.printStackTrace(); } } } }
顶级父抽象类Reader和Writer,一次一字符的操作,实现了Closeable接口。如果涉及中文信息,则需 要考虑编码字符集的问题,如果编码字符集错误,则显示乱码
close():void 关闭流
类型 | 字符流 | 节点流 |
---|---|---|
文件 | FileReader和FileWriter | FileInputStream和FileOutputStream |
数组 | CharArrayReader和 CharArrayWriter | ByteArrayInputStream和 ByteArrayOutputStream |
字符串 | StringReader和StringWriter | |
线程通讯使用的管道 | PipedReader和PipeWriter | PipedInputStream和PipeOutputStream |
FileInputStream和FileReader用于从一个文件中读取数据;FileOutputStream和FileWriter用于向一个 文件中写出数据
FileInputStream文件输入字节流,FileReader文件输入字符流,用法类似
FileOutputStreram文件输出字节流,FileWriter文件输出字符流,用法类似
//从键盘上读取数据,并写入到一个文件中。保留过去的历史数据 public class Test1 { public static void main(String[] args) throws IOException { // 键盘的应用对象,系统标准输入设备默认为键盘System.in InputStream is = System.in; byte[] buffer = new byte[1024]; int len = is.read(buffer); //遇到回车则自动进行读取操作 if (len > 0) { OutputStream os = new FileOutputStream("out/key.txt",true);//不带 参数则默认为false os.write(buffer,0,len); os.close(); } is.close(); } }
注意:文件流属于节点流,输入流的源端和输出流的目标端都是磁盘文件,沟通方法允许通过文件的路 径名称或者文件对象的方式进行构建
如果是文本字符则使用char[],如果二进制数据则使用byte[]
CharArrayReader和CharArrayWriter、ByteArrayInputStream和ByteArrayOutputStream数据来源或 者输出目标为数组,CharArray对应char[],ByteArray对应byte[]
输入流
输出流
读写String中的数据
StringReader用于从一个String中读取数据
String str="我说我爱JAVA";
Reader sr=new StringReader(str); //设置字符串流的数据原来为str对象
int cc;
while((cc=sr.read())!=-1){
System.out.println((char)cc);
}
sr.close()
StringWriter用于向一个StringBuffer中写入数据,实现了一个可变长的字符串
Scanner sc=new Scanner(System.in);
try(StringWriter sw=new StringWriter();
Writer w=new FileWriter("d:/console.txt");
){
String tmp=sc.nextLine();
while(!"quit".equals(tmp)){
if(tmp!=null && tmp.trim().length()>0){
sw.write(tmp+"\n");
}
tmp=sc.nextLine();
}
System.out.println(sw.toString());
w.write(sw.toString());//通过toString方法获取变长字符串内容
}
sc.close()
过滤流的作用就是在节点流的基础上提供额外功能。装饰器模式
处理类型 | 字符流 | 字节流 |
---|---|---|
缓存 | BufferedReader/BufferedWriter | BufferedInputStream / BufferedOutputStream |
过滤处理 | FilterReader / FilterWriter | FilterInputStream / FilterOutputStream |
桥接处理 | InputStreamReader / OutputStreamWriter | |
对象处理 | ObjectInputStream / ObjectOutputStream | |
数据转换 | DataInputStream / DataOutputStream | |
打印功能 | PrintWriter | PrintOutputStream |
行数统计 | LineNumberReader | LineNumberInputStream |
回滚处理 | PushbackReader | PushbackInputStream |
就是装饰器模式中的抽象装饰角色
public abstract class FilterReader extends Reader { //典型的装饰模式,需要继承
Reader
protected Reader in; //被装饰目标
protected FilterReader(Reader in) {//通过构造器要求定义装饰器对象时必须指定被装饰
对象
super(in);
this.in = in;
}
public int read() throws IOException { //调用read方法时,真正执行任务的是被装
饰对象
return in.read();
}
需求:循环13加密
算法思路:要求每个字母向后移动13,但是结果还必须是字母
// 设置'a'==0 +13 ('a'+13)%26=13
// 'n'==13 ('n'+13)%26==0 -->'a'
char c = 'n';
int p = (c + 13 - 'a') % 26 + 'a';
System.out.println((char)p);
注意:这里只进行的小写字母的处理
String ss = "filterreader";
StringBuilder sb = new StringBuilder();
for (int i = 0; i < ss.length(); i++) {
char c = ss.charAt(i);
int p = (c + 13 - 'a') % 26 + 'a';
sb.append((char) p);
}
System.out.println(sb.toString()); //svygreernqre
循环13加密处理过滤流的实现
public class SecurityWriter extends FilterWriter { protected SecurityWriter(Writer out) { super(out); } @Override public void write(int c) throws IOException { if (c >= 'a' && c <= 'z') {// 判定小写字母 // Character.isLowerCase((char)c):boolean c = (c - 'a' + 13) % 26 + 'a'; } else if (c >= 'A' && c <= 'Z') { c = (c - 'A' + 13) % 26 + 'A'; } super.write(c); //调用父类中被覆盖的write方法 } }
测试调用
public class Test { public static void main(String[] args) throws IOException { //第一次执行就是加密,将加密后的内容传入再执行加密获取原文 try ( // Reader r = new FileReader("data/Test5.java"); // Writer w = new SecurityWriter(new FileWriter("out/sec.txt")); Reader r=new FileReader("out/sec.txt"); Writer w=new SecurityWriter(new FileWriter("out/source.txt")); ) { int kk; while((kk=r.read())!=-1) { w.write(kk); } } System.out.println("加密完成!"); } }
用途:实现字节流和字符流之间的自动转换,从字节输入流读取字节数据,并按照编码规范转换为字 符;向字节输出流写出数据时,先将字符按照编码规范转换为字节,然后再进行输出。使用桥接流时应 该指定编码字符集名称,以便实现流转换,如果不指定,则使用当前默认编码字符集
类定义
public class InputStreamReader extends Reader{}
public class OutputStreamWriter extends Writer{}
InputStreamReader
// int kk=System.in.read();
// System.out.println((char)kk);//但是由于字节流只读取了一个字节,所以对于多字节
编码的中文就会出现乱码问题
Reader r = new InputStreamReader(System.in); // public final static
InputStream in = null;
int kk = r.read(); //只读取一个字符
System.out.println((char)kk);
没有指定编码字符集则使用系统默认的编码字符集,允许用户自定义编码字符集,例如new InputStreamReader(System.in, “iso8859-1”)。iso8859-1是用于欧洲地区的编码字符集,使用的是单字 节编码。
//GBK
Reader r = new InputStreamReader(System.in,"utf-8"); //第二个参数用于指定所使用的
编码字符集,注意需要当前控制台上的编码字符集一致,否则乱码。
int kk = r.read();
System.out.println((char)kk);
一般不建议执行设置编码字符集,除非是必须的
OutputStreamWriter的用法
构造器和InputStreamReader一致
String ss = "设置中心信息和english information";
OutputStream os = new FileOutputStream("out/data.data"); //最终输出使用的是字节
流
Writer writer = new OutputStreamWriter(os);//引入桥接流自动实现字符流转换为字节流进
行输出
writer.write(ss);
writer.close();
缓冲流是套接在相应的节点流之上,对读写操作提供缓冲功能,以提高读写的效率,并引入一个新方法
以硬盘为例,字节流和字符流的最大弊端是每次读写都需要访问硬盘,如果读写的频率较高时性能 一定不佳,为了减少硬盘读写次数,提高执行效率可以引入缓冲流。缓冲流在读取数据时,会一次 性读取较多的数据,在以后的每一次读取数据时,都是现在缓冲区中访问,如果有数据则直接访 问,直到缓存中的所有数据读取完毕后,再到硬盘读取
没有使用缓冲流的时间统计
long start=System.currentTimeMillis();//获取从1970-1-1 0:0:0到执行时的毫秒值
InputStream is = new FileInputStream("data/T1.java");
int kk=is.read();
while(kk!=-1) {
System.out.println((char)kk);
kk=is.read();
}
is.close();
long end=System.currentTimeMillis();
System.out.println("执行时间为:"+(end-start)+"ms");//21ms
引入缓冲流:以浪费内存为代价换取高执行效率
long start=System.currentTimeMillis();//获取从1970-1-1 0:0:0到执行时的毫秒值
InputStream is = new BufferedInputStream(new
FileInputStream("data/T1.java"));
int kk=is.read();
while(kk!=-1) {
System.out.println((char)kk);
kk=is.read();
}
is.close();
long end=System.currentTimeMillis();
System.out.println("执行时间为:"+(end-start)+"ms");//11ms
BufferedInputStream(InputStream)不定义缓存大小,则默认8192
public class BufferedInputStream extends FilterInputStream {
private static int DEFAULT_BUFFER_SIZE = 8192;
public BufferedInputStream(InputStream in) {
this(in, DEFAULT_BUFFER_SIZE); //调用自己的2个参数的构造
器,DEFAULT_BUFFER_SIZE是当前类的一个常量值 8192
}
BufferedInputStream(InputStream,int)人为设置缓冲大小,int单位字节
public BufferedInputStream(InputStream in, int size) {//参数2size表示缓冲区的大
小
super(in);
if (size <= 0) {
throw new IllegalArgumentException("Buffer size <= 0");
}
buf = new byte[size];
}
BufferedOutputStream(OutputStream)
public BufferedOutputStream(OutputStream out) {
this(out, 8192);
}
BufferedOutputStream(OutputStream,int size)自定义缓存区大小
BufferedReader(Reader)默认缓冲区大小为8192字符
BufferedReader(Reader,int)int用于定义缓冲区的大小
特殊方法: readLine():String 用于读取一行数据
Reader r=new BufferedReader(new FileReader("data/abc.txt"));
String str=r.readLine(); //系统在编译器不能识别r对象为BufferedReader类型,所以不能直接调用BufferedReader中的特殊
BufferedReader r = new BufferedReader(new
FileReader("data/T1.java"));
String ss=r.readLine(); //也可以使用窄化操作
BufferedWriter(Writer)默认缓冲区大小为8192字符
BufferedWriter(Writer,int)int用于自定义缓冲区的大小,单位为字符B
BufferedReader中提供了一个特殊方法readLine():String,但是在BufferedInputStream中并没有这个 方法
整行读取数据,以\r或者\n为分隔符(换行符)
如果读取内容为null,则表示读取到了流末尾
readLine方法的返回值会自动剔除末尾的换行符
public class T2 {
public static void main(String[] args) throws IOException {
BufferedReader r = new BufferedReader(new
FileReader("data/T1.java"));
String ss = r.readLine();
while (ss != null) {
if (ss.trim().length() > 0)
System.out.println(ss); 使用println表示输出结束后自动换行,
通过这种方式可以给readLine剔除的换行符进行补偿
ss = r.readLine();
}
r.close();
}
}
BufferedWriter中提供了一个方法newLine可以写入一个换行符
public class T2 {
public static void main(String[] args) throws IOException {
BufferedReader r = new BufferedReader(new
FileReader("data/T1.java"));
String ss = r.readLine();
while (ss != null) {
if (ss.trim().length() > 0)
System.out.print(ss+"\n");//在字符串末尾手动添加\n也可以补偿
换行符
ss = r.readLine();
}
r.close();
}
}
对于输出的缓冲流,写入的数据会受限缓存在内存,使用flush方法可以清空缓存,同时将以前的缓存数 据立即写出
简单用法Scanner扫描器接收键盘录入
System.in:InputStream用于指代系统默认的输入设备—键盘
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
System.out.println("请输入一行数据,以回车结尾");
String temp="";
while((temp=br.readLine()).trim().length()>0){
if("quit".equals(temp))
break;
System.out.println(temp);
}
br.close();
System.out:PrintStream用于指代系统默认的标准输出设备—屏幕显示 System.err:PrintStream用于指代系统默认的错误输出设备—屏幕显示
注意:在程序中同时使用System.out和System.err输出时,不能保证准确的输出顺序。所以为了查看执 行顺序,一般只能选择其中一个进行使用
DataInputStream和DataOutputStream允许与机器无关的风格读写java原始类型的数据,比较适合于 网络上的数据传输
构造器
练习题:读写一个double类型的数据到文件
double dd=123.4567;
//使用数据流直接操作的方法
FileOutputStream fos=new FileOutputStream("out/abc.data");
fos.write((dd+"").getBytes()); //FileOutputStream中并没有直接写出double类型数据
的方法,所以只能将数据转换为字符串进行输出
fos.close();
FileInputStream fis=new FileInputStream("out/abc.data");
byte[] buffer=new byte[1024];
int len=fis.read(buffer);
String ss=new String(buffer,0,len);
double kk=Double.parseDouble(ss);
System.out.println(kk);
fis.close();
加入一个double,然后再写一个String,然后再写一个int
为了区分数据,则需要在数据中混入一些特殊符号,例如@@,将数据转换 为"123.4567@@xiaopangzi@@12"这样格式写出到输出文件中,读取数据后,再使用split进行切分。 从功能角度上说来实现是没问题的,但是编码过于繁琐了
针对Java中的各种基本数据类型和String类型,可以直接使用Data数据流以简化编程
DataOutputStream dos = new DataOutputStream(new FileOutputStream("out/abd.data")); dos.writeDouble(123.4567); //为了直接操作字符串类型数据,可以额外引入一个数据,用于记录字串中字符个数。最终读取时使用 String ss="赵小胖说:'我爱张毅'"; int len=ss.length(); dos.writeInt(len); dos.writeChars(ss); dos.writeInt(18); dos.close(); DataInputStream dis=new DataInputStream(new FileInputStream("out/abd.data")); double d1=dis.readDouble(); //对应位置的操作 int len1=dis.readInt();//获取字串中字符个数 StringBuilder sb=new StringBuilder(); for(int i=0;i<len1;i++) sb.append(dis.readChar());//一次读取一个字符,然后通过StringBuilder将其连接为 字符串 int age=dis.readInt(); dis.close(); System.out.println(d1+"\t"+sb.toString()+"\t"+age);
数据类型 | 输入流DataInputStream | 输出流DataOutputStream |
---|---|---|
整型 | readByte、readShort、readInt、 readLong | writeByte、writeShort、writeInt、 writeLong |
浮点型 | readFloat、readDouble | writeFloat、writeDouble |
布尔型 | readBoolean | writeBoolean |
字符型 | readChar | writeChar |
数据流是通过EOFException用于判断流结束end-of-file
DataInputStream dis=new DataInputStream(new FileInputStream("out/不存在的文
件.data"));
double d1=dis.readDouble(); //直接EOFException
针对字符串类型建议使用readUTF和writeUTF进行读写操作
public class T3 { public static void main(String[] args) throws IOException { // 记录张三的个人信息 DataOutputStream dos = new DataOutputStream(new FileOutputStream("out/pang.data")); dos.writeLong(123456); dos.writeUTF("张三"); dos.writeInt(18); dos.close(); // 读取个人信息 DataInputStream dis = new DataInputStream(new FileInputStream("out/pang.data")); //务必写出顺序和读取顺序一致 // int kk=dis.readInt(); long kk=dis.readLong(); String ss=dis.readUTF(); Integer age=dis.readInt(); dis.close(); System.out.println(kk+"\t"+ss+"\t"+age); } }
打印流都是用于实现打印输出,其中有PrintWriter和PrintStream,分别针对字符和字节,都提供了一 系列重载的print和println方法,可以输出多种类型数据
print(Object):void/println(Object):void
PrintWriter pw = new PrintWriter(new FileWriter("out/pw.txt"));
for(int i=0;i<10;i++) {
pw.print("数值:"+i);
pw.print(i);
pw.println(i%2==0);
}
pw.close();
输出引用类型:具体实现是通过调用对象的toString()方法将对象转换为String进行输出
public void println(Object x) {
String s = String.valueOf(x); //输出Object类型数据时,首先调用String类中定义的
valueOf方法,
synchronized (lock) {
print(s); //打印输出字符串
println(); //产生换行 print('\n')
}
}
String中valueOf方法的定义
public static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString(); //为了避免obj对象为null,调用toString出现NullPointerException,所以使用三元表达式,当obj为空时,输出字符串null,否则调用obj的toString方法
}
**注意 **:
PrintWriter和PrintStream的输出操作不会抛出异常,用户可以通过错误状态检查获取错误信息
PrintWriter和PrintStream都有自动的flush功能
特殊构造器
可以使用DataInputStream和DataOutputStream读写对象数据,但是操作繁琐
操作对象为Account账户类型的对象
账户类定义
public class Account {
private Long id;//账户编号
private String username;//账户名称
private String password;//账户口令
private Double balance;//余额
添加账户数据
public class Regist { public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); System.out.println("账户编号:"); String ss=br.readLine(); Long id=Long.valueOf(ss); System.out.println("账户名称:"); String username=br.readLine(); System.out.println("账户口令:"); String password=br.readLine(); System.out.println("存款数量:"); ss=br.readLine(); double balance=Double.parseDouble(ss); DataOutputStream dos=new DataOutputStream(new FileOutputStream("out/account.data",true)); dos.writeLong(id); dos.writeUTF(username); dos.writeUTF(password); dos.writeDouble(balance); dos.close(); } }
显示账户信息
public class Show { public static void main(String[] args) throws Exception { DataInputStream dis = new DataInputStream(new FileInputStream("out/account.data")); while (true) { try { Long id = dis.readLong(); String username=dis.readUTF(); String password=dis.readUTF(); double balance=dis.readDouble(); System.out.println(id+"\t"+username+"\t"+password+"\t"+balance); } catch (EOFException e) { break; } } dis.close(); } }
所有的读写操作都是自行编码实现的,而且真的不面向对象。Account类定义根本没有什么作用 写出操作
dos.writeLong(id);
dos.writeUTF(username);
dos.writeUTF(password);
dos.writeDouble(balance);
读入操作
Long id = dis.readLong();
String username=dis.readUTF();
String password=dis.readUTF();
double balance=dis.readDouble();
Java提供了ObjectInputStream和ObjectOutputStream可以直接读写Object对象,实际上还提供了针对 8种简单类型和String类型的读写操作方法
ObjectInputStream提供了方法readObject():Object
ObjectOutputStream提供了方法writeObject(Object):void
将java.util.Date类型当前时间写入到now.data文件中,然后再重新从文件读取出来
Date now = new Date(); // 获取系统当前时,这是一个引用类型
ObjectOutputStream oos = new ObjectOutputStream(new BufferedOutputStream(new
FileOutputStream("out/now.data")));
oos.writeObject(now); // 将对象直接写出
oos.close();// 关闭流, try/finally或者try-resources
ObjectInputStream ois = new ObjectInputStream(new BufferedInputStream(new
FileInputStream("out/now.data")));
Object obj = ois.readObject(); // 从文件中直接读取对应的对象
System.out.println(obj);
//readObject获取到的是Object类型的数据,如果需要调用Date类中的特殊方法,则需要进行数据
类型转换(窄化操作)
if(obj!=null && obj instanceof Date){
Date dd=(Date)obj;
System.out.println(dd.getYear()+1900);
}
读写一个对象的前提是这个类型的对象是可序列化的
针对对象的序列化和反序列化是通过JVM实现的,编程中只做声明,序列化的目标就是将对象保存到磁 盘中或者允许在网络中直接传动
1、依靠Serializable接口进行声明,如果需要特殊操作可以实现Externalizable接口。Serializable接口 属于旗标接口【标识接口】,仅仅只起到说明的作用,没有需要实现的方法
1 public interface Serializable { }
重新定义Account类
public class Account implements Serializable{
private Long id;//账户编号
private String username;//账户名称
private String password;//账户口令
private Double balance;//余额
2、可以通过ObjectInputStream和ObjectOutputStream提供的方法直接读写对象 通过序列化将一个对象持久化存储到文件中
Account account = new Account();
account.setId(99L);
account.setUsername("张三");
account.setPassword("123456");
account.setBalance(1234.56);
// 存储对象到文件中
ObjectOutputStream oos = new ObjectOutputStream(new BufferedOutputStream(new
FileOutputStream("out/account.data")));
oos.writeObject(account);
oos.close();
System.out.println("序列化存储对象完成");
通过反序列化将一个对象从文件中读取到内存
ObjectInputStream ois = new ObjectInputStream(new
FileInputStream("out/account.data"));
Object obj=ois.readObject();
System.out.println(obj);
ois.close();
注意:通过序列化写出对象,在通过反序列化读取对象的过程中,一般要求不允许修改类定义,否则 InvalidClassException
需要通过对象流读写的对象必须实现序列化接口,否则NotSerializableException
public class Account implements Serializable
潜在要求:要求类必须由无参构造器,因为反序列化时需要调用无参构造器构建对象。否则 Exception in thread “main” java.io.InvalidClassException: com.yan.test01.Account; no valid constructor
如果需要针对某个类型的对象进行序列化,要求该类型的所有属性也需要支持序列化,否则出现类似 Exception in thread “main” java.io.NotSerializableException: com.yan.test03.Role 报 错
public class Account implements Serializable {
private static final long serialVersionUID = 4093553615911145103L;
private Long id;// 账户编号
private String username;// 账户名称
private transient String password;// 账户口令
private Role role;
private Double balance;// 余额
Serializable接口属于标识接口,没有需要实现的方法,所有的序列化和反序列化操作都是由虚拟机负责 实现。 Externalizable接口定义,可以支持用户自定义实现序列化的细节操作,除非特殊需求一般不使用 Externalizable接口,因为没有必要自定义
public interface Externalizable extends java.io.Serializable {
void writeExternal(ObjectOutput out) throws IOException;//自定义实现对象的序
列化操作
void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException;//自定义实现反序列化操作
}
Externalizable用法
public class Account implements Externalizable { private Long id;// 账户编号 private String username;// 账户名称 private String password;// 账户口令 private Double balance;// 余额 @Override public void writeExternal(ObjectOutput out) throws IOException { //执行对象的序列化操作,就是将需要写出的数据写入到out对象中 out.writeLong(this.id); out.writeUTF(this.username); out.writeUTF(this.password); out.writeDouble(this.balance); } @Override public void readExternal(ObjectInput in) throws IOException,ClassNotFoundException { //执行对象的反序列化操作,就是从in对象中读取数据,不需要自行new对象。注意读写顺序必须一致,否则出错 this.id=in.readLong(); this.username=in.readUTF(); this.password=in.readUTF(); this.balance=in.readDouble(); }
某个类型的属性是不可序列化。例如对象中包含InputStream/OutputStream之类的资源类型的属性, 不仅不能进行序列化,而且在序列化之前还应该释放资源,在反序列化时应该重新创建资源连接。
public class Account implements Externalizable{ private Long id; private String username; private transient String password;//可以被序列,但是按照业务需求不进行序列化 private InputStream is; //不能序列化 public void writeExternal(ObjectOutput out) throws IOException { is.close(); //将特定属性资源链接关闭 //执行对象的序列化操作,就是将需要写出的数据写入到out对象中 out.writeLong(this.id); out.writeUTF(this.username); } public void readExternal(ObjectInput in) throws IOException,ClassNotFoundException { this.is=new FileInputStream("photo.jpg"); //执行对象的反序列化操作,就是从in对象中读取数据,不需要自行new对象。注意读写顺序 必须一致,否则出错 this.id=in.readLong(); this.username=in.readUTF(); } }
通过反序列化获取的对象是Object类型,如果调用对象中特殊的方法则必须先进行窄化操作
object obj=objectInputStream.readObject();
if(obj!=null && obj instanceof Account){
Account account=(Account)obj;
//调用Account类中的特殊方法
}
Eclipse中针对实现了序列化接口的类定义会有这样一个警告信息,要求提供一个序列号 serialiVersionUID。这不是错误信息
序列版本号可以不用添加,这个序列版本号是一个中在序列化和反序列化操作中快速识别类型的简单方 法。
一般针对敏感数据不应该进行序列化操作,例如当前账户的口令属性。针对不需要进行序列化操作的属 性可以添加一个关键字transient,表示该属性不参与序列化和反序列化操作
public class Account implements Serializable {
private static final long serialVersionUID = 4093553615911145103L;
private Long id;// 账户编号
private String username;// 账户名称
private transient String password;// transient用于声明该属性不参与序列化操作
读文件可以通过EOFException异常来判断读取文件结束 读取一个文件中所存储的所有对象
try(
ObjectInputStream ois=new ObjectInputStream(new
FileInputStream("account.data"));
){
while(true){
try{
Object tmp=ois.readObject();
... ...
} catch(EOFException e){//用于判断文件结束
break;
}
}
}
追加文件处理的问题
// 存储对象到文件中
ObjectOutputStream oos = new ObjectOutputStream(new BufferedOutputStream(new
FileOutputStream("out/account.data",true)));//true表示对文件进行追加操作
oos.writeObject(account);
oos.close();
写入数据不会有任何问题,但是读取数据则会出现异常Exception in thread "main"java.io.StreamCorruptedException: invalid type code: AC
解决方案:如果需要添加对象数据时,不要使用FileOutputStream中的true进行追加,而是先读取所有 对象数据,然后再次写出覆盖
Object[] arr=new Object[100]; int counter=0; File ff=new File("out/account.data"); if(ff.exists()){ ObjectInputStream ois=new ObjectInputStream(new FileInputStream(ff)); while(true){ try{ Object tmp=ois.readObject(); acc[counter++]=tmp; } catch(EOFException e){ break; } } ois.close(); } //追加新数据 Account newAccount=new Account(); newAccount.setId(50L); newAccount.setUsername("中方"); ...... arr[counter++]=newAccount; //统一将数组中的所有对象写出到文件中 ObjectOutputStream ois=new ObjectOutputStream(new FileOutputStream("out/account.data")); //没有true则表示覆盖 for(int i=0;i<arr.length;i++){ ois.writeObject(arr[i]); } ois.close();
java序列化就是将一个对象转换为一个二进制表示的字节数组,通过保存或者转移这些数组达到传递对 象或者持久化存放对象的目的。序列化要求类必须实现对应的序列接口,两个序列化接口 Serializable【标识接口】和Externalizable【自定义方法实现属性的序列化操作】。反序列化就是将将 二进制数组转换为对象的过程,在执行反序列化时必须有原始的类定义才能将对象进行还原。
serialVersionUID用于确保类序列化和反序列化的兼容性问题。编译器推荐2中方式,一种是生成默认的 versioID,还有一种是根据类名、接口名、成员方法和属性生成一个64为的哈希字段
RandomAccessFile不属于IO流,支持对文件的随机读写
//类定义,属于java.io包中的类,但是不同于IO类的定义
public class RandomAccessFile implements DataOutput, DataInput, Closeable
RandomAccessFile是Java输入/输出流体系中功能最丰富的文件内容访问类,既可以读取文件内容,又 可以向文件中输出数据。提供了文件指针的功能,从而实现随机读写文件
RandomAccessFile在创建对象时,除了指定需要操作的文件之外,还需要指定一个mode参数,表示访 问模式
try {
RandomAccessFile raf=new RandomAccessFile("out/raf01.data", "rw");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
public class Test1 {
public static void main(String[] args) {
try {
RandomAccessFile raf=new RandomAccessFile("out/raf01.data",
"rw");
raf.writeInt(100);
raf.writeUTF("中国热门解放军");
raf.writeDouble(1234.567);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class Test2 { public static void main(String[] args) { try { RandomAccessFile raf=new RandomAccessFile("out/raf01.data", "rw"); int id=raf.readInt(); String name=raf.readUTF(); double salary=raf.readDouble(); System.out.println(id+"\t"+name+"\t"+salary); raf.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
读取文件如果到达文件末尾则抛出异常EOFException,采用的异常进行判断文件结束
典型的应用就是多线程下载和断点续传的实现
RandomAccessFile对象中包含了一个记录指针,用于标识当前读写的具体位置。当创建一个对象时, 该文件指针指向文件头部,也就是0,当读写n个字节后,文件记录指针则向后移动了n个位置。如果需 要允许自由移动该记录指针,移动位置为[0,file.length()]
getFlilePointer():long 返回文件记录指针的当前位置
seek(long):void 将文件记录指针移动到具体的位置
skipBytes(int) 相对于当前位置跳过int个字节
练习:
使用RandomAccessFile将一个文本文件倒置读出,并在控制台上显示
public class Test3 { public static void main(String[] args) throws Exception { File ff = new File("out/abc1.txt"); if(ff.exists()) ff.delete(); //生成文件 try (RandomAccessFile raf = new RandomAccessFile(ff, "rw");) { String str="窗前明月光,疑似地上霜"; for(int i=0;i<str.length();i++) raf.writeChar(str.charAt(i)); } //倒序读出文件 long len = ff.length(); try (RandomAccessFile raf = new RandomAccessFile(ff, "rw");) { while (true) { len-=2; if(len<0) break; raf.seek(len); char cc=raf.readChar(); System.out.print(cc); } } } }
public class Test2 { public static void main(String[] args) { try { RandomAccessFile raf=new RandomAccessFile("out/raf01.data", "rw"); int id=raf.readInt(); String name=raf.readUTF(); double salary=raf.readDouble(); System.out.println(id+"\t"+name+"\t"+salary); raf.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
读取文件如果到达文件末尾则抛出异常EOFException,采用的异常进行判断文件结束
典型的应用就是多线程下载和断点续传的实现
RandomAccessFile对象中包含了一个记录指针,用于标识当前读写的具体位置。当创建一个对象时, 该文件指针指向文件头部,也就是0,当读写n个字节后,文件记录指针则向后移动了n个位置。如果需 要允许自由移动该记录指针,移动位置为[0,file.length()]
getFlilePointer():long 返回文件记录指针的当前位置
seek(long):void 将文件记录指针移动到具体的位置
skipBytes(int) 相对于当前位置跳过int个字节
练习:
使用RandomAccessFile将一个文本文件倒置读出,并在控制台上显示
public class Test3 { public static void main(String[] args) throws Exception { File ff = new File("out/abc1.txt"); if(ff.exists()) ff.delete(); //生成文件 try (RandomAccessFile raf = new RandomAccessFile(ff, "rw");) { String str="窗前明月光,疑似地上霜"; for(int i=0;i<str.length();i++) raf.writeChar(str.charAt(i)); } //倒序读出文件 long len = ff.length(); try (RandomAccessFile raf = new RandomAccessFile(ff, "rw");) { while (true) { len-=2; if(len<0) break; raf.seek(len); char cc=raf.readChar(); System.out.print(cc); } } } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。