Core Java 8 读书笔记-流和文件

315 阅读15分钟

封面14 拷贝.png

作者:老九—技术大黍

原文:Core Java 8th Edition

社交:知乎

公众号:老九学堂(新人有惊喜)

特别声明:原创不易,未经授权不得转载或抄袭,如需转载可联系笔者授权

前言

当我们读完了《Thinking in Java》之后,要进精进地学Java编程语言,那么一定要看《Core Java》一书。下面是我的读书笔记。

在Java API中,我们可以以字节序列读取的对象叫一个输入流(In the Java API, an object from which we can a sequence of bytes is called an input stream)。一个可以被字节序列被写的对象叫做输出流。而这些被操作的序列字节资源和目标常常是文件,也可能是网络连接和内存块。InputStream和OutputStream是抽象类,它们是I/O类的老祖宗。

因为字节流不能直接处理Unicode码存贮的信息,所以API单独把处理Unicode字符的类独立出来Reader和Writer两个抽象类。这两个灰是基于两个字节处理的,而不是一个字节处理的I/O流。

读取和写入字节

InputStream类一个抽象方法

abstract int read();

该方法一个字节一个字节的读取,然后一个字节一个字节的返回读取的内容,如果读完输入资源,那么返回-1。实现类必须重写该方法,以完成自己特定的功能。比如,FileInputStream类它从一个方法读字节。System.in是InputStream类的预定字类对象,它允许你从键盘读取信息。

InputStream类也有非抽象方法用来读取字节或者跳过字节的动作。这些访问会呼叫abstract read方法,所以子类只需要重写一个抽象方法即可。

不管是read和write方法它们在实际读取或者写入动作时,都会锁定(block)资源。这就意味着流对象不能立即被访问(一般为网络连接忙碌),或者当前线程锁定,结果会使其它线程等待流对象,并且该对象可用。available方法可以让我们检查当前读取字节数

int bytesAvailabe = in.available();
if(bytesAvailabe > 0){
   byte[] data = new byte[bytesAvailabe];
   in.read(data);
}

当我们读完流对象之后,需要呼叫close方法关闭流。

image-20210423171155708.png

image-20210423171216058.png

image-20210423171229119.png

字节流对象

Java把流分为字节流和字符流。FileInputStream可以通过openStream方法从URL类得到流对象。PrintWriter和DataInputStream类可以把字符组合成有用的数据类型。Java程序可以整合使用这些流来操作文件。

image-20210423171410863.png

ShowStreams类

package com.jb.arklis.demo;
import com.jb.arklis.zip.*;
import static java.lang.System.*;
import java.io.*;
import java.util.zip.*;
import java.util.*;
import com.jb.arklis.text.*;
import java.nio.charset.*;
import com.jb.arklis.random.*;
import com.jb.arklis.ser.*;
import com.jb.arklis.reg.*;

/**
	功能:书写一个类用来演示流和文件的使用
	作者:技术大黍
	备注:
		java.io包中的相对用户的工作目录(user's working directory),如果想知道当前的
	工作目录,那么呼叫System.getProperty("user.dir");
	*/
public class ShowStreams{
	
	public ShowStreams(){
		out.println(System.getProperty("user.dir"));
		//demoStreamFilter();
		//demoPushbackInputStream();
		//demoReadZipFile();
		//demoCharsetdemoPrintWriter();
		//测试操作文本文档
		//new TextFileOperationShow();
		//demoCharset();
		//demoRandomFileAccess();
		//演示压缩流对象的使用
		//new ZipFileFrame();
		//new ObjectStreamTest();
		//new RegExTest();
		new HrefMatch();
	}
	
	//不管怎样修改文件内容都一个固定的双精度数值
	private void demoStreamFilter(){
		try{
			FileInputStream fileInput = new FileInputStream("employee.dat");
			//然后读入到内容
			DataInputStream dataInput = new DataInputStream(fileInput);
			//最后读成Java的数据内容
			double salary = dataInput.readDouble();
			out.println("当前薪水是:" + salary);
		}catch(Exception e){
			out.print(e.getMessage());
		}
	}
	
	private void demoPushbackInputStream(){
		try{
			PushbackInputStream pushInput = new PushbackInputStream(
				new BufferedInputStream(new FileInputStream("employee.dat")));
			int b = pushInput.read();
			if(b != '<'){
				pushInput.unread(b);
				out.println("当前b是:" + b);
			}
		}catch(Exception e){
			out.print(e.getMessage());
		}
	}
	
	private void demoReadZipFile(){
		try{
			ZipInputStream zipInput = new ZipInputStream(
				new FileInputStream("employee.rar"));
			DataInputStream dataInput = new DataInputStream(zipInput);
			out.println("当前压缩档大小是:" + dataInput.readChar());
		}catch(Exception e){
			out.print(e.getMessage());
		}
	}
	
