1.操作对象的流


作用:用于对象的持久化,序列化存储。


只能序列化堆中数据,也就是不包括方法和静态成员。
如果不想序列化某个堆中数据,用transient修饰。
能直接写入读取各种基本数据类型和Object类型数据。
ObjectInputStream:
Object readObject();如果文件中有多个对象,可以多次读取,直到为null为止,指针会自动移动。
ObjectOutputStream:
writeObject(Object);可以向一个文件中写入多个对象。
对象必须实现Serializable接口,该接口没有方法需要覆盖,这种类型接口一般称为标记接口,就是用于标记类能用于序列化。
类似于食品上的QS标记。
标记:
static final long serialVersionUID = 42L;
本质就是为了对象序列化方便,这是当我们从文件中读取到对象时与类相关联的标示,如果没有自定义该值,那么系统根据成员等算出一个值,因此当你修改了类时,就会得到不同的UID,也就不能将对象与类匹配上了,除非自定义个该值,使其固定为某一个值。


/*
对象的持久化序列化存储
1.写入使用ObjectOutputStream--writeObject(Object)
2.读取使用ObjectInputStream--Object readObject();

可以向一个文件中写入多个对象,读取时readObject方法自动移动指针。
存储到文件中的是对象在堆中的数据,也就是不包括方法和静态等方法区数据。

存取多个对象:
1.try catch捕获异常EOFException 
2.将多个对象存到数组或者集合中,再序列化到文件中
*/
import java.io.*;
import java.util.*;
class ObjectStreamDemo 
{
	public static void main(String[] args) throws IOException,ClassNotFoundException
	{
		writeObj();
		readObj();
	}
	private static void readObj()throws IOException,ClassNotFoundException
	{
		ObjectInputStream ois=new ObjectInputStream(new FileInputStream("person.txt"));
		//使用ObjectOutputStream写入的对象数据只能通过ObjectInputStream读取。

		//读取多个对象方式1:try catch捕获EOFException异常简单处理
		/*
		try
		{
			for(Object obj=ois.readObject();obj!=null;obj=ois.readObject())
			{
				((Person)obj).show();
			}
		}
		catch (EOFException e)
		{
			System.out.println("已到文件末尾");//给出已到文件末尾的提示,或者什么都不写
		}
		*/

		//方式1虽然也能解决问题,但是不建议那样使用,因为对异常的简单处理是不合适的
		Object obj=ois.readObject();
		ArrayList<Person> arr=(ArrayList<Person>)obj;
		for(Person p : arr)
		{
			p.show();
		}
		
	}
	private static void writeObj()throws IOException
	{
		ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("person.txt"));
		/*
		oos.writeObject(new Person("helong",22));
		//被序列化的对象必须是被标记的,也就是实现了Serializable接口。
		//该接口没有任何方法,只是作为标记存在。
		oos.writeObject(new Person("ldy",21));
		oos.writeObject(new Person("laowang",78));
		oos.close();
		*/

		//方式2,将对象存入集合,将集合序列化
		ArrayList<Person> arr=new ArrayList<Person>();
		arr.add(new Person("helong",22));
		arr.add(new Person("ldy",21));
		arr.add(new Person("laowang",56));

		oos.writeObject(arr);
		oos.close();
	}
}


运行图:

android 设置键盘高度_数组


android 设置键盘高度_数据_02



2.管道流


PipedInputStream,PipedOutputStream,PipedWriter,PipedReader
可以直接连接而不需要中转站(例如其他输入输出流交互时通常使用中间变量数组等),与多线程结合。


