Chapter 15 Kotlin File IO Operation and Multithreading
When we use Groovy's file IO operation, it feels very convenient. The same Kotlin also has a useful file IO operation API. Similarly, some practical extensions have been made to the regular expression function of Java in Kotlin. And the multi-threading in Kotlin mainly encapsulates the multi-threading API of Java. Because these Java already has a lot of basic APIs, Kotlin does not repeat the implementation by itself, but expands practical functions on the basis of Java.
In this chapter, we will introduce Kotlin file IO operations, regular expressions, and multithreading related content.
15.1 Introduction to Kotlin IO
Kotlin's IO operations are all under the kotlin.io package. Kotlin's principle is that Java already has it. If it is easy to use, use it directly. If it doesn't or is not easy to use, encapsulate and extend it based on the original class. For example, Kotlin writes an extension function for the File class. This is the same idea as Groovy's extended API.
15.2 Terminal IO
Java's very long output statement System.out.println() has continued to the present! The same work can be done in C++ with a simple cout<<. Of course, if necessary, we can directly encapsulate System.out.println() as a simple printing method in the project.
It's very simple in Kotlin, just use the two global functions println or print. We no longer need a lengthy prefix. Of course, if we are nostalgic and just want to use System.out.println(), Kotlin still supports direct use (seamless interoperability with Java).
>>> System.out.println("K")
K
>>> println("K")
K
The println function here in Kotlin is implemented as follows
@kotlin.internal.InlineOnly
public inline fun println(message: Any?) {
System.out.println(message)
}
Of course, Kotlin is only encapsulated on the basis of System.out.println().
Reading data from the terminal is also very simple. The most basic method is the global function readLine, which reads a line directly from the terminal as a string. If you need further processing, you can use various string processing functions provided by Kotlin to process and convert strings.
Kotlin's class for encapsulating terminal IO is in the source file stdlib/src/kotlin/io/Console.kt.
15.3 File IO operation
Kotlin provides a lot of useful extension functions for java.io.File, these extension functions are mainly in the following three source files:
kotlin/io/files/FileTreeWalk.kt |
---|
kotlin/io/files/Utils.kt |
kotlin / io / FileReadWrite.kt |
At the same time, Kotlin also made simple extensions for InputStream, OutputStream and Reader. They are mainly in the following two source files:
kotlin/io/IOStreams.kt |
---|
kotlin/io/ReadWrite.kt |
Koltin's serialization directly uses the type alias of Java's serialization class:
internal typealias Serializable = java.io.Serializable
Let's briefly introduce the read and write operations of Kotlin files.
15.3.1 Read file
Read the entire contents of the file
If we simply read a file, we can use the readText() method, which directly returns the entire file content. The code example is as follows
/**
* 获取文件全部内容字符串
* @param filename
*/
fun getFileContent(filename: String): String {
val f = File(filename)
return f.readText(Charset.forName("UTF-8"))
}
We directly use the File object to call the readText function to get the entire content of the file, which returns a string. If you specify the character encoding, you can specify it by passing in the parameter Charset, and the default is UTF-8 encoding.
If we want to get the content of each line of the file, we can simply split("\n")
get an array of the content of each line.
Get the content of each line of the file
We can also directly call the readLines function encapsulated by Kotlin to get the content of each line of the file. The readLines function returns a List that holds the content of each line.
/**
* 获取文件每一行内容,存入一个 List 中
* @param filename
*/
fun getFileLines(filename: String): List<String> {
return File(filename).readLines(Charset.forName("UTF-8"))
}
Direct manipulation of byte arrays
If we want to directly manipulate the byte array of the file, we can use readBytes(). If you want to use the traditional Java method, you can also use it as freely as Groovy in Kotlin.
//读取为bytes数组
val bytes: ByteArray = f.readBytes()
println(bytes.joinToString(separator = " "))
//直接像 Java 中的那样处理Reader或InputStream
val reader: Reader = f.reader()
val inputStream: InputStream = f.inputStream()
val bufferedReader: BufferedReader = f.bufferedReader()
}
15.3.2 Write file
Similar to reading files, writing files is also very simple. We can write strings or byte streams. You can also directly use Java Writer or OutputStream.
Overwrite file
fun writeFile(text: String, destFile: String) {
val f = File(destFile)
if (!f.exists()) {
f.createNewFile()
}
f.writeText(text, Charset.defaultCharset())
}
Write file at the end
fun appendFile(text: String, destFile: String) {
val f = File(destFile)
if (!f.exists()) {
f.createNewFile()
}
f.appendText(text, Charset.defaultCharset())
}
15.4 Traverse the file tree
Like Groovy, Kotlin also provides convenient functions to traverse the file tree. To traverse the file tree, you need to call the extension method walk(). It will return a FileTreeWalk object, which has some methods for setting the traversal direction and depth. For details, see FileTreeWalk API documentation.
Tip: FileTreeWalk API document link https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.io/-file-tree-walk/
The following example traverses all files in the specified folder.
fun traverseFileTree(filename: String) {
val f = File(filename)
val fileTreeWalk = f.walk()
fileTreeWalk.iterator().forEach { println(it.absolutePath) }
}
Test code:
@Test fun testTraverseFileTree() {
KFileUtil.traverseFileTree(".")
}
Run the above test code, it will output all subdirectories and their files in the current directory.
We can also traverse all sub-directory files under the current file and store them in an Iterator
fun getFileIterator(filename: String): Iterator<File> {
val f = File(filename)
val fileTreeWalk = f.walk()
return fileTreeWalk.iterator()
}
We traverse all sub-directory files under the current file, we can also filter according to conditions, and store the results in a Sequence
fun getFileSequenceBy(filename: String, p: (File) -> Boolean): Sequence<File> {
val f = File(filename)
return f.walk().filter(p)
}
Test code:
@Test fun testGetFileSequenceBy() {
val fileSequence1 = KFileUtil.getFileSequenceBy(".", {
it.isDirectory
})
fileSequence1.forEach { println("fileSequence1: ${it.absoluteFile} ") }
val fileSequence2 = KFileUtil.getFileSequenceBy(".", {
it.isFile
})
fileSequence2.forEach { println("fileSequence2: ${it.absoluteFile} ") }
val fileSequence3 = KFileUtil.getFileSequenceBy(".", {
it.extension == "kt"
})
fileSequence3.forEach { println("fileSequence3: ${it.absoluteFile} ") }
}
Run the above test code in the project, it will have output similar to the following:
...
...
fileSequence3: /Users/jack/kotlin/chapter15_file_io/./src/main/kotlin/com/easy/kotlin/fileio/KFileUtil.kt
fileSequence3: /Users/jack/kotlin/chapter15_file_io/./src/main/kotlin/com/easy/kotlin/fileio/KNetUtil.kt
fileSequence3: /Users/jack/kotlin/chapter15_file_io/./src/main/kotlin/com/easy/kotlin/fileio/KShellUtil.kt
fileSequence3: /Users/jack/kotlin/chapter15_file_io/./src/test/kotlin/com/easy/kotlin/fileio/KFileUtilTest.kt
15.5 Network IO operation
Kotlin adds two extension methods to java.net.URL, readBytes and readText. We can easily use these two methods with regular expressions to realize the function of web crawlers.
Below we simply write a few function examples.
Get the response HTML function of the url according to the url
fun getUrlContent(url: String): String {
return URL(url).readText(Charset.defaultCharset())
}
Get the url response bit array function according to the url
fun getUrlBytes(url: String): ByteArray {
return URL(url).readBytes()
}
Write url response byte array to file
fun writeUrlBytesTo(filename: String, url: String) {
val bytes = URL(url).readBytes()
File(filename).writeBytes(bytes)
}
The following example simply obtains the source code of Baidu's homepage.
getUrlContent("https://www.baidu.com")
The following example obtains the bit stream of a picture according to the url, and then calls the readBytes() method to read the byte stream and write it to the file.
writeUrlBytesTo("图片.jpg", "http://n.sinaimg.cn/default/4_img/uplaod/3933d981/20170622/2fIE-fyhfxph6601959.jpg")
We can see the downloaded "picture.jpg" in the corresponding folder of the project.
15.6 kotlin.io standard library
Kotlin's io library is mainly an io library that extends Java. Below we briefly give a few examples.
appendBytes
Append byte array to the file
Method signature:
fun File.appendBytes(array: ByteArray)
appendText
Append text to the file
Method signature:
fun File.appendText(
text: String,
charset: Charset = Charsets.UTF_8)
bufferedReader
Get the BufferedReader of the file
Method signature:
fun File.bufferedReader(
charset: Charset = Charsets.UTF_8,
bufferSize: Int = DEFAULT_BUFFER_SIZE
): BufferedReader
bufferedWriter
Get the BufferedWriter of the file
Method signature:
fun File.bufferedWriter(
charset: Charset = Charsets.UTF_8,
bufferSize: Int = DEFAULT_BUFFER_SIZE
): BufferedWriter
copyRecursively
Copy the file or recursively copy the directory and all its sub-files to the specified path. If the file in the specified path does not exist, it will be created automatically.
Method signature:
fun File.copyRecursively(
target: File,
overwrite: Boolean = false, // 是否覆盖。true:覆盖之前先删除原来的文件
onError: (File, IOException) -> OnErrorAction = { _, exception -> throw exception }
): Boolean
Tip: Kotlin's API documentation for File extension functions https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.io/java.io.-file/index.html |
---|
About kotlin.io the following API documentation is here https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.io/index.html |
15.7 Execute Shell command line
We use Groovy's file IO operation feels very easy to use, for example
package com.easy.kotlin
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@RunWith(JUnit4)
class ShellExecuteDemoTest {
@Test
def void testShellExecute() {
def p = "ls -R".execute()
def output = p.inputStream.text
println(output)
def fname = "我图.url"
def f = new File(fname)
def lines = f.readLines()
lines.forEach({
println(it)
})
println(f.text)
}
}
File IO and network IO operations in Kotlin are as simple as Groovy.
In addition, from the above code, we can see that it is very simple to execute terminal commands with Groovy:
def p = "ls -R".execute()
def output = p.inputStream.text
In Kotlin, currently there is no such function to extend the String class and Process. In fact, it is very simple to extend such a function. We can fully expand by ourselves.
First, let's extend the execute() function of String.
fun String.execute(): Process {
val runtime = Runtime.getRuntime()
return runtime.exec(this)
}
Then, let's extend a text function to the Process class.
fun Process.text(): String {
var output = ""
// 输出 Shell 执行的结果
val inputStream = this.inputStream
val isr = InputStreamReader(inputStream)
val reader = BufferedReader(isr)
var line: String? = ""
while (line != null) {
line = reader.readLine()
output += line + "\n"
}
return output
}
After completing the above two simple extension functions, we can execute terminal commands like Groovy in the following test code:
val p = "ls -al".execute()
val exitCode = p.waitFor()
val text = p.text()
println(exitCode)
println(text)
In fact, through the study of many previous examples, we can see that Kotlin's extension functions are quite practical. The Kotlin language API also makes extensive use of extended functions.
15.8 Regular expressions
In addition to the Pattern, Matcher and other classes in Java that we can still use in Kotlin, Kotlin also provides a regular expression class kotlin/text/regex/Regex.kt, we create a regular expression through the Regex constructor .
15.8.1 Constructing Regex Expressions
Use Regex constructor
>>> val r1 = Regex("[a-z]+")
>>> val r2 = Regex("[a-z]+", RegexOption.IGNORE_CASE)
The matching option RegexOption is a regular matching option in the Java class Pattern that is used directly.
Use String's toRegex extension function
>>> val r3 = "[A-Z]+".toRegex()
15.8.2 Regex function
Regex provides a wealth of simple and practical functions, as shown in the following table
Function name | Function Description |
---|---|
matches(input: CharSequence): Boolean | All input strings match |
containsMatchIn(input: CharSequence): Boolean | At least one match of the input string |
matchEntire(input: CharSequence): MatchResult? | All input strings are matched, and a matching result object is returned |
replace(input: CharSequence, replacement: String): String | Replace the matched part of the input string with the content of replacement |
replace(input: CharSequence, transform: (MatchResult) -> CharSequence): String | Replace the matched value in the input string with the new value after the function transform |
find(input: CharSequence, startIndex: Int = 0): MatchResult? | Returns the first matching value in the input string |
findAll(input: CharSequence, startIndex: Int = 0): Sequence | Returns a sequence of MatchResult of all matching values in the input string |
Below we give simple examples of the above functions.
matches
If the input string matches the regular expression, it returns true, otherwise it returns false.
>>> val r1 = Regex("[a-z]+")
>>> r1.matches("ABCzxc")
false
>>>
>>> val r2 = Regex("[a-z]+", RegexOption.IGNORE_CASE)
>>> r2.matches("ABCzxc")
true
>>> val r3 = "[A-Z]+".toRegex()
>>> r3.matches("GGMM")
true
containsMatchIn
Return true if there is at least one match in the input string, and false if there is no match.
>>> val re = Regex("[0-9]+")
>>> re.containsMatchIn("012Abc")
true
>>> re.containsMatchIn("Abc")
false
matchEntire
The input string matches the regular expression and returns a MatcherMatchResult object, otherwise it returns null.
>>> val re = Regex("[0-9]+")
>>> re.matchEntire("1234567890")
kotlin.text.MatcherMatchResult@34d713a2
>>> re.matchEntire("1234567890!")
null
We can access the value of MatcherMatchResult to get the matched value.
>>> re.matchEntire("1234567890")?.value
1234567890
Since the return of the matchEntire function is a MatchResult? nullable object, here we use the safe call symbol ?.
.
replace(input: CharSequence, replacement: String): String
Replace the matched part of the input string with the content of replacement.
>>> val re = Regex("[0-9]+")
>>> re.replace("12345XYZ","abcd")
abcdXYZ
We can see that, "12345XYZ" in 12345
a match the regular expression [0-9]+
content, it is replaced abcd
.
replace(input: CharSequence, transform: (MatchResult) -> CharSequence): String
Replace the matched value in the input string with the new value after the function transform.
>>> val re = Regex("[0-9]+")
>>> re.replace("9XYZ8", { (it.value.toInt() * it.value.toInt()).toString() })
81XYZ64
We can see that 9XYZ8
the number 8 and 9 are regular expression matching [0-9]+
contents, which are respectively transform function maps (it.value.toInt() * it.value.toInt()).toString()
the new values of 81 and 64 replaced.
find
Returns the first matching MatcherMatchResult object in the input string.
>>> val re = Regex("[0-9]+")
>>> re.find("123XYZ987abcd7777")
kotlin.text.MatcherMatchResult@4d4436d0
>>> re.find("123XYZ987abcd7777")?.value
123
findAll
Returns a sequence of MatchResult of all matching values in the input string.
>>> val re = Regex("[0-9]+")
>>> re.findAll("123XYZ987abcd7777")
kotlin.sequences.GeneratorSequence@f245bdd
We can loop through forEach all matched values
>>> re.findAll("123XYZ987abcd7777").forEach{println(it.value)}
123
987
7777
15.8.3 Using Java regular expression classes
In addition to the functions provided by Kotlin above, we can still use Java's regular expression API in Kotlin.
val re = Regex("[0-9]+")
val p = re.toPattern()
val m = p.matcher("888ABC999")
while (m.find()) {
val d = m.group()
println(d)
}
The output of the code above:
888
999
15.9 Kotlin's multithreading
There is no synchronized keyword in Kotlin.
There is no volatile keyword in Kotlin.
Kotlin's Any is similar to Java's Object, but there is no wait(), notify() and notifyAll() methods.
So how does concurrency work in Kotlin? Don't worry, since Kotlin is standing on the shoulders of Java, of course the support for multi-threaded programming is indispensable-Kotlin simplifies our coding by encapsulating thread classes in Java. At the same time, we can also use some specific annotations, directly use the synchronization keywords in Java, etc. Below we briefly introduce the related content of multi-threaded programming using Kotlin.
15.9.1 Create Thread
We usually have two ways to create threads in Java:
- Extend the Thread class
- Or instantiate it and pass a Runnable through the constructor
Because we can easily use Java classes in Kotlin, both methods can be used.
Create using object expressions
object : Thread() {
override fun run() {
Thread.sleep(3000)
println("A 使用 Thread 对象表达式: ${Thread.currentThread()}")
}
}.start()
This code uses Kotlin's object expressions to create an anonymous class and override the run() method.
Use Lambda expressions
Here is how to pass a Runnable to a newly created Thread instance:
Thread({
Thread.sleep(2000)
println("B 使用 Lambda 表达式: ${Thread.currentThread()}")
}).start()
We don't see Runnable here, it is very convenient to directly use the above Lambda expression to express in Kotlin.
Is there an easier way? Look at the explanation below.
Use Kotlin encapsulated thread function
For example, we wrote the following piece of thread code
val t = Thread({
Thread.sleep(2000)
println("C 使用 Lambda 表达式:${Thread.currentThread()}")
})
t.isDaemon = false
t.name = "CThread"
t.priority = 3
t.start()
The next four lines can be said to be boilerplate code. In Kotlin, such an operation package is simplified.
thread(start = true, isDaemon = false, name = "DThread", priority = 3) {
Thread.sleep(1000)
println("D 使用 Kotlin 封装的函数 thread(): ${Thread.currentThread()}")
}
This code is more streamlined and cleaner. In fact, the thread() function abstracts and encapsulates the boilerplate code that is often used in our programming practice. Its implementation is as follows:
public fun thread(start: Boolean = true, isDaemon: Boolean = false, contextClassLoader: ClassLoader? = null, name: String? = null, priority: Int = -1, block: () -> Unit): Thread {
val thread = object : Thread() {
public override fun run() {
block()
}
}
if (isDaemon)
thread.isDaemon = true
if (priority > 0)
thread.priority = priority
if (name != null)
thread.name = name
if (contextClassLoader != null)
thread.contextClassLoader = contextClassLoader
if (start)
thread.start()
return thread
}
This is just a very convenient wrapper function, simple and practical. From the above example, we can see that Kotlin simplifies the boilerplate code by extending Java's threading API.
15.9.2 Synchronization methods and blocks
synchronized is not a keyword in Kotlin, it is replaced by the @Synchronized annotation. The declaration of the synchronization method in Kotlin will look like this:
@Synchronized fun appendFile(text: String, destFile: String) {
val f = File(destFile)
if (!f.exists()) {
f.createNewFile()
}
f.appendText(text, Charset.defaultCharset())
}
The @Synchronized annotation has the same effect as synchronized in Java: it marks the JVM method as synchronized. For the synchronized block, we use the synchronized() function, which uses the lock as a parameter:
fun appendFileSync(text: String, destFile: String) {
val f = File(destFile)
if (!f.exists()) {
f.createNewFile()
}
synchronized(this){
f.appendText(text, Charset.defaultCharset())
}
}
Basically the same as Java.
15.9.3 Variable fields
Similarly, Kotlin does not have the volatile keyword, but has the @Volatile annotation.
@Volatile private var running = false
fun start() {
running = true
thread(start = true) {
while (running) {
println("Still running: ${Thread.currentThread()}")
}
}
}
fun stop() {
running = false
println("Stopped: ${Thread.currentThread()}")
}
@Volatile will mark the JVM backup field as volatile.
Of course, in Kotlin we have a more useful coroutine concurrency library. In the practice of code engineering, we can freely choose according to the actual situation.
chapter summary
Kotlin is a language with strong engineering and practicality. From the file IO, regular expressions, and multithreading introduced in this chapter, we can understand the basic principle of Kotlin: make full use of the existing Java ecosystem, based on this A simpler and more practical extension will greatly improve the productivity of programmers. From this, we also realized the minimalist concept in Kotlin programming-continuous abstraction, encapsulation, and expansion to make it more simple and practical.
Sample code for this chapter: https://github.com/EasyKotlin/chapter15_file_io
In addition, the author integrated the content of this chapter and wrote a simple image crawler web application using SpringBoot + Kotlin. Interested readers can refer to the source code: https://github.com/EasyKotlin/chatper15_net_io_img_crawler
In the next chapter, and our last chapter, let us break away from the JVM and directly use Kotlin Native to develop a Kotlin application that is directly compiled into machine code and run.
Kotlin Developer Community
Focus on sharing Java, Kotlin, Spring/Spring Boot, MySQL, redis, neo4j, NoSQL, Android, JavaScript, React, Node, functional programming, programming ideas, "high availability, high performance, high real-time" large-scale distributed system architecture design theme.
High availability, high performance, high real-time large-scale distributed system architecture design。
Distributed framework: Zookeeper, distributed middleware framework, etc.
Distributed storage: GridFS, FastDFS, TFS, MemCache, redis, etc.
Distributed database: Cobar, tddl, Amoeba, Mycat
cloud computing, big data, AI algorithm
virtualization, cloud native Technology
Distributed computing framework: MapReduce, Hadoop, Storm, Flink, etc.
Distributed communication mechanism: Dubbo, RPC calls, shared remote data, message queues, etc.
Message queue MQ: Kafka, MetaQ, RocketMQ
how to build a highly available system: based on hardware, software Realization of some typical solutions such as middleware and system architecture: HAProxy, Corosync+Pacemaker-based high-availability cluster suite middleware system
Mycat architecture distributed evolution
The problems behind big data Join: contradictions and reconciliation of data, network, memory and computing capabilities
High-performance problems in Java distributed systems: AIO, NIO, Netty or self-developed frameworks?
High-performance event dispatch mechanism: thread pool model, Disruptor model, etc. . .
The embracing wood is born at the end of the mill; the nine-story platform starts from the basement; the journey of a thousand miles begins with a single step. If you don't accumulate steps, you can't reach a thousand miles; if you don't accumulate small streams, you can't become a river.
Introduction to Kotlin
Kotlin is a non-research language. It is a very pragmatic industrial-grade programming language. Its mission is to help programmers solve problems in actual engineering practice. Using Kotlin makes the lives of Java programmers better. The null pointer errors in Java, the lengthy boilerplate code that wastes time, the verbose syntax restrictions, etc., all disappear in Kotlin. Kotlin is simple and pragmatic, with concise and powerful syntax, safe and expressive, and extremely productive.
Java was born in 1995 and has a history of 23 years. The current latest version is Java 9. In the process of continuous development and prosperity of the JVM ecosystem, brother languages such as Scala, Groovy, and Clojure were also born.
Kotlin is also an excellent member of the JVM family. Kotlin is a modern language (version 1.0 was released in February 2016). Its original purpose is to optimize the defects of the Java language like Scala, provide simpler and more practical programming language features, and solve performance problems, such as compilation time. JetBrains has done a great job in these areas.
Features of Kotlin language
After developing in Java for many years, it's great to be able to try something new. If you are a Java developer, Kotlin will be very natural and smooth. If you are a Swift developer, you will feel familiar, such as Nullability. The features of Kotlin language are:
1. Concise
Significantly reduce the amount of boilerplate code.
2. 100% interoperability with Java
Kotlin can directly interact with Java classes and vice versa. This feature allows us to directly reuse our code base and migrate it to Kotlin. Because Java's interoperability is almost everywhere. We can directly access platform APIs and existing code bases, while still enjoying and using all the powerful modern language features of Kotlin.
3. Extension function
Kotlin is similar to C# and Gosu, it provides the ability to provide new functional extensions for existing classes without having to inherit from the class or use any type of design patterns (such as decorator patterns).
4. Functional programming
The Kotlin language first supports functional programming, just like Scala. It has basic functional features such as high-order functions and lambda expressions.
5. Default and named parameters
In Kotlin, you can set a default value for the parameters in a function and give each parameter a name. This helps to write easy-to-read code.
6. Powerful development tool support
And because it is produced by JetBrains, we have great IDE support. Although the automatic conversion from Java to Kotlin is not 100% OK, it is indeed a very good tool. When using IDEA's tools to convert Java code to Kotlin code, 60%-70% of the resulting code can be reused easily, and the cost of modification is small.
In addition to its concise and powerful syntax features, Kotlin also has a very practical API and an ecosystem built around it. For example: collection API, IO extension, reflection API, etc. At the same time, the Kotlin community also provides a wealth of documents and a lot of learning materials, as well as online REPL.
A modern programming language that makes developers happier. Open source forever