输入/输出(二)

推回输入流

在输入/输出流体系中,有两个特殊的流与众不同,就是PushbackReader和PushbackInputStream:
在这里插入图片描述
这两个推回输入流都带有一个推回缓冲区,当程序调用这两个推回输入流的unread()方法时,系统将会把指定数组的内容推回到该缓冲区里,而推回输入流每次调用read()方法时总是先从推回缓冲区读取,只有完全读取了推回缓冲区的内容后,但还没有装满read()所需的数组时才会从原输入流中读取。
在这里插入图片描述
当程序创建一个PushbackReader和PushbackInputStream时需要指定推回缓冲区的大小,默认的推回缓冲区的长度为1。如果程序中推回缓冲区的内容超出了推回缓冲区的大小,将会引发Pushback buffer overflow的IOException异常。
PushbackTest.java

public class PushbackTest
{
	public static void main(String[] args) 
	{		 
		try(PushbackReader pr = new PushbackReader(new FileReader("PushbackTest.java") , 64);)		
		{
			//创建一个PushbackReader对象,指定推回缓冲区的长度为64						
			char[] buf = new char[32];
			//用以保存上次读取的字符串内容
			String lastContent = "";
			int hasRead = 0;
			//循环读取文件内容
			while ((hasRead = pr.read(buf)) > 0)
			{
				//将读取的内容转换成字符串
				String content = new String(buf , 0 , hasRead);
				int targetIndex = 0;
				//将上次读取的字符串和本次读取的字符串拼起来,查看是否包含目标字符串
				//如果包含目标字符串
				if ((targetIndex = (lastContent + content).indexOf("new PushbackReader")) > 0)
				{
					//将本次内容和上次内容一起推回缓冲区
					pr.unread((lastContent + content).toCharArray());
					//再次读取指定长度的内容(就是目标字符串之前的内容)
					pr.read(buf , 0 , targetIndex);
					//打印读取的内容
					System.out.print(new String(buf , 0 ,targetIndex));
					System.exit(0);
				}
				else
				{
					//打印上次读取的内容
					System.out.print(lastContent);
					//将本次内容设为上次读取的内容
					lastContent = content;
				}
			}
		}
		catch (IOException ioe)
		{
			ioe.printStackTrace();
		}		
	}
}

重定向标准输入/输出

Java的标准输入/输出分别通过System.in和System.out来代表,在默认的情况下它们分别代表键盘和显示器,当程序通过System.in来获取输入时,实际上是从键盘读取输入;当程序试图通过System.out执行输出时,程序总是输出到屏幕;
在System类里提供了如下三个重定向标准输入/输出的方法:

  1. static void setErr(PrintStream err):重定向“标准”错误输出流;
  2. static void setIn(InputStream in):重定向“标准”输入流;
  3. static void setOut(PrintStream out):重定向“标准”输出流;

RedirectOut.java

public class RedirectOut
{
	public static void main(String[] args) 
	{		
		try(PrintStream ps = new PrintStream(new FileOutputStream("out.txt"));)    //创建PrintStream输出流
		{		
		   //将标准输出重定向到ps输出流				
			System.setOut(ps);
			//向标准输出输出一个字符串
			System.out.println("普通字符串");
			//向标准输出输出一个对象
			System.out.println(new RedirectOut());
		}
		catch (IOException ex)
		{
			ex.printStackTrace();
		}		
	}
}

在这里插入图片描述
RedirectIn.java

public class RedirectIn
{
	public static void main(String[] args) 
	{
		
		try(FileInputStream fis = new FileInputStream("RedirectIn.java"))
		{			
			//将标准输入重定向到fis输入流
			System.setIn(fis);
			//使用System.in创建Scanner对象,用于获取标准输入
			Scanner sc = new Scanner(System.in);
			//增加下面一行将只把回车作为分隔符
			sc.useDelimiter("\n");
			//判断是否还有下一个输入项
			while(sc.hasNext())
			{
				//输出输入项
				System.out.println("键盘输入的内容是:" + sc.next());
			}

		}
		catch (IOException ex)
		{
			ex.printStackTrace();
		}		
	}
}

运行,程序不会等待用户输入,而是直接输出了RedirectIn.java文件的内容,这表明程序不再使用键盘作为标准输入,而是使用RedirectIn.java作为标准输入源。

RandomAccessFile

RandomAccessFile是Java输入/输出流体系中功能最丰富的文件内容访问类,它提供了众多的方法来访问文件内容,它既可以读取文件内容,也可以向文件输出数据。与普通的输入/输出流不同的是,RandomAccessFile支持“随机访问”的形式,程序可以直接跳转到文件的任意地方来读写数据。

