2.1 输入 / 输出流

No relationship with java.util.stream.

抽象类 Readers/writes process characters, not bytes (P43). 

2.1.1-2.1.3 读写字节

Easiest to use static methods from the java.nio.file.Files class:

1 Path path = Paths.get(filenameString);
2 InputStream in = Files.newInputStream(path);
3 OutputStream out = Files.newOutputStream(path);

Get an input stream from any URL:

1 URL url = new URL("http://horstmann.com/index.html");
2 InputStream in = url.openStream();

Get an input stream from a byte[] array:

1 byte[] bytes = ...;
2 InputStream in = new ByteArrayInputStream(bytes);

Conversely, you can write to a ByteArrayOutputStream and then collect the bytes:

1 ByteArrayOutputStream out = new ByteArrayOutputStream();
2 Write to out
3 byte[] bytes = out.toByteArray();

The read method returns a single byte (as an int) or -1 at the end of input:

1 InputStream in = ...;
2 int b = in.read();
3 if (b != -1) { byte value = (byte) b; ...}

It is more common to read bytes in bulk:

1 byte[] bytes = ...;
2 int len = in.read(bytes);

No method for reading all bytes from a stream. Here is one solution:

1 ByteArrayOutputStream out = new ByteArrayOutputStream();
2 byte[] bytes = new byte[1024];
3 while ((len = in.read(bytes)) != -1) {out.write(bytes, 0 , len);} // -1: end of the input stream
4 bytes = out.toByteArray();

For files, just call:

1 byte[] bytes = Files.readAllBytes(path);

You can write one byte or bytes from an array:

1 OutputStream out = ...;
2 int b = ...;
3 out.write(b);
4 byte[] bytes = ...;
5 out.write(bytes);
6 out.write(bytes, start, length);

When writing to a stream, close it when you are done:

out.close();

Or better, use a try-with-resources block (resource will be automatically closed):

1 try (OutputStream out = ...) {
2 out.write(bytes);
3 }

To save an input stream to a file, call:

1 Files.copy(in, path, StandardCopyOption.REPLACE_EXISTING);

Java new feature:

  • 1. There is finally a method to read all bytes from an input stream: byte[] bytes = url.openStream().readAllBytes();
  • There is also readNBytes.
  • 2. InputStream.transferTo(OutputStream) transfer all bytes from an input stream to an output stream.
  • 3. Java 10: Reader.transferTo(Writer)
  • 4. Java 10: Character sets in PrintWriter, Scanner, etc. can be specified as Charset instead of String.  new Scanner(path, StandCharsets.UTF_8)
  • 5. Scanner.tokens gets a stream of tokens, similiar to Pattern.splitAsStream from Java 8: Stream<String> tokens = new Scanner(path).useDelimiter("\\s*,\\s*).tokens();

   

2.1.4 读写文本文件

1. Summary:

  • Input/Output streams process bytes.
  • Text files contain characters.
  • Java uses Unicode for characters.
  • Readers/writes convert between bytes and characters.
  • Always specify the character encoding. Use StandardCharsets.UTF_8 for Charset parameters, "UTF-8" for string parameters.

2. You can obtain a Reader for any input stream:

1 InputStream inStream = ...;
2 Reader in = new InputStreamReader(inStream, charset);

The read methods reads one char value, it's too low-level for most purposes.

1) You can read a short file into a string:

1 String content = new String(Files.readAllBytes(path), charset);

2) You can get all lines as a list or stream:

1 List<String> lines = Files.readAllLines(path, charset);
2
3 try (Stream<String> lines = Files.lines(path, charset)) {
4 ...
5 }

3) Use a Scanner to split input into numbers, words, and so on:

1 Scanner in = new Scanner(path, "UTF-8");
2 while (in.hasNextDouble()) {
3 double value = in.nextDouble();
4 ...
5 }

To read words, set the delimeter to any sequence of non-letters:

1 in.useDelimiter("\\PL+");
2 while (in.hasNext()) {
3 String word = in.next();
4 ...
5 }
6 Or
7 Stream<String> words = in.tokens();

3. To write to a file, make one of these calls as following. Then call out.print, out.println, or out.printf to produce output.

1 PrintWriter out = new PrintWriter(Files.newBufferedWriter(path, charset));
2
3 PrintWriter out = new PrintWriter(filenameString, charsetString);

Remeber to close the file: try (PrintWriter out = ... ) {...}

If you already have the entire output in a string, or a collection of lines, call:

1 Files.write(path, contentString.getBytes(charset));
2 Files.write(path, lines, charset);

You can also append output to a file:

1 Files.write(path, lines, charset, StandardOpenOption.APPEND);

4. Sometimes, a library method wants a Writer object. Example:

1 Throwable.printStackTrace(PrintWriter out)

If you want to capture the output in a string, not a file, use a StringWriter:

1 StringWriter writer = new StringWriter();
2 throwable.printStackTrace(new PrintWriter(writer));

Now you can process the stack trace as string:

1 String stackTrace = writer.toString();

2.2/2.5 读写二进制数据

1. 处理二进制文件

DataInput / DataOutput interfaces have methods readInt / writeInt, readDouble / writeDouble, and so on.

Can wrap any stream into a DataInputStream / DataOutputStream:

1 DataInput in = new DataInputStream(new FileInputStream(path));
2 DataOutput out = new DataOutputStream(new FileOutputStream(path));

Reading / writing stream data is sequential.

2. 随机访问文件

2.1 方式一: RandomAccessFile (section 2.2.2)

"Random access file": You can jump to any file position and start reading/writing. Open with "r" for reading or "rw" for writing:

1 RandomAccessFile file = new RandomAccessFile(filenameString, "rw");

The getFilePointer method yields the current position (as a long).

The seek method moves to a new position.

Example: Increment an integer that you just read:

1 int value = file.readInt();
2 file.seek(file.getFilePointer() - 4); // 第1句读取一个整数,此时位置偏移。此时读取当前位置 - 4(整数长度),即回到了刚才的位置
3 file.writeInt(value + 1);

2.2 方式二:内存映射文件 Memory-Mapped Files(section 2.5)

A memory-mapped file provides very efficient random access for large files. (Uses operating system mechanism for virtal memory.)

step1: Get a channel for the file:

1 FileChannel channel = FileChannel.open(path, StandardOpenOption.READ, StandOpenOption.WRITE)

step2: Map an area of the file (or all of it) into memory:

1 ByteBuffer buff = channel.map(FileChannel.MapMode.READ_WRITE, 0, channel.size());

step3: You use methods get, getInt, getDouble, and so on to read, and the equivalent put methods to write:

1 int position = ...;
2 int value = buffer.getInt(position);
3 buffer.put(position, value + 1);

The file is updated at some point, and certainly when the channel is closed.


2.4 操作文件(创建、访问、删除文件和目录): Path, Files

1.  Working with Path

Path objects specify abstract path names (which may not currently exist on disk). Sequence of directory names, optionally followed by a file name.

Use Paths.get to create paths:

1 Path absolute = Paths.get("/", "home", "cay");
2 Path relative = Paths.get("myapp", "conf", "user.properties");

Path separator / or \ is suppiled for the default file system. If you know which platform your program is running, you can provide a string with separators:

1 Path homeDirectory = Paths.get("/home/cay");

1.1. The call p.resolve(q) computes "p then q". If q is absolute, that's just q, otherwise, first follow p, then follow q:

1 Path workPath = homeDirectory.resolve("myapp/work");

1.2. The oppostie of resolve is relativize, yielding "how to get from p to q". 

1 Paths.get("/home/cay").relativize(Paths.get("/home/fred/myapp"))
2 // yields "../fred/myapp"

1.3. normalize removes . or directory/../ and other redundancies.

1.4. toAbsolutePath makes a path absolute.

2. Taking Paths Apart

Utility methods to get at the most important parts:

1 Path p = Paths.get("/home", "cay", "myapp.properties");
2 Path parent = p.getParent(); // The path /home/cay
3 Path file = p.getFileName(); // The last element, myapp.properties
4 Path root = p.getRoot(); // The initial segment / (null for a relative path)
5 Path first = p.getName(0); // The first element, home
6 Path dir = p.subpath(1, p.getNameCount()); // All but the first element, cay/myapp.properties