	private void demoPrintWriter(){
		try{
			PrintWriter outInput = new PrintWriter("employee.dat","GBK");
			String name = "Arklis 曾";
			double salary = 75000.0;
			outInput.print(name + ' ' + salary);
			outInput.flush();
			outInput.close();
		}catch(Exception e){
			out.println(e.getMessage());
		}
	}
	
	//字符集是大小写敏感的
	private void demoCharset(){
		Charset charset = Charset.forName("ISO-8859-1");
		//得到该字符集的别名
		Set<String> aliases = charset.aliases();
		for(String x : aliases){
			out.println(x);
		}
	}
	
	private void demoRandomFileAccess(){
		new RandomwFileTest();
	}
	
	public static void main(String[] args){
		new ShowStreams();
	}
}

文本的输入与输出

当我们保存数据时,必须选择是使用二进制方式还是使用文本格式来保存。比如如果把整数1234保存为二进制形式,那么它的二进制字节序列是00 00 04 D2(16进制形式)。如果使用文本格式,那么它就是字符串“1234”格式。虽然二进制的I/O流的速度快,但是它的可读性不好。

当我们保存为文本字符串时,那么我们需要考虑字符编码问题(character encoding)。在UTF-16编码,字符串”1234”的编码是00 31 00 32 00 33 00 34(16进制方式),大多数程序都有不同的编码要求。ISO 8859-1编码是美国与西欧使用的试,这些“1234”会被编码为31 32 33 34没有0字节。

OutputStreamWriter类会把unicode字符转转换成字节流,转换时使用指定的编码方式对字符串进行编码。相对于InputSreamReader类会把不同的、指定的编码字符转换成unicode码字符。比如,我们使用如下命令:

InputStreamReader in = new InputStreamReader(System.in);

该命令是把控制输入的内容转换为unicode码字符。而下面命令是:

InputStreamReader in = new InputStreamReader(new FileInputStream(“kremli.dat”),”iso8859-1”);

它把.kremli.dat文档转换成iso8859-1编码格式的字符串。

TextFileOperationShow类

package com.jb.arklis.text;
import java.util.*;
import static java.lang.System.*;
import java.io.*;
import com.jb.arklis.random.*;
/*
	测试的模式类
	*/
public class Employee implements Serializable{
	public static final int NAME_SIZE = 40;
	public static final int RECORD_SIZE = 2 * NAME_SIZE + 8 + 4 + 4 + 4;

	private String name;
	private double salary;
	private Date hireDay;

	public Employee(){
		
	}
	
	public Employee(String name, double salary, int year, int month, int day){
		this.name = name;
		this.salary = salary;
		GregorianCalendar calendar = new GregorianCalendar(year,month -1, day);
		hireDay = calendar.getTime();		
	}
	
	/**
		加薪
		*/
	public void raiseSalary(double byPercent){
		double raise = salary * byPercent / 100;
		salary += raise;
	}
	
	public String toString(){
		return getClass().getName() + "[name=" + name + ",salary="
			+ salary + ",hireDay=" + hireDay + "]";
	}
	
	//员工执行输出持久化动作--把每个员的信息持久化文本文档中去
	public void writeData(PrintWriter output){
		GregorianCalendar calendar = new GregorianCalendar();
		calendar.setTime(hireDay);
		output.println(name + "|" + salary + "|" + calendar.get(Calendar.YEAR)
			+ "|" + (calendar.get(Calendar.MONTH) + 1) + "|" +
			calendar.get(Calendar.DAY_OF_MONTH));
	}
	
	//重载writeData方法
	public void writeData(DataOutput output){
		try{
			//处理字符串比较特殊一些
			DataIO.writeFixedString(name,NAME_SIZE, output);
			//执行持久化输出动作
			output.writeDouble(salary);
			GregorianCalendar calendar = new GregorianCalendar();
			calendar.setTime(hireDay);
			//输出日期
			output.writeInt(calendar.get(Calendar.YEAR));
			output.writeInt(calendar.get(Calendar.MONTH) + 1);
			output.writeInt(calendar.get(Calendar.DAY_OF_MONTH));
		}catch(Exception e){
			e.printStackTrace();
		}
	}
	
	/**
		从持久化文本文件中读取每个员工的信息
		*/
	public void readData(Scanner input){
		String line = input.nextLine(); //读取每一行字符串
		//out.println("当前持久文档中的第一行数据是:" + line);
		String[] tokens = line.split("\\|"); //把|排出
		name = tokens[0];
		salary = Double.parseDouble(tokens[1]);
		int year = Integer.parseInt(tokens[2]);
		int month = Integer.parseInt(tokens[3]);
		int day = Integer.parseInt(tokens[4]);
		GregorianCalendar calendar = new GregorianCalendar(year,month - 1,day);
		hireDay = calendar.getTime();
	}
	
	//重载readData()方法
	public void readData(DataInput input)throws IOException{
		//读取字符串比较特殊
		name = DataIO.readFixedString(NAME_SIZE, input);
		salary = input.readDouble();
		//读取日期
		int y = input.readInt();
		int m = input.readInt();
		int d = input.readInt();
		GregorianCalendar calendar = new GregorianCalendar(y,m-1,d);
		hireDay = calendar.getTime();
	}
	
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public double getSalary() {
		return salary;
	}

	public void setSalary(double salary) {
		this.salary = salary;
	}

	public Date getHireDay() {
		return hireDay;
	}

	public void setHireDay(Date hireDay) {
		this.hireDay = hireDay;
	}

}

Employee类

package com.jb.arklis.text;
import java.util.*;
import static java.lang.System.*;
import java.io.*;
import com.jb.arklis.random.*;
/*
	测试的模式类
	*/
public class Employee implements Serializable{
	public static final int NAME_SIZE = 40;
	public static final int RECORD_SIZE = 2 * NAME_SIZE + 8 + 4 + 4 + 4;

	private String name;
	private double salary;
	private Date hireDay;

	public Employee(){
		
	}
	
	public Employee(String name, double salary, int year, int month, int day){
		this.name = name;
		this.salary = salary;
		GregorianCalendar calendar = new GregorianCalendar(year,month -1, day);
		hireDay = calendar.getTime();		
	}
	
	/**
		加薪
		*/
	public void raiseSalary(double byPercent){
		double raise = salary * byPercent / 100;
		salary += raise;
	}
	
	public String toString(){
		return getClass().getName() + "[name=" + name + ",salary="
			+ salary + ",hireDay=" + hireDay + "]";
	}
	
	//员工执行输出持久化动作--把每个员的信息持久化文本文档中去
	public void writeData(PrintWriter output){
		GregorianCalendar calendar = new GregorianCalendar();
		calendar.setTime(hireDay);
		output.println(name + "|" + salary + "|" + calendar.get(Calendar.YEAR)
			+ "|" + (calendar.get(Calendar.MONTH) + 1) + "|" +
			calendar.get(Calendar.DAY_OF_MONTH));
	}
	
	//重载writeData方法
	public void writeData(DataOutput output){
		try{
			//处理字符串比较特殊一些
			DataIO.writeFixedString(name,NAME_SIZE, output);
			//执行持久化输出动作
			output.writeDouble(salary);
			GregorianCalendar calendar = new GregorianCalendar();
			calendar.setTime(hireDay);
			//输出日期
			output.writeInt(calendar.get(Calendar.YEAR));
			output.writeInt(calendar.get(Calendar.MONTH) + 1);
			output.writeInt(calendar.get(Calendar.DAY_OF_MONTH));
		}catch(Exception e){
			e.printStackTrace();
		}
	}
	
	/**
		从持久化文本文件中读取每个员工的信息
		*/
	public void readData(Scanner input){
		String line = input.nextLine(); //读取每一行字符串
		//out.println("当前持久文档中的第一行数据是:" + line);
		String[] tokens = line.split("\\|"); //把|排出
		name = tokens[0];
		salary = Double.parseDouble(tokens[1]);
		int year = Integer.parseInt(tokens[2]);
		int month = Integer.parseInt(tokens[3]);
		int day = Integer.parseInt(tokens[4]);
		GregorianCalendar calendar = new GregorianCalendar(year,month - 1,day);
		hireDay = calendar.getTime();
	}
	
	//重载readData()方法
	public void readData(DataInput input)throws IOException{
		//读取字符串比较特殊
		name = DataIO.readFixedString(NAME_SIZE, input);
		salary = input.readDouble();
		//读取日期
		int y = input.readInt();
		int m = input.readInt();
		int d = input.readInt();
		GregorianCalendar calendar = new GregorianCalendar(y,m-1,d);
		hireDay = calendar.getTime();
	}
	
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public double getSalary() {
		return salary;
	}

	public void setSalary(double salary) {
		this.salary = salary;
	}

	public Date getHireDay() {
		return hireDay;
	}

	public void setHireDay(Date hireDay) {
		this.hireDay = hireDay;
	}

}

写数据为二进制时使用DataOutputStream类;如果想写成文本格式时使用PrintWriter类来实现。那么,当我们需要读入数据到内存中,我们使用使用DataInputStream类来读二进制数据,在J2SE 5.0以后使用Scanner类来读文本字符,但是在J2SE 5.0以前都使用BufferedReader类来读取文本字符。

读取和写入二进制数据