由于 RandomAccessFile可以自由访问文件的任意位置,所以如果只需要访问文件部分内容,而不是把文件从头都到尾,使用RandomAccessFile将是更好的选择。

RandomAccessFile允许自由定位文件记录指针,RandomAccessFile可以不从开始的地方开始输出,因此RandomAccessFile可以向已存在的文件后追加内容。如果程序需要向已存在的文件后追加内容,则应该使用RandomAccessFile。
注:RandomAccessFile的方法虽多,但它有一个很大的局限,就是只能读写文件,不能读写其他IO节点。
在这里插入图片描述
在这里插入图片描述
RandomAccessFileTest.java

public class RandomAccessFileTest
{
	public static void main(String[] args) 
	{	
		    //以只读方式打开一个RandomAccessFile对象
		try(RandomAccessFile raf = new RandomAccessFile("RandomAccessFileTest.java" , "r");)
		{						
			//获取RandomAccessFile对象文件指针的位置,初始位置是0
			System.out.println("RandomAccessFile的文件指针的初始位置:" 
				+ raf.getFilePointer());
			//移动raf的文件记录指针的位置
			raf.seek(300);
			byte[] bbuf = new byte[1024];
			//用于保存实际读取的字节数
			int hasRead = 0;
			//使用循环来重复“取水”过程
			while ((hasRead = raf.read(bbuf)) > 0 )
			{
				//取出“竹筒”中水滴(字节),将字节数组转换成字符串输出!
				System.out.print(new String(bbuf , 0 , hasRead ));
			}
		}
		catch (IOException ex)
		{
			ex.printStackTrace();
		}		
	}
}

在这里插入图片描述
程序将从300字节处开始读。运行,看到程序只读取后面部分的效果;
AppendContent.java

public class AppendContent
{
	public static void main(String[] args) 
	{		
		   //以读、写方式打开一个RandomAccessFile对象	
		try(RandomAccessFile raf = new RandomAccessFile("out.txt" , "rw"))
		{					
			//将记录指针移动的out.txt文件的最后
			raf.seek(raf.length());
			raf.write("追加的内容!\r\n".getBytes());
		}
		catch (IOException ex)
		{
			ex.printStackTrace();
		}		
	}
}

在这里插入图片描述
在这里插入图片描述
InsertContent.java

public class InsertContent
{
	public static void insert(String fileName , long pos , 
		String insertContent)throws IOException
	{		
		//创建一个临时文件来保存插入点后的数据
		File tmp = File.createTempFile("tmp" , null);
		
		tmp.deleteOnExit();
		try(RandomAccessFile raf =  new RandomAccessFile(fileName , "rw");
			FileOutputStream tmpOut = new FileOutputStream(tmp);
			FileInputStream tmpIn = new FileInputStream(tmp);)
		{						
			raf.seek(pos);
			//--------下面代码将插入点后的内容读入临时文件中保存---------
			byte[] bbuf = new byte[64];
			//用于保存实际读取的字节数
			int hasRead = 0;
			//使用循环方式读取插入点后的数据
			while ((hasRead = raf.read(bbuf)) > 0 )
			{
				//将读取的数据写入临时文件
				tmpOut.write(bbuf , 0 , hasRead);
			}
			//----------下面代码插入内容----------
			//把文件记录指针重新定位到pos位置
			raf.seek(pos);
			//追加需要插入的内容
			raf.write(insertContent.getBytes());
			//追加临时文件中的内容
			while ((hasRead = tmpIn.read(bbuf)) > 0 )
			{
				raf.write(bbuf , 0 , hasRead);
			}
		}		
	}
	public static void main(String[] args) throws IOException
	{
		insert("InsertContent.java" , 45 , "插入的内容\r\n");
	}
}

每次运行,都会看到InsertContent.java中插入了一行字符串:
在这里插入图片描述

序列化

序列化机制允许将实现序列化的Java对象转换成字节序列,这些字节序列可以保存在磁盘上,或通过网络传输,以备以后重新恢复成原来的对象。序列化机制使得对象可以脱离程序的运行而独立存在。
对象的序列化(Serialize)指将一个Java对象写入IO流中,与此对应的是,对象的反序列化(Deserialize)则指从IO流中恢复该Java对象。
如果需要让某个对象支持序列化机制,则必须让它的类是可序列化的。为了让某个类是可序列化的,该类必须实现如下两个接口之一:

  • Serializable
  • Externalizable
    在这里插入图片描述
    大部分基本都采用Serializable接口方式来实现序列化;

