thinking-in-java(18) java io

【0】README:本篇文章是以 thinking in java 为基础梳理的;


【18.1.1 目录列表器】 

// 传入正则表达式以过滤文件名如  (.*src)*
public class DirList {
	public static void main(String[] args) {
		File path = new File(".");
		String[] list; 
		if (args.length == 0)
			list = path.list();
		else {
			list = path.list(new DirFilter(args[0]));
		}
		Arrays.sort(list, String.CASE_INSENSITIVE_ORDER);
		for (String dirItem : list)
			System.out.println(dirItem);
	}
}

class DirFilter implements FilenameFilter {
	private Pattern pattern;

	public DirFilter(String regex) {
		// 编译正则表达式生成 Pattern模式对象
		pattern = Pattern.compile(regex);
	}

	public boolean accept(File dir, String name) {
		return pattern.matcher(name).matches();
	}
}  
 public String[] list(FilenameFilter filter) { // File.list()
        String names[] = list();
        if ((names == null) || (filter == null)) {
            return names;
        }
        List<String> v = new ArrayList<>();
        for (int i = 0 ; i < names.length ; i++) {
            if (filter.accept(this, names[i])) {
                v.add(names[i]);
            }
        }
        return v.toArray(new String[v.size()]);
    }

【代码解说】创建 DirFilter 这个类的目的在于把 accept() 方法  提供给 File.list() 使用, 使 list() 方法可以回调 accept() 方法。。(干货——这是一个策略模式的荔枝)


【匿名内部类编写 DirFilter 的 变体类】

public class DirList3 { 
	public static void main(final String[] args) { // 传入匿名内部类的参数是不可更改的,注意这里的final
		File path = new File(".");
		String[] list;
		if (args.length == 0)
			list = path.list();
		else
			list = path.list(new FilenameFilter() { // 匿名内部类
				private Pattern pattern = Pattern.compile(args[0]);

				public boolean accept(File dir, String name) {
					return pattern.matcher(name).matches();
				}
			});
		Arrays.sort(list, String.CASE_INSENSITIVE_ORDER);
		for (String dirItem : list)
			System.out.println(dirItem);
	}
}

(干货——匿名内部类的一个优点就是可以将解决问题的代码隔离,聚拢在一点)


【练习18.1,p528】


public class Exercise1801NEW {
	public static void main(String[] args) {
		mainTest(new String[] { "(chapter)+" });
	}

	private static void mainTest(final String[] args) {
		File dir = new File(
				"E:\\bench-cluster\\spring_in_action_eclipse\\AThinkingInJava\\Exercise"); 
		String[] results = dir.list(new FilenameFilter(){
			// 编译正则表达式生成 Pattern模式对象
			private Pattern pattern = Pattern.compile(args[0]);
			public boolean accept(File dir, String name) {
				/* 正则表达式\\W+ 表示 一个或多个非词字符;即按照非词字符进行分割 */
				ArrayList<String> words = new ArrayList<>(new TextFile(dir.getAbsolutePath() + File.separator +name,"\\W+"));
				for(String word : words) {
					if(pattern.matcher(word).matches() == true)
						return true;
				}
				return false;
			}
		});
		// 排序
		Arrays.sort(results, String.CASE_INSENSITIVE_ORDER);
		for(String r : results)
			System.out.println(r);
		System.out.println("num = " + results.length);
	}  
}

public class TextFile extends ArrayList<String> {
	// Read a file as a single string:
	public static String read(String fileName) {
		StringBuilder sb = new StringBuilder();
		try {
			BufferedReader in = new BufferedReader(new FileReader(new File(
					fileName).getAbsoluteFile()));
			try {
				String s;
				while ((s = in.readLine()) != null) {
					sb.append(s);
					sb.append("\n");
				}
			} finally {
				in.close();
			}
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
		return sb.toString();
	}

	// Write a single file in one method call:
	public static void write(String fileName, String text) {
		try {
			PrintWriter out = new PrintWriter(
					new File(fileName).getAbsoluteFile());
			try {
				out.print(text);
			} finally {
				out.close();
			}
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	// Read a file, split by any regular expression:
	public TextFile(String fileName, String splitter) {
		super(Arrays.asList(read(fileName).split(splitter)));
		// Regular expression split() often leaves an empty
		// String at the first position:
		if (get(0).equals(""))
			remove(0);
	}

	// Normally read by lines:
	public TextFile(String fileName) {
		this(fileName, "\n");
	}

	public void write(String fileName) {
		try {
			PrintWriter out = new PrintWriter(
					new File(fileName).getAbsoluteFile());
			try {
				for (String item : this)
					out.println(item);
			} finally {
				out.close();
			}
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	// Simple test:
	public static void main(String[] args) {
		String file = read("TextFile.java");
		write("test.txt", file);
		TextFile text = new TextFile("test.txt");
		text.write("test2.txt");
		// Break into unique sorted list of words:
		TreeSet<String> words = new TreeSet<String>(new TextFile(
				"TextFile.java", "\\W+"));
		// Display the capitalized words:
		System.out.println(words.headSet("a"));
	}
}
1.txt

english
english
(chapter)
english
english

2.txt

english
english
(chapter)2

【打印结果】

1.txt
2.txt
num = 2

=======================================================================

【练习18.2,p528】


public class Exercise1802 {
	public static void main(String[] args) {
		Exercise1802 e = new Exercise1802();
		Exercise1802.SortedDirList dirList = e.new SortedDirList(
				"E:\\bench-cluster\\spring_in_action_eclipse\\AThinkingInJava\\Exercise");
		
		System.out.println("====== pure list method. ======");
		for(String s : dirList.list()) {
			System.out.println(s);
		}
		
		String regex = "(chapter.*)+";
		System.out.printf("====== the list method with regex [%s]. ======\n", regex);
		for(String s : dirList.list(regex)) {
			System.out.println(s);
		}
	}
	
	class SortedDirList {
		String[] filenames = new String[]{""};
		File file;
		
		SortedDirList(String path) {
			file = new File(path);
			if(file != null) {
				filenames = file.list();
				Arrays.sort(filenames, String.CASE_INSENSITIVE_ORDER);
			}
		}
		// list重载方法一:列出所有文件名
		public String[] list() {
			return filenames;
		}
		// list重载方法二,带有正则表达式:列出过滤后的文件名;当然 其过滤策略可以按照练习18.1的思路
		public String[] list(final String regex) {
			if(file == null) return new String[]{""};
			return file.list(new FilenameFilter() {
				private Pattern pattern = Pattern.compile(regex);
				@Override
				public boolean accept(File dir, String name) {
					return pattern.matcher(name).matches();
				}
			}); 
		}
	}
}
【打印结果】

====== pure list method. ======
1.txt
2.txt
chapter1.txt
chapter2.txt
====== the list method with regex [(chapter.*)+]. ======
chapter1.txt
chapter2.txt

【练习18.3】


public class Exercise1803NEW {
	public static void main(String[] args) {
		String path = "E:\\bench-cluster\\spring_in_action_eclipse\\AThinkingInJava\\Exercise";
		Exercise1803NEW e = new Exercise1803NEW();
		Exercise1803NEW.SortedDirList dirList = e.new SortedDirList(path); // 这里使用练习二中的SortedDirList 类
		
		String regex = "(chapter.*)+";  
        System.out.printf("====== the list method with regex [%s] start.======\n", regex);
        String[] filenames;
        for(String s : (filenames = dirList.list(regex))) {  
            System.out.println(s);  
        }  
        System.out.printf("====== the list method with regex [%s] end.======\n", regex);
		// 打开选出的文件名所对应的文件,计算他们的 尺寸 总和。
		FileInputStream fis;
		int sum = 0;
		for(String filename: filenames) {
			StringBuilder sb = new StringBuilder();  
	        try {  
	            BufferedReader in = new BufferedReader(new FileReader(new File(  
	            		path+ File.separator + filename).getAbsoluteFile()));  
	            try {  
	                String s;  
	                while ((s = in.readLine()) != null) {  
	                    sb.append(s);  
	                    sb.append("\n");  
	                }  
	            } finally {  
	                in.close();  
	            }  
	        } catch (IOException e1) {  
	            throw new RuntimeException(e1);  
	        }  
	        sum += sb.length();
		}  
		System.out.println("总尺寸大小为 = " + sum);
	}

chapter1.txt
english
english
(chapter)2

chapter2.txt

english
english
(chapter)2

【打印结果】

====== the list method with regex [(chapter.*)+] start.======
chapter1.txt
chapter2.txt
====== the list method with regex [(chapter.*)+] end.======
总尺寸大小为 = 54


【18.1.2】 目录实用工具

public final class Directory {
	// 根据正则表达式过滤掉 dir 目录下的文件(File.list() 是文件名列表,而File.listFiles()是文件列表)
	public static File[] local(File dir, final String regex) {
		return dir.listFiles(new FilenameFilter() { // 静态内部类
			private Pattern pattern = Pattern.compile(regex);

			public boolean accept(File dir, String name) {
				return pattern.matcher(new File(name).getName()).matches();
			}
		});
	}
	// local() 方法重载
	public static File[] local(String path, final String regex) { // Overloaded
		return local(new File(path), regex);
	}

	// A two-tuple for returning a pair of objects:
	public static class TreeInfo implements Iterable<File> { // 静态内部类
		public List<File> files = new ArrayList<File>();
		public List<File> dirs = new ArrayList<File>();

		// The default iterable element is the file list:
		public Iterator<File> iterator() {
			return files.iterator();
		}

		void addAll(TreeInfo other) {
			files.addAll(other.files);
			dirs.addAll(other.dirs);
		}

		public String toString() {
			return "dirs: " + PPrint.pformat(dirs) + "\n\nfiles: "
					+ PPrint.pformat(files);
		}
	}
	
	// 4个重载方法开始
	public static TreeInfo walk(String start, String regex) { // Begin recursion
		return recurseDirs(new File(start), regex);
	}

	public static TreeInfo walk(File start, String regex) { // Overloaded
		return recurseDirs(start, regex);
	}

	public static TreeInfo walk(File start) { // Everything
		return recurseDirs(start, ".*");
	}

	public static TreeInfo walk(String start) {
		return recurseDirs(new File(start), ".*");
	}
	// 这个方法就是递归遍历目录树,并添加目录名和文件名,并根据正则表达式进行过滤。
	static TreeInfo recurseDirs(File startDir, String regex) {
		TreeInfo result = new TreeInfo(); 
		for (File item : startDir.listFiles()) { // File.list() 和 File.listFiles()区别
			if (item.isDirectory()) {
				result.dirs.add(item);
				result.addAll(recurseDirs(item, regex));
			} 
			else if (item.getName().matches(regex)) // 普通文件 
				result.files.add(item);
		}
		return result;
	}

	// Simple validation test:
	public static void main(String[] args) {
		if (args.length == 0)
			System.out.println(walk("."));
		else
			for (String arg : args)
				System.out.println(walk(arg));
	}
} // /:~
【代码解说】 local() 方法使用 被称为 File.list() 方法的变体方法 File.listFiles() 来产生 File数组。。如果需要List而不是数组,可以使用 Arrays.asList() 对结果进行转换。

此外, TreeInfo 实现了 Iterable<File>, 他将产生文件, 拥有在文件列表上的默认迭代。

此外,TreeInfo.toString() 使用了一个 打印机类 PPrint  来输出。

//打印机类 
public class PPrint {
	public static String pformat(Collection<?> c) {
		
		if (c.size() == 0)
			return "[]";
		StringBuilder result = new StringBuilder("[");
		for (Object elem : c) {
			if (c.size() != 1)
				result.append("\n  ");
			result.append(elem);
		}
		if (c.size() != 1)
			result.append("\n");
		result.append("]");
		return result.toString();
	}

	public static void pprint(Collection<?> c) {
		System.out.println(pformat(c));
	}

	public static void pprint(Object[] c) {
		System.out.println(pformat(Arrays.asList(c)));
	}
} // /:~


【对目录类工具 Directory的使用】

public class DirectoryDemo {
	public static void main(String[] args) {
		// All directories:
		PPrint.pprint(Directory.walk(".").dirs);
		System.out.println("---------------------seperator 1---------------------");
		// All files beginning with 'T'
		for (File file : Directory.local(".", "T.*"))
			print(file);
		
		print("-------------seperator 2---------");
		// All Java files beginning with 'T':
		// 正则表达式过滤
		for (File file : Directory.walk(".", "T.*\\.java"))
			print(file);
		
		print("==========seperator 3============");
		// Class files containing "Z" or "z":
		// 正则表达式过滤
		for (File file : Directory.walk(".", ".*[Zz].*\\.class"))
			print(file);
	}
}
public class Print {
  // Print with a newline:
  public static void print(Object obj) {
    System.out.println(obj);
  }
  // Print a newline by itself:
  public static void print() {
    System.out.println();
  }
  // Print with no line break:
  public static void printnb(Object obj) {
    System.out.print(obj);
  }
  // The new Java SE5 printf() (from C):
  public static PrintStream printf(String format, Object... args) {
    return System.out.printf(format, args);
  }
}

【回调函数+匿名内部类+目录树遍历器】

public class ProcessFiles {
	public interface Strategy {
		void process(File file);
	}

	private Strategy strategy;
	private String ext;
	
	// strategy可以通过匿名内部类传入
	public ProcessFiles(Strategy strategy, String ext) {
		this.strategy = strategy;
		this.ext = ext;
	}
	
	public void start(String[] args) {
		try {
			if (args.length == 0)
				processDirectoryTree(new File("."));
			else
				for (String arg : args) {
					File fileArg = new File(arg);
					if (fileArg.isDirectory())
						processDirectoryTree(fileArg);
					else {
						// Allow user to leave off extension:
						if (!arg.endsWith("." + ext))
							arg += "." + ext;
						strategy.process(new File(arg).getCanonicalFile());
					}
				}
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}
	
	// root == "." 当前目录
	public void processDirectoryTree(File root) throws IOException {
		String absPath = root.getAbsolutePath();
		// Directory.walk 遍历整个目录
		for (File file : Directory.walk(root.getAbsolutePath(), ".*\\." + ext)) { // 实现了Iterable接口就可以通过foreach 遍历
			File f2 = file.getCanonicalFile();
			System.out.println("file.getCanonicalFile() = " + file.getCanonicalFile());
			System.out.println("file.getAbsolutePath() = " + file.getAbsolutePath());
			/* 这个方法回调匿名内部接口实现类的响应方法。main方法中,你定义什么方法,就调用什么方法,牛逼。*/
			strategy.process(file.getCanonicalFile()); 
		}
	}

	// Demonstration of how to use it:
	public static void main(String[] args) {
		new ProcessFiles(new ProcessFiles.Strategy() { //匿名内部接口实现类
			public void process(File file) {
				System.out.println(file);
			}
		}, "java").start(args);
	}
}

【练习18.4】

public class Exercise1804 {
	public static void main(String[] args) {
		String path =
				"E:\\bench-cluster\\spring_in_action_eclipse\\AThinkingInJava\\Exercise";
		TreeInfo info = Directory.walk(new File(path), ".*");
		/* 因为TreeInfo 实现了Iterable接口,所以可以使用 foreach */
		int sum = 0;
		for(File file : info) { // 这里返回的是文件File,而不是文件名 filename
			StringBuilder sb = new StringBuilder();
			try {
				BufferedReader reader = new BufferedReader(new FileReader(file));
				try {
					String s;
					while(( s = reader.readLine()) != null) {
						sb.append(s);
					}
				} finally {
					reader.close();
				}
			} catch(Exception e) {
				throw new RuntimeException(e);
			}
			sum += sb.length();
		}
		System.out.println("总尺寸大小为 = " + sum);
	}
}

【练习18.5】


public class Exercise1805 {
	public interface Strategy {
		void process(File file);
	}

	private Strategy strategy;
	private String regex;
	
	//匹配正则表达式 regex
	public Exercise1805(Strategy strategy, String regex) {
		this.strategy = strategy;
		this.regex = regex;
	}

	public void start(String[] args) {
		try {
			if (args.length == 0)
				processDirectoryTree(new File("."));
			else
				for (String arg : args) {
					File fileArg = new File(arg);
					if (fileArg.isDirectory())
						processDirectoryTree(fileArg);
					else {
						// Allow user to leave off extension:
						if (!arg.endsWith("." + regex))
							arg += "." + regex;
						strategy.process(new File(arg).getCanonicalFile());
					}
				}
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}
	
	// root == "." 当前目录
	public void processDirectoryTree(File root) throws IOException {
		// 这里调用 Directory.walk() 的带有正则表达式的重载方法
		for (File file : Directory.walk(root.getAbsolutePath(), regex)) {
			File f2 = file.getCanonicalFile();
			strategy.process(file.getCanonicalFile());
		}
	}

	// Demonstration of how to use it:
	public static void main(String[] args) {
		new Exercise1805(new Exercise1805.Strategy() {
			public void process(File file) {
				System.out.println(file);
			}
		}, "T.*\\.java").start(args);
	}
}
【18.1.3】目录的检查与创建

File类可以用来创建新的目录或尚不存在的整个目录路径。

public class MakeDirectories {
	private static void usage() {
		System.err.println("Usage:MakeDirectories path1 ...\n"
				+ "Creates each path\n"
				+ "Usage:MakeDirectories -d path1 ...\n"
				+ "Deletes each path\n"
				+ "Usage:MakeDirectories -r path1 path2\n"
				+ "Renames from path1 to path2");
		System.exit(1);
	}

	private static void fileData(File f)  {
		try {
			System.out.println("Absolute path: " + f.getAbsolutePath()
					+ "\n Canonical Path: " + f.getCanonicalPath() 
					+ "\n Can read: " + f.canRead() + "\n Can write: "
					+ f.canWrite() + "\n getName: " + f.getName()
					+ "\n getParent: " + f.getParent() + "\n getPath: "
					+ f.getPath() + "\n length: " + f.length()
					+ "\n lastModified: " + f.lastModified());
		} catch(Exception e) {
			e.printStackTrace();
		}
		if (f.isFile()) 
			System.out.println("It's a file");
		else if (f.isDirectory())
			System.out.println("It's a directory");
	}

	public static void main(String[] args) {
		if (args.length < 1)
			usage();
		
		// -r path1 path2 means renames from path1 to path2.
		// -r == args[0]
		if (args[0].equals("-r")) {
			if (args.length != 3)
				usage();
			File old = new File(args[1]), rname = new File(args[2]);
			old.renameTo(rname); // 把old文件移动(注意是移动而不是复制)到rname的父目录下并把文件名改为rname,腻害了。
			fileData(old);
			fileData(rname);
			return; // Exit main
		}
		int count = 0;
		boolean del = false;
		if (args[0].equals("-d")) { // -d path1 means deletes files in path1.
			count++;
			del = true;
		}
		count--; 
		while (++count < args.length) { 
			File f = new File(args[count]);
			if (f.exists()) { // 是否存在
				System.out.println(f + " exists");
				if (del) {
					System.out.println("deleting..." + f);
					f.delete(); //删除文件
				}
			} else { // Doesn't exist
				if (!del) {
					f.mkdirs(); // 简历目录
					System.out.println("created " + f);
				}
			}
			fileData(f);
		}
	}
}
【练习6】


【18.2】 输入和输出

IO类库中的流: 表示任何有能力产出数据的数据源对象或是有能力接收数据的接收端对象;

【18.2.1】 InputStream 类型

InputStream 用来表示那些从不同数据源产生输入的类。包括:

字节数组;

String对象;

文件;

管道,从一端输入,另一端输出;

一个由其他种类的流组成的序列,以便我们可以合并到一个流内;

其他数据源;


FilterInputStream 属于一种 InputStream, 为装饰器提供基类;




【18.2.2】OutputStream类型

1)输出的目标是字节数组, 文件或管道;

同样, FilterOutputStream 为 装饰器类提供了一个基类。。



【18.3】 添加属性和有用的接口

【18.3.1】 通过 FilterInputStream 从 InputStream 读取数据

FilterInputStream 类能够完成两件完全不同的事情。。其中, DataInputStream 允许读取不同基本类型数据以及String类型的对象。DataOutputStream 能够将 基本类型的数据从一个地方迁到另一个地方。




【18.3.2】通过 FilterOutputStream 向 OutputStream 写入

1)OutputStream 将各种基本类型数据以及String对象格式化输出到流中;

2)PrintStream的目的是为了以可视化格式打印所有的基本数据类型以及String对象(有两个重要的方法 print() 和 println()方法);而 OutputStream 的目的是将数据元素置入到流中;

3)BufferedOutputStream: 对数据流使用缓冲技术;因此当每次向流写入时,都不必进行实际的物理写动作。(干货——可能更经常使用BufferedOutputStream)


【18.4】Reader 和 Writer

1)Reader 和 Writer: 面向字符的;Java的一个字符 == 两个字节; 它们提供了兼容 Unicode 与 面向字符的 IO功能; 而 java的char字符是 16位==2个字节的Unicode编码;

2)InputStream 和 OutputStream : 面向字节的 ;

3)有时,我们必须将 面向字符的类 和 面向字节的类 结合起来使用。 这就要用到 适配器类: 

3.1)InputStreamReader: 可以把 InputStream 转换为 Reader;

3.2)OutputStreamWriter: 可以把 OutputStream 转换为 Writer;

4)设计Reader 和 Writer的目的是 为了 国际化(干货)


【18.4.1】数据的来源和出处

1)最明智的做法是: 尽量使用Reader 和 Writer,一旦程序无法成功编译,就使用面向字节的 InputStream 和 OutputStream 类来解决,比如 java.util.zip 类库就是面向字节的而不是字符;



【18.4.2】更改流的行为

1)对于InputStream 和 outputStream 来说,我们会使用 FilterInputStream 和 FilterOutputStream 的 装饰器子类来修改流以满足特殊需要。。


2)需要使用 readline() 方法, 就要使用  BufferedReader 类。

3)此外, DataInputStream 仍然是IO类库的 首选成员。(干货)

4)PrintWriter: 既能够接受 Writer对象 又能够接受任何 OutputStream对象的 构造器。。PrintWriter的 格式化接口与 PrintStream 相同。 PrintWriter 还有一个 自动执行清空的选项。。如果构造器设置这个选项, 则在每个 println() 之后, 便会自动清空。。


【18.4.3】 未发生变化的类



【18.5】自我独立的类: RandomAccessFile

1)RandomAccessFile: 适用于由大小已知的记录组成的文件,可以使用 seek() 方法 将记录从一处转移到另一处,然后读取或修改记录;

2)这个类实现了 DataInput 和 DataOutput接口,她是一个完全独立的类,从头开始编写其所有的方法。。有了这个类,我们可以在文件内向前或向后移动;

3)本质上说: RandomAccessFile 类似于 把 DataInputStream 和 DataOutputStream 组合起来使用; 方法列表有:

3.1)getFilePointer(): 查找当前所处的文件位置;

3.2)seek(): 用于在文件内移到新的位置;

3.3) length() : 用于判断文件的最大尺寸;

4)其构造器还有第二个参数: 用来指示我们只是随机读(r)还是既读又写(rw),它并不支持只写文件(干货)

5)只有RandomAccessFile 支持搜寻方法, 并且只适用于文件。。BufferedInputStream 却能允许标注位置(mark()方法) 和 重置位置(reset()方法),但BufferedInputStream 的这些方法不是特别常用;


【18.6】IO流的典型使用方式

【18.6.1】缓冲输入文件

可以使用String或 File对象作为 FileInputReader 的输入参数。 引入BufferedReader 缓冲器,其提供了 readline() 方法。当readline() 返回null时,达到了文件的末尾。

public class BufferedInputFile {
	// Throw exceptions to console:
	public static String read(String filename) throws IOException {
		// Reading input by lines:
		// 
		BufferedReader in = new BufferedReader(new FileReader(filename));
		String s;
		StringBuilder sb = new StringBuilder();
		while ((s = in.readLine()) != null)
			sb.append(s + "\n");
		in.close(); // 别忘记关闭字符输入流
		return sb.toString();
	}

	public static void main(String[] args) throws IOException {
		String path = 
				"E:\\bench-cluster\\spring_in_action_eclipse\\AThinkingInJava\\src\\chapter18";
		System.out.print(read(path + File.separator + "BufferedInputFile.java"));
	}
} /* (Execute to see output) */// :~
【代码解说】public class FileReader extends InputStreamReader ;而 InputStreamReader 作为适配器可以将 InputStream 对象转换为 Reader 对象 ;

【练习7】


public class Exercise1807 {
	public static void main(String[] args) {
		LinkedList<String> list = new LinkedList<>();
		String path = 
				"E:\\bench-cluster\\spring_in_action_eclipse\\AThinkingInJava\\src\\chapter18\\";
		FileReader reader = null;
		try {
			// 如BufferedInputFile,用BufferedReader 封装 FileReader。
			reader = new FileReader(new File(path + "Exercise1807.java"));
			BufferedReader br = new BufferedReader(reader);
			
			String in;
			while ((in=br.readLine()) != null) {
				list.add(in);
			}
		} catch(IOException e) {
			throw new RuntimeException(e); // 转换为运行时异常,无需调用者处理
		} finally {
			try {
				reader.close(); // 记得关闭字符流。
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		print(list);
		System.out.println("\n\n====== 对 list 执行相反的顺序后: ");
		// Linkedlist 实现了双向链表,所以可以逆向打印。
		ListIterator<String> li = list.listIterator(list.size());
		while (li.hasPrevious()) {
			System.out.println(li.previous());
		}
	}
	
	public static void print(Collection<String> list) {
		for (Object t:list) {
			System.out.println(t.toString());
		}
	}
}

【练习8】(无难点)


【练习9】


【ERROR】:这里应该是 LinkedList 而不是 ArrayList ;参见练习7的描述;

for(String s : list) {
			// 字符串转换为大写,无难度。
			System.out.println(s.toUpperCase());
		}
【练习10】

public class Exercise1810NEW {
	public static void main(String[] args) {
		LinkedList<String> list = new LinkedList<>();
		String path = 
				"E:\\bench-cluster\\spring_in_action_eclipse\\AThinkingInJava\\src\\chapter18\\";
		FileReader reader = null;
		try {
			// 如BufferedInputFile,用BufferedReader 封装 FileReader。
//			reader = new FileReader(new File(path + "Exercise1809.java"));
			reader = new FileReader(new File(path + args[0])); // 命令行参数
			BufferedReader br = new BufferedReader(reader);
			//通过正则表达式来过滤
			Pattern pattern = Pattern.compile(".*"+args[1] + ".*");
			String in;
			while ((in=br.readLine()) != null) {
				if(pattern.matcher(in).matches()) { // 是否匹配
					list.add(in);
				}
			}
		} catch(IOException e) {
			throw new RuntimeException(e); // 转换为运行时异常,无需调用者处理
		} finally {
			try {
				reader.close(); // 记得关闭字符流。
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		for(String s : list) {
			// 字符串转换为大写,无难度。
			System.out.println(s.toUpperCase());
		}
	}
}
运行参数: Exercise1810NEW.java String

运行结果: 

PUBLIC STATIC VOID MAIN(STRING[] ARGS) {
LINKEDLIST<STRING> LIST = NEW LINKEDLIST<>();
STRING PATH = 
STRING IN;
FOR(STRING S : LIST) {

【练习11】

【18.6.2】从内存输入

将String 作为字符输入流 (BufferedInputFile.read() 方法 参见18.6.1)

public class MemoryInput {
	public static void main(String[] args) throws IOException {
		String path = 
				"E:\\bench-cluster\\spring_in_action_eclipse\\AThinkingInJava\\src\\chapter18\\MemoryInput.java";
		/* BufferedInputFile.read(path) 会返回带分行号的字符串 */
		/* 将String 作为字符输入流  */
		StringReader in = new StringReader(
				BufferedInputFile.read(path)); 
		
		int c;
		while ((c = in.read()) != -1)
			System.out.print((char) c); // java中的char 本来就占两个字节
	}
}
注意, StringReader.read()方法返回的是int 类型数据,需要强制转换为 char 类型。


【18.6.3】格式化内存输入: 读取格式化数据(DataInputStream),DataInputStream 是以输入字节流的形式读入数据。

我们可以用 InputStream 以字节的形式读取任何数据(例如一个文件)。。

public class FormattedMemoryInput {
	public static void main(String[] args) throws IOException {
		String path = 
				"E:\\bench-cluster\\spring_in_action_eclipse\\AThinkingInJava\\src\\chapter18\\FormattedMemoryInput.java";
		try {
			/* 字节数组-》字节数组输入流-》数据输入流。 */
			/* DataInputSteam存在的意义在于可以读取格式化的数据 */
			DataInputStream in = new DataInputStream(new ByteArrayInputStream(
					BufferedInputFile.read(path).getBytes())); // getBytes()方法:String 转换为 字节数组
			while (true)
				System.out.print((char) in.readByte()); //以字节的形式读取
		} catch (EOFException e) {
			System.err.println("End of stream");
		} finally{ in.close();} // 记得关闭输入流。
	}
} 
【注意】:因为DataInputStream().readBytes() 是一个字节一个字节的读取数据,所以任何字节的值都是合法的,当然也包括文件结束符,所以无法判断文件是否达到末尾。只能在异常中捕获 EOF异常;

available() 方法:查看还剩多少字节没有读取。不过要慎用 available() 方法(干货-慎用 available() 方法)
【我的观点】  
慎用的意思是不是禁用。。正式生产环境禁用 available() 方法。。为什么慎用? 作者说:“”available方法的工作方式会随着读取媒介的类型的不同而不同,换句换说, 是在没有阻塞情况下所能读取的字节数目。。“” 

public class TestEOF {
	public static void main(String[] args) throws IOException {
		String path = 
				"E:\\bench-cluster\\spring_in_action_eclipse\\AThinkingInJava\\src\\chapter18\\TestEOF.java";
		DataInputStream in = new DataInputStream(new BufferedInputStream(
				new FileInputStream(path)));
		/* 慎用 available() 方法 */
		while (in.available() != 0)
			System.out.print((char) in.readByte());
	} finally {in.close()} // 再次提醒: 记得关闭输入流
}
以上的装饰过程是: 文件名(代路径) -> 文件输入流FileInputStream -> 缓冲输入流BufferedInputStream -> 数据输入流DatainputStream ;


【18.6.4】 基本的文件输出 FileWriter

public class BasicFileOutput {
	static String file = "E:\\bench-cluster\\spring_in_action_eclipse\\AThinkingInJava\\src\\chapter18\\BasicFileOutput.out";

	public static void main(String[] args) throws IOException {
		String path = 
				"E:\\bench-cluster\\spring_in_action_eclipse\\AThinkingInJava\\src\\chapter18\\BasicFileOutput.java";
		/* 输入流装饰过程:String->StringReader->BufferedReader */
		BufferedReader in = new BufferedReader(new StringReader(
				BufferedInputFile.read(path)));
		/* 输出流装饰过程:String->FileWriter->BufferedWriter-> PrintWriter */
		/* 输出到BasicFileOutput.out 文件中 */
		PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(
				file)));
		
		int lineCount = 1;
		String s;
		while ((s = in.readLine()) != null)
			out.println(lineCount++ + ": " + s);
		out.close(); // 记得关闭输出流。
		
		// Show the stored file:
		/* 输出到标准输出 */
		System.out.println(BufferedInputFile.read(file));
	}
}

【注意】必须调用 close() 方法,该方法会清空缓冲区内容,输出内容才会完整。

【文本文件输出的快捷方式】

Java SE5 在 PrintWriter 中添加了一个辅助构造器。使得可以快捷创建字符输出流。

public class FileOutputShortcut {
	static String file = 
			"E:\\bench-cluster\\spring_in_action_eclipse\\AThinkingInJava\\src\\chapter18\\FileOutputShortCut.out";

	public static void main(String[] args) throws IOException {
		String path = 
				"E:\\bench-cluster\\spring_in_action_eclipse\\AThinkingInJava\\src\\chapter18\\FileOutputShortCut.java";
		BufferedReader in = new BufferedReader(new StringReader(
				BufferedInputFile.read(path)));
		
		/* BasicFileOutput中的实现方式 ——创建字符输出流的基本方式*/
//		PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(file))); // 快捷的构造器
		// Here's the shortcut: 创建字符输出流的快捷方式
		PrintWriter out = new PrintWriter(file);
		int lineCount = 1;
		String s;
		while ((s = in.readLine()) != null)
			out.println(lineCount++ + ": " + s);
		out.close();
		// Show the stored file:
		System.out.println(BufferedInputFile.read(file));
	}
}

你仍旧是在缓存,只不过不是自己去手动实现,如下:

public PrintWriter(String fileName) throws FileNotFoundException {
        this(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(fileName))),
             false);
    }
【练习12】

【练习13】

【练习14】

【18.6.5】 存储和恢复数据

1. PrintWriter 可以对数据进行格式化。需要用DataOutputStream 写入数据, 并用 DataInputStream 恢复数据。

/* 通过DataInputStream 和 DataOutputStream字节流存储和恢复数据 */
public class StoringAndRecoveringData {
	public static String path = 
			"E:\\bench-cluster\\spring_in_action_eclipse\\AThinkingInJava\\src\\chapter18\\";
	public static void main(String[] args) throws IOException {
		DataOutputStream out = new DataOutputStream(new BufferedOutputStream(
				new FileOutputStream(path + "StoringAndRecoveringData.out")));
		/* DataOutputStream 输出字节流存储数据 */
		out.writeDouble(3.14159);
		out.writeUTF("That was pi");
		out.writeDouble(1.41413);
		out.writeUTF("Square root of 2");
		out.writeUTF("唐");
		out.close();
		
		/* DataInputStream 输入字节流恢复数据 */
		DataInputStream in = new DataInputStream(new BufferedInputStream(
				new FileInputStream(MyConstants.path + "StoringAndRecoveringData.out")));
		System.out.println(in.readDouble());
		// Only readUTF() will recover the
		// Java-UTF String properly:
		System.out.println(in.readUTF());
		System.out.println(in.readDouble());
		System.out.println(in.readUTF());
		System.out.println(in.readUTF());
	}
}

StoringAndRecoveringData.out 的内容如下:

@ !���n

字节输入流的装饰过程: FileOutputStream-> BufferedOutputStream -> DataoutputStream 

字节输出流的装饰过程: FileInputStream -> BufferedInputStream -> DataInputStream

DataInputStream 和 DataOutputStream 存在的意义在于: 使用DataOutputStream 写入数据, java可以保证用 DataInputStream 准确无误地恢复该数据;因为使用了 UTF-8编码,通过writeUTF()  和 readUTF() 方法来实现;  

【注意】UTF-8将 ASCII 字符编码成 单一字节的形式, 而非 ASCII 字符则编码成 两到三个字节的形式。 而且字符串长度存储在 UTF-8字符串的前两个字节中。

【注意】对象序列化或xml文件格式 可能是更容易存储和读取复杂数据结构的方式。

【练习15】

【18.6.6】读写随机访问文件 RandomAccessFile

public class UsingRandomAccessFile {
	static String file = "E:\\bench-cluster\\spring_in_action_eclipse\\AThinkingInJava\\src\\chapter18\\" 
				+ "UsingRandomAccessFile.out";

	static void display() throws IOException {
		RandomAccessFile rf = new RandomAccessFile(file, "r"); // 只读r 方式
		for (int i = 0; i < 7; i++)
			System.out.println("Value " + i + ": " + rf.readDouble());
		System.out.println(rf.readUTF());
		rf.close();
	}

	public static void main(String[] args) throws IOException {
		RandomAccessFile rf = new RandomAccessFile(file, "rw"); // 可读可写rw 方式
		for (int i = 0; i < 7; i++)
			rf.writeDouble(i * 1.414);
		rf.writeUTF("The end of the file");
		rf.close();
		
		display();
		
		rf = new RandomAccessFile(file, "rw");
		rf.seek(5 * 8); // double占用8个字节,跳过40个字节;
		rf.writeDouble(47.0001);
		rf.close();
		
		display();
	}
}
打印结果:

Value 0: 0.0
Value 1: 1.414
Value 2: 2.828
Value 3: 4.242
Value 4: 5.656
Value 5: 7.069999999999999
Value 6: 8.484
The end of the file
Value 0: 0.0
Value 1: 1.414
Value 2: 2.828
Value 3: 4.242
Value 4: 5.656
Value 5: 47.0001  // highlight line .
Value 6: 8.484
The end of the file

【18.6.7】管道流

PipedInputStream, PipedOutputStream, PipedReader 和 PipedWriter 的作用,只有在理解多线程之后才会显现, 因为管道流用于任务之间的通信。。


【18.7】文件读写的实用工具

Java SE5 在 PrintWriter中添加了方便的构造器,可以和方便的对一个文件进行读写。

public class TextFile extends ArrayList<String> {
	public TextFile(){}
	// Read a file as a single string:
	public static String read(String fileName) {
		StringBuilder sb = new StringBuilder();
		try {
			BufferedReader in = new BufferedReader(new FileReader(new File(
					fileName).getAbsoluteFile()));
			try {
				String s;
				while ((s = in.readLine()) != null) {
					sb.append(s);
					sb.append("\n");
				}
			} finally {
				in.close(); // 关闭输入输出流
			}
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
		return sb.toString();
	}

	// Write a single file in one method call:
	/* 注意这种try try finaly catch 的结构 */
	public static void write(String fileName, String text) {
		try {
			PrintWriter out = new PrintWriter(
					new File(fileName).getAbsoluteFile());
			try {
				out.print(text);
			} finally {
				out.close(); // 关闭输入输出流
			}
		} catch (IOException e) {
			/* 把io异常转换为 运行时异常,不需要调用者捕获他 。 */
			throw new RuntimeException(e); 
		}
	}

	// Read a file, split by any regular expression:
	public TextFile(String fileName, String splitter) {
		super(Arrays.asList(read(fileName).split(splitter)));
		// Regular expression split() often leaves an empty
		// String at the first position:
		if (get(0).equals(""))
			remove(0);
	}

	// Normally read by lines:
	public TextFile(String fileName) {
		this(fileName, "\n");
	}

	public void write(String fileName) {
		try {
			// PrintWriter便利的构造器,便于写文件 
			PrintWriter out = new PrintWriter(
					new File(fileName).getAbsoluteFile());
			try {
				for (String item : this)
					out.println(item);
			} finally {
				out.close(); // 关闭输入输出流
			}
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	// Simple test:
	public static void main(String[] args) {
		/*public static String path = 
				"E:\\bench-cluster\\spring_in_action_eclipse\\AThinkingInJava\\src\\chapter18\\";*/
		String file = read(MyConstants.path + "TextFile.java");
		write(MyConstants.path + "test1.out", file);
		
		TextFile text = new TextFile(MyConstants.path + "test1.out");
		text.write(MyConstants.path + "test2.txt");
		
		// Break into unique sorted list of words:
		// 基于树结构的Set容器
		TreeSet<String> words = new TreeSet<String>(new TextFile(
				MyConstants.path + "TextFile.java", "\\W+"));
		// Display the capitalized words:
		System.out.println(words.headSet("a"));
	}
}
【注意】另外一种读取文件的方式是 使用 java.util.Scanner类, 只能用于读入文件,不能用于写入,大多数case下是读取命令行输入。

【练习17】

【练习18】

【18.7.1】 读取二进制文件

BinaryFile 简化了读取二进制文件的过程:

public class BinaryFile {
	private static String path = 
			"E:\\bench-cluster\\spring_in_action_eclipse\\AThinkingInJava\\src\\chapter18";
	public static byte[] read(File bFile) throws IOException {
		BufferedInputStream bf = new BufferedInputStream(new FileInputStream(
				bFile));
		try {
			/*  作者之前不是说要慎用 available() 方法吗? */ 
			byte[] data = new byte[bf.available()];
			/* 将字节数组作为输入流  */
			bf.read(data);
			return data;
		} finally {
			bf.close();
		}
	}

	public static byte[] read(String bFile) throws IOException {
		return read(new File(bFile).getAbsoluteFile());
	}
	public static void main(String[] args) {
		try {
			byte[] array = read(path+File.separator + "BinaryFile.java");
			for(byte b : array) {
				/* 强转为 char 类型输出 */
				System.out.print((char)b); // 中文字符无法正确显示.
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
} // /:~
【练习19】


public class Exercise1819 {
	public static String path = 
			"E:\\bench-cluster\\spring_in_action_eclipse\\AThinkingInJava\\src\\chapter18\\";
	public static void main(String[] args) throws IOException {
		byte[] array = BinaryFile.read(path + "Exercise1819.java");
		Map<Byte, Integer> map = new HashMap<>();
		
		for (byte b : array) {
			if (map.get(b)==null) {
				map.put(b, 1);
			} else {
				map.put(b, map.get(b)+1);
			}
		}
		Iterator<Byte> it = map.keySet().iterator();
		while (it.hasNext()) {
			byte b = it.next();
			System.out.println("[" + (char)b + "] = " + map.get(b));
		}
	}
}

【练习20】(这里注意是16进制字符)


public class Exercise1820 {
	public static String path = 
			"E:\\bench-cluster\\spring_in_action_eclipse\\AThinkingInJava\\src\\chapter18\\";
	public static void main(String[] args) throws IOException {
		TreeInfo info = Directory.walk(path, ".*.class");
		List<File> list = info.files;
		int counter = 0;
		
		/* 未完待续 */
		for (File file : list) {
			byte[] array = BinaryFile.read(file);
			if (new String(array, 0, 16).equals("CAFEBABE") == true) {
				counter++;
			}
		}
		
		if(counter == list.size()) {
			System.out.println("Y");
		} else {
			System.out.println("N");
		}
	}
}

【18.8】 标准IO

【18.8.1】从标准输入中读取

标准输入:System.in, 没有包装过的 InputStream 对象;

标准输出:System.out,  被包装成了 PrintStream 对象;

标准错误: System.err, 被包装成了 PrintStream 对象;

所以,我们可以立即使用 System.out System.err, 但是在读取 System.in 之前必须对其包装, 用Scanner 实现;

InputStream in = System.in;
PrintStream out = System.out;
PrintStream err = System.err;
// public final class Scanner implements Iterator<String>, Closeable { // Scanner 可迭代
		Scanner s = new Scanner(System.in);
		while (true) {
			String r = s.next();
			if (r.equals("exit")) 
				break;
			System.out.println(r);
		}
通常,我们会用readLine() 一次一行的读取输入,所以需要将 System.in 包装为 BufferedReader, 而InputStreamReader 可以把 InputStream 转换为  Reader ,然后Reader 再 转换为 BufferedReader , bingo。 (干货——System.in 和大多数的输入流一样,通常都是需要缓冲的)

public class Echo {
	public static void main(String[] args) throws IOException {
		/* InputStream -> Reader -> BufferedReader */
		BufferedReader stdin = new BufferedReader(new InputStreamReader(
				System.in));
		String s;
		/* 回车结束 */
		while ((s = stdin.readLine()) != null && s.length() != 0)
			System.out.println(s);
		// An empty line or Ctrl-Z terminates the program
	}
} // /:~
【练习21】

【18.8.2】 将 System.out 转换为 PrintWriter

System.out 是一个PrintStream, 而 PrintStream 是一个 OutputStream。 PrintWrite 是一个可以接受 OutputStream 作为参数的构造器。

public class ChangeSystemOut {
	public static void main(String[] args) {
		// 将System.out 转为 PrintWriter的构造器
		PrintWriter out = new PrintWriter(System.out, true); // true 开启自动清空功能;否则,可能看不到输出;
		out.println("Hello, world");
	}
}


【18.8.3】标准IO重定向

public class Redirecting {
	public static String path = 
			"E:\\bench-cluster\\spring_in_action_eclipse\\AThinkingInJava\\src\\chapter18\\";
	public static void main(String[] args) throws IOException {
		PrintStream console = System.out;
		BufferedInputStream in = new BufferedInputStream(new FileInputStream(path + "Redirecting.java"));
		PrintStream out = new PrintStream(new BufferedOutputStream(
				new FileOutputStream(path + "Redirecting.out")));
		System.setIn(in); // 标准输入重定向为 文件输入
		System.setOut(out); // 标准输出重定向为 文件输出
		System.setErr(out); // 标准错误输出 重定向为 文件输出
		
		// InputStreamReader 把 inputStream字节流 转为 Reader字符流
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); // 标准输入已重定向
		String s;
		while ((s = br.readLine()) != null)
			System.out.println(s); // 标准输出已重定向 
		out.close(); // Remember this!
		System.setOut(console); // 重置标准输出
		System.out.println("success");
	}
}
【注意】IO重定向操作的是 字节流 不是字符流;所以使用了 InputStream 和 OutputStream 数据源,而不是 Reader 和 Writer 数据源;


【18.9】 进程控制

【18.10】新 IO

jdk 1.4  的 java.nio.* 引入了新的 java IO类库。

速度提高的方法类似于 操作系统执行IO 的方式: 通道和缓冲器。

唯一直接与通道交互的缓冲器是 ByteBuffer,可以存储未加工字节的 缓冲器 。

旧IO类库有3个类被修改了,用以产生 FileChannel,分别是 FileInputStream, FileOutputStream 和 可读可写的 RandomAccessFile。

Reader 和 Writer 等字符流不能用于产生通道,只有字节流可以产生通道。

java.nio.channels.Channels 类提供了方法,用来在通道中产生 Reader   和 Writer。

public class GetChannel {
	private static final int BSIZE = 1024;
	public static String path = 
			"E:\\bench-cluster\\spring_in_action_eclipse\\AThinkingInJava\\src\\chapter18\\";
	public static void main(String[] args) throws Exception {
		// Write a file:
		FileChannel fc = new FileOutputStream(path + "A.txt").getChannel();
		fc.write(ByteBuffer.wrap("Some text ".getBytes())); // 把字节数组封装到 ByteBuffer 并输出到通道
		fc.close();
		
		// Add to the end of the file:
		fc = new RandomAccessFile(path + "A.txt", "rw").getChannel();
		fc.position(fc.size()); // Move to the end 文件指针移动到文件末尾
		fc.write(ByteBuffer.wrap("Some more".getBytes())); // 把字节数组封装到 ByteBuffer 并输出通道
		fc.close();
		
		// Read the file:
		fc = new FileInputStream(path + "A.txt").getChannel();
		ByteBuffer buff = ByteBuffer.allocate(BSIZE); // 分配多少内存大小
		fc.read(buff); // 从通道读取数据到 ByteBuffer 
		buff.flip(); // 一旦调用read() 方法来告知 FileChannel 向 ByteBuffer 存储字 ( fc.read(buff) ) ),就必须调用 ByteBuffer上的 flip() 轻击/敲打方法,让它做好让别人读取字节的准备
		while (buff.hasRemaining())
			System.out.print((char) buff.get());
	}
}

 public final Buffer flip() {
        limit = position;
        position = 0;
        mark = -1;
        return this;
    }

显然,  fc.read(buff) 表示  FileChannel通道向 缓冲区 ByteBuffer 存储字节,然后buff 内的 文件指针会移动到 存储字节末尾;然后 因为要调用 buff.get() 方法用于 读取字节,这需要指针来做字节定位; flip() 方法的作用就是 重置文件指针到 ByteBuffer 存储空间的最开始位置,这样就可以读取刚刚存储的字节了;

【注意】 nio的目标是快速移动大量数据,因此 ByteBuffer 的大小就很重要,必须使用静态的allocate() 方法类分配ByteBuffer的容量。。使用 allocateDirect() 而不是 allocate() 方法,以产生与 操作系统有更高耦合性的 直接缓冲器。

ByteBuffer 有两个子类: HeapByteBuffer  和 DirectByteBuffer 。

public class ChannelCopy {
	private static final int BSIZE = 1024;
	private static final String path = System.getProperty("user.dir") + File.separator + "src" + File.separator + "chapter18" + File.separator;
	
	public static void main(String[] args) throws Exception {
		FileChannel in = new FileInputStream(new File(path + "ChannelCopy.java")).getChannel();
		FileChannel out = new FileOutputStream(new File(path + "ChannelCopy.out")).getChannel();
		ByteBuffer buffer = ByteBuffer.allocate(BSIZE);
		
		while (in.read(buffer) != -1) { // 从通道中读取数据到 字节缓冲器 ByteBuffer
			buffer.flip(); // Prepare for writing  (将文件指针重置到 缓冲器开始位置)
			out.write(buffer); // 从 buffer 输出数据到 输出文件通道out。
			buffer.clear(); // Prepare for reading。。重置文件指针到开始位置,便于 输入通道从文件读取数据到 缓冲区 ByteBuffer
			// Attention: clear() 方法 与 flip() 方法的区别。
		}
	}
}

public final Buffer clear() {
        position = 0;
        limit = capacity;
        mark = -1;
        return this;
    }

 public final Buffer flip() {
        limit = position;
        position = 0;
        mark = -1;
        return this;
    }

【注意比较 clear() 方法 和 flip()  方法的区别,仅有一个区别】

【注意】 输入通道与输出通道相连通常不用上面的方法,而是用 transferTo()  and transferFrom() 方法;

//  输入通道与输出通道相连的常用方法。(尽管不常用)
public class TransferTo {
	private static final String path = System.getProperty("user.dir") + File.separator + "src" + File.separator + "chapter18" + File.separator;
	
	public static void main(String[] args) throws Exception {
		FileChannel in = new FileInputStream(new File(path + "ChannelCopy.java")).getChannel();
		FileChannel out = new FileOutputStream(new File(path + "ChannelCopy.out")).getChannel();
		
		in.transferTo(0, in.size(), out);
		// Or:
		// out.transferFrom(in, 0, in.size());
	}
}

【18.10.1】转换数据

把 ByteBuffer 当做 CharBuffer 会输出乱码; 

public class BufferToText {
	private static final int BSIZE = 1024;
	public static String path = System.getProperty("user.dir") + File.separator + "src" + File.separator + "chapter18" + File.separator;
	
	public static void main(String[] args) throws Exception {
		System.out.println("=== 1 === ");
		// 利用文件输出通道 输出数据
		FileChannel fc = new FileOutputStream(path + "BufferToText.out").getChannel();
		fc.write(ByteBuffer.wrap("小甜甜".getBytes())); // 缓冲区的数据输出到 通道
		fc.close(); // 不要忘记关闭通道
		
		System.out.println("=== 2 === ");
		// 利用 文件输入流通道 读入 数据
		fc = new FileInputStream(path + "BufferToText.java").getChannel();
		ByteBuffer buff = ByteBuffer.allocate(BSIZE);
		fc.read(buff);
		buff.flip();
		// Doesn't work: // 为什么不起作用呢? 
		System.out.println(buff.asCharBuffer()); // attention, 这里输出的是乱码 
		
		System.out.println("=== 3 === ");
		// Decode using this system's default Charset:
		buff.rewind(); // buff内部指针 返回到 缓冲区的首部
		String encoding = System.getProperty("file.encoding");
		System.out.println("Decoded using " + encoding + ": "
				+ Charset.forName(encoding).decode(buff)); // 对 缓冲区的字节数据 进行 解码操作 
		
		System.out.println("=== 4 === ");
		// 将字符串 编码后 放入缓冲区, 然后 通过通道 输出到 文件.
		// Or, we could encode with something that will print:
		fc = new FileOutputStream(path + "BufferToText2.out").getChannel();
		fc.write(ByteBuffer.wrap("唐荣".getBytes("UTF-8"))); // 按照字符串编码把缓冲区的数据写出到通道
		fc.close(); 
		
		System.out.println("=== 5 === ");
		// 利用文件输入流通道 读入数据
		fc = new FileInputStream(path + "BufferToText.java").getChannel();
		buff.clear();
		fc.read(buff);
		buff.flip();
		System.out.println(buff.asCharBuffer()); // 这里还是乱码
		System.out.println(Charset.forName("UTF-8").decode(buff));
		
		System.out.println("=== 6 === ");
		// 使用 CharBuffer 将 数据 放入  缓冲区中.
		// Use a CharBuffer to write through:
		fc = new FileOutputStream(path + "BufferToText3.out").getChannel();
		buff = ByteBuffer.allocate(24); // More than needed
		buff.asCharBuffer().put("Some text 小唐"); // 中文会有乱码	
		fc.write(buff);
		fc.close();
		
		System.out.println("=== 7 === ");
		// 读取数据 进行显示
		// Read and display:
		fc = new FileInputStream(path + "BufferToText.java").getChannel();
		buff.clear(); // 在 Channel.read() 读取操作之前 要 clear 缓冲区 以便有 读取数据
		fc.read(buff); // 数据 从 文件 经过通道 读入到 缓冲区.
		buff.flip(); // 在 write 方法前 要 flip() 提醒 通道 可以 输出数据 到 通道了.
		System.out.println(buff.asCharBuffer()); // 乱码
	}
} 

public CharBuffer asCharBuffer() {
        int size = this.remaining() >> 1;
        int off = offset + position();
        return (bigEndian
                ? (CharBuffer)(new ByteBufferAsCharBufferB(this,
                                                               -1,
                                                               0,
                                                               size,
                                                               size,
                                                               off))
                : (CharBuffer)(new ByteBufferAsCharBufferL(this,
                                                               -1,
                                                               0,
                                                               size,
                                                               size,
                                                               off)));
    }


【乱码的解决方法】 要么在输入到缓冲器的时候对其进行编码; 要么从缓冲器输出的时候对其进行解码; java.nio.charset.Charset 类提供了这些功能;

public class AvailableCharSets {
	public static void main(String[] args) {
		/* SortedMap有序Map */
		SortedMap<String, Charset> charSets = Charset.availableCharsets();
		Iterator<String> it = charSets.keySet().iterator();
		
		/* 输出编码字符集  */
		while (it.hasNext()) {
			String csName = it.next();
			printnb(csName);
			Iterator aliases = charSets.get(csName).aliases().iterator(); // 别名
			if (aliases.hasNext())
				printnb(": ");
			while (aliases.hasNext()) {
				printnb(aliases.next());
				if (aliases.hasNext())
					printnb(", ");
			}
			print();
		}
	}
}
打印的编码字符集:
Big5: csBig5
Big5-HKSCS: big5-hkscs, big5hk, Big5_HKSCS, big5hkscs
CESU-8: CESU8, csCESU-8
EUC-JP: csEUCPkdFmtjapanese, x-euc-jp, eucjis, Extended_UNIX_Code_Packed_Format_for_Japanese, euc_jp, eucjp, x-eucjp
EUC-KR: ksc5601-1987, csEUCKR, ksc5601_1987, ksc5601, 5601, euc_kr, ksc_5601, ks_c_5601-1987, euckr
GB18030: gb18030-2000
GB2312: gb2312, euc-cn, x-EUC-CN, euccn, EUC_CN, gb2312-80, gb2312-1980
GBK: CP936, windows-936
IBM-Thai: ibm-838, ibm838, 838, cp838
IBM00858: cp858, 858, PC-Multilingual-850+euro, cp00858, ccsid00858
IBM01140: cp1140, 1140, cp01140, ebcdic-us-037+euro, ccsid01140
IBM01141: 1141, cp1141, cp01141, ccsid01141, ebcdic-de-273+euro
IBM01142: 1142, cp1142, cp01142, ccsid01142, ebcdic-no-277+euro, ebcdic-dk-277+euro
IBM01143: 1143, cp01143, ccsid01143, cp1143, ebcdic-fi-278+euro, ebcdic-se-278+euro
IBM01144: cp01144, ccsid01144, ebcdic-it-280+euro, cp1144, 1144
IBM01145: ccsid01145, ebcdic-es-284+euro, 1145, cp1145, cp01145
IBM01146: ebcdic-gb-285+euro, 1146, cp1146, cp01146, ccsid01146
IBM01147: cp1147, 1147, cp01147, ccsid01147, ebcdic-fr-277+euro
IBM01148: cp1148, ebcdic-international-500+euro, 1148, cp01148, ccsid01148
IBM01149: ebcdic-s-871+euro, 1149, cp1149, cp01149, ccsid01149
IBM037: cp037, ibm037, ibm-037, csIBM037, ebcdic-cp-us, ebcdic-cp-ca, ebcdic-cp-nl, ebcdic-cp-wt, 037, cpibm37, cs-ebcdic-cp-wt, ibm-37, cs-ebcdic-cp-us, cs-ebcdic-cp-ca, cs-ebcdic-cp-nl
IBM1026: cp1026, ibm-1026, 1026, ibm1026
IBM1047: ibm-1047, 1047, cp1047
IBM273: ibm-273, ibm273, 273, cp273
IBM277: ibm277, 277, cp277, ibm-277
IBM278: cp278, 278, ibm-278, ebcdic-cp-se, csIBM278, ibm278, ebcdic-sv
IBM280: ibm280, 280, cp280, ibm-280
IBM284: csIBM284, ibm-284, cpibm284, ibm284, 284, cp284
IBM285: csIBM285, cp285, ebcdic-gb, ibm-285, cpibm285, ibm285, 285, ebcdic-cp-gb
IBM290: ibm290, 290, cp290, EBCDIC-JP-kana, csIBM290, ibm-290
IBM297: 297, csIBM297, cp297, ibm297, ibm-297, cpibm297, ebcdic-cp-fr
IBM420: ibm420, 420, cp420, csIBM420, ibm-420, ebcdic-cp-ar1
IBM424: ebcdic-cp-he, csIBM424, ibm-424, ibm424, 424, cp424
IBM437: ibm437, 437, ibm-437, cspc8codepage437, cp437, windows-437
IBM500: ibm-500, ibm500, 500, ebcdic-cp-bh, ebcdic-cp-ch, csIBM500, cp500
IBM775: ibm-775, ibm775, 775, cp775
IBM850: cp850, cspc850multilingual, ibm850, 850, ibm-850
IBM852: csPCp852, ibm-852, ibm852, 852, cp852
IBM855: ibm855, 855, ibm-855, cp855, cspcp855
IBM857: ibm857, 857, cp857, csIBM857, ibm-857
IBM860: ibm860, 860, cp860, csIBM860, ibm-860
IBM861: cp861, ibm861, 861, ibm-861, cp-is, csIBM861
IBM862: csIBM862, cp862, ibm862, 862, cspc862latinhebrew, ibm-862
IBM863: csIBM863, ibm-863, ibm863, 863, cp863
IBM864: csIBM864, ibm-864, ibm864, 864, cp864
IBM865: ibm-865, csIBM865, cp865, ibm865, 865
IBM866: ibm866, 866, ibm-866, csIBM866, cp866
IBM868: ibm868, 868, cp868, csIBM868, ibm-868, cp-ar
IBM869: cp869, ibm869, 869, ibm-869, cp-gr, csIBM869
IBM870: 870, cp870, csIBM870, ibm-870, ibm870, ebcdic-cp-roece, ebcdic-cp-yu
IBM871: ibm871, 871, cp871, ebcdic-cp-is, csIBM871, ibm-871
IBM918: 918, ibm-918, ebcdic-cp-ar2, cp918
ISO-2022-CN: csISO2022CN, ISO2022CN
ISO-2022-JP: csjisencoding, iso2022jp, jis_encoding, jis, csISO2022JP
ISO-2022-JP-2: csISO2022JP2, iso2022jp2
ISO-2022-KR: csISO2022KR, ISO2022KR
ISO-8859-1: 819, ISO8859-1, l1, ISO_8859-1:1987, ISO_8859-1, 8859_1, iso-ir-100, latin1, cp819, ISO8859_1, IBM819, ISO_8859_1, IBM-819, csISOLatin1
ISO-8859-13: iso_8859-13, ISO8859-13, iso8859_13, 8859_13
ISO-8859-15: ISO8859-15, LATIN0, ISO8859_15_FDIS, ISO8859_15, cp923, 8859_15, L9, ISO-8859-15, IBM923, csISOlatin9, ISO_8859-15, IBM-923, csISOlatin0, 923, LATIN9
ISO-8859-2: ISO8859-2, ibm912, l2, ISO_8859-2, 8859_2, cp912, ISO_8859-2:1987, iso8859_2, iso-ir-101, latin2, 912, csISOLatin2, ibm-912
ISO-8859-3: ISO8859-3, ibm913, 8859_3, l3, cp913, ISO_8859-3, iso8859_3, latin3, csISOLatin3, 913, ISO_8859-3:1988, ibm-913, iso-ir-109
ISO-8859-4: 8859_4, latin4, l4, cp914, ISO_8859-4:1988, ibm914, ISO_8859-4, iso-ir-110, iso8859_4, csISOLatin4, iso8859-4, 914, ibm-914
ISO-8859-5: ISO_8859-5:1988, csISOLatinCyrillic, iso-ir-144, iso8859_5, cp915, 8859_5, ibm-915, ISO_8859-5, ibm915, 915, cyrillic, ISO8859-5
ISO-8859-6: ASMO-708, 8859_6, iso8859_6, ISO_8859-6, csISOLatinArabic, ibm1089, arabic, ibm-1089, 1089, ECMA-114, iso-ir-127, ISO_8859-6:1987, ISO8859-6, cp1089
ISO-8859-7: greek, 8859_7, greek8, ibm813, ISO_8859-7, iso8859_7, ELOT_928, cp813, ISO_8859-7:1987, sun_eu_greek, csISOLatinGreek, iso-ir-126, 813, iso8859-7, ECMA-118, ibm-813
ISO-8859-8: 8859_8, ISO_8859-8, ISO_8859-8:1988, cp916, iso-ir-138, ISO8859-8, hebrew, iso8859_8, ibm-916, csISOLatinHebrew, 916, ibm916
ISO-8859-9: ibm-920, ISO_8859-9, 8859_9, ISO_8859-9:1989, ibm920, latin5, l5, iso8859_9, cp920, 920, iso-ir-148, ISO8859-9, csISOLatin5
JIS_X0201: JIS0201, csHalfWidthKatakana, X0201, JIS_X0201
JIS_X0212-1990: JIS0212, iso-ir-159, x0212, jis_x0212-1990, csISO159JISX02121990
KOI8-R: koi8_r, koi8, cskoi8r
KOI8-U: koi8_u
Shift_JIS: shift_jis, x-sjis, sjis, shift-jis, ms_kanji, csShiftJIS
TIS-620: tis620, tis620.2533
US-ASCII: ANSI_X3.4-1968, cp367, csASCII, iso-ir-6, ASCII, iso_646.irv:1983, ANSI_X3.4-1986, ascii7, default, ISO_646.irv:1991, ISO646-US, IBM367, 646, us
UTF-16: UTF_16, unicode, utf16, UnicodeBig
UTF-16BE: X-UTF-16BE, UTF_16BE, ISO-10646-UCS-2, UnicodeBigUnmarked
UTF-16LE: UnicodeLittleUnmarked, UTF_16LE, X-UTF-16LE
UTF-32: UTF_32, UTF32
UTF-32BE: X-UTF-32BE, UTF_32BE
UTF-32LE: X-UTF-32LE, UTF_32LE
UTF-8: unicode-1-1-utf-8, UTF8
windows-1250: cp1250, cp5346
windows-1251: cp5347, ansi-1251, cp1251
windows-1252: cp5348, cp1252
windows-1253: cp1253, cp5349
windows-1254: cp1254, cp5350
windows-1255: cp1255
windows-1256: cp1256
windows-1257: cp1257, cp5353
windows-1258: cp1258
windows-31j: MS932, windows-932, csWindows31J
x-Big5-HKSCS-2001: Big5_HKSCS_2001, big5-hkscs-2001, big5hk-2001, big5-hkscs:unicode3.0, big5hkscs-2001
x-Big5-Solaris: Big5_Solaris
x-euc-jp-linux: euc_jp_linux, euc-jp-linux
x-EUC-TW: euctw, cns11643, EUC-TW, euc_tw
x-eucJP-Open: eucJP-open, EUC_JP_Solaris
x-IBM1006: ibm1006, ibm-1006, 1006, cp1006
x-IBM1025: ibm-1025, 1025, cp1025, ibm1025
x-IBM1046: ibm1046, ibm-1046, 1046, cp1046
x-IBM1097: ibm1097, ibm-1097, 1097, cp1097
x-IBM1098: ibm-1098, 1098, cp1098, ibm1098
x-IBM1112: ibm1112, ibm-1112, 1112, cp1112
x-IBM1122: cp1122, ibm1122, ibm-1122, 1122
x-IBM1123: ibm1123, ibm-1123, 1123, cp1123
x-IBM1124: ibm-1124, 1124, cp1124, ibm1124
x-IBM1166: cp1166, ibm1166, ibm-1166, 1166
x-IBM1364: cp1364, ibm1364, ibm-1364, 1364
x-IBM1381: cp1381, ibm-1381, 1381, ibm1381
x-IBM1383: ibm1383, ibm-1383, 1383, cp1383
x-IBM300: cp300, ibm300, 300, ibm-300
x-IBM33722: 33722, ibm-33722, cp33722, ibm33722, ibm-5050, ibm-33722_vascii_vpua
x-IBM737: cp737, ibm737, 737, ibm-737
x-IBM833: ibm833, cp833, ibm-833
x-IBM834: ibm834, 834, cp834, ibm-834
x-IBM856: ibm856, 856, cp856, ibm-856
x-IBM874: ibm-874, ibm874, 874, cp874
x-IBM875: ibm-875, ibm875, 875, cp875
x-IBM921: ibm921, 921, ibm-921, cp921
x-IBM922: ibm922, 922, cp922, ibm-922
x-IBM930: ibm-930, ibm930, 930, cp930
x-IBM933: ibm933, 933, cp933, ibm-933
x-IBM935: cp935, ibm935, 935, ibm-935
x-IBM937: ibm-937, ibm937, 937, cp937
x-IBM939: ibm-939, cp939, ibm939, 939
x-IBM942: ibm-942, cp942, ibm942, 942
x-IBM942C: ibm942C, cp942C, ibm-942C, 942C
x-IBM943: ibm943, 943, ibm-943, cp943
x-IBM943C: 943C, cp943C, ibm943C, ibm-943C
x-IBM948: ibm-948, ibm948, 948, cp948
x-IBM949: ibm-949, ibm949, 949, cp949
x-IBM949C: ibm949C, ibm-949C, cp949C, 949C
x-IBM950: cp950, ibm950, 950, ibm-950
x-IBM964: ibm-964, cp964, ibm964, 964
x-IBM970: ibm970, ibm-eucKR, 970, cp970, ibm-970
x-ISCII91: ISCII91, iso-ir-153, iscii, ST_SEV_358-88, csISO153GOST1976874
x-ISO-2022-CN-CNS: ISO2022CN_CNS, ISO-2022-CN-CNS
x-ISO-2022-CN-GB: ISO2022CN_GB, ISO-2022-CN-GB
x-iso-8859-11: iso-8859-11, iso8859_11
x-JIS0208: JIS0208, JIS_C6226-1983, iso-ir-87, x0208, JIS_X0208-1983, csISO87JISX0208
x-JISAutoDetect: JISAutoDetect
x-Johab: ms1361, ksc5601_1992, johab, ksc5601-1992
x-MacArabic: MacArabic
x-MacCentralEurope: MacCentralEurope
x-MacCroatian: MacCroatian
x-MacCyrillic: MacCyrillic
x-MacDingbat: MacDingbat
x-MacGreek: MacGreek
x-MacHebrew: MacHebrew
x-MacIceland: MacIceland
x-MacRoman: MacRoman
x-MacRomania: MacRomania
x-MacSymbol: MacSymbol
x-MacThai: MacThai
x-MacTurkish: MacTurkish
x-MacUkraine: MacUkraine
x-MS932_0213
x-MS950-HKSCS: MS950_HKSCS
x-MS950-HKSCS-XP: MS950_HKSCS_XP
x-mswin-936: ms936, ms_936
x-PCK: pck
x-SJIS_0213
x-UTF-16LE-BOM: UnicodeLittle
X-UTF-32BE-BOM: UTF_32BE_BOM, UTF-32BE-BOM
X-UTF-32LE-BOM: UTF_32LE_BOM, UTF-32LE-BOM
x-windows-50220: cp50220, ms50220
x-windows-50221: cp50221, ms50221
x-windows-874: ms-874, ms874, windows-874
x-windows-949: windows949, ms949, windows-949, ms_949
x-windows-950: ms950, windows-950
x-windows-iso2022jp: windows-iso2022jp


【如何解码】 System.getProperty("file.encoding") 发现默认字符集,把该字符集传给 Charset.forName() 用以产生 Charset 对象,可以用该字符对象对字符串进行解码 。

【如何编码】 使用encode() 方法 把文本编码后写入到文件中;当读取时,需要把他转换为 CharBuffer ;

// 测试如何编码和解码
public class AvailableCharSets2 {
	public static String path = System.getProperty("user.dir") + File.separator + "src" + File.separator + "chapter18" + File.separator;
	
	public static void main(String[] args) throws Exception {
		ByteBuffer buff = ByteBuffer.allocateDirect(1024);
		FileChannel in = new FileInputStream(path + "AvailableCharSets2.java").getChannel();
		FileChannel out = new FileOutputStream(path + "AvailableCharSets2.out").getChannel();
		
		in.read(buff);
		buff.flip();
		out.write(buff);
		buff.flip();
		String encoding = System.getProperty("file.encoding");
		System.out.println("encoding = " + encoding);
		CharBuffer cb = Charset.forName(encoding).decode(buff); // (对字节进行解码得到字符缓冲器)解码参数为ByteBuffer,返回结果为 CharBuffer;
		ByteBuffer bb = Charset.forName(encoding).encode(cb); // (对字符进行编码得到字节缓冲器)编码参数为 CharBuffer,返回结果为 ByteBuffer;
		
		System.out.println(cb.array()); // 字符缓冲器转为字符数组输出到控制台
		out.write(bb); // FileChannel.write(ByteBuffer); 
		in.close();
		out.close();
		System.out.println("success.");
	}
} 
打印结果:
encoding = UTF-8
package chapter18;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;

// 测试如何编码和解码
public class AvailableCharSets2 {
	public static String path = System.getProperty("user.dir") + File.separator + "src" + File.separator + "chapter18" + File.separator;
	
	public static void main(String[] args) throws Exception {
		ByteBuffer buff = ByteBuffer.allocateDirect(1024);
		FileChannel in = new FileInputStream(path + "AvailableCharSets2.java").getChannel();
		FileChannel out = new FileOutputStream(path + "AvailableCharSets2.out").getChannel();
		
		in.read(buff);
		buff.flip();
		out.write(buff);
		buff.flip();
		String encoding = System.getProperty("file.encoding");
		System.out.println("encoding = " + encoding);
		CharBuffer cb = Charset.forName(encoding).decode(buff); // (对字节进行解码得到字符缓冲
public abstract class CharBuffer
    extends Buffer
    implements Comparable<CharBuffer>, Appendable, CharSequence, Readable
public static Charset forName(String charsetName) {
        Charset cs = lookup(charsetName);
        if (cs != null)
            return cs;
        throw new UnsupportedCharsetException(charsetName);
    }


如何理解 (对字节进行解码得到字符缓冲器) 和 (对字符进行编码得到字节缓冲器) ?

中文字符 唐 的 UTF-8 编码为 E59490, 很明显 中文字符“唐”占用了3个字节; 这3个字节可以存储在CharBuffer 缓冲器中;通过对这3个字节进行 UTF-8格式解码,得到唐这个字符; 同理,对中文字符“唐”进行UTF-8编码得到 E59490;进行UTF-16编码得到 FEFF5510 ;进行UTF-32编码得到 0000FEFF00005510;

【18.10.2】 获取基本类型

ByteBuffer 存取基本类型的方法如下:

public class GetData {
	private static final int BSIZE = 1024;

	public static void main(String[] args) {
		ByteBuffer bb = ByteBuffer.allocate(BSIZE);
		// Allocation automatically zeroes the ByteBuffer:
		int i = 0;
		while (i++ < bb.limit())
			if (bb.get() != 0) // 检测缓冲区是否将内容默认为0,确实默认为0
				print("nonzero");
		print("i = " + i);
		
		bb.rewind();
		// Store and read a char array:
		bb.asCharBuffer().put("Howdy!");
		char c;
		while ((c = bb.getChar()) != 0)
			printnb(c + " ");
		print();
		
		bb.rewind();
		// Store and read a short:
		bb.asShortBuffer().put((short) 471142);
		print(bb.getShort());
		
		bb.rewind();
		// Store and read an int:
		bb.asIntBuffer().put(99471142);
		print(bb.getInt());
		
		bb.rewind();
		// Store and read a long:
		bb.asLongBuffer().put(99471142);
		print(bb.getLong());
		
		bb.rewind();
		// Store and read a float:
		bb.asFloatBuffer().put(99471142);
		print(bb.getFloat());
		
		bb.rewind();
		// Store and read a double:
		bb.asDoubleBuffer().put(99471142);
		print(bb.getDouble());
		bb.rewind();
	}
}
// 运行结果: 
i = 1025
H o w d y ! 
12390
99471142
99471142
9.9471144E7
9.9471142E7
上述荔枝中, 利用ByteBuffer 的 asCharBuffer()  和 asShortBuffer() 等方法获得该缓冲器上的视图,然后使用 视图的put() 方法; 在使用 asShortBuffer() 的 put() 方法时,需要进行类型转换;


【18.10.3】 视图缓冲器

视图缓冲器: 可以让我们查看其底层的 ByteBuffer; ByteBuffer 依然是实际存储数据的地方;对视图的修改都会映射到对 ByteBuffer的修改;

public class IntBufferDemo {
	private static final int BSIZE = 1024;

	public static void main(String[] args) {
		ByteBuffer bb = ByteBuffer.allocate(BSIZE);
		/* IntBuffer 就是 视图缓冲器 */
		IntBuffer ib = bb.asIntBuffer(); // 转换为 IntBuffer,同ByteBuffer 转换为 CharBuffer类似
		
		// Store an array of int:
		/* 视图缓冲器的put 方法修改其内部的数据,同时也映射到 ByteBuffer存储内容的修改  */
		ib.put(new int[] { 11, 42, 47, 99, 143, 811, 1016 });
		// Absolute location read and write:
		/* get() 和 put() 方法直接访问底层ByteBuffer 中的某个整数位置。  */
		System.out.println(ib.get(3));
		ib.put(3, 1811);
		// Setting a new limit before rewinding the buffer.
		ib.flip();
		
		while (ib.hasRemaining()) {
			int i = ib.get();
			System.out.println(i);
		}
	}
} 
public abstract class IntBuffer
    extends Buffer
    implements Comparable<IntBuffer>

99
11
42
47
1811
143
811
1016

【在同一个 ByteBuffer上建立不同的视图缓冲器】

将同一个字节序列翻译为 short, int, float, long 和 double 类型的数据。

public class ViewBuffers {
	public static void main(String[] args) {
		ByteBuffer bb = ByteBuffer.wrap(new byte[] { 0, 0, 0, 0, 0, 0, 0, 'a' });
		bb.rewind();
		printnb("Byte Buffer ");
		while (bb.hasRemaining())
			printnb(bb.position() + " -> " + bb.get() + ", "); // Byte Buffer 0 -> 0, 1 -> 0, 2 -> 0, 3 -> 0, 4 -> 0, 5 -> 0, 6 -> 0, 7 -> 97,
		print();   
		
		CharBuffer cb = ((ByteBuffer) bb.rewind()).asCharBuffer();
		printnb("Char Buffer ");
		while (cb.hasRemaining())
			printnb(cb.position() + " -> " + cb.get() + ", "); // Char Buffer 0 ->  , 1 -> , 2 -> , 3 -> a,  
		print();
		
		FloatBuffer fb = ((ByteBuffer) bb.rewind()).asFloatBuffer();
		printnb("Float Buffer ");
		while (fb.hasRemaining())
			printnb(fb.position() + " -> " + fb.get() + ", "); // Float Buffer 0 -> 0.0, 1 -> 1.36E-43,
		print();
		
		IntBuffer ib = ((ByteBuffer) bb.rewind()).asIntBuffer();
		printnb("Int Buffer ");
		while (ib.hasRemaining())
			printnb(ib.position() + " -> " + ib.get() + ", "); // Int Buffer 0 -> 0, 1 -> 97,
		print();
		
		LongBuffer lb = ((ByteBuffer) bb.rewind()).asLongBuffer();
		printnb("Long Buffer ");
		while (lb.hasRemaining())
			printnb(lb.position() + " -> " + lb.get() + ", "); // Long Buffer 0 -> 97, 
		print();
		
		ShortBuffer sb = ((ByteBuffer) bb.rewind()).asShortBuffer();
		printnb("Short Buffer ");
		while (sb.hasRemaining())
			printnb(sb.position() + " -> " + sb.get() + ", "); // Short Buffer 0 -> 0, 1 -> 0, 2 -> 0, 3 -> 97, 
		print();
		
		DoubleBuffer db = ((ByteBuffer) bb.rewind()).asDoubleBuffer();
		printnb("Double Buffer ");
		while (db.hasRemaining())
			printnb(db.position() + " -> " + db.get() + ", "); // Double Buffer 0 -> 4.8E-322,
	}
} 


ByteBuffer 通过一个呗包装过的 8字节 数组产生, 然后通过各种不同的基本类型的视图缓冲器显示了出来。


【字节存放次序】

ByteBuffer 是以高位优先的形式存储数据的,并且数据在网上传送时也常常使用 高位优先的形式。。我们可以使用带有参数的 ByteOrder.BIG_ENDIAN 或 ByteOrder.LITTLE_ENDIAN 的 order() 方法改变 ByteBuffer 的字节排序方式。。

Big Endian(高位优先): 将最重要字节存放在地址最低的存储器单元;

Little Endian(低位优先): 将最重要字节存放在地址最高的存储器单元;


public class Endians {
	public static void main(String[] args) {
		ByteBuffer bb = ByteBuffer.wrap(new byte[12]);
		bb.asCharBuffer().put("abcdef");
		print(Arrays.toString(bb.array()));
		// 由打印结果知: ByteBuffer默认是 高位优先存储
		
		bb.rewind();
		bb.order(ByteOrder.BIG_ENDIAN); // 设置字节存放次序为高位优先
		bb.asCharBuffer().put("abcdef");
		print(Arrays.toString(bb.array()));
		
		bb.rewind();
		bb.order(ByteOrder.LITTLE_ENDIAN); // 设置字节存放次序为低位优先
		bb.asCharBuffer().put("abcdef");
		print(Arrays.toString(bb.array()));
	}
}
// 打印结果
[0, 97, 0, 98, 0, 99, 0, 100, 0, 101, 0, 102]
[0, 97, 0, 98, 0, 99, 0, 100, 0, 101, 0, 102]
[97, 0, 98, 0, 99, 0, 100, 0, 101, 0, 102, 0]

 public final Buffer rewind() {
        position = 0;
        mark = -1;
        return this;
    }

【18.10.4】用缓冲器操纵数据

ByteBuffer 是将数据移进移出通道的唯一方式,并且只能创建一个独立的 基本类型缓冲器,或适用 as 方法;


【18.10.5】缓冲器细节

Buffer 由 数据和4个索引组成,mark标记,position位置, limit界限, capacity容量;



【荔枝: 对CharBuffer中的字符进行编码和译码】 scramble and unscramble

public class UsingBuffers {
	private static void symmetricScramble(CharBuffer buffer) {
		while (buffer.hasRemaining()) {
			buffer.mark(); // mark = position
			char c1 = buffer.get(); // 读取当前position 的值,然后 position++;
			char c2 = buffer.get(); // position++;
			buffer.reset(); // position = mark;
			buffer.put(c2).put(c1); // 在当前position写 c2, 然后 position++ .
		}
	}

	public static void main(String[] args) {
		char[] data = "UsingBuffers".toCharArray();
		ByteBuffer bb = ByteBuffer.allocate(data.length * 2); //分配缓冲器大小
		
		CharBuffer cb = bb.asCharBuffer(); // 转换为 字符缓冲器
		cb.put(data); // 将字节数组 存入缓冲器
		print(cb.rewind());
		
		symmetricScramble(cb); // 奇数地址上的字符与偶数地址上的字符调换
		print(cb.rewind());
		
		symmetricScramble(cb); // 奇数地址上的字符与偶数地址上的字符调换
		print(cb.rewind());
	}
}
// 打印结果
UsingBuffers
sUniBgfuefsr
UsingBuffers
【解说】 程序员总是会操纵 ByteBuffer,因为ByteBuffer 可以和 通道进行交互;


【18.10.6】内存映射文件

作用: 内存映射文件允许我们创建和修改 那些因为太大而不能放入内存的文件;这样我们就可以把大文件当做非常大的数组来访问,就好像该大文件在内存中一样;

public class LargeMappedFiles {
	public static String path = System.getProperty("user.dir") + File.separator + "src" + File.separator + "chapter18" + File.separator;
	static int length = 0x8FFFFFF; // 128 MB

	public static void main(String[] args) throws Exception {
		// RandomAccessFile(可读可写) -> 通道 -> 内存映射文件MappedByteBuffer
		MappedByteBuffer out = new RandomAccessFile(path + "LargeMappedFiles.java", "rw")
				.getChannel().map(FileChannel.MapMode.READ_WRITE, 0, length);
		
		for (int i = 0; i < length; i++)
			out.put((byte) 'x');
		print("Finished writing");
		
		for (int i = length / 2; i < length / 2 + 6; i++)
			printnb((char) out.get(i));
	}
}
public abstract MappedByteBuffer map(MapMode mode,
                                         long position, long size)
        throws IOException;

public abstract class MappedByteBuffer
    extends ByteBuffer // 所以MappedByteBuffer 拥有 ByteBuffer的所有方法;
{

【代码说明】  MappedByteBuffer 是一种特殊的直接缓冲器。。我们必须指定映射文件的初始位置 和 size;这样一来我们就可以映射某个大文件的 较小部分;

【内存映射文件的作用】 利用内存映射文件可以很容易修改 大文件(如2GB)。。注意底层os 的文件映射工具是用来最大化地提高性能用的;


【性能】 尽管旧的 IO流在用 nio实现后性能有所提高,但是  映射文件访问 往往可以更加显著地加快速度。。

public class MappedIO {
	private static int numOfInts = 4000000;
	private static int numOfUbuffInts = 200000;
	public static String path = 
			System.getProperty("user.dir") + File.separator + "src" + File.separator + "chapter18" + File.separator;
	
	private abstract static class Tester {
		private String name;

		public Tester(String name) {
			this.name = name;
		}

		public void runTest() {
			System.out.print(name + ": ");
			try {
				long start = System.nanoTime();
				test();
				double duration = System.nanoTime() - start; // 持续时间
				System.out.format("%.2f秒\n", duration / 1.0e9); // 以秒计
			} catch (IOException e) {
				throw new RuntimeException(e);
			}
		}

		public abstract void test() throws IOException;
	}

	private static Tester[] tests = {  // Tester子类对象数组
		new Tester("DataOutputStream Write") { // 匿名内部类实现,牛逼。
			public void test() throws IOException {
				DataOutputStream dos = new DataOutputStream(
						new BufferedOutputStream(new FileOutputStream(new File(path + "temp.tmp"))));
				for (int i = 0; i < numOfInts; i++)
					dos.writeInt(i);
				dos.close();
			}
	}, new Tester("Mapped Write") { // 写速度较快
		public void test() throws IOException {
			FileChannel fc = new RandomAccessFile(path + "temp.tmp", "rw").getChannel(); // 可读可写文件 RandomAccessFile
			IntBuffer ib = fc.map(FileChannel.MapMode.READ_WRITE, 0, fc.size()).asIntBuffer(); // 注意map方法的参数
			for (int i = 0; i < numOfInts; i++)
				ib.put(i); // ib 是视图缓冲器,操作视图缓冲器实质上是操作 MappedByteBuffer,进而通过通道写入文件。
			fc.close();
		}
	}, new Tester("DataInputStream Read") {
		public void test() throws IOException {
			DataInputStream dis = new DataInputStream(new BufferedInputStream(
					new FileInputStream(path + "temp.tmp")));
			for (int i = 0; i < numOfInts; i++)
				dis.readInt();
			dis.close();
		}
	}, new Tester("Mapped Read") { // 读速度较快
		public void test() throws IOException {
			FileChannel fc = new FileInputStream(new File(path + "temp.tmp")).getChannel();
			IntBuffer ib = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).asIntBuffer();
			while (ib.hasRemaining())
				ib.get();
			fc.close();
		}
	}, new Tester("RandomAccessFile Read/Write") {
		public void test() throws IOException {
			RandomAccessFile raf = new RandomAccessFile(new File(path + "temp.tmp"), "rw");
			raf.writeInt(1);
			for (int i = 0; i < numOfUbuffInts; i++) {
				raf.seek(raf.length() - 4);
				raf.writeInt(raf.readInt());
			}
			raf.close();
		}
	}, new Tester("Mapped Read/Write") { // 读写速度较快
		public void test() throws IOException {
			FileChannel fc = new RandomAccessFile(new File(path + "temp.tmp"), "rw").getChannel();
			IntBuffer ib = fc.map(FileChannel.MapMode.READ_WRITE, 0, fc.size()).asIntBuffer(); //视图缓冲器
			ib.put(0);
			for (int i = 1; i < numOfUbuffInts; i++)
				ib.put(ib.get(i - 1));
			fc.close();
		}
	} };

	public static void main(String[] args) {
		for (Tester test : tests)
			test.runTest();
	}
}
// 打印结果
DataOutputStream Write: 0.27秒
Mapped Write: 0.02秒
DataInputStream Read: 0.44秒
Mapped Read: 0.01秒
RandomAccessFile Read/Write: 2.60秒
Mapped Read/Write: 0.01秒


【18.10.7】文件加锁

 文件加锁机制使得可以同步访问某个作为共享资源的文件; java 文件加锁直接映射到了本地os的加锁工具;

【荔枝】

public class FileLocking {
	public static void main(String[] args) throws Exception {
		FileOutputStream fos = new FileOutputStream(MyConstants.path + "FileLocking.txt");
		/* 文件锁 FileLock */ 
		FileLock fl = fos.getChannel().tryLock(); // tryLock() 方法试图获取锁,若获取失败,则返回;
		FileLock f2 = fos.getChannel().lock(); // lock() 方法试图获取锁,阻塞进程直至锁可以获得,或调用线程中断;或调用通道关闭;
		
		if (fl != null) {
			System.out.println("Locked File");
			TimeUnit.MILLISECONDS.sleep(100);
			fl.release(); // 释放锁
			System.out.println("Released Lock");
		}
		fos.close();
	}
} 

public class FileLocking {
	public static void main(String[] args) throws Exception {
		FileOutputStream fos = new FileOutputStream(MyConstants.path + "FileLocking.txt");
		/* 文件锁 FileLock */ 
		FileLock fl = fos.getChannel().tryLock(); // tryLock() 方法试图获取锁,若获取失败,则返回;
		FileLock f2 = fos.getChannel().lock(); // lock() 方法试图获取锁,阻塞进程直至锁可以获得,或调用线程中断;或调用通道关闭;
		
		if (fl != null) {
			System.out.println("Locked File");
			TimeUnit.MILLISECONDS.sleep(100);
			fl.release(); // 释放锁
			System.out.println("Released Lock");
		}
		fos.close();
	}
} 

public final FileLock tryLock() throws IOException {  // File.tryLock() 方法, also 无参的加锁方法 
        return tryLock(0L, Long.MAX_VALUE, false);
    }
	
 public abstract FileLock tryLock(long position, long size, boolean shared) // FileChannel.tryLock() 方法, also 有参的加锁方法
        throws IOException; // shared 表示 共享锁(true) 还是 独占锁(false);
		
 public final FileLock lock() throws IOException { // File.lock() 方法, 无参加锁方法
        return lock(0L, Long.MAX_VALUE, false);
    }
	
public abstract FileLock lock(long position, long size, boolean shared) // FileChannel.lock() 方法,有参加锁方法
        throws IOException;

加锁区域: 由size-position 决定。。。第三个参数指定是否是共享锁;

FileChannel.tryLock 或 lock() 方法可以获得整个文件的 FIleLock 。tryLock()方法 和 lock() 方法 分别是 非阻塞式的 和 阻塞式的;

【加锁方法分类】

无参加锁方法: 加锁区域将根据文件的尺寸大小的变化而变化;无参加锁方法会对整个文件加锁,当文件内容变大后,变化后的文件的所有区域都会受到加锁限制;

有参加锁方法: 加锁区域固定;相反;


【对映射文件的部分加锁】

有时需要对大文件进行部分加锁,以便其他线程可以修改其他未加锁部分;

public class LockingMappedFiles {
	static final int LENGTH = 0x8FFFFFF; // 128 MB
	static FileChannel fc; // 文件通道

	public static void main(String[] args) throws Exception {
		fc = new RandomAccessFile(MyConstants.path + "test.dat", "rw").getChannel();
		MappedByteBuffer out = fc
				.map(FileChannel.MapMode.READ_WRITE, 0, LENGTH); // 映射的字节缓冲器
		for (int i = 0; i < LENGTH; i++)
			out.put((byte) 'x');
		
		new LockAndModify(out, 0, 0 + LENGTH / 3);
		new LockAndModify(out, LENGTH / 2, LENGTH / 2 + LENGTH / 4);
	}
	private static class LockAndModify extends Thread {
		private ByteBuffer buff;
		private int start, end;

		LockAndModify(ByteBuffer mbb, int start, int end) {
			this.start = start;
			this.end = end;
			mbb.limit(end);
			mbb.position(start);
			buff = mbb.slice(); // 用于修改的缓冲区
			start();
		}
		public void run() {
			try {
				// Exclusive lock with no overlap:
				FileLock fl = fc.lock(start, end, false); // 有参文件锁,独占
				System.out.println("Locked: " + start + " to " + end);
				
				// Perform modification:
				while (buff.position() < buff.limit() - 1)
					buff.put((byte) (buff.get() + 1));
				fl.release();
				System.out.println("Released: " + start + " to " + end);
			} catch (IOException e) {
				throw new RuntimeException(e);
			}
		}
	}
}


【18.11】压缩

压缩类库是按字节方式而不是字符方式处理的;

【java压缩类列表】


【18.11.1】用 GZIP 进行简单压缩
GZIP接口 适合对单个数据流进行压缩;

【荔枝】

/* GZIP压缩荔枝  */
public class GZIPcompress {
	public static String path = 
			System.getProperty("user.dir") + File.separator + "src" + File.separator + "chapter18" + File.separator;
	public static void main(String[] args) throws IOException {
		args = new String[]{path + "GZIPcompress.txt"}; 
		
		BufferedReader in = new BufferedReader(new FileReader(args[0]));
		BufferedOutputStream out = new BufferedOutputStream(
				new GZIPOutputStream(new FileOutputStream(path + "test.gz"))); // 压缩文件输出流
		
		System.out.println("Writing file");
		int c; 
		while ((c = in.read()) != -1) {
			System.out.print((c-48) + " ");
			out.write(c);
		}
		in.close();
		out.close();
		
		System.out.println("Reading file"); 
		BufferedReader in2 = new BufferedReader(new InputStreamReader(
				new GZIPInputStream(new FileInputStream(path + "test.gz")))); // 压缩文件输入流
		String s;
		while ((s = in2.readLine()) != null)
			System.out.println(s);
	}
}

【代码解说】

直接将输出流封装成 GZIPOutputStream  ZipOutputStream, 并将输入流封装成GZIPInputStreamZipInputStream即可;


【18.11.2】 用Zip 进行多文件保存

支持Zip格式的java 库更加全面。

【荔枝】

下面的荔枝能够根据需要来处理任意多个命令行参数 。。该荔枝还显示了用 Checksum类来计算和校验文件的校验和的方法。一共有两种 Checksum类型: Adler32 快一些, CRC32 慢一些;

/* 该荔枝能够根据需要来处理任意多个命令行参数  */
public class ZipCompress { 
	public static String path = 
			System.getProperty("user.dir") + File.separator + "src" + File.separator + "chapter18" + File.separator;
	public static void main(String[] args) throws IOException {
		FileOutputStream f = new FileOutputStream(path + "test.zip"); // zip文件输出流
		CheckedOutputStream csum = new CheckedOutputStream(f, new Adler32()); // Adler32 是校验和类(文件输出流 -> 校验和输出流)
		ZipOutputStream zos = new ZipOutputStream(csum); // 校验和输出流 -> zip 输出流
		BufferedOutputStream out = new BufferedOutputStream(zos); // zip输出流 -> 缓冲输出流
		zos.setComment("A test of Java Zipping"); 
		
		// No corresponding getComment(), though.
		args = new String[]{path + "ZIPCompress.txt", path + "ZIPCompress1.txt"};
		for (String arg : args) {
			print("Reading file " + arg);
			BufferedReader in = new BufferedReader(new FileReader(arg));
			zos.putNextEntry(new ZipEntry(arg)); // 向压缩文件中添加文件名
			int c;
			while ((c = in.read()) != -1)
				out.write(c);
			in.close(); // 关闭输入流
			out.flush(); // 刷新缓冲区到文件
		}
		out.close(); // 关闭输出流 
		
		// Checksum valid only after the file has been closed!
		print("Checksum: " + csum.getChecksum().getValue());
		// Now extract the files:
		print("Reading file"); 
		FileInputStream fi = new FileInputStream(path + "test.zip"); // zip文件输入流
		CheckedInputStream csumi = new CheckedInputStream(fi, new Adler32()); // zip文件输入流 -> 校验和输入流
		ZipInputStream in2 = new ZipInputStream(csumi); // 校验和输入流 -> zip输入流
		BufferedInputStream bis = new BufferedInputStream(in2); // zip输入流 -> 缓冲输入流 
		
		ZipEntry ze;
		while ((ze = in2.getNextEntry()) != null) { // 获取下一个文件项 
			print("Reading file " + ze);
			int x; 
			while ((x = bis.read()) != -1)
				System.out.write(x);
		}
//		if (args.length == 1)
			print("\nChecksum: " + csumi.getChecksum().getValue());
		bis.close();
		
		// Alternative way to open and read Zip files:
		/* 其他打开和读取zip文件的方式 */
		ZipFile zf = new ZipFile(path + "test.zip");
		Enumeration e = zf.entries(); // 获取压缩文件中的文件项列表(枚举)
		while (e.hasMoreElements()) { // 遍历文件项列表
			ZipEntry ze2 = (ZipEntry) e.nextElement();
			print("File: " + ze2);
			// ... and extract the data as before
		}
		/* if(args.length == 1) */
	}
}
public ZipEntry(String name) { // ZipEntry 构造器
        Objects.requireNonNull(name, "name");
        if (name.length() > 0xFFFF) {
            throw new IllegalArgumentException("entry name too long");
        }
        this.name = name;
    }
// 打印结果
Reading file E:\bench-cluster\spring_in_action_eclipse\AThinkingInJava\src\chapter18\ZIPCompress.txt
Reading file E:\bench-cluster\spring_in_action_eclipse\AThinkingInJava\src\chapter18\ZIPCompress1.txt
Checksum: 2375793323
Reading file
Reading file E:\bench-cluster\spring_in_action_eclipse\AThinkingInJava\src\chapter18\ZIPCompress.txt
18782962387
147258369Reading file E:\bench-cluster\spring_in_action_eclipse\AThinkingInJava\src\chapter18\ZIPCompress1.txt
18782962387
147258369
��
Checksum: 2375793323
File: E:\bench-cluster\spring_in_action_eclipse\AThinkingInJava\src\chapter18\ZIPCompress.txt
File: E:\bench-cluster\spring_in_action_eclipse\AThinkingInJava\src\chapter18\ZIPCompress1.txt
【代码解说】

1.对于要加入压缩档案的文件,都必须调用 putNextEntry(), 并将其传递给 一个 ZipEntry 对象;

2.尽管Zip 格式提供了设置密码的方法,但 java 的zip类库并不提供支持;

3.ZipEntry 仅支持 CRC类型的校验和, 不能使用速度更快的 Adler32;


【18.11.3】java档案文件 

Zip格式也用于 JAR(Java ARchive, java档案文件)文件格式中 ; 




【荔枝】jar 文件打包荔枝

E:\bench-cluster\spring_in_action_eclipse\AThinkingInJava\src\a\b\c\tr>jar cmf tr1.jar MANIFEST.MF *.class
java.io.FileNotFoundException: tr1.jar (系统找不到指定的文件。)
        at java.io.FileInputStream.open0(Native Method)
        at java.io.FileInputStream.open(FileInputStream.java:195)
        at java.io.FileInputStream.<init>(FileInputStream.java:138)
        at java.io.FileInputStream.<init>(FileInputStream.java:93)
        at sun.tools.jar.Main.run(Main.java:175)
        at sun.tools.jar.Main.main(Main.java:1288)

E:\bench-cluster\spring_in_action_eclipse\AThinkingInJava\src\a\b\c\tr>jar cfm tr1.jar MANIFEST.MF *.class

E:\bench-cluster\spring_in_action_eclipse\AThinkingInJava\src\a\b\c\tr>


如果不需要指定 清单文件(*.mf), 则 jar cf  *.jar *.class; 即可;



【18.12】对象序列化

(1)如果对象能够在程序不运行的情况下仍能存在并保存其信息,那会非常有用;

(2)java对象序列化机制将 实现了 Serializable 接口 的对象转换成一个 字节序列 持久化到文件,并能够将这个字节序列完全恢复到原来的对象;(干货——这样实现持久化的效果)

(3)序列化为了支持两种特性: 

特性1: java的远程方法调用RMI;当向远程对象发送消息时,需要通过序列化来传送参数和返回值;

特性2: java beans, 需要对java bean的状态信息进行配置;状态信息必须保存下来;

(4)序列化对象的方法: 创建某些 OutputStream 对象, 将其封装在一个 ObjectOutputStream 对象内;调用 writeObject() 方法进行对象序列化;

(5)反序列化方法: 创建某些 InputStream 对象, 将其封装在一个 ObjectInputStream 对象内;调用  readObject() 方法进行反序列化;

(干货——对象序列化能够追踪对象内所包含的所有引用,并保存那些对象 和 对象网 或 引用网)


【看个荔枝】

class Data implements Serializable {
	public static String path = 
			System.getProperty("user.dir") + File.separator + "src" + File.separator + "chapter18" + File.separator;
	private int n;
	
	public Data(int n) {
		this.n = n;
	}
	public String toString() {
		return Integer.toString(n);
	}
}
// 需要对对象进行序列化,就要实现 Serializable 接口
public class Worm implements Serializable {
	private static Random rand = new Random(47); //随机数类
	/* 随机数数组  */
	private Data[] d = { new Data(rand.nextInt(10)),
			new Data(rand.nextInt(10)), new Data(rand.nextInt(10)) };
	private Worm next;
	private char c;

	// Value of i == number of segments
	public Worm(int i, char x) {
		print("Worm constructor: " + i);
		c = x;
		if (--i > 0) 
			next = new Worm(i, (char) (x + 1)); // highlight,好经典的构造方法(迭代调用)。 
	}

	public Worm() {
		print("Default constructor");
	}

	public String toString() {
		// StringBuilder 做字符串拼接的时候 效率较高
		StringBuilder result = new StringBuilder(":");
		result.append(c);
		result.append("(");
		for (Data dat : d)
			result.append(dat); //highlight.
		result.append(")");
		if (next != null)
			result.append(next); //highlight,实际调用 next.toString()方法;
		return result.toString();
	}

	public static void main(String[] args) throws ClassNotFoundException,
			IOException {
		Worm w = new Worm(6, 'a');
		print("w = " + w); // 调用 worm的toString() 方法, w = :a(853):b(119):c(802):d(788):e(199):f(881), 853=[8,5,3]共3个随机数
		
		// 对象序列化,FileOutputStream -> ObjectOutputStream
		ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(Data.path + "worm.out"));
		out.writeObject("Worm storage\n");// 
		out.writeObject(w); // 将worm对象进行序列化
		out.close(); // Also flushes output,清空输出缓冲区
		
		// 对象反序列化,FileInputStream -> ObjectInputStream
		print("====== deserilize begins. ======");
		ObjectInputStream in = new ObjectInputStream(new FileInputStream(Data.path +  "worm.out"));
		String s = (String) in.readObject();
		Worm w2 = (Worm) in.readObject();
		print(s + "w2 = " + w2);
		
		// 对象序列化,ByteArrayOutputStream -> ObjectOutputStream
		ByteArrayOutputStream bout = new ByteArrayOutputStream();
		ObjectOutputStream out2 = new ObjectOutputStream(bout);
		out2.writeObject("Worm storage\n");
		out2.writeObject(w); 
		out2.flush();
		
		// 对象反序列化,Byte Array -> ByteArrayInputStream -> ObjectInputStream
		ObjectInputStream in2 = new ObjectInputStream(new ByteArrayInputStream(
				bout.toByteArray()));
		s = (String) in2.readObject();
		Worm w3 = (Worm) in2.readObject();
		print(s + "w3 = " + w3);
	}
} 
打印结果如下: 
Worm constructor: 6
Worm constructor: 5
Worm constructor: 4
Worm constructor: 3
Worm constructor: 2
Worm constructor: 1
w = :a(853):b(119):c(802):d(788):e(199):f(881)
====== deserilize begins. ======
Worm storage
w2 = :a(853):b(119):c(802):d(788):e(199):f(881)
Worm storage
w3 = :a(853):b(119):c(802):d(788):e(199):f(881)
【代码说明】

(1)一旦创建了 ObjectOutputStream, writeObject() 方法就会将对象序列化;也可以为一个 String 调用 writeObject() ;也可以用 与 DataOutputStream 相同的方法写入所有基本数据类型;

(2)以上代码仅展现了两种序列化的方式: 一种读写的是文件;而另外一种读写的是 字节数组 ByteArray;

(3)在对 序列化对象 进行反序列化的过程中, 没有调用任何构造器,包括默认的构造器。。整个对象都是通过从  InputStream 中取得数据恢复过来的;


【18.12.1】寻找类

 【看个荔枝】

// 实现  Serializable 接口以保证 Alien对象可序列化
public class Alien implements Serializable {
} // /:~

public class FreezeAlien {
	public static String path = 
			System.getProperty("user.dir") + File.separator + "src" + File.separator + "chapter18" + File.separator;
	public static void main(String[] args) throws Exception {
		// 对象序列化的输出流
		ObjectOutput out = new ObjectOutputStream(
				new FileOutputStream(path + "X.file"));
		Alien quellek = new Alien();
		// Alien对象序列化 (如果 Alien 没有实现 Serializable 接口,则抛出java.io.NotSerializableException异常)
		out.writeObject(quellek);
	}
} // /:~

public class ThawAlien {
	public static String path = 
			System.getProperty("user.dir") + File.separator + "src" + File.separator + "chapter18" + File.separator;
	public static void main(String[] args) throws Exception {
		// 对象反序列化的输入流
		ObjectInputStream in = new ObjectInputStream(new FileInputStream(
				new File(path + "X.file")));
		// 恢复序列化对象
		Object mystery = in.readObject();
		System.out.println(mystery.getClass());
	}
}

打印结果: 
class chapter18.Alien

【代码说明】

打开或读取恢复的序列化对象的内容时,必须保证 jvm 找到相关的 class 文件, 否则抛出 ClassNotFoundException 异常;


【18.12.2】序列化的控制

【看个荔枝】(干货——Externalizable接口的实现荔枝)

// 实现Externalizable 而不是 Serializable接口,
// 这样可以在序列化或反序列化的时候自动调用 writeExternal() 或 readExternal() 执行一些特殊逻辑;
class Blip1 implements Externalizable {
	public static String path = 
			System.getProperty("user.dir") + File.separator + "src" + File.separator + "chapter18" + File.separator;
	
	public Blip1() { // 公共构造器
		print("Blip1 Constructor");
	}

	public void writeExternal(ObjectOutput out) throws IOException { // 序列化自动调用 
		print("Blip1.writeExternal");
	}

	public void readExternal(ObjectInput in) throws IOException,
			ClassNotFoundException { // 反序列化自动调用 
		print("Blip1.readExternal");
	}
}

class Blip2 implements Externalizable {
	Blip2() { // 包可见性 构造器
		print("Blip2 Constructor");
	}

	public void writeExternal(ObjectOutput out) throws IOException { // 序列化自动调用
		print("Blip2.writeExternal");
	}

	public void readExternal(ObjectInput in) throws IOException,
			ClassNotFoundException { // 反序列化自动调用 
		print("Blip2.readExternal");
	}
}

public class Blips {
	public static void main(String[] args) throws IOException,
			ClassNotFoundException {
		print("Constructing objects:");
		Blip1 b1 = new Blip1();
		Blip2 b2 = new Blip2();
		// 序列化对象的输出流
		ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream(Blip1.path + "Blips.out"));
		print("Saving objects:");
		// 序列化操作
		o.writeObject(b1); // 自动调用 Blip1.writeExternal 方法
		o.writeObject(b2); // 自动调用 Blip2.writeExternal 方法
		o.close();
		
		// Now get them back:
		// 反序列化对象的输入流
		ObjectInputStream in = new ObjectInputStream(new FileInputStream(Blip1.path + "Blips.out"));
		print("Recovering b1:");
		// 反序列化
		b1 = (Blip1) in.readObject(); // 先调用 Blip1的构造方法 ,然后调用其 readExternal 方法
		// OOPS! Throws an exception:
		// ! print("Recovering b2:");
		// b2 = (Blip2)in.readObject();
	}
} 

Constructing objects:
Blip1 Constructor
Blip2 Constructor
Saving objects:
Blip1.writeExternal
Blip2.writeExternal
Recovering b1:
Blip1 Constructor
Blip1.readExternal

【代码解说】

实现Externalizable 而不是 Serializable接口,这样可以在序列化或反序列化的时候自动调用 writeExternal() 或 readExternal() 执行一些特殊逻辑;

特殊逻辑包括但不限于: 一个对象被还原后,某子对象需要重新创建,从而不必将该子对象序列化;


【Externalizable 与Serializable 的区别 】

// Externalizable  继承自 Serializable 
public interface Externalizable extends java.io.Serializable { 

Serializable : 对象恢复时(反序列化),对象完全以它存储的二进制位为基础来构造,不调用构造器;

Externalizable :需要调用公共构造器,如果构造器不是公共的, 抛出异常;(先调用默认的构造器,然后再调用 readExternal() 方法)


【如何完整保存和恢复一个  Externalizable 对象】

public class Blip3 implements Externalizable {
	public static String path = 
			System.getProperty("user.dir") + File.separator + "src" + File.separator + "chapter18" + File.separator;
	
	private int i; // 默认等于 0
	private String s; // 默认等于 null

	public Blip3() { // 默认构造器
		print("Blip3 Constructor");
		// s, i not initialized
	}

	public Blip3(String x, int a) {
		print("Blip3(String x, int a)");
		s = x;
		i = a;
		// s & i initialized only in non-default constructor.
	}

	public String toString() {
		return s + i;
	}

	public void writeExternal(ObjectOutput out) throws IOException { // writeObject() 方法 自动调用
		print("Blip3.writeExternal");
		// You must do this:
		//out.writeObject(s);
		//out.writeInt(i);
	}

	public void readExternal(ObjectInput in) throws IOException,
			ClassNotFoundException {
		print("Blip3.readExternal");
		// You must do this:
		//s = (String) in.readObject();
		//i = in.readInt();
	}

	public static void main(String[] args) throws IOException,
			ClassNotFoundException {
		print("Constructing objects:");
		Blip3 b3 = new Blip3("A String ", 47);
		print(b3);
		ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream(path + "Blip3.out"));
		print("Saving object:");
		o.writeObject(b3);
		o.close();
		
		// Now get it back:
		ObjectInputStream in = new ObjectInputStream(new FileInputStream(path + "Blip3.out"));
		print("Recovering b3:");
		b3 = (Blip3) in.readObject(); //先调用默认构造方法,后调用 readExternal() 方法,因为 Blip3 实现了 Externalizable 接口
		print(b3); // null0,因为调用的是 默认构造器,所以 i=0, s=null;
	}
}

打印结果: 
Constructing objects:
Blip3(String x, int a)
A String 47
Saving object:
Blip3.writeExternal
Recovering b3:
Blip3 Constructor
Blip3.readExternal
null0
【注意】如果从一个 Externalizable 对象继承,需要调用基类版本的 writeExternal 和 readExternal 方法 为基类组件提供 存储和恢复功能;

【看个荔枝】

public void writeExternal(ObjectOutput out) throws IOException { // writeObject() 方法 自动调用
		print("Blip3.writeExternal");
		// You must do this:
		out.writeObject(s); // 显式序列化 字符串 
		out.writeInt(i); // 显式序列化 整数
	}

	public void readExternal(ObjectInput in) throws IOException,
			ClassNotFoundException {
		print("Blip3.readExternal");
		// You must do this:
		s = (String) in.readObject();  // 显式恢复 字符串
		i = in.readInt();  // 显式恢复 字符串 
	}
// 打印结果比较:
Constructing objects:
Blip3(String x, int a)
A String 47
Saving object:
Blip3.writeExternal
Recovering b3:
Blip3 Constructor
Blip3.readExternal
A String 47

【transient】 瞬时关键字(干货——以下内容是对 transient 及序列化内容的全面阐释,小心阅读)

防止对象的敏感部分被序列化方法:

方法1: 将类实现为  Externalizable, 且在 writeExternal方法中 不序列化任何东西, 在readExternal方法中不反序列化任何东西,这样一来,就没有任何东西自动序列化和反序列化了;(干货——Externalizable是手动序列化接口

方法2: 如果操作的是一个 Serializable对象,所有序列化对象包括敏感部分就会自动序列化操作;为了能够防止敏感信息被序列化,可以将其声明为  transient;(干货——Serializable是自动序列化接口, transient的言外之意是 “不用麻烦你保存或恢复数据——我自己会处理的!”) 

【看个荔枝 Serializable + transient】

public class Logon implements Serializable {
	private Date date = new Date();
	private String username;
	/* 当对Logon对象进行自动序列化的时候,这个字段不进行序列化和反序列化 */
	private transient String password;

	public Logon(String name, String pwd) {
		username = name;
		password = pwd;
	}
	public String toString() {
		return "logon info: \n   username: " + username + "\n   date: " + date
				+ "\n   password: " + password;
	}
	public static void main(String[] args) throws Exception {
		Logon a = new Logon("Hulk", "myLittlePony");
		print("logon a = " + a);
		
		// 序列化输出对象
		ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream(
				MyConstants.path + "Logon.out"));
		o.writeObject(a); // 自动序列化操作,不需要调用构造方法 或 其他任何方法。
		o.close();
		TimeUnit.SECONDS.sleep(1); // Delay
		// Now get them back:
		ObjectInputStream in = new ObjectInputStream(new FileInputStream(
				MyConstants.path + "Logon.out"));
		print("Recovering object at " + new Date());
		a = (Logon) in.readObject();
		print("logon a = " + a);
		/* 反序列化后,password 为 null,表明它未参与 序列化和反序列化的过程!*/
	}
}

logon a = logon info: 
   username: Hulk
   date: Fri Oct 13 22:56:27 CST 2017
   password: myLittlePony
Recovering object at Fri Oct 13 22:56:28 CST 2017
logon a = logon info: 
   username: Hulk
   date: Fri Oct 13 22:56:27 CST 2017
   password: null //  password 未参与序列化和反序列化操作(这里的null 是 空字符串 而不是 null指针)
(干货—— transient 仅对 Serializable 奏效)

防止对象的敏感部分被序列化方法:

方法3: 实现 Serializable接口,并 添加(是添加,并非覆盖或实现)以下两个方法(注意private)

private void writeObject(ObjectOutputStream stream) throws IOException;
private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException;
(干货——14.9 节展示了如何在类的外部访问 private方法)


【技巧】在 writeObject() 和 readObject() 方法内部可以分别调用 defaultWriteObject() 和 defaultReadObject() ;

	private void writeObject(ObjectOutputStream stream) throws IOException {
		stream.defaultWriteObject(); // 选择执行默认的 writeObject(); 和 
	}
	
	private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
		stream.defaultReadObject(); // 选择执行默认的 readObject();
	}

【看个荔枝】

public class SerialCtl implements Serializable {
	private String a;
	private transient String b;

	public SerialCtl(String aa, String bb) {
		a = "Not Transient: " + aa;
		b = "Transient: " + bb;
	}

	public String toString() {
		return "a = " + a + "\n" + ", b = " + b;
	}
	
	// 调用自定义的 writeObject(),而不是 ObjectOutputStream.writeObject();
	private void writeObject(ObjectOutputStream stream) throws IOException {
		//stream.defaultWriteObject(); // 【注释or取消注释,就能看到效果】defaultWriteObject()方法默认序列化 不被 transient 修饰的字段 (序列化==保存)
		stream.writeObject(b); // 对 transient 修饰的 字段b 【序列化】
		System.out.println("SerialCtl.writeObject() 方法");
	}
	// 调用自定义的 readObject(), 而不是 ObjectInputStream.readObject();
	private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
		//stream.defaultReadObject();   // 【注释or取消注释,就能看到效果】defaultReadObject()方法默认反序列化 不被 transient 修饰的字段 (反序列化==恢复)
		b = (String) stream.readObject(); // 对 transient 修饰的 字段b 【反序列化】
		System.out.println("SerialCtl.readObject() 方法");
	}

	public static void main(String[] args) throws IOException, ClassNotFoundException {
		SerialCtl sc = new SerialCtl(MyConstants.path + "Test1", 
				MyConstants.path + "Test2");
		System.out.println("Before:\n" + sc);
		
		ByteArrayOutputStream buf = new ByteArrayOutputStream();
		ObjectOutputStream o = new ObjectOutputStream(buf);
		o.writeObject(sc); // 调用 SerialCtl.writeObject()方法
		
		// Now get it back:
		ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(
				buf.toByteArray()));
		SerialCtl sc2 = (SerialCtl) in.readObject(); // 调用 SerialCtl.readObject()方法
		System.out.println("After:\n" + sc2);
	}
} 

// 注释后的打印效果
Before:
a = Not Transient: E:\bench-cluster\spring_in_action_eclipse\AThinkingInJava\src\chapter18\Test1
, b = Transient: E:\bench-cluster\spring_in_action_eclipse\AThinkingInJava\src\chapter18\Test2
SerialCtl.writeObject() 方法
SerialCtl.readObject() 方法
After:
a = null
, b = Transient: E:\bench-cluster\spring_in_action_eclipse\AThinkingInJava\src\chapter18\Test2 

// 注释后的打印效果
Before:
a = Not Transient: E:\bench-cluster\spring_in_action_eclipse\AThinkingInJava\src\chapter18\Test1
, b = Transient: E:\bench-cluster\spring_in_action_eclipse\AThinkingInJava\src\chapter18\Test2
SerialCtl.writeObject() 方法
SerialCtl.readObject() 方法
After:
a = null
, b = Transient: E:\bench-cluster\spring_in_action_eclipse\AThinkingInJava\src\chapter18\Test2 

(干货——序列化叫保存,非序列化叫恢复)

【代码解说】

这说明 非 transient 字段是由 defaultWriteObject() 方法来保存的 和由  defaultReadObject() 方法来恢复的;而transient字段必须在程序中明确保存和恢复。。

【注意事项】

1)stream.defaultReadObject();  和 stream.defaultWriteObject(); 必须首先被调用;

2)序列化输出流连接的是 字节数组缓冲区;

public ByteArrayOutputStream() {
        this(32);
    }
public ByteArrayOutputStream(int size) {
        if (size < 0) {
            throw new IllegalArgumentException("Negative initial size: "
                                               + size);
        }
        buf = new byte[size];
    }

【18.12.3】使用持久性

【问题】如果有两个对象,都具有指向第三个对象的引用;而又要序列化这两个对象,会发生什么样的情况呢? 或者说 第三个对象会序列化一次 还是两次?

反序列化的情况又是怎样的呢?

【看个荔枝】

class House implements Serializable {
}

class Animal implements Serializable {
	private String name;
	private House preferredHouse;

	Animal(String nm, House h) {
		name = nm;
		preferredHouse = h;
	}

	public String toString() {
		return name + "[" + super.toString() + "], " + preferredHouse + "\n";
	}
}

public class MyWorld {
	public static void main(String[] args) throws IOException,
			ClassNotFoundException {
		House house = new House();
		List<Animal> animals = new ArrayList<Animal>();
		animals.add(new Animal("Bosco the dog", house));
		animals.add(new Animal("Ralph the hamster", house));
		animals.add(new Animal("Molly the cat", house));
		print("animals:\n " + animals);
		
		ByteArrayOutputStream buf1 = new ByteArrayOutputStream(); //字节数组输出缓冲区
		ObjectOutputStream o1 = new ObjectOutputStream(buf1); // 序列化输出流
		o1.writeObject(animals);
		o1.writeObject(animals); // Write a 2nd set, 序列化了两次
		
		// Write to a different stream:
		ByteArrayOutputStream buf2 = new ByteArrayOutputStream(); // 不同的输出流
		ObjectOutputStream o2 = new ObjectOutputStream(buf2); // 不同的序列化输出流
		o2.writeObject(animals); // 再次序列化
		
		// Now get them back: 恢复或反序列化对象
		ObjectInputStream in1 = new ObjectInputStream(new ByteArrayInputStream(buf1.toByteArray()));
		ObjectInputStream in2 = new ObjectInputStream(new ByteArrayInputStream(buf2.toByteArray()));
		List animals1 = (List) in1.readObject(); 
		List animals2 = (List) in1.readObject();
		List animals3 = (List) in2.readObject();
		
		print("\nanimals1:\n " + animals1);
		print("\nanimals2:\n " + animals2);
		print("\nanimals3:\n " + animals3);
	}
} 

// 打印结果:
animals: // 序列化前的内存地址
 [Bosco the dog[chapter18.Animal@2a139a55], chapter18.House@15db9742
, Ralph the hamster[chapter18.Animal@6d06d69c], chapter18.House@15db9742
, Molly the cat[chapter18.Animal@7852e922], chapter18.House@15db9742
]
// 以下是序列化后的对象所存储的内存地址,肯定不一样。
animals1:
 [Bosco the dog[chapter18.Animal@330bedb4], chapter18.House@2503dbd3 // 相同
, Ralph the hamster[chapter18.Animal@4b67cf4d], chapter18.House@2503dbd3
, Molly the cat[chapter18.Animal@7ea987ac], chapter18.House@2503dbd3
]

animals2:
 [Bosco the dog[chapter18.Animal@330bedb4], chapter18.House@2503dbd3 // 第二次序列化到相同的输出流,相同
, Ralph the hamster[chapter18.Animal@4b67cf4d], chapter18.House@2503dbd3
, Molly the cat[chapter18.Animal@7ea987ac], chapter18.House@2503dbd3
]

animals3:
 [Bosco the dog[chapter18.Animal@12a3a380], chapter18.House@29453f44 // 序列化到不同的输出流, 不相同
, Ralph the hamster[chapter18.Animal@5cad8086], chapter18.House@29453f44
, Molly the cat[chapter18.Animal@6e0be858], chapter18.House@29453f44
]

【深度复制】通过一个字节数组来使用对象序列化,从而实现对任何 可 Serializable 对象的 深度复制;深度复制是复制整个对象网,而不仅仅是基本对象及其引用;

【代码解说】当恢复 animals1 和 animals2的时候,他们都被序列化到了同一个流内,所以他们的恢复后的对象网是相同的;而恢复animals3时, 系统无法知道 animals3 和 animals1 或 animals2 一样,所以 animals3 产生了不同的对象网;

【原子序列化】想要保存系统状态, 最安全的做法是 把 序列化操作作为原子操作进行;


【看个荔枝: 序列化类变量和Class对象】

abstract class Shape implements Serializable {
	public static final int RED = 1, BLUE = 2, GREEN = 3; // 类变量
	private int xPos, yPos, dimension;
	private static Random rand = new Random(47); // 类变量
	private static int counter = 0; // 类变量

	public abstract void setColor(int newColor);

	public abstract int getColor();

	public Shape(int xVal, int yVal, int dim) {
		xPos = xVal;
		yPos = yVal;
		dimension = dim;
	}

	public String toString() {
		return getClass() + "color[" + getColor() + "] xPos[" + xPos
				+ "] yPos[" + yPos + "] dim[" + dimension + "]\n";
	}

	public static Shape randomFactory() { // 类方法
		int xVal = rand.nextInt(100);
		int yVal = rand.nextInt(100);
		int dim = rand.nextInt(100);
		switch (counter++ % 3) {
		default:
		case 0:
			return new Circle(xVal, yVal, dim);
		case 1:
			return new Square(xVal, yVal, dim);
		case 2:
			return new Line(xVal, yVal, dim);
		}
	}
}

class Circle extends Shape {
	private static int color = RED; // 类变量

	public Circle(int xVal, int yVal, int dim) {
		super(xVal, yVal, dim);
	}

	public void setColor(int newColor) {
		color = newColor;
	}

	public int getColor() {
		return color;
	}
}

class Square extends Shape {
	private static int color; // 类变量

	public Square(int xVal, int yVal, int dim) {
		super(xVal, yVal, dim);
		color = RED;
	}

	public void setColor(int newColor) {
		color = newColor;
	}

	public int getColor() {
		return color;
	}
}

class Line extends Shape {
	private static int color = RED; // 类变量

	public static void serializeStaticState(ObjectOutputStream os)
			throws IOException { // 类方法,序列化静态状态, highlight
		os.writeInt(color); 
	}

	public static void deserializeStaticState(ObjectInputStream os)
			throws IOException { // 类方法
		color = os.readInt();
	}

	public Line(int xVal, int yVal, int dim) {
		super(xVal, yVal, dim);
	}

	public void setColor(int newColor) {
		color = newColor;
	}

	public int getColor() {
		return color;
	}
}

public class StoreCADState {
	public static void main(String[] args) throws Exception {
		List<Class<? extends Shape>> shapeTypes = new ArrayList<>();
		// Add references to the class objects:
		shapeTypes.add(Circle.class);
		shapeTypes.add(Square.class);
		shapeTypes.add(Line.class);
		
		List<Shape> shapes = new ArrayList<>();
		for (int i = 0; i < 10; i++)
			shapes.add(Shape.randomFactory());
		
		// Set all the static colors to GREEN: 把所有的静态变量颜色设置为 绿色;
		for (int i = 0; i < 10; i++)
			((Shape) shapes.get(i)).setColor(Shape.GREEN);
		// Save the state vector: // 保存状态向量
		ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(
				MyConstants.path + "CADState.out"));
		
		out.writeObject(shapeTypes); // 序列化 Class对象 列表
		Line.serializeStaticState(out); // 序列化静态状态(类变量)
		out.writeObject(shapes); // 序列化 Shape 列表
		// Display the shapes:
		System.out.println(shapes);
	}
}
// 打印结果:
[class chapter18.Circlecolor[3] xPos[58] yPos[55] dim[93]
, class chapter18.Squarecolor[3] xPos[61] yPos[61] dim[29]
, class chapter18.Linecolor[3] xPos[68] yPos[0] dim[22]
, class chapter18.Circlecolor[3] xPos[7] yPos[88] dim[28]
, class chapter18.Squarecolor[3] xPos[51] yPos[89] dim[9]
, class chapter18.Linecolor[3] xPos[78] yPos[98] dim[61]
, class chapter18.Circlecolor[3] xPos[20] yPos[58] dim[16]
, class chapter18.Squarecolor[3] xPos[40] yPos[11] dim[22]
, class chapter18.Linecolor[3] xPos[4] yPos[83] dim[6]
, class chapter18.Circlecolor[3] xPos[75] yPos[10] dim[42]
]
【代码解说】

1)Class对象之所以可以被序列化,是因为,Class类实现了Serializable 接口;