DataOutput接口定义了写入数字、字符和boolean值为二进制格式的方法:

  • writeChars
  • writeByte
  • writeInt
  • writeShort
  • writeLong
  • writeFloat
  • writeDouble
  • writeChar
  • writeBoolean
  • writeUTF

比如writeInt方法总是会把一个整数写成四个字节的二进制数字,而不管数的进制;writeDouble总是会把双精度数写成八个字节的二进制数字。它们的输出没有阅读性,但是每个数据类型的所使用的空间是一样大小的,所以它们解析成文本时的速度很快。其中writeUTF方法把字符数据转换成八位的unicode编码格式。

RandomAccessFile类可以让我们在一个文件中随机查找或者写入数据。注意:该磁盘文件是可以随机访问的,但是来自网络流数据是不可以随机访问的。如果使用该类,那么可以实现对二者的只读和读/写操作,方式是通过在构造方法中使用“r”和”rw”字符串指定即可。

DataIO类

package com.jb.arklis.random;
import static java.lang.System.*;
import java.io.*;


/**
	书写一个工具类,用来实现二进制字符集的读写操作
	作者:技术大黍
	*/
public class DataIO{
	
	public static String readFixedString(int size, DataInput input)throws IOException{
		StringBuilder message = new StringBuilder(size);
		int i = 0;
		boolean more = true;
		while(more && i < size){
			char ch = input.readChar();
			i++;
			if(ch == 0){//如果字符是结束符
				more = false;//那么不读
			}else{
				//否则所字符添加到字符串中去
				message.append(ch);
			}
		}
		input.skipBytes(2 * (size - i)); //该语句是关键语句,该方法不抛出 EOFException异常
		return message.toString();
	}
	
	//对比Employee类的writeData方法看看有何不同--一个是字符串,一个字符方式。
	public static void writeFixedString(String temp, int size, DataOutput output)throws IOException{
		for(int i = 0 ; i < size; i++){
			char ch = 0;
			//进行字符串的写动作
			if(i < temp.length()){
				//那么取出字符串的每个字符
				ch = temp.charAt(i);
			}
			//输出字符
			output.writeChar(ch);
		}
	}
}

RandomwFileTest类

package com.jb.arklis.random;
import static java.lang.System.*;
import com.jb.arklis.text.*;
import java.io.*;
import java.nio.charset.*;
/**
	功能:书写一个随机访问文件类,用来演示通过字节集来进行读取和写入文件的动作
	作者:技术大黍
	备注:
		我们使用二进制流处理字符集的方式实现文件的读取与写入动作。
	*/
public class RandomwFileTest{
	private Employee[] staff = new Employee[5];
	
	public RandomwFileTest(){
		staff[0] = new Employee("Carl Cracker", 75000, 1987, 12,15);
		staff[1] = new Employee("Arklis Zeng", 75000, 1989, 10,1);
		staff[2] = new Employee("Harray Hacker", 50000, 1990, 3,15);
		staff[3] = new Employee("Tony Tester", 40000, 1991, 4,22);
		staff[4] = new Employee("Huward Beast", 100000, 1988, 9,21);
		try{
			//指定输出的文件路径
			DataOutputStream output = new DataOutputStream(
				new FileOutputStream("./src/com/jb/arklis/random/employee.dat"));
			for(Employee e: staff){
				e.writeData(output);
			}
			output.close();
			out.println("输出完毕!");
			//下面获取数据,然后初始化对象数组
			RandomAccessFile input = new RandomAccessFile("./src/com/jb/arklis/random/employee.dat","r");
			//计算数据的大小
			int n = (int)(input.length() / Employee.RECORD_SIZE);
			Employee[] newStaff = new Employee[n];
			
			//读取
			for(int i = n - 1; i >= 0; i--){
				newStaff[i] = new Employee();
				//读取当前文件中每一行内容
				input.seek(i * Employee.RECORD_SIZE);
				newStaff[i].readData(input);
			}
			input.close();
			
			//打印出来
			for(Employee e: newStaff){
				out.println(e);
			}
		}catch(Exception e){
			e.printStackTrace();
		}
	}
}

压缩文件

ZIP压缩文档可以保存一个或者多个压缩格式的文件,每个ZIP文档都有一个头信息供压缩方法使用。我们可以通过使用ZipInputStream类读取通过ZipOutputStream类写成的压缩档。

ZipFileFrame类

package com.jb.arklis.zip;
import static java.lang.System.*;
import java.io.*;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.zip.*;
import java.util.*;

/**
	功能:书写一个窗体类,用来演示怎样压缩文档
	作者:技术大黍		
	*/
public class ZipFileFrame extends JFrame{
	private JComboBox fileCombo;
	private JTextArea fileText;
	private String zipName;
	
