学习目标
●1.掌握字符流的基本使用
●2.掌握序列化流的基本使用
●3.了解Properties类
1.字符流
● 一个字符一个字符的处理
● 在字节流的基础上,会对流中数据做字符的编码 a -> 97 和解码 97 -> a
● 字符流不能处理二进制文件(比如图片、音频、视频等),这些二进制文件只能用字节流处理;
● 字符流常用处理文本文件,目前还只能是纯文本文件(txt、java等,不能处理doc、xls等)
● 字符输入流为Reader。 字符输出流为Writer
● 这两个类都是抽象类,封装了字符流里面通用的功能。
1.1 字符流体系
1.2 Reader
1.2.1 层级
public abstract class Reader extends Object implements Readable, Closeable{}
1.2.2 常用方法
方法 | 描述 |
---|---|
public int read() | 从输入流读取一个字符。 虽然读取了一个字符,但是会自动提升为int类型。返回该字符的Unicode编码值。如果已经到达流末尾了,则返回-1。 |
public int read(char[] cbuf) | 从输入流中读取一些字符,并将它们存储到字符数组 cbuf中 。每次最多读取cbuf.length个字符。返回实际读取的字符个数。如果已经到达流末尾,没有数据可读,则返回-1。 |
public int read(char[] cbuf,int off,int len) | 从输入流中读取一些字符,并将它们存储到字符数组 cbuf中,从cbuf[off]开始的位置存储。每次最多读取len个字符。返回实际读取的字符个数。如果已经到达流末尾,没有数据可读,则返回-1。 |
public void close() | 关闭此流并释放与此流相关联的任何系统资源。 |
1.2.3 常用子类构造
FileReader(String fileName) ;
FileReader(File file) //BufferedReader 高效字符输入流BufferedReader(Reader in);//装饰了基本的字符输入流的功能String readLine() ;//一次读取一行的内容 \n 文件末尾 null
1.2.4 使用方法
//输入流: 文件不存在 会报错
private static void testRead() throws FileNotFoundException {//读取指定文件的数据//输入流----> 保证文件必须是存在//使用字符流操作中文数据 不可能出现乱码Reader reader = new FileReader("src/a.txt");try (reader) {//循环读取文件中所有的数据/*int data = reader.read();System.out.println(data);System.out.println((char)data);*//*int len;while ((len = reader.read()) != -1) {System.out.print((char) len);}*/char[] buf = new char[100];//1024的整数倍int len;while ((len = reader.read(buf)) != -1) {//字符数组转StringSystem.out.print(new String(buf, 0, len));}} catch (IOException e) {e.printStackTrace();}
}
1.3 Writer
1.3.1 层级
public abstract class Writer
extends Object
implements Appendable, Closeable, Flushable{}
1.3.2 常用方法
方法 | 描述 |
---|---|
public void write(int c) | 写入单个字符。 |
public void write(char[] cbuf) | 写入字符数组。 |
public void write(char[] cbuf, int off, int len) | 写入字符数组的某一部分,off数组的开始索引,len写的字符个数。 |
public void write(String str) | 写入字符串。 |
public void write(String str, int off, int len) | 写入字符串的某一部分,off字符串的开始索引,len写的字符个数。 |
public void flush() | 刷新该流的缓冲。 |
public void close() | 关闭此输出流并释放与此流相关联的任何系统资源。 |
1.3.3 常用子类构造
//FileWriter 底层自带缓冲 缓冲区大小为 1024个字符。 char[] buf = new char[1024];
FileWriter(String fileName) ;
FileWriter(String fileName, boolean append) ;// append: false
FileWriter(File file) ;
FileWriter(File file, boolean append) ;private static int defaultCharBufferSize = 8192;
public BufferedWriter(Writer out) {this(out, defaultCharBufferSize);
}
1.3.4 使用方法
public static void main(String[] args) throws IOException {Writer writer = new FileWriter("src/b.txt");writer.write('我');writer.write(97);writer.write('\n');writer.write("你太美".toCharArray());writer.write("abc".toCharArray(),0,2);writer.write('\n');writer.write("学java!!!");writer.write("hello",0,4);writer.append('abc');writer.append("java");//没有释放资源//写到了底层缓冲区里面-----> 数组-----> char[] buf 1024//writer.flush(); 不建议手动调用flush//writer.close();}
● 刷新和关闭
○ 注意点:FileWriter与FileOutputStream不同。因为内置缓冲区的原因,如果不关闭输出流,无法写出字符到文件中。
但是关闭的流对象,是无法继续写出数据的。如果我们既想写出数据,又想继续使用流,就需要flush 方法了。
○ flush :刷新缓冲区,流对象可以继续使用。
○ close: 先刷新缓冲区,然后通知系统释放资源。流对象不可以再被使用了。
1.4 案例体会
1.4.1 文本文件读写
public static void uploadTxt(String txtFilePath) throws IOException {//使用字符流实现文件读写Reader reader = new FileReader(txtFilePath);Writer writer = new FileWriter("E:\\workspace\\day18\\a.txt");try (reader; writer) {char[] chars = new char[1024];int len;while ((len = reader.read(chars)) != -1) {writer.write(chars, 0, len);//writer.write(new String(chars,0,len));}}System.out.println("success");
}
1.4.2 模拟用户注册
持久化保存用户注册数据
//持久化保存用户注册的信息。多注册。
//指定的数据格式: id-name-pass-age
private static int idIndex = 1000;private static void userRegister(String userInfoPath) throws IOException {Scanner input = new Scanner(System.in);//Writer writer = new FileWriter(userInfoPath,true);BufferedWriter writer = new BufferedWriter(new FileWriter(userInfoPath));List<UserInfo> userInfoList = findAllUserInfo(userInfoPath);if (!userInfoList.isEmpty()) {idIndex = userInfoList.get(userInfoList.size()-1).getId();}String next;try (input; writer) {do {System.out.println("录入name:");String name = input.next();System.out.println("录入pass:");String pass = input.next();System.out.println("录入age:");String age = input.next();//id-name-pass-age String.join()String userInfoStr = String.join("-", String.valueOf(++idIndex), name, pass, age);//将userInfoStr写入到文件中 write 输出流 Writerwriter.write(userInfoStr);writer.newLine();//writer.write("\n");//writer.flush();System.out.println("是否继续注册?y/n");next = input.next();} while (Objects.equals("y", next));}System.out.println("注册成功");
}
1.4.3 查询用户信息
//读取指定文件的所有的用户信息
//java语言是面向对象的: 一个用户就是一个对象 将我们读取每行信息转成一个UserInfo的对象。
//集合: Collection(List Set) Map
public static List<UserInfo> findAllUserInfo(String userInfoPath) throws FileNotFoundException {//读取文件的每一行信息 输入流 字符输入流 Reader//FileReader 操作读取一个字符/字符数组//BufferedReader 高效字符输入流: 1. 自带缓冲 2.String readLine()BufferedReader reader = new BufferedReader(new FileReader(userInfoPath));List<UserInfo> userInfoList = new ArrayList<>(10);try (reader) {//1.循环读取每一行信息//2.将每一行信息转换成UserInfo对象//3. 存储集合String line;while ((line = reader.readLine()) != null) {String[] array = line.split("-");int id = Integer.parseInt(array[0]);int age = Integer.parseInt(array[array.length - 1]);userInfoList.add(new UserInfo(id, array[1], array[2], age));}} catch (IOException e) {e.printStackTrace();}System.out.println("success");return userInfoList;
}
2.其他流
● 我们讲的其它流也是字节流或者字符流的子级。
● 只是满足的特殊的开发场景。这些流也能实现单独的读与写功能。
2.1 序列化流
● Java 提供了一种对象序列化的机制。用一个字节序列可以表示一个对象,该字节序列包含该
对象的类型
和对象中存储的属性
等信息。字节序列写出到文件之后,相当于文件中持久保存
了一个对象的信息。
● 反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化。对象的数据
、对象的类型
和对象中存储的数据
信息,都可以用来在内存中创建对象。
● 序列化: 把内存中的对象 持久化到底层的存储介质(文件)
● 反序列化:把文件中的数据恢复回内存中的对象
● 某个类的对象需要序列化输出时,该类必须实现java.io.Serializable
接口,Serializable
是一个标记接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出NotSerializableException
。
○ 如果对象的某个属性也是引用数据类型,那么如果该属性也要序列化的话,也要实现Serializable 接口;
● transient用来修饰成员变量,表示不需要被序列化 ,反序列化的时候值是其数据类型的默认值 ;
● static修饰的静态变量的值不会序列化。因为静态变量的值不属于某个对象。
● 源文件一旦改动 serialVersionUID会发生改动 反序列化失败,该类的序列版本号与从流中读取的类描述符的版本号不匹配;
2.1.1 需求
● 在模拟注册多用户的案例过程中,可以使用基本字符流或者高效流实现存储用户或者读取用户信息功能。
● 但是存在一些问题。
● 比如存储用户: 必须使用指定的格式使用+或者StringBuilder进行拼接
● 比如读取用户: 需要每次读取一行,然后进行 split,再构造一个新的用户对象。
● 流程比较复杂。功能核心: 读写用户对象信息。
● 需求: 能不能直接读写对象,减少转换操作。
● 在这样的场景需求下,就可以使用对象流或者序列化流实现功能。
//序列化流是字节流的子类。InputStream OutputStream
//序列化流等价于增强了基本流的功能,可以对对象进行读写操作。
//ObjectInput----> ObjectInputStream 对象输入流 readObject-----> 反序列化
//ObjectInputStream(InputStream in)
//Object readObject() //ObjectOutput----->ObjectOutputStream 对象输出流 writeObject----->序列化
//ObjectOutputStream(OutputStream out)
//void writeObject(Object obj)
//想实现序列化:
//1.类型必须实现 java.io.Serializable 标识接口
//2.必须先写对象 才能读取对象。
2.1.2 writeObject
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class User {private Integer id;private String name;private String pass;private Integer age;
}
private static int id = 1000;private static void userReg(String userPath) throws IOException {@CleanupScanner input = new Scanner(System.in);@CleanupObjectOutput objectOutput = new ObjectOutputStream(new FileOutputStream(userPath));String next;do {System.out.println("录入name:");String name = input.next();System.out.println("录入pass:");String pass = input.next();System.out.println("录入age:");int age = input.nextInt();User user = new User(++id, name, pass, age);//对象输出流 ObjectOutput--->ObjectOutputStreamobjectOutput.writeObject(user);System.out.println("是否继续注册?y/n");next = input.next();} while (Objects.equals("y", next));System.out.println("注册成功");
}
程序可能会报错?
原因:没有支持序列化处理。对象的类型必须支持序列化
Exception in thread "main" java.io.NotSerializableException: com.java.io2.Userat java.base/java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1185)at java.base/java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:349)at com.java.io2.UserModule.userReg(UserModule.java:69)at com.java.io2.UserModule.main(UserModule.java:25)
解决方法
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class User implements Serializable {//是一个标识接口。// 类对象可以作为网络数据进行传输(RMI),将对象写入文件(缓存Redis)// 规则: 实体类必须实现Serializable。private Integer id;private String name;private String pass;private Integer age;}
2.1.3 readObject
private static void findUser(String path) throws IOException {//读取对象的数据//字节流---> -1//字符流----> -1 null@CleanupObjectInput objectInput = new ObjectInputStream(new FileInputStream(path));try {//User user = (User) objectInput.readObject();//1个对象//对于对象输入流而言 无法判断是否已经读取到了文件末尾//1.在程序不停的前提下 write +1 使用全局变量维护读取的次数问题//2.序列化流里面: 建议读写一次。User user;while ((user = (User) objectInput.readObject())!=null){System.out.println(user);}} catch (ClassNotFoundException e) {e.printStackTrace();// jvm加载class文件 代表没有找到User.class//1.没有编译//2.压根不存在这个类//3.没有引入第三方的jar}
}
==存在问题: ==
● 能够查询成功 写入的多个对象的数据,但是也可能会: java.io.EOFException end of file
● 对于对象输入流而言 无法判断是否已经读取到了文件末尾
● 解决方式:
● 1.在程序不停的前提下 write +1 使用全局变量维护读取的次数问题. 不太优雅
● 2.序列化流里面: 建议读写一次。
- 解决方式:
private static void userReg(String userPath) throws IOException {@CleanupScanner input = new Scanner(System.in);@CleanupObjectOutput objectOutput = new ObjectOutputStream(new FileOutputStream(userPath));String next;List<User> userList = new ArrayList<>(10);//集合的元素类型必须实现Serializable 否则依然报错。do {System.out.println("录入name:");String name = input.next();System.out.println("录入pass:");String pass = input.next();System.out.println("录入age:");int age = input.nextInt();User user = new User(++id, name, pass, age);//对象输出流 ObjectOutput--->ObjectOutputStreamuserList.add(user);System.out.println("是否继续注册?y/n");next = input.next();} while (Objects.equals("y", next));objectOutput.writeObject(userList);System.out.println("注册成功");
}
private static void findUser(String path) throws IOException {//读取对象的数据//字节流---> -1//字符流----> -1 null@CleanupObjectInput objectInput = new ObjectInputStream(new FileInputStream(path));try {//User user = (User) objectInput.readObject();//1个对象//对于对象输入流而言 无法判断是否已经读取到了文件末尾//1.在程序不停的前提下 write +1 使用全局变量维护读取的次数问题//2.序列化流里面: 建议读写一次。List<User> userList = (List<User>) objectInput.readObject();userList.forEach(System.out::println);} catch (ClassNotFoundException e) {e.printStackTrace();// jvm加载class文件 代表没有找到User.class//1.没有编译//2.压根不存在这个类//3.没有引入第三方的jar}
}
2.1.4 问题
//1.写入的对象与读取的对象是否是同一个对象? 不是
//2.读取的对象(反序列化的对象)是使用构造方法创建的吗? 不是
//序列化等价于克隆的深克隆。
public static void main(String[] args) throws IOException {//1.写入的对象与读取的对象是否是同一个对象? 不是//2.读取的对象(反序列化的对象)是使用构造方法创建的吗? 不是//序列化等价于克隆的深克隆。//3.序列化成功的前提下(写对象),多次修改原代码,会影响下一次的读取?String file = "src/a.txt";/* User user = new User(1, "张三", "1234", 20);//user.setHobby(new String[]{"code","music","game"});//System.out.println("user:" + user);String file = "src/a.txt";ObjectOutput objectOutput = new ObjectOutputStream(new FileOutputStream(file));objectOutput.writeObject(user);objectOutput.close();
*/System.out.println("--------------------");ObjectInput objectInput = new ObjectInputStream(new FileInputStream(file));try {User readObj = (User) objectInput.readObject();System.out.println(readObj);/*readObj.setName("张三丰");readObj.getHobby()[0] = "code123";System.out.println("readObj:" + readObj);System.out.println(readObj.getName());System.out.println(Arrays.toString(readObj.getHobby()));System.out.println(user.getName());System.out.println(Arrays.toString(user.getHobby()));*/} catch (ClassNotFoundException e) {e.printStackTrace();}objectInput.close();/*ObjectInput objectInput1 = new ObjectInputStream(new FileInputStream(file));try {Object otherObj = objectInput1.readObject();//有多个新的User类对象System.out.println(otherObj);} catch (ClassNotFoundException e) {e.printStackTrace();}objectInput1.close();*/}
//3.序列化成功的前提下(写对象),或者说在生产期间(开发期间),多次修改原代码,会影响下一次的读取?
//可能会报错
Exception in thread "main" java.io.InvalidClassException: com.java.io2.User; local class incompatible: stream classdesc serialVersionUID = -4617081159981173318, local class serialVersionUID = -3100539536271597976at java.base/java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:689)at java.base/java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:2025)at java.base/java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1875)at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2206)at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1692)at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:499)at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:457)at com.java.io2.Demo.main(Demo.java:34)//每个class文件都有1个唯一的版本号的id//本地的最新的User.class的版本号的id -3100539536271597976//使用Stream里面操作的User.class的版本号的id 4617081159981173318
2.1.5 解决
- 解决方式:
- 重写重读
- 固定一个class的版本号的id。 serialVersionUID
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class User implements Serializable {private static final long serialVersionUID = 3758989682616608190L;//是一个标识接口。// 类对象可以作为网络数据进行传输(RMI),将对象写入文件(缓存Redis)// 规则: 实体类必须实现Serializable。private Integer id;private String name;private String pass;private Integer age;}
2.2 转换流
● 转换流是字符流的子类。是字节流和字符流进行转换的桥梁。
● java.io.InputStreamReader,是Reader的子类,是从字节流到字符流的桥梁。
它读取字节,并使用指定的字符集将其解码为字符。它的字符集可以由名称指定,也可以接受平台的默认字符集。
○ InputStreamReader(InputStream in): 创建一个使用默认字符集的字符流。
○ InputStreamReader(InputStream in, String charsetName): 创建一个指定字符集的字符流
● 转换流java.io.OutputStreamWriter ,是Writer的子类,是从字符流到字节流的桥梁。
使用指定的字符集将字符编码为字节。它的字符集可以由名称指定,也可以接受平台的默认字符集。
○ OutputStreamWriter(OutputStream in): 创建一个使用默认字符集的字符流。
○ OutputStreamWriter(OutputStream in, String charsetName): 创建一个指定字符集的字符流。
2.2.1 InputStreamReader
需求: 下载网络上的小说资源。
//需求: 网络资源----> 字节形式处理-----> 字节流
//需求:
//app--->阅读小说---->关联很多小说资源----> 下载这些免费小说内容
//使用IO: 下载万维网中的小说的资源内容。private static void downloadNovel() throws IOException {String novelPath = "https://www.qidian.com/chapter/1037014329/752586881/";//是万维网的资源路径----> 资源指针 URL-----> 网络编程----->网络之间数据传输 java.net.*//所有的网络之间的数据传递 都是字节流、//URL url = new URL(novelPath);//System.out.println(url.getContent());//sun.net.www.protocol.http.HttpURLConnection$HttpInputStream@33990a0c//InputStream content = (InputStream) url.getContent();//InputStream inputStream = url.openStream();//想读取的内容全部都在inputStream里面//本来应该要使用创建inputStream对象读取资源//想使用高效字符输入流进行read//转换: 字节转换字符 InputStreamReader//小说内容存储到指定的磁盘文件中//读取网络小说内容 read---->输入流--->字节/字符输入流//将内容写入指定的文件中 write--->输出流--->字节/BufferedReader reader = new BufferedReader(new InputStreamReader(new URL(novelPath).openStream(), StandardCharsets.UTF_8));BufferedWriter writer = new BufferedWriter(new FileWriter("novel/1.txt"));//循环读写String line;while ((line = reader.readLine()) != null) {if (line.contains("<main data-type=\"cjk\"")) {line = line.substring(line.indexOf("<p>"), line.lastIndexOf("<p>"));line = line.replaceAll("<p>", "\n");
// line = line.replaceAll("<main\\s(.*)>","");writer.write(line);writer.newLine();break;}}writer.close();reader.close();System.out.println("success");}
2.2.2 OutputStreamWriter
private static void demo() throws IOException {//字符转字节//OutputStreamWriter//读取指定文件内容 在控制台打印输出(将内容写到控制台)。//读写@CleanupBufferedReader reader = new BufferedReader(new FileReader("src/novel.txt"));@CleanupBufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out));String line;while ((line = reader.readLine()) != null) {writer.write(line);//字符流write---> 字节流/打印流 write/printwriter.newLine();}
}
3.Properties
是一个集合类。 称属性集(属性名称-属性的数据) 还是map集合的实现类 key-value
3.1 常用方法
方法 | 描述 |
---|---|
void load(InputStream in) | 使用字节输入流加载文件内容 |
void load(Reader reader) | 使用字符流记载文件内容 |
String getProperty(String key) | 根据key获得value,key不存在,获得null。 |
String getProperty(String key,String default) | 根据key获得value,key不存在获得默认值。 |
Object setProperty(String key,String value) | 存储一组数组 |
void store(OutputStre out, String comment) | 将属性集对象中的数据写入输出流 |
3.2 使用方法
public static void main(String[] args) {//properties文件中 存储中文 可能会乱码 底层是latin(ISO8859-1)//idea可以配置properties文件的编码格式的//在开发中 禁止在配置文件中 配置中文的数据System.out.println(PropUtil.getValue("name"));}private static void demo4() {//普通流读取 c.properties//read 输入流//使用Properties读取文件数据//1.格式是 key=value//2.一行只能是一组数据Properties properties = new Properties();//加载配置文件数据try {properties.load(new FileInputStream("src/c.properties"));} catch (IOException e) {e.printStackTrace();}//map集合: {}//Collection集合: []System.out.println(properties);}private static void demo3() {//IO 读写//Properties 集成IO使用//1.Properties读取配置文件数据 存储Properties对象中//2.可以将Properties集合里面的所有数据 写入文件中Properties properties = new Properties();properties.setProperty("id", "1001");properties.setProperty("name", "admin");properties.setProperty("money", "2000D");//将数据写入文件中----> write ---->输出流//存储 store()//key=value//在后期学习中 配置文件 *.properties *.xml *.ymltry {properties.store(new FileOutputStream("src/c.properties"), "hsdgdsf");} catch (IOException e) {e.printStackTrace();}}
public class PropUtil {private static final Properties properties;//程序不停的前提下 没有必要读取多次配置文件数据//读取一次文件内容即可static {properties = new Properties();try {properties.load(new FileInputStream("src/info.properties"));} catch (IOException e) {e.printStackTrace();}}private PropUtil() {}public static String getValue(@NonNull String key) {return properties.getProperty(key, "");}
}