public final class Class<T> implements java.io.Serializable,
                              GenericDeclaration,
                              Type,
                              AnnotatedElement {
2)Shape类实现了 Serializable接口,所以其子类都是可以被序列化的 ;


【荔枝: 对 StoreCADState 中序列化的对象进行反序列化操作】

public class RecoverCADState {
	@SuppressWarnings("unchecked")
	public static void main(String[] args) throws Exception {
		ObjectInputStream in = new ObjectInputStream(new FileInputStream(
				MyConstants.path + "CADState.out"));
		
		// Read in the same order they were written:
		// 反序列化操作
		List<Class<? extends Shape>> shapeTypes = (List<Class<? extends Shape>>) in
				.readObject();
		Line.deserializeStaticState(in); // 反序列化静态状态
		
		List<Shape> shapes = (List<Shape>) in.readObject();
		System.out.println(shapes);
		
		Class class1;
	}
}
// 和 StoreCADState 的打印结果做比较;
[class chapter18.Circlecolor[1] xPos[58] yPos[55] dim[93]
, class chapter18.Squarecolor[0] xPos[61] yPos[61] dim[29]
, class chapter18.Linecolor[3] xPos[68] yPos[0] dim[22]
, class chapter18.Circlecolor[1] xPos[7] yPos[88] dim[28]
, class chapter18.Squarecolor[0] xPos[51] yPos[89] dim[9]
, class chapter18.Linecolor[3] xPos[78] yPos[98] dim[61]
, class chapter18.Circlecolor[1] xPos[20] yPos[58] dim[16]
, class chapter18.Squarecolor[0] xPos[40] yPos[11] dim[22]
, class chapter18.Linecolor[3] xPos[4] yPos[83] dim[6]
, class chapter18.Circlecolor[1] xPos[75] yPos[10] dim[42]
]
	public static void deserializeStaticState(ObjectInputStream os) // Line.StoreCASStore.deserializeStaticState() 方法
			throws IOException { // 类方法
		color = os.readInt(); // 反序列化
	}