	public ZipFileFrame(){
		setTitle("压缩流对象的操作演示");
		Container container = getContentPane();
		init(container);
		setSize(400,300);
		setDefaultCloseOperation(EXIT_ON_CLOSE);
		setLocationRelativeTo(null);
		setVisible(true);
		zipFile();
	}
	
	private void zipFile(){
		try{
			FileOutputStream fout = new FileOutputStream("test.zip");
			ZipOutputStream zout = new ZipOutputStream(fout);
		    ZipEntry ze = new ZipEntry("./src/com/jb/arklis/demo/ShowStreams.java");
		    zout.putNextEntry(ze);
		    zout.closeEntry();
		    ze = new ZipEntry("./src/com/jb/arklis/random/RandomwFileTest.java");
		    zout.putNextEntry(ze);
		    zout.closeEntry();
			zout.close();
		}catch(Exception e){
			e.printStackTrace();
		}
	}
	
	private void init(Container container){
		//添加菜单对象
		JMenuBar menuBar = new JMenuBar();
		JMenu menu = new JMenu("文件");
		
		JMenuItem openItem = new JMenuItem("打开");
		menu.add(openItem); //添加打开菜单项
		//处理打开文件事件
		openItem.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent event){
				//创建一个文件选择器对象,用来指定需要压缩的对象
				JFileChooser chooser = new JFileChooser();
				chooser.setCurrentDirectory(new File("."));//指定当前工作目录
				//打开当前文档所在路径
				int read = chooser.showOpenDialog(ZipFileFrame.this);
				//如果可以添加到对话框
				if(read == JFileChooser.APPROVE_OPTION){
					//那么取得当前选中文件的路径
					zipName = chooser.getSelectedFile().getPath();
					fileCombo.removeAllItems();
					//扫描压缩文档
					scanZipFile();
				}
			}
		});
		
		JMenuItem exitItem = new JMenuItem("退出");
		menu.add(exitItem);
		exitItem.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent event){
				exit(0);
			}
		});
		
		menuBar.add(menu);
		setJMenuBar(menuBar); //添加菜单条
			
		//添加文本域和combo box
		fileText = new JTextArea();
		fileCombo = new JComboBox();
		fileCombo.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent event){
				//装载zip文件
				loadZipFile((String)fileCombo.getSelectedItem());
			}
		});
		
		container.add(fileCombo,BorderLayout.SOUTH);
		container.add(new JScrollPane(fileText),BorderLayout.CENTER);
	}
	
	private void loadZipFile(final String name){
		fileCombo.setEnabled(false);
		fileText.setText("");
		new SwingWorker<Void, Void>(){
			protected Void doInBackground()throws Exception{
				try{
					//读取压缩文件
					ZipInputStream zipInput = new ZipInputStream(new FileInputStream(zipName));
					ZipEntry entry = zipInput.getNextEntry();
					//找到匹配的压缩档
					while(entry!= null){
						if(entry.getName().equals(name)){
							//那么读到内存中
							//Scanner scanner = new Scanner(new FileReader(zipName));
							String entryName = entry.getName();
							File newFile = new File(entryName);
							String directory = newFile.getParent();
							
							//判断是否是一个文件
							if(directory == null){
								if(newFile.isDirectory()){
									break;
								}
							}
							//readByRandomAccessFile(entryName);//使用RandomAccessFile类读,中文有乱码问题
							//readByFileReader(entryName); //正确显示不会有乱码问题
							readByScanner(entryName); //使用scanner来读文件
						}
						zipInput.closeEntry(); //读下一压缩项
						entry = zipInput.getNextEntry();
					}
					zipInput.close();
				}catch(Exception e){
					e.printStackTrace();
				}
				return null;
			}
			
			private void readByScanner(String entryName)throws IOException{
				Scanner scanner = new Scanner(new FileReader(entryName));
				while(scanner.hasNextLine()){
					fileText.append(scanner.nextLine());
					fileText.append("\n");
				}
			}
			
			//使用BufferedReader不用担心文件的长度问题与文件指针移动问题
			private void readByFileReader(String entryName)throws IOException{
				FileReader fileReader = new FileReader(entryName);
				BufferedReader bufferedReader = new BufferedReader(fileReader);
				String line = bufferedReader.readLine();
				while(line != null){
					fileText.append(line);
					fileText.append("\n");
					line = bufferedReader.readLine();
				}
				fileReader.close();
			}
			
			private void readByRandomAccessFile(String entryName)throws IOException{
				RandomAccessFile randomFile = new RandomAccessFile(entryName,"r");
				String line;
				while((line = randomFile.readLine()) != null){
					fileText.append(line);
					fileText.append("\n");
				}
				randomFile.close();
			}
			
			//重写done()方法
			protected void done(){
				fileCombo.setEnabled(true);
			}
		}.execute();
	}
	
	/**
		扫描压缩档内容,并且生产combo box对象
		*/
	private void scanZipFile(){
		new SwingWorker<Void, String>(){
			protected Void doInBackground()throws Exception{
				ZipInputStream zipInput = new ZipInputStream(new FileInputStream(zipName));
				//声明一个压缩项
				ZipEntry entry;
				while((entry = zipInput.getNextEntry()) != null){//如果有数据项
					//那么把资源发送到当前线程来处理
					publish(entry.getName());
					zipInput.closeEntry();
				}
				zipInput.close();
				return null;
			}
			
			//重写process方法,用来处理压缩项
			protected void process(java.util.List<String> names){
				for(String x : names){
					fileCombo.addItem(x); //combo组件添加项
				}
			}
		}.execute();
	}
}

