JavaSE | IO流

java.io.File类(文件和目录路径名的抽象表示形式)

如果希望在程序中操作文件和目录都可以通过File类来完成,File类能新建、删除、重命名文件和目录。

File类是文件或目录的路径,而不是文件本身,因此File类不能直接访问文件内容本身,如果需要访问文件内容本身,则需要使用输入/输出流。

File类代表磁盘或网络中某个文件或目录的路径名称,

但他不能直接通过File对象读取和写入数据,如果要操作数据,需要IO流。就好比“地址”不代表“水库”,要“存取”里面的水到你“家里”,需要“管道”。

* java.io包
* 1、java.io.File类:
* 例如:d:/1.txt 文件的路径名
* D:/新建文件夹 目录的路径名
* /2.txt 文件的路径名
* ../../3.txt 目录的路径名
*
* 在Java中一个File对象可能表示一个文件,也可能表示一个目录(文件夹)
* 所以在IO流操作时,要注意,我们的读写数据必须对应的是代表文件的File,
* 如果是代表目录的File对象,是否无法直接读写数据的。
*
* 接触:
* java.lang:String,Object,System,Math,Comparable,Iterable...
* java.util:集合,Arrays,Comparator,Scanner,日期
* java.text:SimpleDateFormat,Collator
* java.math:BigInteger,BigDecimal
* ....
*
*
* 2、File类的常用的方法
* (1)构造器:new File(路径)
* file对象不一定代表的是已存在的文件或目录
* (2)路径:
* getPath():构造器中指定的路径
* getAbsolutePath():绝对路径
* getCanonicalPath():规范路径,会对..等进行解析
* (3)文件名
* getName()
* getLength():字节 只能获取文件的大小,不能获取目录的大小 如果不存在返回0
* lastModified():毫秒 如果不存在返回0
* (4)其他的属性:如果不存在,返回false
* exists()
* isFile()
* isDirectory()
* isHidden()
* canRead()
* ...
*
* 这些属性,如果文件或目录存在,在创建File对象是,它会自动从操作系统层面获取数据为属性赋值,否则都是默认值
* 但是path是根据你创建File对象时指定路径进行赋值的
*
* (5)文件和目录的创建、删除等操作
* 创建目录:
* mkdir()
* mkdirs()
* 删除目录:
* delete()只能删除空目录 如果要删除非空目录,需要先删除里面的东西,在删除它
*
* 创建文件:
* createNewFile():如果对应的路径不存在,会报异常
* 删除文件:
* delete()
*
* 重命名:
* renameTo(File 新)
*
* (6)获取父目录和获取目录的下一级
* String getParent()
* File getParentFile()
* String[] list()
* File[] listFiles()
*
* (7)如何统计目录的大小
*
* (8)如何删除一个非空目录

路径名

File类可以使用文件路径字符串来创建File实例,该文件路径字符串既可以是绝对路径,也可以是相对路径,默认情况下,系统总是依据用户的工作路径来解释相对路径,这个路径由系统属性“user.dir”指定,通常也就是运行Java虚拟机时所作的路径。

  @Test
public void test1() throws IOException{
  //路径名的表示方式;绝对路径、相对路径
File fil = new File("F:\\code\\day01_全天资料\\day01_exam");
File file = new File("/day01_exam/day01_words.txt"); //因为我的工作空间在D盘;相对路径,相对项目
File file2 = new File("F:"+ File.separator+ "code" + File.separator + "day01_全天资料" + File.separator + "day01_exam");//直接使用File.separator常量值表示分隔符

     System.out.println("user.dir =" + System.getProperty("user.dir"));
System.out.println("文件名:" + file.getName()); //如果路径是目录or文件名,就打印目录文件夹or文件名字;
System.out.println("文件路径:" + file.getPath()); //构造器中指定的路径
System.out.println("文件绝对路径:" + file.getAbsolutePath());//绝对路径; window的路径分隔符使用“\”,而Java程序中的“\”表示转义字符,所以在Windows中表示路径,需要用“\\”。或者直接使用“/”也可以
System.out.println("文件规范路径:" + file.getCanonicalFile());//规范路径,会对..等进行解析
     System.out.println("File对象的父目录名:" + file.getParentFile()); //父目录,上一级的;
     System.out.println("File对象的父目录名所对应的对象:" + file.getParentFile());

    }