【打印结果比较】对上述两个打印结果比较发现, StoreCADState中的所有color 都等于3;而 RecoverCADState中的,只有从 反序列化取出的 color 才等于3,其他的color 等于 其初始值,而不是构造器里设置的值, 因为 Serializable的反序列化操作是自动进行的,不会调用其 构造方法; (干货——Line类中有 serializeStaticState 和 deserializeStaticState 显式对类变量进行序列化和反序列化,所以 对Line对象的序列化能够达到我们的目的)


【代码解说】

不知道大家注意到 序列化和反序列化类变量 和 普通变量的顺序 必须要一致;

// 序列化的顺序这样:
Line.serializeStaticState(out); // 序列化静态状态(类变量)
		out.writeObject(shapes); // 序列化 Shape 列表

// 反序列化顺序这样的话:
List<Shape> shapes = (List<Shape>) in.readObject(); // 后序列化普通对象
		Line.deserializeStaticState(in); // 先进行反序列化静态状态
// 打印结果:抛出异常;
Exception in thread "main" java.io.OptionalDataException
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1363)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:371)
	at chapter18.RecoverCADState.main(RecoverCADState.java:20)

【代码解说】 意思是:如果序列化的顺序是: 1.序列化类变量;2.序列化Class对象;3.序列化普通java对象;那么反序列化的顺序必须是:  1.反序列化类变量;2.反序列化Class对象;3.反序列化普通java对象;