运行效果

image-20210424115810038.png

对象流和序列化

image-20210424120003664.png

这张图表述了两个经理共用一个秘书。

image-20210424133248536.png

内存与文件存贮的比较。

Manager类

package com.jb.arklis.ser;
import static java.lang.System.*;
import com.jb.arklis.text.*;

public class Manager extends Employee{
	private Employee secretary;
	
	public void setSecretary(Employee secretary){
		this.secretary = secretary;
	}
	
	public Employee getSecretary(){
		return secretary;
	}
	
	public Manager(){
		
	}
	
	public String toString(){
		return super.toString() + "[secretary=" + secretary + "]";
	}
	
	public Manager(String name, double salary, int year, int month, int day){
		super(name,salary,year,month,day);
		secretary = null;
	}
}

ObjectStreamTest类

package com.jb.arklis.ser;
import static java.lang.System.*;
import java.io.*;
import com.jb.arklis.text.*;

/**
	功能:书写一个演示对象流的类
	作者:技术大黍
	*/
public class ObjectStreamTest{
	private Employee[] staff = new Employee[3];
	
	public ObjectStreamTest(){
		Employee harry = new Employee("Harray Hacker", 50000, 1990, 3,15);
		Manager arklis = new Manager("Arklis Zeng", 75000, 1989, 10,1);
		arklis.setSecretary(harry);
		Manager tony = new Manager("Tony Tester", 70000, 1991, 4,22);
		tony.setSecretary(harry);
		
		staff[0] = harry;
		staff[1] = arklis;
		staff[2] = tony;
		//对数组执行序列化和反序列化
		try{
			ObjectOutputStream output = new ObjectOutputStream(new FileOutputStream("./src/com/jb/arklis/ser/employee.dat"));
			output.writeObject(staff);
			output.close();
			//读取对象
			ObjectInputStream input = new ObjectInputStream(new FileInputStream("./src/com/jb/arklis/ser/employee.dat"));
			Employee[] newStaff = (Employee[])input.readObject();
			input.close();
			//给第二个员工加薪10%
			newStaff[1].raiseSalary(10);
			//显示出来
			for(Employee e: newStaff){
				out.println(e);
			}
		}catch(Exception e){
			e.printStackTrace();
		}
	}
}

NIO

NIO包的导入是为了解决原IO包读/写速度的问题:

image-20210424133832117.png

演示代码

package com.jb.arklis.io.nio;
import static java.lang.System.*;
import java.nio.*;
import java.nio.channels.*;
import java.io.*;

/**
	功能:书写一个类用来演示文件锁的使用
	作者:技术大黍
	备注:
		注意这里使用内存影射文件与文件锁的功能
	*/
public class LockingMappedFiles{
	static final int LENGTH = 0x4FFFF; //128MB 0x8FFFFFF
	static FileChannel channel;
	
	public static void showFileLock()throws IOException{
		channel = new RandomAccessFile("testfilelock.dat","rw").getChannel();
		//内存影射文件
		MappedByteBuffer output = channel.map(FileChannel.MapMode.READ_WRITE, 0, LENGTH);
		//使用内存影射文件对通道进行写操作
		for(int i = 0; i < LENGTH; i++){
			output.put((byte)'x');
		}
		//对通道进行锁定
		new LockAndModify(output, 0, 0 + LENGTH / 3);
		new LockAndModify(output, LENGTH / 2, LENGTH / 2 + LENGTH / 4);
	}
	
	//定义一个私有表态类,用来处理锁定义文件的动作
	private static class LockAndModify extends Thread{
		private ByteBuffer buffer;
		private int start, end;
		
		//在构造方法初始化成员变量
		LockAndModify(ByteBuffer buffer, int start, int end){
			this.start = start;
			this.end = end;
			buffer.limit(end);//指定缓存上限
			buffer.position(start);//指定缓存起始位置
			this.buffer = buffer.slice();
			start(); //起动线程
		}
		
