File类

java.io.File只用于表示文件(目录)的信息(名称、大小等),不能用于文件内容的访问,我们可以通过通过给其构造函数传一个路径来构建以文件,传入的路径名有一个小问题,就是Windows和UNIX 中的路径分隔符斜杠方向的问题:"/" 表示 UNIX 中的根目录,"\" 表示Windows 的根目录。File类有静态的参数可以很简单的解决这个问题:pathSeparator就是其中一个。File类中常用的方法有:

createNewFile():在指定目录下创建文件,如果该文件已存在,则不创建。

注意:对于操作文件的输出流而言,输出流对象已建立,就会创建文件,如果文件已存在,会覆盖。除非续写,这是流创建文件与File类这个方法的不同

exists():测试此抽象路径名表示的文件或目录是否存在

isDirectory()测试此抽象路径名表示的文件是否是一个目录

isFile():测试此抽象路径名表示的文件是否是一个标准文件

mkdir():创建此抽象路径名指定的目录

mkdirs():创建此抽象路径名指定的目录,包括所有必需但不存在的父目录

listFiles()返回一个抽象路径名数组,这些路径名表示此抽象路径名表示的目录中的文件

还有一些方法,我们需要的时候查API就好了

一个遍历读取文件名例子:(用于练习File类的API)

import java.io.File;
import org.junit.Test; public class FileTest { /**
* 遍历读取文件名
* @param path
*/
private void listFile(File file){ if(!file.exists()){
throw new IllegalArgumentException("文件或目录不存在");
} File[] listFile=file.listFiles();
for (File file2 : listFile) {
if(file2.isDirectory()){ //如果文件时目录则进行递归
listFile(file2);
}else{
System.out.println(file2.getPath());
}
}
} @Test
public void fun1(){
listFile(new File("D:"+File.separator+"Java"));
}
}

认识文本和文本文件

java的文本(char)是16位无符号整数,是字符的unicode编码(双字节编码),文件是以byte byte byte ...的数据序列存储的,而文本文件是文本(char)序列按照某种编码方案(utf-8,utf-16be,gbk)序列化为byte的存储结果,所以就会引出一个乱码的问题,当写入文件的文本编码和读出文本的编码不一致时,就会出现乱码问题,解决的方法就是将这两个方式使用的编码统一起来。

一点小知识:

UTF-8中的一个中文字符需要3个字节数来表示,一个英文字符是1个字节表示。

GBK中的一个中文字符需要2个字节数来表示,一个英文字符是1个字节表示。

I/O

I/O操作主要是依靠流来进行操作,流可以理解成数据的流动,我们可以把流进行分类:

  1. 输入流和输出流(输入和输出都是相对于内存而说的,加载到内存就是输入流,从内存中加载出来就是输出流)
  2. 字节流和字符流

所有操作I/O的类都在java.io包中。流的操作只有两种:读和写。

流的体系因为功能不同,但是有共性内容,不断抽取,形成继承体系。该体系一共有四个基类,而且都是抽象类。

字节流:InputStream  OutputStream

字符流:Reader  Writer

在这四个系统中,它们的子类,都有一个共性特点:子类名后缀都是父类名,前缀名都是这个子类的功能名称。

字节流:处理字节数据的流对象。设备上的数据无论是图片或者dvd,文字,它们都以二进制存储的。二进制的最终都是以一个8位为数据单元进行体现,所以计算机中的最小数据单元就是字节。意味着,字节流可以处理设备上的所有数据,所以字节流一样可以处理字符数据。

那么为什么要有字符流呢?因为字符每个国家都不一样,所以涉及到了字符编码问题,那么GBK编码的中文用unicode编码解析是有问题的,所以需要获取中文字节数据的同时+ 指定的编码表才可以解析正确数据。为了方便于文字的解析,所以将字节流和编码表封装成对象,这个对象就是字符流。只要操作字符数据,优先考虑使用字符流体系。

字符流的底层实现一定是使用字节流实现的。

close()和flush()的区别:

flush():将缓冲区的数据刷到目的地中后,流可以使用。

close():将缓冲区的数据刷到目的地中后,流就关闭了,该方法主要用于结束调用的底层资源。这个动作一定做。

注意:io异常的处理方式:io一定要写finally,我们将关闭流的操作放在finally中

IO中的使用到了一个设计模式:装饰设计模式。

装饰设计模式解决:对一组类进行功能的增强。

包装:写一个类(包装类)对被包装对象进行包装;

  1. 包装类和被包装对象要实现同样的接口;
  2. 包装类要持有一个被包装对象;
  3. 包装类在实现接口时,大部分方法是靠调用被包装对象来实现的,对于需要修改的方法我们自己实现;