【18.13】XML

【对象序列化 的已知限制】只有java程序才能反序列化这种对象; 更为通用的解决方案是将数据转换为 XML 格式, 这可以被 其他各种平台和语言调用;

【XML类库】作者推荐用 Elliotte Rusty Harold 的 XOM类库(www.xom.nu下载),它是最直观用 java 产生和修改XML 的方式;(干货——XOM类库,XML类库)

【创建XML文件的荔枝】

public class Person {
	private String first, last;

	public Person(String first, String last) {
		this.first = first;
		this.last = last;
	}

	// Produce an XML Element from this Person object:
	// 创建两个XML节点:first 和 last 节点
	public Element getXML() {
		Element person = new Element("person");
		Element firstName = new Element("first");
		firstName.appendChild(first);
		Element lastName = new Element("last");
		lastName.appendChild(last);
		person.appendChild(firstName);
		person.appendChild(lastName);
		return person;
	}

	// Constructor to restore a Person from an XML Element:
	public Person(Element person) {
		first = person.getFirstChildElement("first").getValue();
		last = person.getFirstChildElement("last").getValue();
	}

	public String toString() {
		return first + " " + last;
	}

	// Make it human-readable:
	public static void format(OutputStream os, Document doc) throws Exception {
		/* 产生文件头  */
		Serializer serializer = new Serializer(os, "ISO-8859-1");
		serializer.setIndent(4);
		serializer.setMaxLength(60);
		serializer.write(doc);
		serializer.flush();
	}