		//重写run()方法
		public void run(){
			try{
				//执行锁定动作
				FileLock lock = channel.lock(start, end, false);
				//显示锁定结果
				out.println("Locked:(锁定) " + start + " to(到) " + end);
				//执行修改动作
				while(buffer.position() < buffer.limit() - 1){
					buffer.put((byte)(buffer.get() + 1));
				}
				//释放锁
				lock.release();
				out.println("Released:(释放) " + start + " to(到) " + end);
			}catch(Exception e){
				out.println(e.getMessage());
			}
		}
	}
}

/**
	功能:书写一个显示可用字节码来显示字符的类
	作者:技术大黍
	*/
public class GetDataFromByteBuffer{
	private static final int BUFFER_SIZE = 1024;
	
	public static void showCharFromByte(String test)throws IOException{
		ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
		//允许缓存自动增长
		int i = 0;
		while(i++ < buffer.limit()){
			if(buffer.get() != 0){
				print("不是零");
			}
		}
		//否则打印i值
		print("i = " + i);
		buffer.rewind(); //输出
		//然后存贮字符,接着显示它们
		buffer.asCharBuffer().put(test);
		char c;
		while((c = buffer.getChar()) != 0){
			printnb(c + " "); //把字符显示出来
		}
		print();
		buffer.rewind();
		
		//在缓存中存整数,然后显示出来
		buffer.asShortBuffer().put((short)471143);
		print(buffer.getShort());
		buffer.rewind();
		
		buffer.asIntBuffer().put(99471143);
		print(buffer.getInt());
		buffer.rewind();
		
		buffer.asFloatBuffer().put(99471143);
		print(buffer.getFloat());
		buffer.rewind();
	}
}

/**
	功能:书写一个nio类,用来学nio包的使用
	作者:技术大黍
	*/
public class GetChannel{
	//定义一个数据传送单位
	private static final int BYTE_SIZE = 1024;
	
	public static void useChannel(String filename){
		try{
			//使用channel来生成一个文件
			FileChannel channel = new FileOutputStream(filename).getChannel();
			//使用channel把向缓存中写内容
			channel.write(ByteBuffer.wrap("测试数据字符(Test Data)\n".getBytes()));
			channel.close();
			//经今夜的添加一些内容
			channel = new RandomAccessFile(filename,"rw").getChannel();
			//把文件指针移动到尾部
			channel.position(channel.size());
			//写一些数据
			channel.write(ByteBuffer.wrap("添加一些测试数据而已(Add some test data)。。。".getBytes()));
			//关闭通道对象
			channel.close();
			//演示出来
			channel = new FileInputStream(filename).getChannel();
			//使用缓存来装载
			ByteBuffer buffer = ByteBuffer.allocate(BYTE_SIZE);
			//读取通道中去
			channel.read(buffer);
			//拉出数据
			buffer.flip();
			//如果缓存有
			while(buffer.hasRemaining()){
				//那么从里面合出来
				out.print((char)buffer.get());
			}
		}catch(Exception e){
			out.println(e.getMessage());
		}
	}
}

/**
	功能:书写一个nio类,用来实现文件的拷贝功能
	作者:技术大黍
	*/
public class ChannelCopy{
	//定义每次使用字节的单元
	private static final int BUFFER_SIZE = 1024;
	
	public static void copy(String source, String target)throws IOException{
		//定义一个两个文件通道对象,用来完成拷贝的动作
		FileChannel input = new FileInputStream(source).getChannel();
		FileChannel output = new FileOutputStream(target).getChannel();
		//声明一个ByteBuffer对象,用来具体执行拷贝的功能
		ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
		//搬东西--实现从源文件(source)拷贝到目标文件target中去
		while(input.read(buffer) != -1){
			buffer.flip(); //准备写入的动作
			output.write(buffer);
			buffer.clear(); //准备读取的动作
		}
		//以上循环可以使用下面语句代替
		//input.transferTo(0,input.size(),output); //输入对象使用TO
		//output.transferFrom(input, 0, input.size());//输出对象使用From
		JOptionPane.showMessageDialog(null,"文件拷贝完毕!");
	}
}

/**
	功能:书写一个显示可用字符集的类
	作者:技术大黍
	*/
public class AvailableCharSets{
	
	public void showCharSets(){
		//使用Map来列字可用字符集
		SortedMap<String,Charset> charSets = Charset.availableCharsets();
		//使用迭代器取出现键
		Iterator<String> iterator = charSets.keySet().iterator();
		//循环显示集合中的内容
		while(iterator.hasNext()){
			String charSetName = iterator.next();
			//打印出来
			printnb(charSetName);
			Iterator aliases = charSets.get(charSetName).aliases().iterator();
			//如果有键
			if(aliases.hasNext()){
				printnb(": "); //打印冒号
			}
			//然后显示出值
			while(aliases.hasNext()){
				printnb(aliases.next());
				//如果有多个值
				if(aliases.hasNext()){
					printnb(", ");//打印逗号
				}
			}
			print();//换行
		}
	} 
}