//测试管道流的使用
import java.io.*;
class Read implements Runnable
{
	private PipedInputStream in;
	Read(PipedInputStream in)
	{
		this.in=in;
	}
	public void run()
	{
		try
		{
			byte[] by=new byte[1024];
			System.out.println("正在等待读取数据......");
			int len = in.read(by);
			System.out.println("数据读取完毕,打印:");
			System.out.println(new String(by,0,len));
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
		finally
		{
			try
			{
				in.close();
			}
			catch (IOException e)
			{
			}
		}
	}
}
class Write implements Runnable
{
	private PipedOutputStream out;
	Write(PipedOutputStream out)
	{
		this.out=out;
	}
	public void run()
	{
		try
		{
			System.out.println("开始写入数据.....");
			out.write("数据:测试,,测试,,测试,,测试,,测试".getBytes());
			System.out.println("数据写入完毕");
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
		finally
		{
			try
			{
				out.close();
			}
			catch (IOException e)
			{
			}
		}
	}
}
class PipedStreamDemo 
{
	public static void main(String[] args) throws IOException
	{
		PipedInputStream in=new PipedInputStream();
		PipedOutputStream out=new PipedOutputStream();
		in.connect(out);

		new Thread(new Read(in)).start();
		new Thread(new Write(out)).start();
	}
}

运行图:

android 设置键盘高度_数据_03



3.特殊类RandomAccessFile