	public static void main(String[] args) throws Exception {
		/* 将Person 数组转为 List<Person> */
		List<Person> people = Arrays.asList(
				new Person("Dr. Bunsen", "Honeydew"), new Person("Gonzo",
						"The Great"), new Person("Phillip J.", "Fry"));
		
		System.out.println(people);
		/* 构建一个XML 元素 */
		Element root = new Element("people");
		for (Person p : people)
			root.appendChild(p.getXML());
		Document doc = new Document(root);
		/* 输出到控制台  */
		format(System.out, doc); 
		/* 输出到缓冲输出流 */
		format(new BufferedOutputStream(new FileOutputStream(MyConstants.path + "People.xml")), doc);
	}
}
// 打印结果:
[Dr. Bunsen Honeydew, Gonzo The Great, Phillip J. Fry]
<?xml version="1.0" encoding="ISO-8859-1"?>
<people>
    <person>
        <first>Dr. Bunsen</first>
        <last>Honeydew</last>
    </person>
    <person>
        <first>Gonzo</first>
        <last>The Great</last>
    </person>
    <person>
        <first>Phillip J.</first>
        <last>Fry</last>
    </person>
</people>
XOM java类库地址:  https://gitee.com/pacosonswjtu/thinkInJava/blob/chapter18/xom-1.2.10.jar