所以我们可以看到I/O体系中类很多,比较学院派,设计的很优雅

字节流

输入流:

InputStream是表示字节输入流的所有类的超类。

FileInputStream从文件系统中的某个文件中获得输入字节。哪些文件可用取决于主机环境。FileInputStream 用于读取诸如图像数据之类的原始字节流。要读取字符流,请考虑使用 FileReader。

BufferedInputStream该类实现缓冲的输入流。

常用方法:

  • read()从此输入流中读取一个数据字节(这个没有参数的方法默认一次只读一字节)
  • read(byte[] b)从此输入流中将最多 b.length个字节的数据读入一个 byte 数组中
  • read(byte[] b,int off,int len)从此输入流中将最多 len 个字节的数据读入一个
    byte 数组中。

这些read方法如果到达流末尾,则返回 -1。

还可以看到有的read方法中有一个byte数组,这就是需要我们自定义一个缓冲区

强烈建议读取数据的时候使用自定义缓冲区(不论是使用字节流还是字符流),可以明显提升速度。

输出流:

OutputStream此抽象类是表示输出字节流的所有类的超类。

FileOutputStream文件输出流是用于将数据写入 File 或 FileDescriptor 的输出流。

BufferedOutputStream该类实现缓冲的输出流。

常用方法:

  • write(int b)将指定字节写入此文件输出流。
  • write(byte[] b)b.length 个字节从指定 byte 数组写入此文件输出流中。
  • write(byte[] b,int off,int len)将指定 byte 数组中从偏移量 off 开始的
    len 个字节写入此文件输出流。

同样写的方法也有一个byte数组,需要写的数据是存在byte数组中的

需求:复制文件

不使用自定义缓冲区:

/**
* @param srcFile
* @param destFile
* @throws IOException
* 最原始一次读一字节
*/
public static void copyFile(File srcFile,File destFile){
FileInputStream in=null;
FileOutputStream out=null;
try {
if(!srcFile.exists()){
throw new IllegalArgumentException(srcFile+"不存在");
}
if(!srcFile.isFile()){
throw new IllegalArgumentException(srcFile+"不是文件");
}
in = new FileInputStream(srcFile);
out = new FileOutputStream(destFile);
int b ;
while((b = in.read())!=-1){
out.write(b);
out.flush();
}
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
if(in!=null){
in.close();
} if(out!=null){
out.close();
}
} catch (Exception e) {
e.printStackTrace();
}
} }

使用自定义缓冲区:

/**
* @param srcFile
* @param destFile
* @throws IOException
*/
public static void copyFileByByte(File srcFile,File destFile){
FileInputStream in=null;
FileOutputStream out=null;
try {
if(!srcFile.exists()){
throw new IllegalArgumentException("文件:"+srcFile+"不存在");
}
if(!srcFile.isFile()){
throw new IllegalArgumentException(srcFile+"不是一个文件");
}
in = new FileInputStream(srcFile);
out = new FileOutputStream(destFile);
int c ;
byte[] buff=new byte[6*1024]; //自定义一个缓冲区6K
while((c = in.read(buff,0,buff.length))!=-1){
out.write(buff,0,c);
out.flush();
}
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
if(in!=null){
in.close();
} if(out!=null){
out.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

我们还可以使用Buffer打头的字节流,它内部有一个默认大小的缓存区:

/**
* @param srcFile
* @param destFile
* @throws IOException
*/
public static void copyFileByBuffer(File srcFile,File destFile){
BufferedInputStream bis=null;
BufferedOutputStream bos=null;
try {
if(!srcFile.exists()){
throw new IllegalArgumentException("文件:"+srcFile+"不存在");
}
if(!srcFile.isFile()){
throw new IllegalArgumentException(srcFile+"不是一个文件");
}
bis = new BufferedInputStream(
new FileInputStream(srcFile));
bos = new BufferedOutputStream(
new FileOutputStream(destFile));
int c ;
byte[] bytes=new byte[6*1024]; //改造(添加一个byte数组,使read一次可以读bytes.length大小的数据,明显提升速度)
while((c = bis.read(bytes,0,bytes.length))!=-1){
bos.write(bytes,0,c);
bos.flush();
}
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
if(bis!=null){
bis.close();
}
if(bos!=null){
bos.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

注意:缓冲区对象建立时,必须要先有流对象。明确要提高具体的流对象的效率。从BufferedInputStream 的构造方法可以看到

字符流

输入流:

Reader用于读取字符流的抽象类。子类必须实现的方法只有 read(char[], int, int) 和 close()。

BufferedReader从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。 可以指定缓冲区的大小,或者可使用默认的大小。大多数情况下,默认值就足够大了。

InputStreamReader是字节流通向字符流的桥梁:它使用指定的 charset 读取字节并将其解码为字符。它使用的字符集可以由名称指定或显式给定,或者可以接受平台默认的字符集。

FileReader用来读取字符文件的便捷类。此类的构造方法假定默认字符编码和默认字节缓冲区大小都是适当的。要自己指定这些值,可以先在 FileInputStream 上构造一个 InputStreamReader。

Read类中也有很多的read方法,使用方法和字节流的一致,这里也强烈建议使用自定义缓冲区的方法去读,不要使用无参的read 一个字符一个字符去读

这里给出一个带自定义缓冲区的Reader例子:

import java.io.*;
class FileReaderDemo2 {
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("demo.txt"); //创建读取流对象和指定文件关联。
//因为要使用read(char[])方法,将读取到字符存入数组。所以要创建一个字符数组,一般数组的长度都是1024的整数倍。
char[] buf = new char[1024];
int len = 0;
while(( len=fr.read(buf)) != -1) {
System.out.println(new String(buf,0,len));
}
fr.close();
}
}

输出流:

Writer写入字符流的抽象类。子类必须实现的方法仅有 write(char[], int, int)、flush() 和 close()。

BufferedWriter将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。

OutputStreamWriter是字符流通向字节流的桥梁:可使用指定的 charset 将要写入流中的字符编码成字节。它使用的字符集可以由名称指定或显式给定,否则将接受平台默认的字符集。

FileWriter用来写入字符文件的便捷类。此类的构造方法假定默认字符编码和默认字节缓冲区大小都是可接受的。要自己指定这些值,可以先在 FileOutputStream 上构造一个 OutputStreamWriter。

PrintWriter向文本输出流打印对象的格式化表示形式。此类实现在 PrintStream 中的所有 print 方法。它不包含用于写入原始字节的方法,对于这些字节,程序应该使用未编码的字节流进行写入

字符流经常将BufferedReader和PrintWriter配合使用。而且BufferedReader有一个readLine读一行的操作很方便:

public class BrAndBwOrPwDemo {
public static void main(String[] args) throws IOException{
BufferedReader br = new BufferedReader(
new InputStreamReader(
new FileInputStream("e:\\test.txt"))); PrintWriter pw = new PrintWriter("e:\\test.txt");
String line ;
while((line = br.readLine())!=null){
System.out.println(line);//一次读一行,并不能识别换行符
pw.println(line); //可以实现换行
pw.flush();
}
br.close();
pw.close();
} }

Write类也有很多write方法,使用方法类似,这里给出一个FileWriter使用追加的方式写一个日志文件:

// 日志
public void logg(String msg) { FileWriter writer=null;
try { //如果目录不存在则创建
if(!new File("log").exists()){
new File("log").mkdirs();
} File file = new File("log/logging.txt");
//日志格式
// \r\n在输出的文件中就是换行
String mString = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + "\t" + msg+"\r\n";
if (file.exists()) {
writer=new FileWriter(file,true); //第二个参数为true就是追加的方式写内容
writer.write(mString);
writer.flush();
}else { file.createNewFile();
writer=new FileWriter(file);
writer.write(mString);
writer.flush();
}
} catch (Exception e) {
e.printStackTrace();
} finally { try {
writer.close();
} catch (Exception e) {
e.printStackTrace();
}
} }

对象序列化

所谓的对象序列化就是将Object对象转化为byte数组,为什么要转换为byte数组?,因为例如在网络上传输使用的字节,所以需要序列化。反之就是反序列化

序列化使用 ObjectOutputStream类的writeObject方法  ,反序列使用的是ObjectInputStream类的readObject方法

注意:需要序列化的类一定要实现Serializable接口,这是一个强制规定,如果需要序列化的类不实现这个接口,会报下面这个异常:

需求:将一个Student 的POJO类序列化

Student:

import java.io.Serializable;

public class Student implements Serializable{          //需要序列化的类一定要实现Serializable接口
private String stuno;
private String stuname; public Student(String stuno, String stuname, int stuage) {
super();
this.stuno = stuno;
this.stuname = stuname;
} public String getStuno() {
return stuno;
}
public void setStuno(String stuno) {
this.stuno = stuno;
}
public String getStuname() {
return stuname;
}
public void setStuname(String stuname) {
this.stuname = stuname;
} @Override
public String toString() {
return "Student [stuno=" + stuno + ", stuname=" + stuname + "]";
} }

先序列化成byte数组存在一个文件中,再从文件中反序列化成一个对象输出:

import java.io.FileInputStream;
import java.io.ObjectInputStream; public class ObjectSeriaDemo1 {
public static void main(String[] args) throws Exception{
String file = "demo/obj.dat";
//1.对象的序列化
/*ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream(file));
Student stu = new Student("10001", "张三", 20);
oos.writeObject(stu);
oos.flush();
oos.close();*/ //反序列化
ObjectInputStream ois = new ObjectInputStream(
new FileInputStream(file));
Student stu = (Student)ois.readObject();
System.out.println(stu);
ois.close(); }
}

transient关键字

使用transient关键字声明属性不进行序列化,或者是按照需求自己去完成这个属性的序列化。这个关键字之前在进行POJO类转JSON数据的时候使用过,使用这个关键字修饰的属性不会生成到JSON中,说了一点题外话,我们可以在student类中添加一个属性,把这个属性声明为transient,不进行序列化

Student:

import java.io.Serializable;

public class Student implements Serializable{
private String stuno;
private String stuname;
//该元素不会进行jvm默认的序列化,可以按需求自己完成这个元素的序列化
private transient int stuage; public Student(String stuno, String stuname, int stuage) {
super();
this.stuno = stuno;
this.stuname = stuname;
this.stuage = stuage;
} public String getStuno() {
return stuno;
}
public void setStuno(String stuno) {
this.stuno = stuno;
}
public String getStuname() {
return stuname;
}
public void setStuname(String stuname) {
this.stuname = stuname;
}
public int getStuage() {
return stuage;
}
public void setStuage(int stuage) {
this.stuage = stuage;
}
@Override
public String toString() {
return "Student [stuno=" + stuno + ", stuname=" + stuname + ", stuage="
+ stuage + "]";
} }

Stuage属性就不会序列化

自己完成属性的序列化和反序列化

我们可以查看ArrayList的源码,ArrayList的writeObject方法就是自己去完成序列化,readObject实现了反序列化,为什么ArrayList需要自行去做这些事情?是因为ArrayList底层使用数组来完成工作,但是数组中空的元素不需要序列化,所以它自己去做序列化可以提升性能,这也是自行序列化的一个优点。

我们可以将这两个方法的方法签名拿到Student类中,来自行序列化和反序列化:

     private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
s.defaultWriteObject();//把jvm能默认序列化的元素进行序列化操作
s.writeInt(stuage);//自己完成stuage的序列化
}
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException{
s.defaultReadObject();//把jvm能默认反序列化的元素进行反序列化操作
this.stuage = s.readInt();//自己完成stuage的反序列化操作
}

序列化中子父类构造函数的调用问题

只要父类实现了Serializable接口,那么子类就可以序列化了,序列化中构造函数的调用是先调用父类构造,然后是子类,和我们不进行序列化操作的父子类调用构造的方法一致

但是反序列化有些不同,如果父类实现Serializable接口,子类在进行反序列化的时候就只调用自己的构造函数,不调用父类的构造;如果父类没有实现序列化接口,只是子类实现了,在反序列的时候就会先调用父类的,再调用子类的构造。

这也是一个特点,相当于父类实现了序列化接口,子类序列化的时候就可以直接读出来,不用再调用父类构造,而父类没有实现序列化接口,就要先调用父类构造

最后来看一个错误

我们使用InputStream来读一个文件:

        //使用InputStream
InputStream inputStream=new FileInputStream("D:\\text.txt");
byte[] b=new byte[1024];
int acc=inputStream.read(b);
while(acc!=-1){
System.out.println(new String(b));
acc=inputStream.read(b);
}

我使用一个名为b的缓冲,大小为1024个字节,一次从text.txt中读取1024个字节到b这个缓冲中并输出成String。

这样的写法存在一些问题,如果剩余的数据小于1024个字节,我们还将其以1024个字节输出,就会出现一些无用的空数据,这不是我们想要的,所以在转换为String的时候要使用:

System.out.println(new String(b,0,acc));

这样有多少个字节我们就转换多少个字节,就不会出现空的无用数据了。writer数据时也存在该问题,需要注意。

Java复习——I/O与序列化的更多相关文章

  1. java 复习总结

    java 复习总结 命名方法 创建文件的名称应该和类的名称一致,不然会报错. 类采用首字母大写的方式来命名,如果是多个单词的类名,则每个单词首字母都大写,例如:HelloWorld . 方法采用驼峰命 ...

  2. java中可定制的序列化过程 writeObject与readObject

    来源于:[http://bluepopopo.iteye.com/blog/486548] 什么是writeObject 和readObject?可定制的序列化过程 这篇文章很直接,简单易懂.尝试着翻 ...

  3. spring mvc返回json字符串数据,只需要返回一个java bean对象就行,只要这个java bean 对象实现了序列化serializeable

    1.spring mvc返回json数据,只需要返回一个java bean对象就行,只要这个java bean 对象实现了序列化serializeable 2. @RequestMapping(val ...

  4. java 复习003 之排序篇

    由java 复习003跳转过来的C语言实现版见some-sort-algorithms 快速排序(不稳定 O(n log n)) package vell.bibi.sort_algorithms; ...

  5. java 复习001

    java 复习001 比较随意的记录下我的java复习笔记 ArrayList 内存扩展方法 分配一片更大的内存空间,复制原有的数据到新的内存中,让引用指向新的内存地址 ArrayList在内存不够时 ...

  6. java复习(1)---java与C++区别

    [系列说明]java复习系列适宜有过java学习或C++基础或了解java初步知识的人阅读,目的是为了帮助学习过java但是好久没用已经遗忘了的童鞋快速捡起来.或者教给想快速学习java的童鞋如何应用 ...

  7. Java复习11. 单例编程

    Java复习11. 单例编程 1.最简单的写法,那个方式是线程不安全的 public class Singleton {     private static Singleton instance; ...

  8. Java复习9网路编程

    Java 复习9网路编程 20131008 前言: Java语言在网络通信上面的开发要远远领先于其他编程语言,这是Java开发中最重要的应用,可以基于协议的编程,如Socket,URLConnecti ...

  9. Java复习8.多线程

    Java复习8 多线程知识 20131007 前言: 在Java中本身就是支持多线程程序的,而不是像C++那样,对于多线程的程序,需要调用操作系统的API 接口去实现多线程的程序,而Java是支持多线 ...

随机推荐

  1. 疑问:@Autowired的作用?[待解答]

    有下面一个Spring的工程,工程结构如下: 代码如下: applicationContext.xml: <?xml version="1.0" encoding=" ...

  2. java入门学习(6)—封装,继承,多态,this,super,初始代码块

    1.[封装]:将对象的状态信息隐藏,不允许直接访问,而是通过该类提供的的方法阿里实现内部信息的访问和操作. 使用到的修饰符:private,不用修饰符(default),protected,publi ...

  3. Node 抓取非utf-8编码页面

    代码示例 Nodejs抓取非utf8字符编码的页面 -- Ruby's Louvre var http = require('http'); var iconv = require('iconv-li ...

  4. C# ListBox 子项数据更新

    今天在倒腾ListBox控件的数据编辑时,遇到了一个小小的问题,现在就把解决方法记录下来,如果各位道友有更好的方法,一定要留言赐教. 问题还原: 有一个界面,有这么一个ListBox用来显示所有的角色 ...

  5. BZOJ2763 JLOI2011 飞行路线 【最短路+DP】

    BZOJ2763 JLOI2011 飞行路线 Description Alice和Bob现在要乘飞机旅行,他们选择了一家相对便宜的航空公司.该航空公司一共在n个城市设有业务,设这些城市分别标记为0到n ...

  6. BestCoder Round #1 第二题 项目管理

    // 第二题 我记得很久很久很久以前看过这样的题目,忘记是哪的区域赛了 // 记得有人说和节点度数有关,我记不清了,反正当时完全不懂 // 然后我想了想,估计就是更新节点度数有关,YY出来可能只要更新 ...

  7. 使用stsadm.exe工具实现SharePoint网站备份还原

    一.过程描述: 首先在源站点机器上用stsadm.exe备份网站集,讲备份文件拷贝到目标服务器(也可直接在备份时配置备份路径为目标机器路径),然后执行还原操作:首先新建网站集,然后用SharePoin ...

  8. python笔记-10(socket提升、paramiko、线程、进程、协程、同步IO、异步IO)

    一.socket提升 1.熟悉socket.socket()中的省略部分 socket.socket(AF.INET,socket.SOCK_STREAM) 2.send与recv发送大文件时对于黏包 ...

  9. iOS 模态框覆盖导航栏

    1.使用window 覆盖 2.试图添加到 如果有一个场景:首页三个tab,要求只覆盖Navigation ,而不影响Tab使用,那么使用window 覆盖就不满足了.      这里我们可以使用如下 ...

  10. lapis docker 运行说明

    1. lapis docker 镜像制作 因为openresty 新版本一个json 库的问题,我们使用的是 openresty:1.11.2.1 基础镜像 FROM openresty/openre ...