使用对象流实现序列化

一旦某个类实现了Serializable接口,该类的对象就是可序列化的,程序可以通过如下两个步骤来序列化该对象:
1:创建一个ObjectOutputStream,这个输出流是一个处理流,所以必须建立在其他节点流的基础之上;

//创建一个ObjectOutputStream输出流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.txt"));

2:调用ObjectOutputStream对象的writeObject()方法输出可序列化对象;

//将per对象写入输出流
oos.writeObject(per);

Person.java

public class Person
	implements java.io.Serializable
{
	private String name;
	private int age;

	public Person(String name , int age)
	{
		System.out.println("有参数的构造器");
		this.name = name;
		this.age = age;
	}
}

WriteObject.java

public class WriteObject
{
	public static void main(String[] args) 
	{
		    //创建一个ObjectOutputStream输出流	
		try(ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.txt")))
		{					
			Person per = new Person("孙悟空", 500);
			//将per对象写入输出流
			oos.writeObject(per);
		}
		catch (IOException ex)
		{
			ex.printStackTrace();
		}		
	}
}

运行,将会生成一个object.txt文件,该文件的内容就是Person对象:
在这里插入图片描述
如果希望从二进制流中恢复Java对象,则需要使用反序列化。反序列化的步骤如下:
1:创建一个ObjectInputStream,这个输入流是一个处理流,所以必须建立在其他节点流的基础之上;

//创建一个ObjectInputStream输出流
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.txt"));

2:调用ObjectInputStream对象的readObject()方法读取流中的对象,该方法返回一个Object类型的Java对象,如果程序知道该Java对象的类型,则可以将该对象强制类型转换成其真实的类型;

//从输入流中读取一个Java对象,并将其强制类型转换为Person类
Person p = (Person)ois.readObject();

ReadObject.java

public class ReadObject
{
	public static void main(String[] args) 
	{
		//创建一个ObjectInputStream输出流
		try(ObjectInputStream ois = new ObjectInputStream(
				new FileInputStream("object.txt"));)
		{					
			//从输入流中读取一个Java对象,并将其强制类型转换为Person类
			Person p = (Person)ois.readObject();
			System.out.println("名字为:" + p.getName()
				+ "\n年龄为:" + p.getAge());
		}
		catch (Exception ex)
		{
			ex.printStackTrace();
		}
	}
}

在这里插入图片描述

对象引用的序列化

如果某个类的成员变量的类型不是基本类型或String类型,而是另一个引用类型,那么这个引用类型必须是可序列化的,否则拥有该类型成员变量的类也是不可序列化的;
在这里插入图片描述
在这里插入图片描述

版本

反序列化Java对象时必须提供该对象的class文件,现在的问题是,随着项目的升级,系统的class文件也会升级,Java如何保证两个class文件的兼容性?
Java序列化机制允许为序列化类提供一个private static final的serialVersionUID值,该类变量的值用于标识该Java类的序列化版本,也就是说,如果一个类升级后,只要它的serialVersionUID类变量的值保持不变,序列化机制也会把它们当成同一个序列化版本。

private static final long serialVersionUID =  512L;

在这里插入图片描述
在这里插入图片描述

NIO

在这里插入图片描述
在这里插入图片描述

使用Buffer

在Buffer中有三个重要的概念:容量(capacity)、界限(limit)和位置(position)。