    随机访问文件,这是这个类的直译,根据Java见名知意的特点,我们知道这也就是这个类的功能特点。


特点:任意位置向文件中写入,或者从任意文件位置读取数据,通过设置指针。


该类不是IO体系的子类,它直接继承自Object,但是它是IO包的成员,因为其具备读写功能内部封装了一个数组,通过指针对数组中元素进行操作
它能完成读写原因就是内部封装了字节输入输出流。
内部将数据放到一个大型byte[]中。
getFilePointer:得到指针位置
seek(int):设置指针位置
skipBytes(int):向后跳过N个字节,不能往前
通过构造函数看出,它只能操作文件。通过mode参数"r","rw"来确定是只读还是读写
"r":如果文件不存在,则不会创建,直接报错。
"rw":如果文件不存在,则创建,如果存在,不会覆盖。

(为什么每次运行程序看着都好像是被覆盖了呢?这是因为每次指针都会回到文件开头,因此如果每次程序中的数据不更改的话是看不出变化的,我们可以第一次运行不做变化,第二次是先seek(100),再写入就能看到没有覆盖的效果了)
若模式为只读,不会创建文件,如果文件不存在,报异常。
若模式为读写,操作文件不存在,会创建,如果存在,不会覆盖。
作用:实现多线程下载(每个线程负责一整块,互不干扰,写入的时候写到指定的地方(随机))。

//RandomAccessFile测试
//小练习:结合多线程,在一个文件中分段写入数据
//例如:位置1位置2位置3位置1位置2位置3......
//用处:多线程下载,下载好之后,按照格式分段取出来就可以了
import java.io.*;
import java.util.*;
//线程1下载的内容放到文件的位置1处
/*
0	  20		40
60	  80		100
120 140     160
20字节为步长。
*/
class Load1 implements Runnable
{
	private RandomAccessFile raf=null;
	Load1(RandomAccessFile raf)
	{
		this.raf=raf;
	}
	public void run()
	{
		try
		{
			for(int i=0;i<100;i++)
			{
				raf.seek(60*i);
				byte[] by=new byte[16];
				by=Arrays.copyOf("(数据1)".getBytes(),16);
				raf.write(by);
				raf.writeInt(i);
			}
			raf.close();
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
		
	}
}
//线程2下载的内容放到文件的位置2处
class Load2 implements Runnable
{
	private RandomAccessFile raf=null;
	Load2(RandomAccessFile raf)
	{
		this.raf=raf;
	}
	public void run()
	{
		try
		{
			for(int i=0;i<100;i++)
			{
				raf.seek(60*i+20);
				byte[] by=new byte[16];
				by=Arrays.copyOf("(数据2)".getBytes(),16);
				raf.write(by);
				raf.writeInt(i);
			}
			raf.close();
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
	}
}
//线程3下载的内容放到文件的位置3处
class Load3 implements Runnable
{
	private RandomAccessFile raf=null;
	Load3(RandomAccessFile raf)
	{
		this.raf=raf;
	}
	public void run()
	{
		try
		{
			for(int i=0;i<100;i++)
			{
				raf.seek(60*i+40);
				byte[] by=new byte[16];
				by=Arrays.copyOf("(数据3)".getBytes(),16);
				raf.write(by);
				raf.writeInt(i);
			}
			raf.close();
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
	}
}
class RandomAccessDemo 
{
	public static void main(String[] args) throws IOException
	{
		//write();
		//read();
		//load();
		test_load();
	}
	private static void read()throws IOException
	{
		//该类只能操作文件,可以设置模式
		RandomAccessFile raf=new RandomAccessFile("random.txt","r");
		
		for(int i=0;i<raf.length();i+=20)
		{
			raf.seek(i);//以20个字节为步长读取数据
			byte[] by=new byte[16];
			raf.read(by);
			int age=raf.readInt();
			System.out.println("姓名:"+new String(by)+"年龄:"+age);
		}
	}
	private static void write()throws IOException
	{
		RandomAccessFile raf=new RandomAccessFile("random.txt","rw");
		BufferedReader br=new BufferedReader(new FileReader("randomCount.txt"));
		//BufferedWriter bw=new BufferedWriter(new FileWriter("randomCount.txt"));
		//该语句不能放在这里,应该放下面,因为如果放在这里在打开文件randomCount.txt时会采用覆盖的方式,那么br就取不到其中的值了。
		int count=Integer.parseInt(br.readLine());
		BufferedWriter bw=new BufferedWriter(new FileWriter("randomCount.txt"));
		bw.write(count+1+"");
		raf.seek(20*count);//指针移动单位为字节,根据此处规定:姓名用16个字节,年龄为int用4个字节,供20个字节一个人。
		byte[] by=new byte[16];//规定名字用16个字节表示
		by=Arrays.copyOf("何龙123".getBytes(),16);
		raf.write(by);
		raf.writeInt(232);//write方法虽然接收int,但是只是写入int的低8位,因此容易数据丢失
		//观察文件看到,姓名后的年龄位占用两个位置,也就是四个字节,正式int大小
		raf.close();
		br.close();
		bw.close();
	}
	//多线程下载练习
	private static void load()throws FileNotFoundException
	{
		RandomAccessFile raf1=new RandomAccessFile("load.txt","rw");
		RandomAccessFile raf2=new RandomAccessFile("load.txt","rw");
		RandomAccessFile raf3=new RandomAccessFile("load.txt","rw");
		new Thread(new Load1(raf1)).start();
		new Thread(new Load2(raf2)).start();
		new Thread(new Load3(raf3)).start();
	}
	//从多线程下载文件中分批取出数据
	private static void test_load()throws IOException
	{
		RandomAccessFile raf=new RandomAccessFile("load.txt","r");

		for(int k=0;k<=40;k+=20)
		{
			for(int i=0;(60*i+k)<raf.length();i++)
			{
				raf.seek(60*i+k);
				byte[] by=new byte[16];
				raf.read(by);
				int age=raf.readInt();
				System.out.println(new String(by)+"...."+age);
			}
		}
	}
}

运行图:

android 设置键盘高度_java_04

android 设置键盘高度_数据_05



4.操作基本数据类型的流对象


DataOutputStream,DataInputStream
    凡是操作基本数据类型就是用这两个类,由于是读取,非常方便。


/*
操作基本数据类型的流对象
writeInt,writeBoolean....
writeUTF:以UTF-8形式写入数据,也就只能使用readUTF读取
*/
import java.io.*;
class DataStreamDemo 
{
	public static void main(String[] args) throws IOException
	{
		write();
		read();
		writeUTF_8();
		readUTF_8();
		writeUTF();
		
	}

	private static void writeUTF()throws IOException
	{
		//使用UTF,GBK等指定编码表写入数据需要用到转换流
		OutputStreamWriter osw=new OutputStreamWriter(new FileOutputStream("utf.txt"),"utf-8");

		osw.write("何龙");
		osw.write("小小红");
		osw.write("小明");

		osw.close();
	}

	private static void readUTF_8()throws IOException
	{
		DataInputStream dis=new DataInputStream(new FileInputStream("utf-8.txt"));

		sop(dis.readUTF());
		sop(dis.readUTF());
		sop(dis.readUTF());

		dis.close();
	}

	private static void writeUTF_8()throws IOException
	{
		DataOutputStream dos=new DataOutputStream(new FileOutputStream("utf-8.txt"));

		dos.writeUTF("何龙");
		dos.writeUTF("小小红");
		dos.writeUTF("小明");

		dos.close();
	}
	private static void read()throws IOException
	{
		DataInputStream dis=new DataInputStream(new FileInputStream("data.txt"));

		//怎么写的,就怎么读
		sop(dis.read());
		sop(dis.read());
		sop(dis.read());
		sop(dis.readInt());
		sop(dis.readBoolean());
		sop(dis.readDouble());

		dis.close();
		
	}
	private static void write()throws IOException
	{
		//可以想象,该对象内部是封装了与数据有关的方法,也就是说
		//还需要一个流就OK了,因此可知构造函数就是传入一个流对象
		DataOutputStream dos=new DataOutputStream(new FileOutputStream("data.txt"));

		dos.write(255);//写入的是255的最低八位
		dos.write(256);
		dos.write(-127);//该方法无法正确的写入读取负数
		dos.writeInt(123);
		dos.writeBoolean(true);
		dos.writeDouble(234.234);

		dos.close();
	}
	private static void sop(Object obj)
	{
		System.out.println(obj);
	}
}

运行图:

android 设置键盘高度_数组_06

android 设置键盘高度_数据_07



5.操作字节数组的流对象

   

ByteArrayOutputStream,ByteArrayInputStream


    特点:

ByteArrayOutputStream :构造函数不需要参数(也可以指定缓冲区大小)

目的为该对象内部封装了的一个数组中

         

ByteArrayInputStream :构造函数需要传入一个byte数组作为源

  不操作任何系统资源:1.不需要关闭(关了也能使用)2.不会报IO异常(除了writeTo方法)
    本质:将数据从源数组导入目的数组中。
    意义:1.很多操作封装好了,使用方便。2.使用流操作的思想来操作数组。

