字符流
说在前面:每个类的构造和方法查API即可
原则:
先创建的流后关闭,后创建的流先关闭,否则可能会报错
为什么要有字符流?
因为世界语言的多样性
学会字符流有啥用?
字符流更加便利在读字方面,字节流读出来的字节,不好进行处理,容易乱码。
特点:
- 所有的字符流,自带(小)缓冲区,是在编解码的时候来使用的
- 字符流的read()方法,会一次多次读入一个或者字节(看编码的符号位)。存入int中。(也是字节流和字符流的根本区别)
引例:
从文件中按每次两个字节读出中文:
public static void main(String[] args) throws IOException {
FileInputStream inputStream = new FileInputStream("E:\JavaProjectStation\JavaBaseHomeworkFile\123.txt");
// 123.txt里面是“爱我中国”
// 按每两个字节去读出
byte[] bytes = new byte[2];
// byte[] bytes = new byte[1024];
int readCount;
while ((readCount = inputStream.read(bytes)) != -1){
String s = new String(bytes, 0, readCount);
System.out.println(s);
}
inputStream.close();
}
// 按每两个字节去读出 输出为:������������ 一堆乱码。
// 按1024个字节读出 输出为:爱我中国
从文件中按每次两个字节读出,然后写入到另一个文件中:
// 读入到另一个文件试试
FileInputStream in = new FileInputStream("E:\JavaProjectStation\JavaBaseHomeworkFile\123.txt");
FileOutputStream out = new FileOutputStream("E:\JavaProjectStation\JavaBaseHomeworkFile\copy123.txt");
int readCount;
byte[] bytes1 = new byte[2];
while ((readCount = in.read(bytes1)) != -1){
out.write(bytes1, 0, readCount);
}
in.close();
out.close();
// 两个文件内容一样
// 那么问题来了 读出和写入 是否都采用了utf-8的编码解码方式?
如何将字符串指定编码集,指定解码集 ?
- 解码:字符串用getBytes(编码格式)方法转化为一个字节数组
- 编码: 以字符数组创建字符串对象 new String(字符数组,编码格式)
- 输出字符串,或者其他操作
例子:
String str = "不要忘记你的目标";
// 默认为utf-8解码 然后放入字节数组
byte[] bytes = str.getBytes();
// 编码码 指定为:以GBK编码方式编码码。
String gbk = new String(bytes, "GBK");
System.out.println(gbk);
// 默认utf-8 GBK编码:正常输出
// 指定GBK编码,乱码
字符流 = 字节流 + 编码表,为什么要这么说?
字符转化流:
主要就是可以指定解码和编码格式,且也只有它能
// OutputStreamWriter(OutputStream out)
// 创建使用默认字符编码的 OutputStreamWriter。
FileOutputStream fileOutputStream = new FileOutputStream("b.txt");
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream);
//OutputStreamWriter(OutputStream out, String charsetName)
// 创建使用指定字符集的 OutputStreamWriter。
//OutputStreamWriter outputStreamWriter = new OutputStreamWriter(
// new FileOutputStream("b.txt"),"GBK");
//String getEncoding()
// 返回此流使用的字符编码的名称。
String encoding = outputStreamWriter.getEncoding();
System.out.println(encoding);
字符简化流:
输入:
通过简化字符输入流对象将从文件读出数据并打印出来:
// 新建一个字符输入流对象
FileReader in = new FileReader("E:\JavaProjectStation\JavaBaseHomeworkFile\123.txt");
// 注意:用字符数组来存储 同样也需要while循环
char[] chars = new char[1024];
int readCount;
while ((readCount = in.read(chars)) != -1){
System.out.println("读出内容为:" + new String(chars, 0, readCount));
}
in.close();
输出:
将字符串写入文件:
File file = new File("E:\JavaProjectStation\JavaBaseHomeworkFile\123.txt");
// 父类引用指向子类对象
Writer out = new FileWriter(file);
String str = "hello world";
// 字符输出流 不用像之前字节输出流一样,用必须一个个字节输出,或者用字节数组输出
// 直接写入字符串就行了
out.write(str);
out.close();
将字符串追加写入文件
File file = new File("E:\JavaProjectStation\JavaBaseHomeworkFile\123.txt");
Writer out = new FileWriter(file, true);
String str = "追加写入成功";
out.write(str);
out.close();
通过简化流对象将文件中读出以后输入到另一个文件中去:
// 新建一个字符输入输出流对象
Reader in = new FileReader("E:\JavaProjectStation\JavaBaseHomeworkFile\123.txt");
Writer out = new FileWriter("E:\JavaProjectStation\JavaBaseHomeworkFile\copy123.txt", true);
// 注意:用字符数组来存储 同样也需要while循环
char[] chars = new char[1024];
int readCount;
while ((readCount = in.read(chars)) != -1){
out.write(new String(chars, 0, readCount));
}
// 关闭
in.close();
out.close();
字符缓冲流
输入:
字符缓冲输入流操作步骤
- 创建简化输入流
- 创建缓冲输入流
- 循环读出数据,readline时候,读完文件返回null
题:从磁盘上读取一个文本文件(如某个java源代码),分别统计出文件中英文字母
/*
思路:
* 1.创建缓冲字符输入流对象
* 2.一个一个的读出来,读出来的得用int型接收
* 3.if判断计数
*
* @modified By:
*/
public class Homework1 {
public static void main(String[] args) throws IOException {
BufferedReader in = new BufferedReader(new FileReader("E:\JavaProjectStation\JavaBaseHomeworkFile\123.txt"));
// 存储读出的数据
int readData;
// 记录各种类型字符的个数
int countLetter = 0; // 字母
int countSpace = 0; // 空格
int countNumber = 0; // 数字
while ((readData = in.read()) != -1){
// char readDataChar = (char) readData;
if (Character.isLetter(readData)) {
countLetter++;
}
if (Character.isDigit(readData)) {
countNumber++;
}
if (Character.isWhitespace(readData)) {
countSpace++;
}
}
System.out.println("该文件中数字有" + countNumber + "个 字母有" + countLetter + "个 空格有" + countSpace);
}
}
输出:
字符缓冲输出流操作步骤
- 创建简化输出流
- 再创建缓冲输出流
- write写入数据
- 关闭
一道题看懂:在一个磁盘的文件里保存26个英文小写字母(乱序),将他们读入内存中,进行排序,把排好顺序的数再重新追加写到磁盘的该文件中。
/*
* 思路:
* 1.创建字符缓冲输入流 输出流
* 2.按字符数组读出
* 3.char数组做个Arrays.sort排序 直接将char[]里面的字符按照ascll表排好。
* 4.排完以后就写入。
* @modified By:
*/
public class Homework2 {
public static void main(String[] args) throws IOException {
BufferedReader in = new BufferedReader(
new FileReader("E:\JavaProjectStation\JavaBaseHomeworkFile\123.txt"));
char[] chars = new char[1024];
int readCount = in.read(chars);
// Arrays.sort直接按照 字符的ascll码值排序完毕
Arrays.sort(chars, 0, readCount);
// 写入
BufferedWriter out = new BufferedWriter(
new FileWriter("E:\JavaProjectStation\JavaBaseHomeworkFile\123.txt"));
out.write(chars, 0, 26);
in.close();
out.close();
}
}
注意:
字符流在对图像操作的过程中
图像,视频文件有自己的编码。
各种流的对比:
框架:
各种流的优缺点对比
简化流:
- 无法指定解码字符集。
- 使用简单,因为可以直接传入文件名作为构造参数
转化流
- 使用麻烦,得新建一个字节流对象作为构造参数
缓冲流
-
可以设置缓冲区大小
-
使用相对麻烦,得新建一个简化流对象作为构造参数,也可以用转化流
-
输入流有个readLine方法读出来返回一个字符串,结束返回null
字节流和字符流的区别:
- 字符流read()方法一次读出一个,或者多个字节。
一个困扰很深的问题:
为什么两个字节char型数组能够存储3个字节的字符 比如中文
答:utf-8解码后的汉字确实是三个字节,但是其中有8个符号位,当该数据存储形式从以字节数组转化为,字符数组时,符号位会被去掉,再拼接,组成两个字节存储于字符数组。
补充知识:utf-8是怎么取
UTF-8不同,它定义了一种“区间规则”,这种规则可以和ASCII编码保持最大程度的兼容:
它将Unicode编码为00000000-0000007F的字符,用单个字节来表示 0111 1111 = 7F
它将Unicode编码为00000080-000007FF的字符用两个字节表示
它将Unicode编码为00000800-0000FFFF的字符用3字节表示
1字节 0xxxxxxx
2字节 110xxxxx 10xxxxxx
3字节 1110xxxx 10xxxxxx 10xxxxxx
细枝末节:
-
换行
换行符
- System.lineSeparator() :得到系统换行符
- windos:" "
- linux:" "
- mac:" "
-
字符流,有缓冲区的字节流都要close,或者刷新
-
utf-8变长 可以兼容ascll
其他流
数据流:
DataIn/OutputStream
创建形式:
还是包装流,包装普通字节流
为啥要有数据流:
普通的字节流无法写入很大的整数,或者小数。
数据输入流读出来的是啥玩意啊
要知道文件中,当时写入数据类型的顺序,才能正确的读出,不讲武德的读出来当然有问题。
打印流
-
字节打印流 PrintStream
-
字符打印流PrintWriter
特点:
- 也是一个包装流(包装字节流)
- 可以打印各种类型的数据
- 无法改变数据来源,只能操作目的地
- 能够自动刷新
字节打印流 PrintStream
方法:
void print(String str): 输出任意类型的数据,
void println(String str): 输出任意类型的数据,自动写入换行操作
例子:
PrintStream ps = new PrintStream(new FileOutputStream(("E:\JavaProjectStation\JavaBaseHomeworkFile\123.txt")));
// 就啥玩意都可以打印到这个文件中去
ps.print(100);
ps.print("hello world");
字节打印流 PrintStream
PrintWrite
构造方法摘要 |
---|
PrintWriter(OutputStream out, boolean autoFlush) 通过现有的 OutputStream 创建新的 PrintWriter。 |
PrintWriter(Writer out, boolean autoFlush) 创建新 PrintWriter |
例子:
PrintWriter pw = new PrintWriter(new FileWriter("E:\JavaProjectStation\JavaBaseHomeworkFile\123.txt"), true);
pw.println("ni好啊"); // 自带刷新
自动刷新:
有自动刷新,和手动刷新两种构造器
自动刷新的触发:
打印方式只有是println printf format 才能触发刷新
标准输入输出流
重定向
输出重定向:
实现步骤:
- 以打印输出流包装字节输出流
- System.setOut(打印流对象) 将标准输出重定向
- 随便打印一点什么
try( PrintStream out = new PrintStream(
new FileOutputStream("E:\JavaProjectStation\JavaBaseHomeworkFile\Out.txt"));)
{
// 重定向输出到out.txt文件中
System.setOut(out);
// 向标准输出输出一个字符串
System.out.println("重定向成功");
// 向标准输出输出一个对象
System.out.println(new ChangeOutDirection());
} catch (FileNotFoundException e) {
e.printStackTrace();
}
输入重定向:
实现步骤:
- 以普通字节输入流创建对象
- System.setIn(字节输入流对象)
- 新建Scanner对象
- Scanner.useDelimiter(分隔模式)
- sc.hasNext() 返回值是true 意思是接下来要扫描的一行不是末尾就返回true
- sc.next() 读出下一个分割符之间的内容
try( FileInputStream fis = new FileInputStream("E:\JavaProjectStation\JavaBaseHomeworkFile\src\Day20\PrintHelloWorld.java"); ) { // 重定向到fis输入流 System.setIn(fis); // Scanner sc = new Scanner(System.in); // 增加下面一行只把回车作为分隔符 设置每次读取以什么为终点 sc.useDelimiter(" "); // sc只要遇到 while (sc.hasNext()){ System.out.println("键盘输入的内容时:" + sc.next()); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }
有啥用:
将某个操作的数据,输入到日志文件中保存
序列化与反序列化流
输出对象的啥玩意?
对象信息,普通人看起来就是乱码。我看起来就是乱码。
为啥要有序列化?
实例对象的信息需要永久保存的时候,需要序列化,将信息存入磁盘。
序列化操作步骤:
- 创建字节流对象
- 包装字节流对象创建ObjectOutputStream对象
- 调用.writeObject()方法写入
- 关闭ObjectOutputStream对象
- 注意 写入的对象,必须实现一个接口Serializable
// 序列化
try(
ObjectOutputStream SerOut = new ObjectOutputStream(
new FileOutputStream("E:\JavaProjectStation\JavaBaseHomeworkFile\Person.tmp"))
)
{
// 注意这里序列化的时候transient int ID是赋予了初值的
SerOut.writeObject(new Person(18, "熊孩子", true, 11));
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
反序列化步骤:
- 创建字节流对象(该文件应该存储了一个类的信息)
- 包装字节流对象创建ObjectInputStream对象
- 调用readObject方法反序列化,读出该对象的信息
- 输出
// 反序列化
try(
ObjectInputStream deserIn = new ObjectInputStream(new FileInputStream("E:\JavaProjectStation\JavaBaseHomeworkFile\Person.tmp"));
)
{
// 将反序列化生成的对象输出
// 反序列化出来发现transient int ID; 变为了默认值 0;
System.out.println(deserIn.readObject());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
transient关键字
- 类的声明中用该关键字修饰的成员,不加入序列化。
- 静态修饰的成员也不会序列化
- 对象的信息被反序列化后的内容为默认值。
学了这么多流,实际开发到底怎么用的?
IO概括:
文件的读写 ,在java中是非常常用。
而设计者设计读写控制的时候,也整理的颇为详细,可能的结果就是,给调用者带来很大的困惑,这么多类,咋用。
其实完全不用担心:java写文件只有三步,百变不离其宗
1:我找到三样东西: 铲子(IO工具类); 沙子(我要读取的数据); 篓子(我要放的东西)
2:用铲子把沙子放到篓子里
3:我把铲子还给人家
至于我用 何种铲子,我要铲的是沙子 还是面粉,我要放到篓子还是框子里。先别管,也别上来就看那个javaIIO的类示意图
按铲子区分:一般单一的铲子不适用,需要组合多种功能
java中有读写字符的(reader/writer) 读写字节的(inputstream,outputstream)。自由选择
java按照功能 还会有 filereader xmlreder imgio buffereader 等
按照沙子来看:
字符串,byte[] , List
按照篓子来看
可以放到Map String 图片 文件 xml
补充学习: