字符集
各个国家为自己国家的字符取的一套编号规则,计算机底层只能存储二进制,二进制可以转成十进制,十进制可以进行整数编号,所以计算机底层可以存储编号规则
I/O流的分类与概述
(IO输入输出流-Input/Output)
File类只能操作文件对象本身,并不能操作文件的内容(对文件内容进行读/写)。如果需要读写数据的操作,就需要使用I/O流
I/O流的分类
按照流的方向来分
- 输入流:已内存为基准,把内存中的数据写出到磁盘文件或者网络介质中去的流称为输入流。例如:将数据写入文件
- 输出流:以内存为基准,把磁盘文件中的数据或者网络中的数据读入到内存中去的流称为输入流。输入流的作用就是读取数据到内存
按照流的内容来分
- 字节流:流中的数据最小单位是一个一个的字节,这个流就是字节流
- 字符流:流中的数据最小单位是一个一个的字符,这个流就是字符流
FileInputStream-文件字节输入流
以内存为基准,将磁盘文件中的数据按照字节的形式读入到内存中的流,简单来说,就是按照字节读取文件数据到内存
构造器
- public FileInputStream(File path):创建一个字节输入流管道与源文件对象接通
- public FileInputStream(String pathName):创建一个字节输入流管道与文件路径对接
方法
代码语言:javascript复制public int read(); //每次读取一个字节返回,读取完毕会返回-1
实例-读取一个字节
代码语言:javascript复制package FileInputStreamDemo;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class FileInDemo {
public static void main(String[] args) throws IOException {
//创建File对象
File f1=new File("src/FileInputStreamDemo/text.txt");
//创建文件输入流对象
FileInputStream in = new FileInputStream(f1);
//读取一个字节
int code=in.read();
//不断循环输出,直到数据末尾返回-1
while(code!=-1) {
char c=(char)code;
System.out.println(c);
code = in.read();
}
}
}
但这种读取方式并不有效,在中文出现后无法避免出现乱码(因为会截断中文字节),并且这种方式效率较差,不建议采用
实例-读取一个字节数组
代码语言:javascript复制package FileInputStreamDemo;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class FileInDemo {
public static void main(String[] args) throws IOException {
FileInputStream in = new FileInputStream("src/FileInputStreamDemo/text.txt");
//按照字节数组读取
byte[] buffer=new byte[1024];
//返回值是读取的字节数
int len=in.read(buffer);
System.out.println("读取了" len "个字节");
String rs=new String(buffer);
System.out.println(rs);
}
}
这种方式仍然无法避免中文输出乱码的情况
易错点
在定义一个字节数组用于缓存数据后,不断从文件中读取数据到字节数组中,假如下一次读取仍然利用这个字节数组,但读取的字节数小于第一次字节数组被占用长度,则后续部分的字节不被覆盖
例如第一次读取5字节abcde,第二次读取2字节fg。则读取完后字节数组的组成是fgcde,只有前两位被覆盖,后三位并没有改变
处理方法,可以在输出时使用相应方法,限制输出内容长度,只要保证输出内容的长度和本次读取字符长度相同,就能保证旧数据(未被覆盖数据)不被输出
代码语言:javascript复制String rs=new String(buffer,0,len); //限制输出范围
解决字符乱码的方式
- 使用字符流
- 使用一个大小与文件字符大小刚好一致的字节数组(可以先通过文件对象,获取文件大小再获取文件对象的字节输出流并输出)缺陷是文件过大时占用内存严重,可能导致程序崩溃
- 使用readAllbytes()方法定义字符数组大小,例如:
public class FileInDemo {
public static void main(String[] args) throws IOException {
FileInputStream in = new FileInputStream("src/FileInputStreamDemo/text.txt");
byte[] buffer=in.readAllBytes();
String rs=new String(buffer);
System.out.println(rs);
}
}
综上,字节流在处理文件读写的表现上处理并不良好,一般读写文件建议采用字符流,但这并不影响字节流处理文件的赋值等操作,因为复制操作不涉及字符的编码,只需要把全部字节原封不动转移到其他文件即可
FileOutputStream-文件字节输出流
以内存为基准,把内存中的数据,按照字节的形式写出到磁盘中去,简言之,就是把内存数据按照字节写出到磁盘中去
构造器
- public FileOutputStream(File file):创建一个字节输出流管道通向目标文件对象
- public FileOutputStream(String file):创建一个字节输出流管道通向目标文件路径
- public FileOutputStream(File file,boolean append):创建一个追加数据的字节输出流管道通向目标文件对象
- public FileOutputStream(String file,boolean append):创建一个追加数据的字节输出流管道通向目标文件路径
方法
- public void write(int a):写一个字节
- public void write(byte[] buffer):写一个字节数组出去
- public void write(byte[] buffer,int pos,int len):写一个字节数组的一部分出去,第二个参数表示起始字节索引位置,第三个参数表示长度
package FileInputStreamDemo;
import java.io.*;
public class FileOutDemo {
public static void main(String[] args) throws IOException {
//创建字节输出流管道与目标文件对象连通
FileOutputStream out=new FileOutputStream("src/FileInputStreamDemo/text.txt");
//写一个字节出去,这种方式不能写入中文字符,因为一个中文字符3个字节,超出限制
out.write(97); //注意这里写的是写入字符的字符编码,不是要写入的字符
out.write('b');
//写一个字节数组出去
byte[] bytes=new byte[]{'a','b','c',100,101,102};
out.write(bytes);
byte[] bytes1="Less is more!--工程学名言".getBytes("GBK"); //可以指定编码格式,也可以直接默认采用系统当前编码格式
out.write(bytes1);
//写字节数组的一部分出去
out.write(bytes1,0,13);
//换行
out.write("rn".getBytes()); //这里利用rn来换行的原因是为了保证兼容性更好,Windows可以直接用n换行
out.flush(); //立即刷新数据到文件中去,刷新后管道out还是可以正常调用
//以下的关闭操作在每一次进行读写操作后都必须运行
out.close(); //结束运行。结束后管道out不能继续使用,关闭操作包含刷新操作
}
}
IO流管道默认是覆盖管道,每次启动新的Stream管道之前,都会清空文件对象之前的内容,注意,这里是启动新管道之前,不是调用管道执行方法时
如果想要追加数据,而不是覆盖,只需要在创建管道时,设置管道第二个参数为true即可(第二个参数表示是否为追加数据管道)
字节流做文件的复制
字节是计算机中文件存储的最基本单位,所以字节流适合做一切文件的复制。
复制是把源文件的全部字节一个不漏的全部转移到目标文件,只要保证前后的格式一样,绝对不会出现错误
步骤
- 创建一个字节输入流管道与源文件接通
- 创建一个字节输出流管道与目标文件接通
- 创建一个字节数组作为中间传递媒介
- 从字节输入流管道读取数据,写出到字节输出流管道即可
- 关闭打开的管道资源
package FileInputStreamDemo;
import java.io.*;
public class CopyDemo {
public static void main(String[] args) throws IOException {
//创建字节输入流管道与源文件接通
FileInputStream in = new FileInputStream("src/FileInputStreamDemo/text.txt");
//创建字节输出流管道与目标文件连通
FileOutputStream out=new FileOutputStream("src/FileInputStreamDemo/output.txt");
//创建字节数组用来作为中间传播媒介,并从字节输入流中读取数据
byte[] medium=in.readAllBytes();
//向字节输出流中写入数据
out.write(medium);
//关闭所有管道资源
in.close();
out.close();
}
}
FileReader-字符输入流
以内存为基准,把磁盘文件的数据以字符的形式读入到内存
构造器
- public FileReader(File file):创建一个字符输入流与源文件对象接通
- public FileReader(String filePath):创建一个字符输入流与源文件路径接通
方法
- public int read():读取一个字符的编号并返回,读取完毕返回-1
- public int read(char[] buffer):读取一个字符数组,读取多少个字符就返回对应整数
package FileInputStreamDemo;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
public class FileReaderDemo {
public static void main(String[] args) throws IOException {
FileReader fd = new FileReader("src/FileInputStreamDemo/input.txt");
//按照字符逐个读取
int c=fd.read();
while(c!=-1){
System.out.printf("%c",(char)c);
c=fd.read();
}
FileReader fd2 = new FileReader("src/FileInputStreamDemo/input.txt");
//读取字符数组
char[] chars=new char[1024];
int len=fd2.read(chars);
System.out.printf("nn共读取%d个字符!nn",len);
String str=new String(chars);
System.out.println(str);
}
}
可见字符流按照字符数组循环读取数组。可以解决中文读取乱码的问题,并且性能较好
FileWriter-文件字符输出流
以内存为基准,把内存中的数据按照字符形式写出到磁盘文件中去,简单来说,就是把内存的数据以字符形式写出到文件中去
构造器
- public FileWriter(File file):创建一个字符输出流管道通向目标文件对象
- public FileWriter(String file):创建一个字符输出流管道通向目标文件路径
- public FileWriter(File file,boolean append):创建一个追加数据的字符输出流管道通向目标文件对象
- public FileWriter(String file,boolean append):创建一个追加数据的字符输出流管道通向目标文件路径
方法
- public void write(int c):写1个字符出去
- public void write(String c):写一个字符串出去
- public void write(char[] buffer):写一个字符数组出去
- public void write(String c,int pos,boolean append):写字符串的一部分出去
用法基本与文件字节输出流一致
缓冲流
缓冲流可以提高字节流和字符流的读写数据的性能
BufferedInputStream-字节缓冲输入流
用于提高相对应的文件字节输入流读写数据的性能
可以把低级的字节输入流(FileInputStream)包装成一个高级的缓冲字节输入流(BufferedInputStream)管道,从而提高字节输入流读数据的性能
构造器
代码语言:javascript复制public BufferedInputStream(InputStream in);
原理
缓冲字节输入流管道自带一个8KB的缓冲池,每次可以直接借用操作系统的功能最多提取8KB的数据到缓冲池中去,以后我们直接从缓冲池读取数据,所以性能较好
BufferedOutputStream-缓冲字节输出流
用于提高相对应的文件字节输出流读写数据的性能
可以把低级的字节输出流(FileInputStream)包装成一个高级的缓冲字节输出流(BufferedInputStream)管道,从而提高字节输入流读数据的性能
构造器
代码语言:javascript复制public BufferedOutputStream(OutputStream in);
BufferedReader-缓冲字符输入流
与之同理
BufferedWriter-缓冲字符输出流
与之同理
对象的序列化与反序列化
我们在日常操作中经常需要把对象作为一种数据保存在文件中,典型的如涉及登录的cookies等。
这个过程中将对象作为数据保存到文件中的过程称为序列化,将文件中的数据重写读取出来并转换为对象的过程称为反序列化
序列化与反序列化使用到了相较于Reader,Writer更高级的对象输入输出流
代码语言:javascript复制package FileInputStreamDemo;
import java.io.*;
public class SerizalizeDemo {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//对象序列化
//创建准备序列化的对象
User user = new User("Leslie", 18, 95);
//创建文件输出字节流,指向用于储存对象的文件
OutputStream out = new FileOutputStream("src/FileInputStreamDemo/text.txt");
//将文件输出字节流包装成高级的对象字节输出流
ObjectOutputStream out_object=new ObjectOutputStream(out);
//将对象写入文件
out_object.writeObject(user);
//关闭资源
out_object.close();
out.close();
System.out.println("对象序列化成功!");
//对象反序列化
//创建空对象用于接收输入的对象
User user_new=new User();
//创建文件输入字节流指向要读取的文件
FileInputStream in=new FileInputStream("src/FileInputStreamDemo/text.txt");
//将文件输入字节流包装成高级的能够用于对象反序列化的对象字节输入流
ObjectInputStream in_object=new ObjectInputStream(in);
//读入对象并强转为指定类型
user_new= (User) in_object.readObject();
System.out.println("反序列化成功!");
System.out.printf("姓名:%s 年龄:%d 分数:%dn",user_new.getName(),user_new.getAge(),user_new.getScore());
}
}
在实际存储过程中还涉及到密码这种危险变量的存储问题,一般情况下,如果我们并不想将这种可能存在隐患的数据一并存储到文件中,我们可以在定义对象类时,在不想存储的成员变量前用transient变量修饰,由此修饰符修饰的变量,在对象被存储进文件中时,不会一并被存储(不参与序列化)
加入版本序列号
加入一个对象数据可能经过多次迭代更新,这时我们就可以在定义时为这个类添加版本序列号
代码语言:javascript复制private static final long serialVersionUID=1L;
版本序列号用长整型定义,结尾的L可加可省略,在定义了版本序列号后,序列化与反序列化要求前后的序列号必须一致,即版本1定义的变量存储在文件中,只能用版本同样为1的变量来接收,如果前后的版本号不一致,在反序列化时就会报错
代码语言:javascript复制Exception in thread "main" java.io.InvalidClassException: FileInputStreamDemo.User; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2
打印流
用途
- 方便,快速的将数据写出
- 可以保证打印内容前后一致(打印的是什么,结果就是什么)
构造器
- public PrintStream(OutpputStream os);
- public PrintStream(String filePath);
public class PrintStreamDemo {
public static void main(String[] args) throws FileNotFoundException {
//打印流可以接低级流管道,或者直接接文件路径等等
PrintStream ps=new PrintStream("src/FileInputStreamDemo/input.txt");
ps.println(955); //注意这里写入的就是数字97,不是字符集的编码,因为打印流写什么就打印什么
ps.println("Leslie"); //这里直接打印字符串
ps.println(99.90); //打印浮点数
ps.println(false); //打印布尔值
//写字节出去
ps.write(97); //这里的97表示字符集的编码,也就是a
ps.write(100);
ps.close();
}
}
注意,在向缓冲流中传入低级流时不能传入打印流,因为打印流本身功能相较于缓冲流更强大,打印流本身以及包含了缓冲流的内容,是比缓冲流更高级的流
改变输出流向-重定向
代码语言:javascript复制public class PrintStreamDemo {
public static void main(String[] args) throws FileNotFoundException {
PrintStream ps=new PrintStream("src/FileInputStreamDemo/input.txt");
System.setOut(ps); //重定向功能,让系统的输出流流向打印流,所以以下输出均不会在控制台显示
System.out.println("Leslie");
System.out.println("John");
ps.close();
}
}
Properties-属性集文件
本质是一个Map集合,即键值对集合。核心用途在于当作属性文件(后缀是.properties结尾的文件,里面的内容都说是键值对,在大型框架中十分常见)。可以把键值对的数据存入到一个属性文件中去
代码语言:javascript复制public class PropertiesDemo {
public static void main(String[] args) throws IOException {
//将数据写入属性集文件
//创建属性集对象
Properties p=new Properties();
//存入键值对数据
p.setProperty("admin","123456");
p.setProperty("root","123456");
//将数据存入属性文件中
OutputStream os=new FileOutputStream("src/FileInputStreamDemo/new.properties");
p.store(os,""); //参数二表示--保存心得,对保存数据进行解释说明
os.close();
//从属性集文件中读出属性集对象
//利用字节输入流加载属性文件中的数据到属性集对象p1中去
Properties p1=new Properties();
InputStream is=new FileInputStream("src/FileInputStreamDemo/new.properties");
p1.load(is);
System.out.println(p1);
}
}