You can iterate over the components:

1 for (Path component : path) {
2 ...
3 }

To interoperate with legacy File class, use:

1 File file = path.toFile();
2 Path path = file.toPath();

3. Files

2.4.3 To create a new directory, call:

1 Files.createDirectory(path);    // All but the last component must exist
2 Files.createDirectories(path); // Missing components are created. 创建路径中的中间目录

You can create an empty file, If the file exists, an exception occurs. Check and creation are atomic.

1 Files.createFile(path);

Convencience methods for creating temporary files:

1 Path tempFile = Files.createTempFile(dir, prefix, suffix);
2 Path tempFile = Files.createTempFile(prefix, suffix);
3
4 Path tempDir = Files.createTempDirectory(dir, prefix);
5 Path tempDir = Files.createTempDirectory(prefix);

Files.createTempFile(null, ".txt") might return a path such as /tmp/1234405522364837194.txt.

Files.exists(path) checks whether a path currently exists.

Use Files.isDirectory(path), Files.isRegularFile(path), Files.isSymbolicLink(path) to find out whether the path is directory, file, or symlink. More infor: isHidden, isExecutable, isReadable, isWritable of the Files class.

Files.size(path) reports the file size as a long value.

2.4.4 Use the copy or move method:

1 Files.copy(fromPath, toPath);
2 Files.move(fromPath, toPath);

Can define behavior with copy options:

1 Files.copy(fromPath, toPath, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES);
2 Files.move(fromPath, toPath, StandardCopyOption.ATOMIC_MOVE);

Delete a file like this:

1 Files.delete(path);    // throws exception if path doesn't exist
2 boolen deleted = Files.deleteIfExists(path);

2.4.6 Files.list(dirpath) yields a Stream<Path> of the directory entries. The directory is read lazily -- efficient for huge directories. Be sure to close the stream. (Files.list 不会进入子目录)

1 try (Stream<Path> entries = Files.list(pathToDirectory)) {...}

Call Files.walk(dirpath) to visit all descendants of subdirectories as well. Descendants are visited in depth-first order.

1 try (Stream<Path> entries = Files.walk(pathToRoot)) {...}

 If you filter results by file attributes (size, creation time, and so on), use find instead of walk for greater efficiency:

1 Files.find(path, maxDepth, (path, attr) -> attr.size() > 10000)

Use Files.walk to copy a directory tree:  // JDK 目前没有提供方法来实现复制目录

Files.walk(source).forEach(p -> {
try {
Path q = target.resolve(source.relativize(p));
if (Files.isDirectory(p)) {
Files.createDirectory(q);
}
else {
Files.copy(p, q);
}
catch (IOException ex) {
throw new UncheckedIOException(ex);
}
});

Unfortunately, this approach doesn't work for deleting a directory tree. Need to vist children before deleting the parent. => Use FileVisitor instead:

 // Delete the directory tree starting at root
1 Files.walkFileTree(root, new SimpleFileVisitor<Path>() {
2 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
3 Files.delete(file);
4 return FileVisitResult.CONTINUE;
5 }
6 public FileVisitResult postVisitDirectory(Path dir, IOException ex) throws IOException {
7 if (ex != null) { throw ex; }
8 Files.delete(dir);
9 return FileVisitResult.CONTINUE;
10 }
11 });

Paths class looks up paths in the default file system.

4. ZIP file sytem

Can have file system for the files in a ZIP archive:

1 FileSystem zipfs = FileSystems.newFileSystem(Paths.get(zipname), null);

Copy out a file if you know its name:

1 Files.copy(zipfs.getPath(sourceName), targetPath);

To list all files in an archive, walk the file tree:

1 Files.walk(zipfs.getPath("/", forEach(p -> { Process p });

Here is the magic incantation for creating a zip file:

1 Path zipPath = Paths.get("myfile.zip);
2 URI uri = new URI("jar", zipPath.toUri().toString(), null);
3 // Constructs the URI jar:file://myfile.zip
4 try (FileSystem zipfs = FileSystems.newFileSystem(uri, Collections.singletonMap("create", "true"))) {
5 // To add files, copy them into the ZIP file system
6 Files.copy(sourcePath, zipfs.getPath("/").resolve(targetPath));
7 }

5. Java 新特性

  • String.lines yields a stream of all lines in a string.
  • String.strip trims Unicode whitespace.
  • Path.of does the same as Paths.get -- more consistent and shorter.
  • Files.readString reads a file into a string.
  • OutputStream nullOutputStream() provides a null stream.
  • Analogous methods for InputStream, Reader, Writer.

2.x 处理 互联网上的数据

You can read data from a given URL. That gets you the contents of the URL(from the GET request).

1 URL url = new URL("http://hostmann.com/index.html");
2 InputStream in = url.openStream();

You need to use the URLConnection class for more complex cases:

  • Making a POST request
  • Setting request headers
  • Reading response headers
1. Get an URLConnection object:
URLConnection connection = url.openConnection(); 2. Set request properties:
connection.setRequestProperty("Accept-Charset", "UTF-8, ISO-8859-1"); 3. Send data to the server:
connection.setDoOutput(true);
try (OutputStream out = connection.getOutputStream()) { Write to out } 4. Read the response headers:
connection.connect(); // If you skipped step 3
Map<String, List<String>> headers = connection.getHeaderFields(); 5. Read the response:
try (InputStream in = connection.getInputStream()) { Read from in }

When writing to a HttpURLConnection, the default encoding is application/x-www-form-urlencoded. But you still need to encode the name/value pairs.

Suppose POST data are given in a map:

URLConnection connection = url.openConnection();
connection.setDoOutput(true);
try (Writer out = new OutputStream(connection.getOutputStream(), StandardCharsets.UTF_8)) {
boolean first = true;
for (Map.Entry<String, String> entry : postData.entrySet()) {
if (first) { first = false; }
else { out.write("&"); }
out.write(URLEncoder.encode(entry.getKey(), "UTF-8");
out.write("=");
out.write(URLEncoder.encode(entry.getValue(), "UTF-8");
}
}

Java 9 HttpClient:

// Build a client:
HttpClient client = HttpClient.newBuilder()
.fllowRedirects(HttpClient.Redirect.ALWAYS)
.build(); // Build a request:
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://horstmann.com"))
.GET()
.build(); // Get and handle response:
HttpResponse<String> reponse = client.send(request, HttpResponse.BodyHandlers.ofString()); // Asynchronous processing:
Client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.completeOnTimeout("<html></html>", 10, TimeUnit.SECONDS)
.thenAccept(response -> Process response.body());

2.7 正则表达式

2.7.1 基本语法

Regualr expressions (regex) specify string patterns.

  • The regex [Jj]e?a.+ matches Java and jealous but not Jim or ja
  • Special characters . * + ? { | ( ) [ \ ^ $
  • . matches any character, * is 0 or more, + 1 or more, ? 0 or 1 repetition
  • Use braces for other multiplicities such as {2, 4}
  • | denotes alternatives: (Java|Scala)
  • () are used for grouping
  • [...] delimit character classes, such as [A-Za-z]
  • Useful predefined character classes such as \s (space), \pL (Unicode letters), completements(补集,即与前面相反) \S, \PL
  • ^ and $ match the beginning and end of input
  • Escape special character with \ to match them literally
  • Caution: Must double-escape \ in Java strings

Two principal ways to use a regex:

  • 应用一:Find all matches within a string;
  • 应用二:Find whether the entire string matches

应用一:This loop iterates over all matches of a regex in a string:

1 Pattern pattern = Pattern.compile(regexString);
2 Matcher matcher = pattern.matcher(input);
3 while (matcher.find()) {
4 String match = matcher.group();
5 ...
6 }

    Use matcher.start(), matcher.end() to get the position of the current match in the string.

应用二:Use the matches method to check wheter a string matches a regex:

1 String regex = "[12]?[0-9]:[0-5][0-9][ap]m";
2 if (Pattern.matches(regex, input)) { ... }

Compile the regex if you need it repeatedly:

1 Pattern pattern = Pattern.compile(regex);
2 Matcher matcher = patter.matcher(input);
3 if (matcher.matches()) ...

Can turn the pattern into a predicate:

1 Stream<String> result = streamOfStrings.filter(pattern.asPredicate());

Use groups to match subexpressions. Group index values start with 1.

// Example: Match records such as: Blackwell Toaster USD29.95

// 1. Regex with groups:
// step1: notes: \p{Alnum} 是预定义字符类,等同于 [A-Za-z0-9]
(\p{Alnum}+(\s+\p{Alnum}+)*)\s+([A-Z]{3})([0-9.]*) // step2: Use the group method to get at each group"
Matcher matcher = pattern.matcher(input);
if (matcher.matches()) {
item = matcher.group(1); // Blackwell Toaster
currency = matcher.group(3);  // USD
price = matcher.group(4);    // 29.95
} // 2. Clearer with named groups:
(?<item>\p{Alnum}+(\s+\p{Alnum}+)*)\s+(?<currency>[A-Z]{3})(?<price>[0-9.]*)
// then you retrive items by name:
item = matcher.group("item");

2.7.4 分隔符分割

1 // Specify the delimiter as a regex:
2 Pattern commas = Pattern.compile("\\s*,\\s*");
3 String[] tokens = commas.split(input); // String "1, 2, 3" truns into array ["1", "2", "3"]
4
5 // Fetch result lazily for large inputs:
6 Stream<String> tokens = commas.splitAsStream(input);
7
8 // If you don't care about efficiency, just use the String.split method:
9 String[] tokens = input.split("\\s*,\\s*");

2.7.5 替换匹配

// To replace all matches, can replaceAll on the matcher
Matcher matcher = commas.matcher(input);
String result = matcher.replaceAll(", "); // If you don't care about efficiency, just use the String.replaceAll method:
String result = input.replaceAll("\s*,\s*", ", "); // Group numbers $n or names $name are replaced with the captured group:
String result = "3:45".replaceAll(
"(\\d{1,2}):(?<minutes>\\d{2})",
"$1 hours and ${minutes} minutes");

Java 9/10 关于 正则表达式的改进:

1) Matcher.stream and Scanner.findAll gets a stream of match results:

1 Pattern pattern = Pattern.compile("[^,]");
2 Stream<String> matches = pattern.match(str).results().map(MatchResult::group);
3
4 matches = new Scanner(path).findAll(pattern).map(MatchResult::group);

2) Matcher.replaceFirst / replaceAll now have a version with a replacement function:

1 String result = Pattern.compile("\\pL{4,}")
2 .matcher("Mary had a little lamb)
3 .replaceAll(m -> m.group().toUpperCase());
4 // yields "MARY had a LITTLE LAMB"

2.3 序列化

Serialization turns an object into a sequence of bytes. Deserailization is the opposite: turning a sequence of bytes into an object.

Useful for sending objects to a different computer and short-term storage (e.g. cache). Not intended for long-term storage.

Participating classes implement the serializable marker interface:

1 public class Employee implements Serializable { ... }
// 1. 输出流
// 1.1 Construct an ObjectOutputStream object:
ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(path)); // 1.2 Call the writeObject method:
Employee peter = new Employee("Peter", 90000);
Employee paul = new Manager("Paul", 180000);
out.writeObject(peter);
out.writeObject(paul); // 2. 输入流
// 2.1 Construct an ObjectInputStream object:
ObjectInputStream in = new ObjectInputStream(Files.newInputStream(path)); // 对于 Employee 类,其包含字符串和浮点数,这些都是可串行化的 // 2.2 Retrieve the objects in the same order as they were saved:
Employee e1 = (Employee) in.readObject();
Employee e2 = (Employee) in.readObject();

使用 writeObject 方法写这些对象,要想正常工作,需要满足两个条件:

  • 1. 这个类需要实现 Serializable 接口;
  • 2. 这个类的所有实例变量也必须是可串行化的;
// Consider this network of objects, 一个对象被多个对象共享时: 需要保存这样的对象网络
Employee peter = new Employee("Peter", 40000);
Manager paul = new Manager("Paul", 105000);
Manager mary = new Manager("Mary", 180000);
paul.setAdmin(peter);
mary.setAdmin(peter);
ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(path));
out.writeObject(peter);
out.writeObject(paul);
out.writeObject(mary); // When reading the managers back in, both of them need to have some admin.
// Each object gets a serial number.
// Each object reference is saved with that serial number.
// When reloading object references, they are matched up with the deserialized object with that serial number.

上面备注的英文的完整的中文解释是:

对象序列化的算法是:

1)保存时:

  • 对遇到的每一个对象都关联一个序列化(serial number);
  • 对于每一个对象,当第一次遇到时,保存其对象数据到输出流中;
  • 如某个对象之前被保存过,只写出“与之前保存过的序列号为 x 的对象相同”

2)读出时:

  • 对于对象输入流中的对象,在第一次遇到其序列号时,构建它,并使用流中数据来初始化它,然后记录这个顺序号和新对象之间的关联;
  • 当遇到“与之前保存过的序列号为 x 的对象相同”这一标记,获取与这个序列号相关联的对象引用;

Declare fields that shouldn't be serialized with the transient modifier.

You can take over serialization of fields by implementing the readObject / writeObject methods. (Useful for saving instances of non-serializable classes.)

You can delegate serialization and deserialization to a proxy by implementing the readResolve/writeReplace methods. (Useful in rare cases when object identity needs to be preserved.)

You can declare multiple versions of serializations.

  • Default serialVersionUID is obtained by hashing fields names and types.
  • If the serialVersionUID changes, readObject throws an exception.
  • You can declare your own version ID and implement deserialization to conside multiple versions.  private static final long serialVersionUID = 2L;    // Version 2
  • Complex and raraly useful.

序列化的其他参考: https://www.cnblogs.com/bruce-he/p/17098132.html

最新文章

  1. C#移动跨平台开发(1)环境准备
  2. 关于 escape、encodeURI、encodeURIComponent
  3. HTML 代码复用实践 (静态页面公共部分提取复用)
  4. bzoj1050: [HAOI2006]旅行comf
  5. 自然语言15.1_Part of Speech Tagging 词性标注
  6. Ubuntu 安装BCM 43142无线网卡驱动
  7. Jquery Ashx 存在缓存问题
  8. ASP防止盗链的一段代码
  9. QT下自定义QQ聊天窗口tab控件
  10. BZOJ 1231: [Usaco2008 Nov]mixup2 混乱的奶牛
  11. NSXMLParser自定义的一个xml解析工具
  12. [0] 关于IComparable和IComparer接口和Comparer类
  13. 解决QZ-SDK静态库libRPToolLib.a中avfoundation.o文件和kxMovie依赖的ffmpeg静态库libavdevice.a函数重复定义的问题
  14. oracle数据库使用心得之与SQL serve数据库的差异
  15. [LeetCode] K Inverse Pairs Array K个翻转对数组
  16. 通过命令修改mysql的提示符(转)
  17. AbstractQueuedSynchronizer源码分析(ReentrantLock锁的实现)
  18. win 10 slmgr.vbs -xpr 无法运行,被豆麦笔记打开解决方法
  19. java定义一个二维数组
  20. servlet 的servletContext

热门文章

  1. sqllabs靶场less1-4
  2. LG P3809 【模板】后缀排序
  3. T-SQL——将字符串转为单列
  4. Vulhub 漏洞学习之:ElasticSearch
  5. Cobalt Strike 之:会话管理
  6. 代码随想录算法训练营day20 | leetcode ● 654.最大二叉树 ● 617.合并二叉树 ● 700.二叉搜索树中的搜索 ● 98.验证二叉搜索树
  7. JS 获取参数、封装
  8. 在 WXML 中使用 JS 代码
  9. 基于Docker使用CTB生成地形切片并加载
  10. 使用vue 搭建猫眼后台演员列表