【18.14】 Preference // 自动存储和读取信息;(键值对)

Preference API 和 对象序列化相比, Preference 与对象持久性更加密切,因为它可以自动存储和读取信息。

【作用】 Preference API 用于存储和读取用户的偏好以及程序配置项的设置;

Preference 是一个键值对集合,存储在一个节点层次结构中; 通常是创建以你的类名命名的单一节点,然后将信息存储其中;

【看个荔枝】

public class PreferencesDemo {
	public static void main(String[] args) throws Exception {
		Preferences prefs = Preferences
				.userNodeForPackage(PreferencesDemo.class);
		prefs.put("Location", "Oz");
		prefs.put("Footwear", "Ruby Slippers");
		prefs.putInt("Companions", 4);
		prefs.putBoolean("Are there witches?", true);
		
		int usageCount = prefs.getInt("UsageCount", 0); // 如果不存在键值为UsageCount的value,则0作为默认值;
		
		usageCount++;
		prefs.putInt("UsageCount", usageCount); 
		for (String key : prefs.keys())
			print(key + ": " + prefs.get(key, null));
		// You must always provide a default value:
		print("How many companions does Dorothy have? "
				+ prefs.getInt("Companions", 0));
	}
}

// 打印结果。
十月 21, 2017 12:04:35 上午 java.util.prefs.WindowsPreferences <init>
WARNING: Could not open/create prefs root node Software\JavaSoft\Prefs at root 0x80000002. Windows RegCreateKeyEx(...) returned error code 5.
Location: Oz
Footwear: Ruby Slippers
Companions: 4
Are there witches?: true
UsageCount: 1
How many companions does Dorothy have? 4
【注意】

1.通常把 Preferences.userNodeForPackage 设置用户偏好,而把  Preferences.system'NodeForPackage 用于通用的配置安装;

public static Preferences userNodeForPackage(Class<?> c) {
        return userRoot().node(nodeName(c));
    }
静态方法传入PreferencesDemo.class, 非静态方法传入 obj.getClass();

【总结】

(1)应该使用一个File对象来判断某个文件是否存在,因为如果使用  FIleOutputStream 或 FileWriter 打开的话,那么他就可能会被覆盖掉;

(2)I/O 流类库确实能够做很多事情,而且具有可移植性。。但是如果我们没有理解装饰器模式,那么可能体会不到这种设计的意义;


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
《Thinking in Java》是一本经典的Java编程入门教材,通过阅读该书可以系统地学习Java编程的基础知识和高级概念。阅读过程中,我结合自己的学习体验和实践,进行了详细的学习笔记。 首先,该书从基础语法开始介绍,包括数据类型、控制语句、数组等。对于初学者来说,这些基础知识是最基本的,通过书中的示例代码和解析,我能够更好地理解这些概念和语法规则。 其次,书中对面向对象编程进行了深入的讲解。通过学习面向对象的思想,我明白了类、对象、继承、多态等概念的含义和使用方法。同时,书中还讲解了接口、内部类、异常处理等较为高级的概念,极大地拓宽了我的Java知识面。 另外,该书还介绍了Java的常见类库和工具,如字符串操作、集合框架、输入输出、线程等。这些内容对于实际开发非常有用,而且书中的示例代码也能帮助我更好地理解和应用这些类库和工具。 此外,该书通过大量的实例和案例,生动地展示了Java编程的实战应用。这些实例涵盖了各种不同的应用场景,从简单的程序到复杂的项目,都有所涉及。通过分析这些实例,我不仅可以学习到实际编程的技巧,还能提高自己的解决问题的能力。 总的来说,读完《Thinking in Java》后,我对Java编程有了更深入的理解和掌握。通过学习笔记的整理,我不仅复习了各个知识点,还加深了对这些概念的理解。希望这份学习笔记能够帮助到其他想要学习Java的同学,让他们能够更快地入门和掌握这门编程语言。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值