ObjectStream
操作对象
ObjectInputStream和ObjectOutputStream
被操作的对象需要实现Serializable(标记接口)
ObjectOutputStream 将 java 对象的基本数据类型和图形写入OutputStream。可以使用ObjectInputStream读取(重构)对象,通过在流中使用文件可以实现对象的持久存储。
java会根据对象自定义一个UID值将其序列化,随着对象的更改,UID值也会变化,导致前后无法相关联 读不出数据。根据这个情况, 可以自定义UID值,可以保证更改对象后依然能读出数据。
public static final long serialVersionUID = 42L;
另外,静态不能被序列化,因为静态在方法区中先于对象加载
static String country = “CN”;
如果对于非静态成员也不想序列化,可以transient修饰
transient int age;
transient修饰age会打印出age = 0
transient修饰name会打印出name = null
package day21;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class ObjectStreamDemo {
public static void main(String[] args) throws Exception{
//writeObj();
readObj();
}
public static void readObj()throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("obj.txt"));
Person p = (Person)ois.readObject();
System.out.println(p);
ois.close();
}
public static void writeObj()throws IOException{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("obj.txt"));
oos.writeObject(new Person("lisi",39,"kr"));
oos.close();
}
}
package day21;
import java.io.Serializable;
public class Person implements Serializable{
// 自定义UID值,可以保证更改对象后依然能读出
public static final long serialVersionUID = 42L;
private String name;
int age;
static String country = "CN";
public Person(String name, int age,String country) {
this.name = name;
this.age = age;
this.country = country;
}
public String toString(){
return name+"..."+age+"....."+country;
}
}
管道流
管道流分为管道输入流(PipedInputStream)和管道输出流(PipedOutputStream)
管道输入流应该连接到管道输出流,管道输入流提供要写入管道输出流的所有数据字节。通常,数据由某个从PipedInputStream对象读取,并由其他线程将其写入到相应的PipedOutputStream
不建议对这两个对象尝试单线程,因为这样可能会死锁线程。要分别用一个线程负责一个管道流,当一个处于等待的情况时, 另一个线程可以运行不会死锁
这是涉及了多线程的IO流
(涉及了集合的IO流是Properties)
package day21;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
public class PipedStream {
public static void main(String[] args) throws IOException {
PipedInputStream pis = new PipedInputStream();
PipedOutputStream pos = new PipedOutputStream();
pis.connect(pos);
Read r = new Read(pis);
Write w = new Write(pos);
new Thread(r).start();
new Thread(w).start();
}
}
class Read implements Runnable {
private PipedInputStream pis;
Read(PipedInputStream pis) {
this.pis = pis;
}
public void run() {
try {
byte[] buf = new byte[1024];
System.out.println("没有数据可以读取,先阻塞");
int len = pis.read(buf);
System.out.println("读到数据,阻塞结束");
String s = new String(buf, 0, len);
System.out.println(s);
pis.close();
} catch (IOException e) {
throw new RuntimeException("管道读取失败");
}
}
}
class Write implements Runnable {
private PipedOutputStream pos;
Write(PipedOutputStream pos) {
this.pos = pos;
}
public void run() {
try {
System.out.println("等待6s后写入数据");
Thread.sleep(6000);
pos.write("piped laile".getBytes());
pos.close();
} catch (Exception e) {
throw new RuntimeException("管道输出失败");
}
}
}
RandomAccessFile-随机访问文件
该类不是IO中的体系的子类,而是直接继承自Object。但是它是IO包中的成员,因为它具备读和写的功能 内部封装了一个数组,而且通过指针对数组的元素进行操作,可以通过getFilePointer获取指针位置,同时可以通过seek改变指针的位置。
其实完成读写的原理就是内部封装了字节输入流和字节输出流。
通过构造函数,可以看出该类只能操作文件。 而且操作文件还有模式:r或者rw等
如果模式为只读r,不会创建文件,会去读取一个已存在的文件,若该文件不存在,报出异常
如果模式为rw,可以创建文件。该对象的构造函数要操作的文件不存在,会自动创建,如果存在则不会覆盖
应用:多线程的分段下载,每个线程负责不同的区域下载
package day21;
import java.io.IOException;
import java.io.RandomAccessFile;
public class RandomAccessFileDemo {
public static void main(String[] args) throws IOException {
//writeFile_2();
//writeFile();
readFile();
}
public static void readFile() throws IOException {
RandomAccessFile raf = new RandomAccessFile("ran.txt", "r");
// 调整指针的位置
// raf.seek(8);
// 跳过指定的字节数
raf.skipBytes(8);
byte[] buf = new byte[4];
raf.read(buf);
String name = new String(buf);
int age = raf.readInt();
System.out.println("name=" + name);
System.out.println("age=" + age);
System.outprintln(raf.getFilePointer());
raf.close();
}
public static void writeFile_2() throws IOException {
RandomAccessFile raf = new RandomAccessFile("ran.txt", "rw");
raf.seek(8 * 0);
// 会覆盖原来起始位置上的数据。李四被赵柳覆盖。
raf.write("赵柳".getBytes());
raf.writeInt(101);
raf.close();
}
public static void writeFile() throws IOException {
RandomAccessFile raf = new RandomAccessFile("ran.txt", "rw");
raf.write("李四".getBytes());
raf.writeInt(97);
raf.write("王五".getBytes());
raf.writeInt(99);
raf.close();
}
}
DataStream:操作基本数据类型的流对象
package day21;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class DataStreamDemo {
/**
* DataInputStream和DataOutputStream 可以用于操作基本数据类型的数据的流对象
*
* 记住:凡是操作基本数据类型,就用DataStream。
*
*/
public static void main(String[] args) throws IOException {
// readData();
// writeData();
// writeUTFDemo();
readUTFDemo();
}
public static void readUTFDemo() throws IOException {
DataInputStream dis = new DataInputStream(new FileInputStream(
"utfdata.txt"));
// 用writeUTF写的文件必须要用readUTF读取。
// 也必须用readUTF读writeUTF写出的文件。
// 否则会报出EOFException - 如果此流在读取所有字节之前到达末尾。
// 因为以打印“你好”为例,utf-8是6个字节,UTF-8 修改版是8个字节。
// 所以用readUTF读取utf-8格式的文件会导致8个字节没读完就到达末尾。
String s = dis.readUTF();
System.out.println(s);
dis.close();
}
public static void writeUTFDemo() throws IOException {
DataOutputStream dos = new DataOutputStream(new FileOutputStream(
"utfdata.txt"));
dos.writeUTF("你好");
// 与普通的UTF-8不同,以打印“你好”为例,utf-8是6个字节,gbk是4个字节,UTF-8 修改版是8个字节
dos.close();
}
public static void readData() throws IOException {
DataInputStream dis = new DataInputStream(new FileInputStream(
"data.txt"));
// 读取的顺序与写入的顺序要一致,否则会乱码
int num = dis.readInt();
boolean b = dis.readBoolean();
double d = dis.readDouble();
System.out.println(num);
System.out.println(b);
System.out.println(d);
dis.close();
}
public static void writeData() throws IOException {
DataOutputStream dos = new DataOutputStream(new FileOutputStream(
"data.txt"));
dos.writeInt(2324);
dos.writeBoolean(true);
dos.writeDouble(234.65);
dos.close();
}
}
ByteArrayStream
用于操作字节数组的流对象
ByteArrayInputStream:在构造的时候,需要接受一个数据源,而且数据源是一个字节数组
ByteArrayOutputStream:在构造的时候,不用定义数据目的,因为该对象中已经内部封装了可变长度的字节数组。这就是数据目的。
因为两个流对象都操作数组,没有使用系统资源,不用close关闭
这两个类中的方法在关闭此流后仍可被调用,而不会产生任何 IOException。
在流操作规律讲解时,
源设备:
键盘System.in,硬盘FileStream,内存ArrayStreeam
目的设备:
控制台System.out,硬盘FileStream,内存ArrayStream
package day21;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
public class ByteArrayStream {
public static void main(String[] args) {
// 数据源
ByteArrayInputStream bis = new ByteArrayInputStream("abcdef".getBytes());
// 数据目的
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int by = 0;
while ((by = bis.read()) != -1) {
bos.write(by);
}
System.out.println(bos.size());
// 把byte数组转成字符串,并变成大写格式
System.out.println(bos.toString().toUpperCase());
}
}
// 另外还有:
// 操作字符数组:
// CharArrayReader:需要抛出IOException,用的是toCharArray()的方法
CharArrayReader car = new CharArrayReader("abcdef".toCharArray());
// CharArrayWrite:关闭无效,不会抛出IOException
CharArrayWriter caw = new CharArrayWriter();
int by = 0;
while ((by = car.read()) != -1) {
caw.write(by);
}
//需要toString()后才能使用toUpperCase方法
System.out.println(caw.size());
System.out.println(caw.toString().toUpperCase());
car.close();
//操作字符串:
// StringReader:需要抛出IOException。
StringReader sr = new StringReader("abcdef");
// StringWriter:关闭无效,不会抛出IOException
StringWriter sw = new StringWriter();
int by = 0;
while ((by = sr.read()) != -1) {
sw.write(by);
}
//需要toString()后才能使用.length方法,toUpperCase方法
System.out.println(sw.toString().length());
System.out.println(sw.toString().toUpperCase());
sr.close();
字符编码
字符流的出现为了方便操作字符。
更重要的是加入了编码转换。
通过子类转换流来完成
InputStreamReader:是字节流通向字符流的桥梁
OutputStreamWriter:是字符流通向字节流的桥梁
在两个对象构造的时候加入了字符集。
package day21;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
public class EncodeStream {
public static void main(String[] args) throws IOException{
//writeText();
readText();
}
public static void readText()throws IOException{
InputStreamReader isr = new InputStreamReader(new FileInputStream("utf.txt"),"gbk");
// 用gbk读取utf-8编写的文件,“你好”打印出了“浣犲ソ”
// 用utf-8读取gbk编写的文件,“你好”打印出了“??”
char[] buf = new char[10];
int len = isr.read(buf);
String s = new String(buf,0,len);
System.out.println(s);
isr.close();
}
public static void writeText()throws IOException{
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("utf.txt"),"utf-8");
osw.write("你好");
osw.close();
}
}
编码与解码
编码:字符串变成字节数组
解码:字节数组变成字符串
String——>byte[]: str.getBytes(charsetName);
byte[]——>String: new String(byte[],charsetName);
package day21;
import java.io.IOException;
import java.util.Arrays;
public class EncodeDemo {
public static void main(String[] args) throws IOException {
String s = "你好";
byte[] b1 = s.getBytes("GBK");
System.out.println("b1=" + Arrays.toString(b1));
String s1 = new String(b1, "ISO8859-1");
System.out.println("s1=" + s1);
//若解码时用错误的表解码错误,可以通过解码所用码表转换回原来的字节数组,再通过这个字节数组按照正确的编码表解码
//gbk与utf-8之间不行,因为utf-8也包含汉字,会出现乱码,变不回来
byte[] b2 = s1.getBytes("ISO8859-1");
System.out.println("b2=" + Arrays.toString(b2));
String s2 = new String(b2, "GBK");
System.out.println("s2=" + s2);
}
}
联通的现象
package day21;
import java.io.IOException;
import java.util.Arrays;
public class EncodeDemo2 {
public static void main(String[] args) throws IOException{
String s = "联通";
byte[] b1 = s.getBytes();
System.out.println("by="+Arrays.toString(b1));
String s1 = new String(b1);
System.out.println("s1="+s1);
}
}
而在记事本中写入“联通”,重新打开却是“��ͨ”。因为“联通”在gbk码中的表现形式与UTF-8编码规律相同。打开时记事本启用了UTF-8码表,造成乱码。
附:UTF-8表编码格式
终极练习:录入学生成绩
有五个学生,每个学生有三门课的成绩,
从键盘输入以上数据(包括姓名,三门课成绩)
输入的格式:张三,30,40,50,计算出总成绩
1,描述学生对象
2,定义一个可操作学生对象的工具类
思想:
1,通过获取键盘录入一行数据,并将该行中的信息取出封装成学生的对象。
2,因为学生有很多,那么就需要存储,使用到集合,因为要对学生的总分排序。 所以可以使用TreeSet
3,将集合信息写入到文件中。
package day21;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Collections;
import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;
public class StudentInfoTest {
public static void main(String[] args) throws IOException{
Comparator<Student> cmp = Collections.reverseOrder();
Set<Student> stus = StudentInfoTool.getStudent(cmp);
StudentInfoTool.write2File(stus);
}
}
class Student implements Comparable<Student>{
private String name;
private int Math,Chinese,English;
private int sum;
public Student(String name, int Math, int Chinese, int English) {
this.name = name;
this.Math = Math;
this.Chinese = Chinese;
this.English = English;
sum = Math+Chinese+English;
}
public int compareTo(Student s){
int num = new Integer(this.sum).compareTo(new Integer(s.sum));
if(num==0)
return this.name.compareTo(s.name);
return num;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getSum() {
return sum;
}
public void setSum(int sum) {
this.sum = sum;
}
public int hashCode(){
return name.hashCode()+sum*78;
}
public boolean equals(Object obj){
if(!(obj instanceof Student))
throw new ClassCastException("输入不是学生");
Student s = (Student)obj;
return this.name.equals(s.name) && this.sum == s.sum;
}
public String toString(){
return "Student["+name+","+Math+","+Chinese+","+English+"]";
}
}
class StudentInfoTool{
public static Set<Student> getStudent()throws IOException{
return getStudent(null);
}
public static Set<Student> getStudent(Comparator<Student> cmp)throws IOException{
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
String line = null;
Set<Student> stus = null;
if(cmp==null)
stus = new TreeSet<Student>();
else
stus = new TreeSet<Student>(cmp);
while((line=bufr.readLine())!=null){
if("over".equals(line))
break;
String[] info = line.split(",");
Student stu = new Student(info[0],Integer.parseInt(info[1]),Integer.parseInt(info[2]),Integer.parseInt(info[3]));
stus.add(stu);
}
bufr.close();
return stus;
}
public static void write2File(Set<Student> stus) throws IOException{
BufferedWriter bufw = new BufferedWriter(new FileWriter("StudentInfo.txt"));
for(Student stu:stus){
bufw.write(stu.toString()+"\t");
bufw.write(stu.getSum()+"");
bufw.newLine();
bufw.flush();
}
bufw.close();
}
}