IO
概述与分类
-
IO流介绍
- IO:输入/输出(Input/Output)
- 流:是一种抽象概念,是对数据传输的总称。也就是说数据在设备间的传输称为流,流的本质是数据传输
- IO流就是用来处理设备间数据传输问题的。常见的应用:文件复制;文件上传;文件下载
-
IO流的分类
-
按照数据的流向
- 输入流:读数据
- 输出流:写数据
-
按照数据类型来分
-
字节流
- 字节输入流
- 字节输出流
-
字符流
- 字符输入流
- 字符输出流
-
-
按照角色分类:按照流是否直接与特定的地方(如磁盘、内存、设备等)相连,分为节点流和处理流两类
-
节点流:可以从或向一个特定的地方(节点)读写数据
字节流和字符流都是节点流
-
处理流:是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写。比如缓冲流。
处理流的构造方法总是要带一个其他的流对象做参数。一个流对象经过其他流的多
-
-
-
IO流的使用场景
- 如果操作的是纯文本文件,优先使用字符流
- 如果操作的是图片、视频、音频等二进制文件。优先使用字节流
- 如果不确定文件类型,优先使用字节流。字节流是万能的流
关流:先用后关,后用先关
字节流
- new FileOutputStream(“name”)会产生一个新文件
- new FileInputStream(“name”)不会创建新文件,若文件不存在会报错;
- new File(“name”)不会创建新文件,在new File()之后需要调用createNewFile()才能创建新文件;
- file=new File(“name”)+new FileInputStream(file),不会创建新文件,若文件不存在也会报错;
- file=new File(“name”)+new FileOutputStream(file),会创建新文件。
定义:字节流是以8位字节为数据单元的IO流
- 字节流抽象基类
类 | 作用 |
---|---|
InputStream | 该抽象类表示字节输入流的所有类的超类(数据由数据源流到程序中,为读) |
OutputStream | 该抽象类表示字节输出流的所有类的超类(数据由程序流到数据源中,为写) |
子类名特点:子类名称都是以其父类名作为子类名的后缀
字节输出流
- FileOutputStream(String name):创建文件输出流以指定的名称写入文件
使用字节输出流写数据的步骤
- 创建字节输出流对象(调用系统功能创建了文件,创建字节输出流对象,让字节输出流对象指向文件)
- 调用字节输出流对象的写数据方法
- 释放资源(关闭此文件输出流并释放与此流相关联的任何系统资源)
常用方法
- 写数据的方法分类
方法名 | 说明 |
---|---|
void write(int b) | 将指定的字节写入此文件输出流 一次写一个字节数据 |
void write(byte[] b) | 将 b.length字节从指定的字节数组写入此文件输出流 一次写一个字节数组数据 |
void write(byte[] b, int off, int len) | 将 len字节从指定的字节数组开始,从偏移量off开始写入此文件输出流 一次写一个字节数组的部分数据 |
void close() | 关闭输出流 |
- 代码示例
@Test
public void test2() throws Exception {
//以字节为单位向文件中写入内容
FileOutputStream fos = new FileOutputStream("E:\\testNomal\\FileTest\\三国演义2.txt");
//将字符串转换成为字节数组写入文件
fos.write("helloJava".getBytes());
System.out.println();
fos.close();
}
/*
参数file后面还有一个可选参数append,
当append为true时,从文件末尾写入数据。
当append为false时,从文件头部写入,就是覆盖,默认为false。
改成了 FileOutputStream fos = new FileOutputStream(file,true);
过后写的数据就不会被覆盖。
*/
字节输入流
-
字节输入流
- FileInputStream(String name):通过打开与实际文件的连接来创建一个FileInputStream ,该文件由文件系统中的路径名name命名
-
字节输入流读取数据的步骤
- 创建字节输入流对象
- 调用字节输入流对象的读数据方法
- 释放资源
常用方法
方法 | 内容 |
---|---|
int read() | 读取单个字节到程序,以int型返回所读取的字节数据,返回值为-1表示读取到了文件的末尾 |
int read(byte[] b) | 读取多个字节到程序,并存储在字节数组中,返回实际读取的字节数,返回值为-1表示读取到了文件的末尾 |
void read(byte[] b, int off, int len) | 将 len字节从指定的字节数组开始,从偏移量off开始写入此文件输入流 一次读一个字节数组的部分数据 |
void close() | 关闭输入流 |
- 代码示例
public class TestFileInputStream {
/**
* 通过字节输入流(FileInputStream),读取电脑中文件的内容
* @throws IOException
*/
@Test
public void test1() throws IOException{
//以字节为单位读取内容
FileInputStream fis = new FileInputStream("E:\\testNomal\\FileTest\\三国演义1.txt");
//读取文件内容,read(),如果返回值不是-1,就说明还存在未读的字节
int i = 0;
String sum = "";
while((i=fis.read()) != -1){
sum += (char)i;
}
//乱码原因:因为现在是字节流,而我文档里面有中文,中文是字符,占两个字节,所以乱码,改成全是英文就可以解决这个问题
System.out.println(sum);//??????°??ú???????ú????????????????
fis.close();
}
}
- 代码实例
*案例题目描述:*
完成学生封装,保存数据到文件
*根据题目要求完成*
-
封装学生类Student,包含属性 学号id,姓名name,年龄age,提供带参数的构造方法(20分)
-
创建四个学生对象,把四个对象存放到List集合中。(30分)
Id:1 name:张三 age:18
Id:2 name:李四 age:19
Id:3 name:王雷 age:28
Id:4 name:李丽 age:39
-
遍历集合中所有的数据,id为偶数的打印在控制台上(20分)
-
id为奇数输出到D盘的test.txt文本文件当中(20)
*其他*
- 要求代码每个方法都有注释。(10分)
student类
public class Student {
private int id;
private String name;
private int age;
public Student(int id, String name, int age) {
super();
this.id = id;
this.name = name;
this.age = age;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student [id=" + id + ", name=" + name + ", age=" + age + "]";
}
}
- 测试类
@Test
public void test1() throws Exception{
//因为在这个地方还没有学习字符流,所以name不能是中文
List<Student> ls = new ArrayList<Student>();
ls.add(new Student(1,"ary",17));
ls.add(new Student(2,"Tom",16));
ls.add(new Student(3,"Amy",18));
ls.add(new Student(4,"Lily",19));
String info = "";
//遍历集合容器
for(Student s : ls){
if(s.getAge()%2==0){//id为偶数的打印在控制台
System.out.println(s);
}else{//id为奇数输出到D盘的test.txt文本文件
info += s+"\r\n";//\r\n是回车换行的转义字符
}
}
FileOutputStream fos = new FileOutputStream("E:\\testNomal\\FileTest\\test.txt");
//将内容写入文件
fos.write(info.getBytes());
fos.close();
System.out.println("一切结束");
}
/*
Student [id=2, name=Tom, age=16]
Student [id=4, name=Lily, age=19]
一切结束
*/
字节缓冲流
缓冲流的基本原理,是在创建流对象时,会创建一个内置的默认大小的缓冲区数组,通过缓冲区读写,减少系统IO次数,从而提高读写的效率。
-
字节缓冲流介绍:以提高读写性能为目的的,具备缓冲区的处理流
-
BufferOutputStream:该类实现缓冲输出流。 通过设置这样的输出流,应用程序可以向底层输出流写入字节,而不必为写入的每个字节而导致底层系统的调用
-
BufferedInputStream:创建BufferedInputStream将创建一个内部缓冲区数组。 当从流中读取或跳过字节时,内部缓冲区将根据需要从所包含的输入流中重新填充,一次输入很多字节
-
-
构造方法:
方法名 说明 BufferedOutputStream(OutputStream out) 创建字节缓冲输出流对象 BufferedInputStream(InputStream in) 创建字节缓冲输入流对象
方法跟上面的差不多,就是换了一个类名,在这里就不演示了
@Test
public void test7() throws Exception{
//创建一个FileInputStream对象,以字节为单位读取内容
FileInputStream fis = new FileInputStream("E:\\testNomal\\FileTest\\三国演义1.txt");
BufferedInputStream bi = new BufferedInputStream(fis,1024);//1024:缓冲区大小
//读取内容
int i;
String msg = null;
if((i=bi.read()) != -1){
msg += (char)i;
}
System.out.println(msg);
bi.close();
fis.close();
}
@Test
public void test8() throws Exception{
//创建一个FileInputStream对象,以字节为单位读取内容
FileOutputStream fos = new FileOutputStream("E:\\testNomal\\FileTest\\三国演义1.txt");
BufferedOutputStream bos = new BufferedOutputStream(fos,1024);//1024:缓冲区大小
bos.write("aiuhdia".getBytes());
bos.close();
fos.close();
}
编码表
-
什么是字符集
是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等
l计算机要准确的存储和识别各种字符集符号,就需要进行字符编码,一套字符集必然至少有一套字符编码。常见字符集有ASCII字符集、GBXXX字符集、Unicode字符集等
-
常见的字符集
-
ASCII字符集:
lASCII:是基于拉丁字母的一套电脑编码系统,用于显示现代英语,主要包括控制字符(回车键、退格、换行键等)和可显示字符(英文大小写字符、阿拉伯数字和西文符号)
基本的ASCII字符集,使用7位表示一个字符,共128字符。ASCII的扩展字符集使用8位表示一个字符,共256字符,方便支持欧洲常用字符。是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等
-
GBXXX字符集:
GBK:最常用的中文码表。是在GB2312标准基础上的扩展规范,使用了双字节编码方案,共收录了21003个汉字,完全兼容GB2312标准,同时支持繁体汉字以及日韩汉字等
-
Unicode字符集:
UTF-8编码:可以用来表示Unicode标准中任意字符,它是电子邮件、网页及其他存储或传送文字的应用 中,优先采用的编码。互联网工程工作小组(IETF)要求所有互联网协议都必须支持UTF-8编码。它使用一至四个字节为每个字符编码
编码规则:
128个US-ASCII字符,只需一个字节编码
拉丁文等字符,需要二个字节编码
大部分常用字(含中文),使用三个字节编码
其他极少使用的Unicode辅助字符,使用四字节编码
-
字符流
-
字符流的介绍
由于字节流操作中文不是特别的方便,所以Java就提供字符流
字符流 = 字节流 + 编码表
-
中文的字节存储方式
用字节流复制文本文件时,文本文件也会有中文,但是没有问题,原因是最终底层操作会自动进行字节拼接成中文。
汉字在存储的时候,无论选择哪种编码存储,第一个字节都是负数
解决编码问题
字符串
- 相关方法
方法名 | 说明 |
---|---|
byte[] getBytes() | 使用平台的默认字符集将该 String编码为一系列字节 |
byte[] getBytes(String charsetName) | 使用指定的字符集将该 String编码为一系列字节 |
String(byte[] bytes) | 使用平台的默认字符集解码指定的字节数组来创建字符串 |
String(byte[] bytes, String charsetName) | 通过指定的字符集解码指定的字节数组来创建字符串 |
@Test
public void test4() throws UnsupportedEncodingException{
//我项目的编码集是UTF-8
String s = "我爱中国";
System.out.println(s);//我爱中国
byte[] bytes = s.getBytes("GBK");
System.out.println(Arrays.toString(bytes));//[-50, -46, -80, -82, -42, -48, -71, -6]
System.out.println(bytes);//[B@14514713
//在这里的bytes这个位置只能是变量,不是的话会报错
String s1 = new String(bytes,"GBK");
System.out.println(s1);//我爱中国
String s2 = new String(bytes,"UTF-8");
System.out.println(s2);//????й?
}
字符流
-
字符流中和编码解码问题相关的两个类
-
InputStreamReader:是从字节流到字符流的桥梁
它读取字节,并使用指定的编码将其解码为字符
它使用的字符集可以由名称指定,也可以被明确指定,或者可以接受平台的默认字符集
-
OutputStreamWriter:是从字符流到字节流的桥梁
是从字符流到字节流的桥梁,使用指定的编码将写入的字符编码为字节
它使用的字符集可以由名称指定,也可以被明确指定,或者可以接受平台的默认字符集
-
-
构造方法
方法名 说明 InputStreamReader(InputStream in) 使用默认字符编码创建InputStreamReader对象 InputStreamReader(InputStream in,String chatset) 使用指定的字符编码创建InputStreamReader对象 OutputStreamWriter(OutputStream out) 使用默认字符编码创建OutputStreamWriter对象 OutputStreamWriter(OutputStream out,String charset) 使用指定的字符编码创建OutputStreamWriter对象
@Test
public void test5() throws Exception{
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("E:\\testNomal\\FileTest\\FileWriterTestUnic.txt"),"GBK");
osw.write("我爱中国");
osw.close();
InputStreamReader isr = new InputStreamReader(new FileInputStream("E:\\testNomal\\FileTest\\FileWriterTestUnic.txt"),"GBK");
//一次读取一个字符数据
int ch;
while ((ch=isr.read())!=-1) {
System.out.println((char)ch);
}
isr.close();
}
/*
我
爱
中
国
*/
字符流读数据
常用方法
方法名 | 说明 |
---|---|
int read() | 一次读一个字符数据 |
int read(char[] cbuf) | 一次读一个字符数组数据 |
- 代码演示
@Test
public void test2() throws Exception{
//创建实例化FileReader对象
FileReader fr = new FileReader("E:\\testNomal\\FileTest\\三国演义3.txt");
int i = 0;
String mess = "";
while((i=fr.read()) != -1){
mess += (char)i;
//如果没有char的话,输出的是一长串数字
//6553365533655331016655336553349765533655336553365533108165533655336553365533140165533655331780655336553365533185565533881655336553365533655336553365533655336553365533655336553342356553365533
}
System.out.println(mess);
//仍然乱码:???????????й?????????????????????????*#??
//原因:文件保存的编码和现在Eclipse里面的编码集不一样,所以乱码,将文件和项目的编码集改为一样的
fr.close();
}
字符流写数据
常用方法
方法名 | 说明 |
---|---|
void write(int c) | 写一个字符 |
void write(char[] cbuf) | 写入一个字符数组 |
void write(char[] cbuf, int off, int len) | 写入字符数组的一部分 |
void write(String str) | 写一个字符串 |
void write(String str, int off, int len) | 写一个字符串的一部分 |
刷新和关闭的方法
方法名 | 说明 |
---|---|
flush() | 刷新流,之后还可以继续写数据 |
close() | 关闭流,释放资源,但是在关闭之前会先刷新流。一旦关闭,就不能再写数据 |
- 代码演示
@Test
public void test3() throws Exception{
//创建一个FileWriter对象,以字符为数据单元想文件中写入内容
FileWriter fw = new FileWriter("E:\\testNomal\\FileTest\\FileWriterTest.txt");
//通过writer方法写入内容
fw.write("是非成败转头空");
//关流
fw.close();
}
- 综合案例
实现注册-——登录——退出功能
@Test
public void test6() throws Exception{
boolean flag = true;
Scanner sc = new Scanner(System.in);
int i = 0;
while(flag){
System.out.println("请选择功能:1.注册;2:登录;3:退出");
i = sc.nextInt();
switch(i){
case 1:
System.out.println("请输入用户名:");
String name = sc.next();
System.out.println("请输入密码:");
String pwd = sc.next();
FileWriter fw = new FileWriter("E:\\testNomal\\FileTest\\login\\"+name+".txt");
fw.write(pwd);
fw.close();
break;
case 2:
File fi = new File("E:\\testNomal\\FileTest\\login");
File[] lf = fi.listFiles();
System.out.println("请输入用户名:");
String Lname = sc.next();
System.out.println("请输入密码:");
String Lpwd = sc.next();
boolean isOk = false;
for(File f : lf){
if((Lname+".txt").equals(f.getName())){
isOk = true;
}
}
if(isOk){
//读文件内容,使用FileReader
FileReader fr = new FileReader("E:\\testNomal\\FileTest\\login\\"+Lname+".txt");
int j = 0;
String msg = "";
while((j=fr.read()) != -1){
msg += (char)j;
}
//关流
fr.close();
if(Lpwd.equals(msg)){
System.out.println("登录成功");
}else{
System.out.println("密码错误,登录失败");
}
}else{
System.out.println("系统中没有该用户,请先注册!");
}
break;
case 3:
flag = false;
break;
default:
System.out.println("输入错误,请重新输入!");
}
}
}
字符缓冲流
缓冲流的基本原理,是在创建流对象时,会创建一个内置的默认大小的缓冲区数组,通过缓冲区读写,减少系统IO次数,从而提高读写的效率。
-
字符缓冲流介绍
- BufferedWriter:将文本写入字符输出流,缓冲字符,以提供单个字符,数组和字符串的高效写入,可以指定缓冲区大小,或者可以接受默认大小。默认值足够大,可用于大多数用途
- BufferedReader:从字符输入流读取文本,缓冲字符,以提供字符,数组和行的高效读取,可以指定缓冲区大小,或者可以使用默认大小。 默认值足够大,可用于大多数用途
-
构造方法
方法名 | 说明 |
---|---|
BufferedWriter(Writer out) | 创建字符缓冲输出流对象 |
BufferedReader(Reader in) | 创建字符缓冲输入流对象 |
特有方法
- BufferedWriter
方法名 | 说明 |
---|---|
void newLine() | 写一行行分隔符,行分隔符字符串由系统属性定义 |
- BufferedReader:
方法名 | 说明 |
---|---|
String readLine() | 读一行文字。 结果包含行的内容的字符串,不包括任何行终止字符如果流的。如果结尾已经到达,则为null |
- 代码测试
@Test
public void test9() throws Exception{
//创建实例化FileReader对象
FileReader fr = new FileReader("E:\\testNomal\\FileTest\\三国演义3.txt");
BufferedReader br = new BufferedReader(fr,1024);//1024:缓冲区大小
String msg = "";
// int i=0;
String i="";
while((i=br.readLine()) != null){
// msg += (char)i;
msg += i;
}
System.out.println(msg);
br.close();
fr.close();
}
@Test
public void test10() throws Exception{
FileWriter fw = new FileWriter("E:\\testNomal\\FileTest\\三国演义3.txt");
BufferedWriter bw = new BufferedWriter(fw,1024);//1024:缓冲区大小
bw.write("折扣价和空间和");
// bw.write("\r\n");
bw.newLine();//与上一行代码意义差不多
bw.write("我不是药神");
bw.close();
fw.close();
}
小结
- 字节流
- 字符流