    特别方法:

writeTo(OutputStream)将数组中数据一次性写入一个输出流


//操作字节数组的流对象
//特殊方法:writeTo
//特点:源和目的均为内存数组
//无需关闭,不会报IO异常,除了writeTo方法
//好处:1.对数组的操作被封装好了,使用方便2.使用流的思想操作数组
import java.io.*;
class ByteArrayStreamDemo 
{
	public static void main(String[] args) 
	{
		ByteArrayInputStream bis=new ByteArrayInputStream("何龙最帅!!!".getBytes());//字节数组为参数

		ByteArrayOutputStream bos=new ByteArrayOutputStream();//无需构造参数

		for(int ch=bis.read();ch!=-1;ch=bis.read())
		{
			bos.write(ch);//将数据写入buf
		}

		System.out.println(bos.size());
		System.out.println(bos.toString());
		try
		{
			bos.writeTo(new FileOutputStream("writeTo.txt"));
		}
		catch (IOException e)
		{
		}
	}
}

运行图:

android 设置键盘高度_java_08



6.操作字符数组的流对象

CharArrayReader(需要一个字符数组作为源)

CharArrayWriter(封装了一个字符数组作为目的)

7.操作字符串的流对象

   

StringReader

(需要一个字符串为源)

StringWriter

(封装了一个字符串为目的)



方法,操作等与操作字节数组的流对象一致。


8.字符编码

码表:
1.ASCII
2.ISO8859-1
3.GB2312
4.GBK(两个字节表示一个中文)
5.unicode
6.UTF-8(
最多三个字节表示一个中文)

IO流中跟字符编码有关的就是转换流了:
OutputStreamWriter,InputStreamReader在它们的构造函数中,可以指定编码表
如果不指定,默认为GBK编码,如果使用默认,那么就等价于FileWriter,FileReader类
编码:字符串变成字节数组。str.getBytes(charsetName);
解码:字节数组变成字符串。new String(byte[],charsetName);

如果编码时指定了错误的编码表,那么就无法正确解码,例如:有中文的文件,指定了iso编码。
如果编码时指定正确编码表,解码时指定了错误的编码表呢?
情况:1.使用GBK编码"何龙",得到[-70,-50,-63,-6]
     2.使用iso解码[-70,-50,-63,-6],得到????
解决:1.使用iso编码????,得到[-70,-50,-63,-6]
     2.使用GBK解码[-70,-50,-63,-6],得到"何龙"
例外:如果使用GBK编码,使用U8解码,无法通过该方法得到正确字符串。

原因:U8也能识别中文,它不会简单的保存原文的字节到文件中,而是到未知码区去比对,找到最接近的就存储进来,这样一来存储的字节本来都是错的。
联通问题:
记事本默认使用GBK编码"联通"得到的四个字节的开头二进制位均符合U8编码的字节头规则,因此被记事本误以为是
用U8编码,因此它使用U8解码,因此得到乱码。
解决问题:
本质:添入别的字符,使得某些字节不符合U8的字节头规则,因此就不会被误以为是U8编码。

U8如何决定之后的几个字节代表一个字符(UTF-8中代表一个字符可以是一个,两个或者三个字节)?
字节头规则:
一个字节存储字符:0--- ----
两个字节存储字符:(1)110- ---- (2)10-- ----
三个字节存储字符:(1)1110 ---- (2)10-- ---- (3)10-- ----

//字符编码
import java.io.*;
class EncodeDemo 
{
	public static void main(String[] args) throws IOException
	{
		//GBK写入
		//OutputStreamWriter osw=new OutputStreamWriter(new FileOutputStream("gbk.txt"),"GBK");
		//UTF-8写入
		OutputStreamWriter osw=new OutputStreamWriter(new FileOutputStream("utf8.txt"),"UTF-8");
		//上句等价于FileWriter fw=new FileWriter("gbk.txt");

		
		//GBK读取
		//InputStreamReader isr=new InputStreamReader(new FileInputStream("gbk.txt"),"GBK");
		//UTF-8读取
		InputStreamReader isr=new InputStreamReader(new FileInputStream("utf8.txt"),"UTF-8");
		//上句等价于FileReader fr=new FileReader("gbk.txt");

		osw.write("何龙");
		osw.close();

		char[] chs=new char[1024];
		int len=isr.read(chs);
		System.out.println(new String(chs,0,len));

		isr.close();
	}
}

运行图:

android 设置键盘高度_java_09


//编码解码情况测试
import java.util.*;
class EncodeDemo2 
{
	public static void main(String[] args) throws Exception
	{
		String str="何龙";

		//解码
		//byte[] b1=str.getBytes();
		//byte[] b1=str.getBytes("utf-8");
		//byte[] b1=str.getBytes("iso8859-1");//如果编码就指定了错误码表(此处iso无法识别中文),那么是无法正确解码的。
		
		//编码正确,解码错误的情况
		byte[] b1=str.getBytes();
		System.out.println(Arrays.toString(b1));//GBK[-70,-50,-63,-6],U8[-28,-67,-107,-23,-66,-103]
		
		//编码
		//String result1=new String(b1);
		//String result1=new String(b1,"utf-8");
		String result1=new String(b1,"iso8859-1");
		System.out.println("str="+result1);

		//解决方法
		//虽然解码错了,但是字节本身是正确的,因此我们将得到的错误字符反向编码
		//得到正确的字节,再用这些字节利用正确编码表解码就可以得到正确的字符
		byte[] by=result1.getBytes("iso8859-1");//得到正确字节数组
		System.out.println(Arrays.toString(by));
		String right=new String(by,"GBK");
		System.out.println("right="+right);

		//这种方法不能解决的问题
		//例如使用GBK编码,使用U8解码,无法利用编码解码来还原为正确字符
		//原因:U8识别中文,它不会原样保留GBK编码得到的字节,而是去查找这些字节,
		//没在正常码表中找到,就到未知码表中查找相似的,就会返回一些奇怪的字节数据
		//对应的字符们,因此此时的字节已经不是通过GBK编码得到的了,也就无法还原到
		//正确的字符了。
		

	}
}

运行图:

android 设置键盘高度_android 设置键盘高度_10


/*
联通问题:
当我们在记事本中写入"联通"时,
保存,关闭,再打开发现就变成了乱码
这是为何呢?
我们知道所谓乱码就是编码和解码不是同一
张码表。
*/
class LianTong 
{
	public static void main(String[] args) throws Exception
	{
		//分析联通原因
		String str="联通";
		byte[] by=str.getBytes("GBK");
		for(byte b : by)
		{
			System.out.println(Integer.toBinaryString(b&255));
			/*
			<strong><span style="color:#ff0000;">我们发现联通这两个字的四个字节的开头二进制位符合U8表的字节头规则
			因此当记事本发现时,它就认为这是由U8编码的,因此它使用U8解码,因此
			就得到了乱码
			解决这个很简单,如果我们在文件中添加别的中文,那么就不存在这个问题了
			本质:让先出现的字节的头不符合U8的字节头规则,那么记事本就不会使用U8
			解码</span></strong>
			*/
		}
	}
}

运行图:

android 设置键盘高度_android 设置键盘高度_11



/*
需求:
将5个学生信息用键盘输入:name,语文成绩,数学成绩,英语成绩
将这些数据按总分从大到小排列存入文件stud.txt中

思路:
1.接收键盘输入一行数据,分割得到信息,存入对象中
2.由于需要处理多个数据,因此需要集合,需要排序因此使用TreeSet
3.将数据写入文件中

源:
a.内存键盘  b.纯文本  c.设备键盘
FileInputStream
目的
a.硬盘文件  b.纯文本  c.设备硬盘文件
FileOutputStream

对学生进行排序时,主要条件是成绩,次要是姓名。
*/
import java.io.*;
import java.util.*;

class Student implements Comparable<Student>
{
	private String name;
	private double chinese;
	private double math;
	private double english;
	Student(String name,double chinese,double math,double english)
	{
		this.name=name;this.chinese=chinese;this.math=math;this.english=english;
	}
	public int compareTo(Student s)
	{
		if(this.getSum()>s.getSum())return 1;
		else if(this.getSum()<s.getSum())return -1;
		return this.getName().compareTo(s.getName());
	}
	public double getSum()
	{
		return chinese+math+english;
	}
	public double getCh()
	{return chinese;}
	public double getMa()
	{return math;}
	public double getEn()
	{return english;}
	public String getName()
	{
		return name;
	}
	public String toString()
	{
		return "Name:"+name+"\tChinese:"+chinese+"\tMath:"+math+"\tEnglish:"+english;
	}
	