补充:Java正则表达式

正则表达式用来指定特定的字符串格式。比如

[Jj]ava.+

它表示匹配的字符串如下:

  1. 开头字母是J或者j
  2. 紧接着右边是三个字母ava
  3. 在ava右边是至少一个任意的字符

那么,”javaness”满足以上表达式,而”Core java”不满足以上表达式。下面我们来讲解正则表达式的语法:

image-20210424134816393.png

image-20210424134732360.png

image-20210424134827725.png

1、大多数字符匹配自己,比如前面的ava 2、’.’符号表示匹配任意字符(除了一行的结束符) 3、使用’\’表示除外符,比如’.’表示一个点(.)而不是匹配任意字符,’\’表示匹配一个backslash 4、^和$分别表示一行字符的开始与结束处 5、如果X和Y是正则表达式,那么’XY’表示任意匹配X之后紧跟匹配Y,而’X|Y’表示任何匹配X的字符或者匹配Y的字符 6、可以使用数量来说明表达式,比如X+, X*或者X? 7、默认情况下,数字修饰符会以最大可能性去重复匹配,直到匹配成功。当使用后缀’?’修改表达式时,会以最少重复的方式来重复匹配;而使用‘+’时与之相反效果。比如,表达式[a-z]ab和[a-z]+ab之间的区别,前都[a-z]只匹配所有字符c,而ab只匹配ab字符;但是贪吃的[a-z]+表示字符集是cab,而剩下的ab不会再被匹配了。 8、使用’()’符号来进行子表达式的分组。比如([+-]?)([0-9]+),其中组号使用’\n’(比如’\1’)来指定分组的表达式。 测试: [+-]?[0-9]+|0[Xx][0-9A-Fa-f]+ 表达式表示的什么样的字符串?

image-20210424134837032.png

在Java中,要求被匹配者必须是一个任意类对象,而该类必须实现CharSequence接口,比如String, StringBuilder, CharBuffer等。然后当我们编译正则表达式时,我们可使用六种标记来设置它。

Pattern pattern = Pattern.compile(patternString, Pattern.CASE_INSENSITIVE + Pattern.UNICODE_CASE);

六种标记如下:

image-20210424135049163.png

如果正则表达式中有分组,那么Matcher对象可以获得分组的情况。组号0表示整个输入字符串内容;所以分组的实际第一个下标是1. 我们呼叫getCount方法得到所有分组数。

RegularTestUtil类

package com.jb.arklis.reg;
import static java.lang.System.*;
import java.util.regex.*;
import java.util.*;
import com.jb.arklis.io.nio.*;

/**
	功能:书写一个测试正则表达式的工具类
	作者:技术大黍
	备注:输入的测试测试数据如下:
		Input: "abcabcabcdefabc"
		Regular expression: "abcabcabcdefabc"
		Match "abcabcabcdefabc" at positions 0-14
		Regular expression: "abc+"
		Match "abc" at positions 0-2
		Match "abc" at positions 3-5
		Match "abc" at positions 6-8
		Match "abc" at positions 12-14
		Regular expression: "(abc)+"
		Match "abcabcabc" at positions 0-8
		Match "abc" at positions 12-14
		Regular expression: "(abc){2,}"
		Match "abcabcabc" at positions 0-8
	*/
public class RegularTestUtil{
	private String pattern;
	private String content;
	private Scanner scanner;
	
	public RegularTestUtil(){
		init(); //初始化成员变量
		test(); //执行测试动作
	}
	
	private void init(){
		scanner = new Scanner(System.in); //从键盘接收输入
		out.println("输入正则表达式:");
		//如果没有输入
		pattern = scanner.nextLine();
		if(pattern == null || pattern.trim().length() == 0){
			out.println("请正则表达式和需要测试的数据^_^");
			//那么结束程序
			exit(0);
		}
		//否则执行测试
	}
	
	private void test(){
		out.println("输入测试数据:");
		content = scanner.nextLine();
		//具体执行测试
		Pattern testPattern = Pattern.compile(pattern);
		Matcher matcher = testPattern.matcher(content);
		//如果找到匹配对象
		while(matcher.find()){
			//那么打印出来 
			Print.print("匹配 \"" + matcher.group() +"\" 位置:" 
				+ matcher.start() + "-" + (matcher.end() - 1));
		}
	}
}

最后

记得给大黍❤️关注+点赞+收藏+评论+转发❤️

作者:老九学堂—技术大黍

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。