@Test
public void test2(){
File file = new File("F:\\code\\day01_全天资料\\day01_video\\day01_01Java历史概述.avi"); System.out.println("文件的大小:" + file.length()); //对应的文件的内容的长度(字节数),如果File对象对应的是目录,则结果是不确定的。
System.out.println("文件的最后修改时间(毫秒值):" + file.lastModified());
long time = file.lastModified();
Date date = new Date(time);
System.out.println(date);//Mon Nov 12 09:57:56 GMT+08:00 2018
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//System.out.println(dateFormat.format(date)); //2018-11-12 09:57:56 } @Test
public void test3(){
File file = new File("");
file.exists(); //文件或目录是否存在
file.isFile();//是否是文件
file.isDirectory(); //是否是文件夹
file.isHidden(); //是否隐藏
file.canRead(); //是否只读;
     file.isAbsolute();//判断File对象对应的文件或目录是否是绝对路径 File file1 = new File("D:/HAH");
//file1.mkdirs();////创建hehe,因为xixi和haha不存在,一并创建了
file1.mkdir();//创建hehe,因为xixi和haha不存在,所以失败,没有报异常;只能创建单层文件夹
file1.delete();//只是删除最里层的文件或者文件夹,一层层的删(也要确保file1的path地址是对应的); }
//创建目录| 创建文件| 删除文件| 重命名文件
@Test
public void test11() throws IOException{
File dir = new File("D:/Jiing/heihei/day03.txt");      dir.mkdir();//创建目录(只能创建一个),不存在不会报错,只是没创建成功,;
dir.mkdirs();//只能创建目录文件;如果带.txt会给你创建一个文件夹;如果前面目录都没有就都给你一块创建了;
dir.createNewFile(); //在目录存在的情况下才能创建文件.txt等,不能创建目录; 如果指定的文件不存在并成功地创建,则返回 true;如果指定的文件已经存在,则返回 false。
//dir.renameTo(new File ("D:/Jiing/heihei/day03.txt")); //重命名
//dir.delete(); //如果文件夹里边有文件,则delete这个文件夹是删除不了的,要先删除里边的文件才能删除这个文件夹;只能一个个的删
System.out.println(dir.listFiles());//[Ljava.io.File;@5cb0d902
}
public static void test1(File file){

        //String[] list = file.list(); //列出当前目录的文件的名称
File[] listFiles = file.listFiles();//列出当前目录对应的File对象,文件路径
for (File string : listFiles) {
System.out.println(string);
}
}   @Test
public void test5(){ //筛选出所有的.java文件
File file = new File("D:\\code\\day01"); File[] listFiles = file.listFiles(new FileFilter() { //File[] listFiles(FileFilter filter)
@Override            //它有一个抽象方法boolean accept(File pathname),它属于SAM接口
public boolean accept(File pathname) { return pathname.getName().endsWith(".java");
}
});
for (File file2 : listFiles) {
System.out.println(file2);
}
} //遍历递归出目录下的所有文件路径
public static void listAllFile(File file){ if(file.isDirectory()){
File[] listFiles = file.listFiles();
for (File file2 : listFiles) {
listAllFile(file2);
}
System.out.println(file);
}
}
--->>>
D:\Jing\1
D:\Jing\2\22
D:\Jing\2
D:\Jing\新建文件夹
D:\Jing public void listAllFiles(File dir){
if(dir.isDirectory()){ //如果是文件夹
System.out.println(dir); //输出目录(文件夹)
File[] list = dir.listFiles();
//System.out.println(list);
for (File file : list) { //每一个file都有可能有下一级
listAllFiles(file); //自己调用自己叫递归
}
}else{
System.out.println(dir);//如果是文件直接打印 //把 file 的所有的下一级(包括下一级的下一级)的文件的大小全部加起来
    public long getSize(File file){
if(file.isFile()){ //如果是文件,直接返回它的大小
return file.length();
}else if(file.isDirectory()){
File[] list = file.listFiles(); //(1)列出dir的下一级
long sum = 0; //2)累加每一个下一级的大小
for (File file2 : list) {
sum += getSize(file2); //file2可能是文件也可能是目录
}
retur 0L; //递归删除目录文件
public static void deleteDir(File file){ if(file.isDirectory()){
File[] listFiles = file.listFiles();
for (File sub : listFiles) {
deleteDir(sub);
}
file.delete(); //递归删除空文件夹
}
//file.delete(); //删除所有的文件夹
}
//跟上边是一样的
public static void deleteAllSubFile(File file){
if(file.isDirectory()){
File[] listFiles = file.listFiles();
for (File file2 : listFiles) {
if(file2.length() == 0){
file2.delete();
}
deleteAllSubFile(file2);
}
} }
/*     递归:
方法自己调用自己
注意:一定要有“出口”,否则就死循环,就栈溢出
递归的效率比较低,能用循环解决的就不要用递归*/ @Test
public void test1(){
System.out.println(diGui(10));
System.out.println(printSum(10));
}
public long diGui(int n){ if(n == 0){
return 1;
}
return n * diGui(n - 1);
}
public long printSum(int n){
long sum = 1;
for (int i = 1; i <= n; i++) {
sum *= i;
}
return sum;
}

IO的四个超级父类,抽象基类。

IO的四个超级父类,抽象基类。

  字节输入流:InputStream

   字节输出流:OutputStream

  字符输入流:Reader

  字符输出流:Writer

IO流类的设计选用了“装饰者”设计模式,即IO流分为两大类,“被装饰”的组件和“装饰”的组件。

  例如:以InputStream为例:其中FileInputStream、ByteArrayInputStream等是“被装饰”的组件,依次用来连接和读取“文件”、“内存中的字节数组”的等。

BufferedInputStream、DataInputStream、ObjectInputStream等是用来“装饰”的组件,依次是给其他InputStream的IO流提供装饰的辅助功能的,依次可以增加“提高效率的缓冲功能”、“按照Java数据类型读取数据的能力”、“读取并恢复Java对象的能力”等。你会发现OutputStream、Reader、Writer系列的流设计方式也是一样的。

  Java的IO流是单向的,只能从输入流(Input、Reader)中读取(read)数据,也只能往输出流(Output、Writer)中写(write、print)出数据。

IO流的选取可以通过以下几个分类来简化选取过程。

按照IO流的方向:输入流和输出流

   I:代表Input

    O:代表Output

Java的IO流是单向的,只能从输入流(Input、Reader)中读取(read)数据,也只能往输出流(Output、Writer)中写(write、print)出数据。

按照IO流的处理数据的基本单位分:

    字节流(XxxStream):直接处理二进制,一个字节一个字节处理,它适用于一切数据,包括纯文本、doc、xls、图片、音频、视频等等

    字符流(XxxReader和XxxWriter):一个字符一个字符处理,只能纯文本类的数据。

按照角色分:节点流、处理流:

  节点流:连接源头、目的地,即装饰者IO流;

   处理流:增强功能,提高性能,即装饰者IO流。

节点流处于IO操作的第一线,所有操作必须通过他们进行;处理流是通过包装节点流来完成功能的,处理流可以增加很多层。处理流必须依赖和包装节点流,而不能单独存在。

装饰模式(Decorator Pattern)也称为包装模式(Wrapper Pattern),其使用一种对客户端透明的方式来动态地扩展对象的功能,它是通过继承扩展功能的替代方案之一。
在现实生活中你也有很多装饰者的例子,例如:人需要各种各样的衣着,不管你穿着怎样,但是,对于你个人本质来说是不变的,充其量只是在外面加上了一些装饰,有,“遮羞的”、“保暖的”、“好看的”、“防雨的”....

读文件(字节) FileInputStream ,read

读文件的步骤:
  (1)先创建输入流
  (2)读取数据
    (3)关闭IO流

InputStream:所有read的方法,如果流中没数据,返回-1
   (1)int read():一次读一个字节,返回的是读到的字节的值
   (2)int read( byte[ ] data ):一次读多个字节,把读到的数就放大data[0]....,返回的本次实际读取的字节数,最多读data.length
   (3)int read(byte[ ] data, int offset, int len):一次读多个字节,把读到的数就放大data[offset]....,返回的本次实际读取的字节数,最多读len个

FileInputStream 字节流 
    public static void main(String[] args) throws IOException {
FileInputStream fil = new FileInputStream("sourse/1.txt");
byte[] data = new byte[10];
int len;
StringBuilder s = new StringBuilder(); //可变字符串
while((len = fil.read(data)) != -1){
s.append(new String(data, 0, len)); //先读入字符串中 ; s.append( data,0 ,len byte()),没有针对字节的字符串append方法
}
System.out.println(s);
fil.close();
}

读文件(字符) FileReader

读文件的步骤:
   (1)先创建输入流
   (2)读取数据
   (3)关闭IO流
  
 Reader:所有read的方法,如果流中没数据,返回-1
   (1)int read():一次读一个字符,返回的是读到的字符的编码值
   (2)int read(char[ ] data):一次读多个字符,把读到的数就放大data[0]....,返回的本次实际读取的字符数,最多读data.length
   (3)int read(char[ ] data, int offset, int len):一次读多个字符节,把读到的数就放大data[offset]....,返回的本次实际读取的字符数,最多读len个

public static void main(String[] args) throws IOException {
FileReader fil = new FileReader("sourse/1.txt"); char[] data = new char[10];
int len;
StringBuilder s = new StringBuilder(); //创建一个可变字符串存数据
while((len = fil.read(data)) != -1){ //fil.read( )--->>>返回的是int长度
s.append(new String(data, 0, len)); //append( ) //形参是char类型,所以byte字节不能用这个方法
       s.append(data, 0, len);
}
System.out.println(s);
fil.close();
}

如果1.txt文件比较大,那么用下面的代码读取时,因为把所有的内容拼接为一个
//StringBuilder对象,它是一个可变字符序列的缓冲区,会一致扩容,以装下所有内容,
//文件太大的话,内存可能溢出

写文件(按字节) FileOutputStream

写文件的步骤:
(1)创建输出流
 (2)写数据
 (3)关闭IO流
 OutputStream:
* (1)write(int b):写一个字节
* (2)write(byte[] data):把整个字节写出去
* (3)write(byte[] data, int offset, int len):把data中的从[offset]开始,写len个

public static void main(String[] args) throws IOException {

        Scanner input = new Scanner(System.in);
System.out.print("请输入:");
String s = input.next();
FileOutputStream file = new FileOutputStream("sourse/2.txt");//如果需要覆盖模式,不用写true,如果追加模式,写,true
file.write(s.getBytes());
file.close();
input.close();
}

写文件按照(字符) FileWriter

* 写文件的步骤:
* (1)创建输出流
* (2)写数据
* (3)关闭IO流
*
* Writer:
* (1)write(int b):写一个字符
* (2)write(char[] data):把整个字符数组写出去
* (3)write(char[] data, int offset, int len):把data中的从[offset]开始,写len个
* (4)write(String str):把整个字符串写出去
* (5)write(String str,int offset ,int len):写字符串的一部分

public static void main(String[] args) throws IOException {
Scanner input = new Scanner(System.in);
System.out.print("请输入:");
String s = input.next();
FileWriter fw = new FileWriter("sourse/2.txt");
fw.write(s); //输入字符
fw.close(); //必须把流关了,不然写不进去喔!!
input.close(); }

把一个文件复制一份(不一定是字符串文件,所以按字节(可能是图片、视频等))

public static void copy(String srcFilePath, String destFilePath) throws IOException{
FileInputStream file = new FileInputStream(srcFilePath); //创建输入流
FileOutputStream f = new FileOutputStream(destFilePath); //输出流
byte[] data = new byte[10]; //读取字节数据
int len;
while((len = file.read(data)) != -1){
f.write(data, 0, len);
}
}

缓冲流(为了提高读写效率)

BufferedReader按行读取

使用缓冲流的目的是为了提高读写效率

*  BufferedInputStream:包装InputStream字节输入流
* BufferedReader:包装Reader字符输入流,按行读取
* BufferedOutputStream:包装OutputStream字节输出流
* BufferedWriter:包装Writer字符输出流

复制

public static void copyBuffer(String srcFilePath, String destFilePath) throws IOException{
FileInputStream fis = new FileInputStream(srcFilePath); //(1)创建IO流
BufferedInputStream bis = new BufferedInputStream(fis); FileOutputStream fos = new FileOutputStream(destFilePath);
BufferedOutputStream bos = new BufferedOutputStream(fos);
//数据读从srcFilePath-->fis --> bis --> data数组;
//数据写从data数组 --> bos --> fos --> destFilePath;
//(2)一边读一边写
byte[] data = new byte[10];
int len;
while((len = bis.read(data)) != -1){ //循环一次,从fis中把数据到data数组,从[0]装,一次len个
bos.write(data, 0, len);//再从data数组[0]取len写到fos中
}
bis.close();
fis.close();
bos.close();
fos.close(); }
* BufferedReader:
(1)String readLine() JDK1.7之后,有一个try...with...resouces
语法格式:
* try(
声明需要关闭的资源
* ){
* 可能发生异常的代码
* }catch(异常类型 e){
* }
*
* 无论是否发生异常,都会自动关闭
    public static void test1(String srcPath) throws IOException{
try(
FileReader fr = new FileReader(srcPath);
BufferedReader br = new BufferedReader(fr); //字节-->字符;
){
while(true){
String line = br.readLine();
if(line == null){
break;
}
System.out.println(line);//边读边打印
}
}catch (FileNotFoundException e) {
e.printStackTrace();
}catch (IOException e){
e.printStackTrace();
}
/*
br.close();
fr.close();*/
}

InputStreamReader(字节-->>字符) |  OutputStreamReader(字符--->>字节)

 InputStreamReader:把字节流转为字符流,并且可以指定“编码方式”
FileReader是按照平台默认的字符编码解码 OutputStreamWriter:把字符流按照指定的编码转为字节流
//字符编码方式GBK的文件,在UTF-8的的控制台上,读取内容显示
@Test
public void test1() throws IOException{
FileInputStream fs = new FileInputStream("sourse/22.txt");//字节输入流
InputStreamReader isr = new InputStreamReader(fs, "UTF-8");//字节--字符;按字节流读取,然后再指定字符编码方式,转为字符流 BufferedReader br = new BufferedReader(isr);
//数据:resources/gbk.txt-->字节的方式-->fs-->按照指定的编码-->isr-->字符流-->缓冲流br
String line;
while((line = br.readLine()) != null){ //readLine()是按行读取;
System.out.println(line);
}
br.close();
isr.close();
fs.close(); }
//尝试用字符输入流读取
@Test
public void test2() throws IOException{
FileInputStream fis = new FileInputStream("sourse/22.txt");
byte[] data = new byte[10]; int len;
while((len = fis.read(data)) != -1){
//String的构造器,也可以指定字符编码方式进行解码
//String(byte[] bytes, int offset, int length, String charsetName)
System.out.println(new String(data, 0, len, "GBK"));
} //也可以转成字节 new String(data, 0, len).getBytes();//转成字节
fis.close();
}
  @Test
public void test1() throws IOException{ String str = "字符串";
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("sourse/1.txt"), "UTF-8");
osw.write(str); osw.close();
} @Test
public void test2() throws UnsupportedEncodingException, IOException{
String str = "字符串";
FileOutputStream fos = new FileOutputStream("sourse/1.txt");
fos.write(str.getBytes("UTF-8"));
fos.close();
}

操作Java各种数据类型的数据  DataOutputStream & DataInputStream

数据输出流允许应用程序以适当方式将基本 Java 数据类型写入输出流中。然后,应用程序可以使用数据输入流将数据读入。
  基本 Java 数据类型:包括Java基本数据类型和String

数据输入流允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型。
 应用程序可以使用数据输出流写入稍后由数据输入流读取的数据。

    @Test
public void testIn() throws IOException{
//因为DataInputStream无法与文件直接相连;//需要文件字节输入流
DataInputStream dis = new DataInputStream(new FileInputStream("sourse/7.txt"));
String readUTF = dis.readUTF();
int readInt = dis.readInt();
double readDouble = dis.readDouble(); char readChar = dis.readChar(); System.out.println(readUTF);
System.out.println(readInt);
System.out.println(readDouble);
System.out.println(readChar);
dis.close(); }
@Test
public void test1() throws IOException{
String name = "白骨精";
int age = 500;
double engry = 789.5;
char gender = '女'; //DataOutputStream无法直接与文件相连,需要一个OutputStream
DataOutputStream dos = new DataOutputStream(new FileOutputStream("sourse/7.txt"));
dos.writeUTF(name); //开始写数据
dos.writeInt(age);
dos.writeDouble(engry);
dos.writeChar(gender); dos.close();
}
/*要存储如下一组数据,到game.dat文件中,并在后面可以重写读取。
完成这个需求,可以使用DataOutputStream进行写,随后用DataInputStream进行读取,而且顺序要一致。*/
public static void saveData() throws IOException{
String name = "巫师";
int age = 300;
char gender = '男';
int energy = 5000;
double price = 75.5;
boolean relive = true; DataOutputStream dos = new DataOutputStream(new FileOutputStream("resource/game.dat")); dos.writeUTF(name);
dos.writeInt(age);
dos.writeChar(gender);
dos.writeDouble(price);
dos.writeBoolean(relive);
}
public static void reload() throws IOException{ DataInputStream dis = new DataInputStream(new FileInputStream("resource/game.dat")); String name = dis.readUTF();
int age = dis.readInt();
char gender = dis.readChar();
double price = dis.readDouble();
boolean relive = dis.readBoolean(); System.out.println("姓名:" + name + ",年龄:" + age + "性别:" + gender + "价格:" + price + ",重新生活" + relive); }
      DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
   dos.writeUTF(pathName); //三.按照字节读取文件内容
FileInputStream fis = new FileInputStream(file); byte[] data = new byte[1024];
int len;
while((len = fis.read(data)) != -1){ //读取文件内容并写入
dos.write(data, 0, len); //可直接读取 }

保存对象

如果你正在编写游戏,就得有存储和恢复游戏的功能。如果程序要存储状态,你可以选择上面的方式(DataOutputStream),对每一个对象逐个地把每一项变量的值写到特定格式的文件中。但是其实,Java还提供了面向对象的处理方式——直接存储对象。不过,这种方式保存的数据,必须是由Java程序来读取。如果在程序所储存的文件数据需要给某些非Java应用程序所读取时,就不能选用这种方式了。

ObjectOutputStream:序列化(把对象转成字节序列输出)

序列化与反序列化

想要输出对象,必须借助ObjectOutputStream,它有一个writeObject(obj)方法可以输出对象。

如果想要反序列化,必须借助ObjectInputStream,它有一个readObject()方法可以读取对象。

注意:

不是所有对象都可以序列化,必须实现java.io.Serializable接口的类或其子类的对象,而且如果该对象的属性也是引用数据类型,并且该属性也要序列化,那么该属性的类型或其父类也要实现java.io.Serializable接口,否则会报java.io.NotSerializableException异常。

序列化的文件是很难让人阅读的,但它比纯文本文件、或一项一项数据保存的数据更容易让程序恢复对象的状态,也比较安全,因为一般人不会知道如何动手修改数据。

* 将 Java 对象的基本数据类型和图形写入 OutputStream。可以使用 ObjectInputStream 读取(重构)对象。
* 通过在流中使用文件可以实现对象的持久存储。如果流是网络套接字流,则可以在另一台主机上或另一个进程中重构对象。
*
* 凡是用ObjectOutputStream输出的对象,它的类必须实现java.io.Serializable接口(序列化接口):
* 只能将支持 java.io.Serializable 接口的对象写入流中。每个 serializable 对象的类都被编码,编码内容包括类名和类签名、对象的字段值和数组值,
以及从初始对象中引用的其他所有对象的闭包。
*
*
* ObjectInputStream:反序列化(把字节序列重构成一个对象)
* ObjectInputStream 对以前使用 ObjectOutputStream 写入的基本数据和对象进行反序列化。
*
* 写对象:
* void writeObject(obj)
* 读对象:
* Object obj = readObject()
*
 Account.setInterestRate(0.02); //静态属性
Account account = new Account("101", "kris", "1234", 100000, 3.2);
//把对象写入数据
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("resource/account.dat"));
oos.writeObject(account); //把对象从文件中读出来
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("resource/account.dat"));
Object obj = ois.readObject();
System.out.println(obj);
} Account类--->>>
class Account implements Serializable{ private static double interestRate;
private String number;
private String name;
private String password;
private double balance;
transient private double interest; //加上这个当初始化的时候就不会给它初始化赋值了,而是还是默认值; 不是3.2了,而是默认值0.0
public Account(String number, String name, String password, double balance, double interest) {
super();
this.number = number;
this.name = name;
this.password = password;
this.balance = balance;
this.interest = interest;
}
public static double getInterestRate() {
return interestRate;
}
public static void setInterestRate(double interestRate) {
Account.interestRate = interestRate;
} ....

java.io.Serializable:标识型接口,没有抽象方法的接口

  默认所有属性都需要序列化,除了static和transient修改的属性。

    public class Account implements Serializable{
private static double interestRate;
private String number;
private String name;
private String password;
private double balance;
transient private double interest;

所有对类的修改都导致原来的数据在反序列化时失败 java.io.InvalidClassException。

解决这个问题的方法,就是在实现java.io.Serializable接口时,增加一个long类型的静态常量serialVersionUID。如果类没有显示定义这个静态变量,它的值是Java运行时环境根据类的内部细节自动生成的,若类的源代码作了修改,serialVersionUID 就会发生变化,从而导致“旧”数据反序列化失败。

如果对类做了会影响数据兼容性的操作时,要么修改serialVersionUID的值,使得原来的反序列化数据失败,要么你要对“旧”对象反序列化后引起的问题负责。

* Role类实现了Serializable接口后,每次编译,会自动生成一个serialVersionUID
* 如果当数据序列化之后,重新修改了类,就会导致 “stream中的serialVersionUID"与"local的serialVersionUID"不一致。
*
* 如何解决?
* 在序列化之前,不让这个类每次自动生成一个serialVersionUID,而是固定一个serialVersionUID值(序列化版本ID)。

private static final long serialVersionUID = 1L; 这是默认的 * 假设engry属性的值,不需要序列化。我们可以在这个属性之前加一个关键字transient
* 一般像计算的结果值等,不需要序列化,在反序列后重新计算,这样的字段,加transient修改
*
* 如果属性的类型是引用数据类型,那么这个引用数据类型也要支持序列化,即实现java.io.Serializable接口,
* 例如:String,Owner
*
* 静态的变量是不会被序列化的。
* 因为序列化的是对象的状态值,而静态变量是类的值。
* 序列化的是对象独有的内容,而静态是所有对象共享的内容。

java.io.Externalizable:

* Externalizable 实例类的唯一特性是可以被写入序列化流中,该类负责保存和恢复实例内容。
* 实现Externalizable接口,哪些属性需要序列化和如何反序列化,都有你自己决定,在
* writeExternal方法和readExternal方法中决定。
* 请保留无参构造

  @Test
public void testIn() throws FileNotFoundException, IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("sourse/66.txt"));
Object obj = ois.readObject();
System.out.println(obj);
ois.close();
} @Test
public void testOut() throws IOException{ FileOutputStream fos = new FileOutputStream("sourse/66.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos); User user = new User(10, 20, 30); oos.writeObject(user);
oos.close();
fos.close();
} } class User implements Externalizable{ private static int a = 1;
private transient int b = 2;
private int c = 3; public User() {
super();
} public User(int b, int c) {
super();
this.b = b;
this.c = c;
}
public User(int a, int b, int c) {
super();
User.a = a;
this.b = b;
this.c = c;
} @Override
public String toString() {
return "a=" + a + ",b=" + b + ", c=" + c;
} @Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeInt(a);
out.writeInt(b);
out.writeInt(c); }
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
a = in.readInt();
b = in.readInt();
c = in.readInt(); }

按行输出文本内容

* System.out:PrintStream
* 如果没有修改,往控制台打印,可以修改为另个PrintStream对象
* System.err:PrintStream ;  System.in:InputStream

* PrintStream:
   1)没有异常; 2)可以打印任意类型的数据(以字符串格式输出),如果是引用数据类型,那么就把对象转成字符串,相当于自动调用toString() ; 3)自动flush()
    PrintStream 打印的所有字符都使用平台的默认字符编码转换为字节。
* PrintWriter:  web  --->> response.getWriter():这个对象服务器是给客户端发送返回消息用的。

这两个类提供了一系列重载的print()和println()方法,用于多种数据类型的输出。

next( )和nextLine( )的区别,next( )遇到空格等空白符就认为输入结束,nextLine()遇到回车换行

在需要写入字符而不是写入字节的情况下,应该使用 PrintWriter 类,PrintWriter类还可以自定义字符编码。

与 PrintStream 类不同,如果启用了自动刷新,则只有在调用 println、printf 或 format 的其中一个方法时才可能完成此操作,而不是每当正好输出换行符时才完成。这些方法使用平台自有的行分隔符概念,而不是换行符。

  @Test
public void test1() throws FileNotFoundException{
PrintStream ps = new PrintStream("sourse/2.txt");
Scanner input = new Scanner(System.in); while(true){
System.out.print("输入内容:");
String next = input.next(); // input.nextLine();
if("bye".equalsIgnoreCase(next)){
break;
}
ps.print(next);
}
ps.close();
input.close();
}
@Test
public void test2() throws FileNotFoundException{
//Scanner扫描仪,从输入流中扫描数据
Scanner input = new Scanner(new FileInputStream("sourse/2.txt"));
while(input.hasNext()){
String nextLine = input.nextLine();
System.out.println(nextLine);
}
input.close();
}
上一篇:第54节:Java当中的IO流(中)


下一篇:自学Java过程