  1. 容量(capacity):缓冲区的容量(capacity)表示该Buffer的最大数据容量,即最多可以存储多少数据。缓冲区的容量不可能为负值,创建后不能改变;
  2. 界限(limit):第一个不应该被读出或者写入的缓冲区位置索引。也就是说,位于limit后的数据既不可被读,也不可被写;
  3. 位置(position):用于指明下一个可以被读出的或者写入的缓冲区位置索引(类似于IO流中的记录指针)。当使用Buffer从Channel中读取数据时,position的值恰好等于已经读到 了多少数据。当刚刚新建一个Buffer对象时,其position为0;如果从Channel中读取了2个数据到该Buffer中,则position为2,指向Buffer中第三个(第一个位置的索引为0)位置。

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
BufferTest.java

public class BufferTest
{
	public static void main(String[] args)
	{
		//创建Buffer
		CharBuffer buff = CharBuffer.allocate(8);	//1
		System.out.println("capacity: "
			+ buff.capacity());
	  	System.out.println("limit: "
			+ buff.limit());
	  	System.out.println("position: "
			+ buff.position());
		//放入元素
	  	buff.put('a');	//2
	  	buff.put('b');	//3
	  	buff.put('c');	//4
	  	
	  	System.out.println("加入三个元素后,position = "
			+ buff.position());
	  	//调用flip()方法
	  	buff.flip();	//5
	  	System.out.println("执行flip()后,limit = "
			+ buff.limit());
	  	System.out.println("position = "
			+ buff.position());
	  	//取出第一个元素
	  	System.out.println("第一个元素(position=0):"
			+ buff.get());	//6	  	
	  	System.out.println("取出一个元素后,position = "
			+ buff.position());
	  	//调用clear方法
	  	buff.clear();	//7
	  	System.out.println("执行clear()后,limit = "
			+ buff.limit());	
	  	System.out.println("执行clear()后,position = "
			+ buff.position());
	  	System.out.println("执行clear()后,缓冲区内容并没有被清除:"
			+ buff.get(2));	//8
		System.out.println("执行绝对读取后,position = "
			+ buff.position());
	} 
}

在这里插入图片描述
1号代码处: CharBuffer的一个静态方法allocate()创建了一个capacity为8的CharBuffer。此时:
在这里插入图片描述
在这里插入图片描述
4号代码处取出一个元素后position向后移动一位;
在这里插入图片描述

使用Channel

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
FileChannelTest.java

public class FileChannelTest
{
	public static void main(String[] args)
	{
		FileChannel inChannel = null;
		FileChannel outChannel = null;
		try
		{
			File f = new File("FileChannelTest.java");
			//创建FileInputStream,以该文件输入流创建FileChannel
			inChannel = new FileInputStream(f)
				.getChannel();
			//将FileChannel里的全部数据映射成ByteBuffer
			MappedByteBuffer buffer = inChannel.map(FileChannel.MapMode.READ_ONLY,
				0 , f.length());
			//使用GBK的字符集来创建解码器
			Charset charset = Charset.forName("GBK");
			//以文件输出流创建FileBuffer,用以控制输出
			outChannel = new FileOutputStream("a.txt")
				.getChannel();
			//直接将buffer里的数据全部输出
			outChannel.write(buffer);
			//再次调用buffer的clear()方法,复原limit、position的位置
			buffer.clear();
			//创建解码器(CharsetDecoder)对象
			CharsetDecoder decoder = charset.newDecoder();
			//使用解码器将ByteBuffer转换成CharBuffer
			CharBuffer charBuffer =  decoder.decode(buffer);
			//CharBuffer的toString方法可以获取对应的字符串
			System.out.println(charBuffer);
		}
		catch (IOException ex)
		{
			ex.printStackTrace();
		}		
	}
}

字符集和Charset

在这里插入图片描述
Charset类提供了一个availableCharsets()静态方法来获取当前JDK所支持的所有字符集。
CharsetTest.java

public class CharsetTest
{
	public static void main(String[] args) 
	{
		//获取全部字符集
		SortedMap<String,Charset>  map = Charset.availableCharsets();
		for (String alias : map.keySet())
		{
			//输出字符集的别名和对应的Charset对象
			System.out.println(alias + "----->" 
				+ map.get(alias));
		}
	}
}

在这里插入图片描述
在这里插入图片描述
CharsetTransform.java

public class CharsetTransform
{
	public static void main(String[] args)
		throws Exception
	{
		//创建简体中文对应的Charset
		Charset cn = Charset.forName("GBK");
		//获取cn对象对应的编码器和解码器
		CharsetEncoder cnEncoder = cn.newEncoder();
		CharsetDecoder cnDecoder = cn.newDecoder();
		//创建一个CharBuffer对象
		CharBuffer cbuff = CharBuffer.allocate(8);
		cbuff.put('孙');
		cbuff.put('悟');
		cbuff.put('空');
		cbuff.flip();
		//将CharBuffer中的字符序列转换成字节序列
		ByteBuffer bbuff = cnEncoder.encode(cbuff);
		//循环访问ByteBuffer中的每个字节
		for (int i = 0; i < bbuff.capacity() ; i++)
		{
			System.out.print(bbuff.get(i) + " ");
		}
		//将ByteBuffer的数据解码成字符序列
		System.out.println("\n"
			+ cnDecoder.decode(bbuff));
	}
}

在这里插入图片描述
在这里插入图片描述
以上只是学习所做的笔记,不做任何用途(其实就是照着书抄啦)!!!
书籍:疯狂Java讲义

猜你喜欢

转载自blog.csdn.net/z1790424577/article/details/82824997