	//定义类该具有的方法
	public int hashCode()
	{
		System.out.println("hashcode");
		return name.hashCode()+(int)(math*13);
	}
	public boolean equals(Object obj)
	{
		System.out.println("equals");
		if(!(obj instanceof Student))throw new ClassCastException("类型不匹配");
		Student temp=(Student)obj;
		return this.name.equals(temp.getName())&&this.getSum()==temp.getSum();
	}
	
}

class IOTest 
{
	public static void main(String[] args) throws IOException
	{
		writeStudentInfo();
	}
	private static void writeStudentInfo() throws IOException
	{
		//读取键盘的固定写法
		BufferedReader fr=new BufferedReader(new InputStreamReader(System.in));
		
		BufferedWriter fw=new BufferedWriter(new OutputStreamWriter(new FileOutputStream("stud.txt")));

		//需求是按照成绩从大到小排序,也就是与自然顺序相反,可以使用Collections.reverseOrder方法
		Comparator<Student> com=Collections.reverseOrder();
		TreeSet<Student> set=new TreeSet<Student>(com);
		//输入以over结尾
		for(String data = fr.readLine();data!=null&&!"over".equals(data);data=fr.readLine())
		{
			String[] info=data.split(",");
			set.add(new Student(info[0],new Double(info[1]),new Double(info[2]),new Double(info[3])));
		}
		for(Student temp : set)
		{
			fw.write(temp.toString());
			fw.newLine();
			fw.flush();//带缓冲区的一定要flush
		}

		fr.close();
		fw.close();
	}
}

运行图:

android 设置键盘高度_java_12

android 设置键盘高度_java_13