Untitled
unknown
plain_text
a year ago
62 kB
8
Indexable
fundamentals.essentials 1. os 1.1. filesandfilesystems 1.1.1. Files 1. Introduction Data is stored on disks under certain addresses. For a computer, such addresses are numbers. It uses them to find the corresponding information. However, humans are not that good at memorizing sets of numbers. It's much easier for us to name our data, for example, family photo, November report, and so on. Therefore, the concept of a file was invented: in a file, one can store some information under a user-friendly name. 2. What is a file A file is associated with a piece of data. However, there are some restrictions on naming a file. For example, filenames must contain only those characters that are supported by a specific file system. There are different types of contents of the files: text, photo, music, video, etc. The type of information stored in a file defines the file format. In order for the computer to be able to distinguish what format a certain file has, file extensions were invented. 3. File extensions Finding out the format of a file right away is pretty useful. One way to do it involves filename extensions. The end of a filename informs users or programs about the file format. The file format designation usually goes after a period, so you get a name ending with ".<extension>". As for more specific examples, here are some of the most common extensions: text files will have the .txt extension, for example, november_report.txt; files with photos may have the .jpg extension, like my_photo.jpg; for videos, the most usual is the .mp4 extension and for music files, it is .mp3. Operating systems use filename extensions to remember which program to use to open files with a certain extension. Filename extensions aren’t strictly necessary, though: they just eliminate the need to guess the format of a file. Now you know that a file extension is the service information used by a computer. Let's find out what other information a file has that helps computers understand what properties it has. This information is called file metadata. 4. File metadata Metadata stands for "data about data". One of the most common pieces of such data is the filename as we've discussed above. Other examples are the file size, creation time, the last access time, etc. Also, metadata consists of file attributes. Each attribute has two possible states: set (toggled on) or cleared (toggled off). File attributes tell the file system or operating system if a file should get some special treatment. For example, if a file has a read-only attribute set, its contents can be read, but all attempts to modify them will be prevented by the file system until this attribute gets cleared. If a file has a hidden attribute set, it won’t show up in a graphical user interface unless the user explicitly tells the operating system to show all hidden files. Attributes can also be used to restrict file access to specific groups of users. 5. Absolute and relative paths In order to find a file, we need to know the path to it. The path is a character set indicating the location of a file in the system. The file path can be seen in the file manager (Explorer in Windows). There are two types of paths: an absolute or a relative path. A path to a file that starts with a root directory is called an absolute path and serves as the file’s unique identifier. If you try to create another file named "my_file" in the same subdirectory, the file system won’t allow you to do that. There is a catch with absolute paths though. When you write a program that will be installed on different computers, you know your own program’s directory, but you don’t know where other users of this program will install it. Your program’s directory can end up in any parent directory on a user’s computer, so you can't use an absolute path in your program to point to its directory. This is where relative paths come into play. Each process that runs on a computer is associated with a working directory on this computer; it is tracked and managed by the operating system. This basically means that the operating systems of other users will be focused on your program’s directory when they run it. Your program can address the working directory by using a special character . instead of the directory’s actual name, so you can use a path like "./my_file" without specifying the whole path from a root. You just let the user’s operating system figure it out! You can also use .. to address a parent directory of the working directory. ############################################# 1.2. multithreadingandmultiprocessing 1.2.1. SynchronousAsynchronousParallel 1. Synchronous, asynchronous, parallel When we are considering some complex process, let's call it workflow, various parts of it may run differently. Sometimes actions go one by one, sometimes they go in random order overlapping each other, and sometimes things go simultaneously and in parallel. The workflow can evolve differently. There are three sorts of workflow executions sequence: synchronous, asynchronous, and parallel. 2. Synchronous workflow There are many models to manage customer flows. The simplest approach is one shop with one seller. The seller deals with each client from the beginning to the end of each sale and performs all the roles from storekeeper to cashier. queue: order -> delivery -> payment -> order -> delivery -> payment ... (client1) (client2) salesman #1: order -> delivery -> payment -> order -> delivery -> payment ... When there are many customers at the same time, this approach is very far from perfect as the seller can deal only with one client per time, while others have to wait in line. They serve each client separately one by one which means starting to serve the next client only after finishing with the current. We name this type of action a synchronous one. Synchronous workflows are very common. Most of the activities should go synchronously if their goal is to achieve some specific results. The number of examples is enormous. Scenes in a movie plot, car assembling, words in a sentence, cooking, you name it. 3. Asynchronous workflow Let's imagine our old shop becomes fancier, this is a pizza shop now. After the first client has ordered their pizza, they need to wait for it to be cooked. At this point, the seller leaves the first customer alone for a while, and now the second one can make their order, then the third, and so on. client #1: order -> waiting for pizza -> payment client #2: order -> waiting for pizza -> payment client #3: order -> waiting for pizza -> payment... ... salesman #1: order1 -> P1 -> order2 -> P2 -> order3 -> P3 -> paym.1 -> P4 When the first client's pizza is ready, the seller returns to them to complete the sale. That's how this story will repeat again and again. Our old friend seller can serve several customers simultaneously in overlapping periods. We call such behavior asynchronous. Operations of this kind often emerge when there is a need for waiting. Imagine you are reading on an aircraft while flying, or you do the dishes while something is cooking; those pairs of activities are asynchronous. 4. Parallel processing As the pizza shop sales are growing, now one worker is not enough for the whole business. So, we should hire several. If each seller has a separate compact oven for preparing exactly one pizza at a time, then we can divide the queue of buyers among the sellers. queue #1: order -> delivery -> payment -> order -> delivery -> payment ... (client1) (client2) salesman #1: order -> delivery -> payment -> order -> delivery -> payment ... queue #2: order -> delivery -> payment -> order -> delivery -> payment ... (client3) (client4) salesman #2: order -> delivery -> payment -> order -> delivery -> payment ... ... ... queue #n: order -> delivery -> payment -> order -> delivery -> payment ... (client m) (client m+1) salesman #n: order -> delivery -> payment -> order -> delivery -> payment ... Now each of them works independently, and this is a case of parallel processing. Each task in parallel processing is running in a continuous period as a whole unit process. Parallel execution is possible only if there is more than one executor. Cashiers in a supermarket are an example of parallel processing in everyday life, as well as highways. ################################## 1.2.2. ProcessesAndThreads 1. Process The process is the self-contained unit of execution that has all the things necessary to accomplish the mission. In short, the process is the container for its threads, all necessities for their work, and their shared resources. It’s cheaper to arrange access to shared resources once than to do it every time you spawn a new thread. The process has to have at least one thread as they do all the work. There is no such thing as a thread without its process or a process without at least one thread. If we look at the pizza business, a single pizza shop would serve as an analogy for the process. It has all the environment and equipment required for a worker to do the job. Equipment is expensive, so it's cheaper and more efficient when workers share it. There is no need for each worker to acquire personal equipment. On the other hand, the shop can't do anything without the workers. It is essential to have at least one worker because without them all the equipment would be useless. Altogether, these things form a process of making and selling pizza. 2. Thread In computer science, the thread of execution is a stream of instructions inside a process that can be scheduled and run independently. Each thread has its executor, and this executor can perform only one thread at a time. Several threads inside the same process can run concurrently or in parallel. To understand what the term thread means, think about employees in a pizzeria. They work according to their job descriptions. They complete various tasks according to the rules stated by the shop using shared resources granted by the shop. Workers in a pizzeria play the role of thread executors. Tasks that workers accomplish are the threads in the pizza shop "process". 3. Internal or lightweight concurrency Workers can play different roles during the process of selling pizza. Each of them can concurrently be a vendor, a cashier, or a cook at different points of the process. This concurrency is not among the workers but among the roles each worker plays. An important thing about these roles is that their tasks are typically fast enough and don't require a considerable amount of time and shared resources. They are lightweight. If tasks are lightweight and don't require access to any shared resources except the executor's time and attention, there's no need to run them in different threads. It's cheaper to arrange their concurrent execution with time-slicing inside one thread. The concurrency of this sort is called internal for obvious reasons. Often it is also called lightweight because the tasks inside such a thread are typically small and quick. ################################### 2. softwareconstruction.softwarequality.Documentation 1. Introduction Imagine you've just finished your program and are ready to share it with users and fellow programmers. While working on it, you probably mostly focused on writing code, so a machine would understand what needs to be done and the final program would be executed correctly. However, how will other people know the way your program works, what it can and cannot do, what tasks it's supposed to solve? Most importantly, will you yourself be able to answer all these questions after some time passes? One way to make sure you and others can make use of the program regardless of time and place is to document your code. Let's find out what ways of documenting code are there. 2. Strategies for documenting code Documentation is any kind of written or illustrated information that accompanies the source code or is embedded into it and aims at explaining how the code works or how to use it depending on whom it's written for (users or developers). Let's look at some ways to document your code: - writing explanatory comments (and other types of annotation provided by a particular language) to help understand what a particular piece of code is for (e.g. "This function takes the name of a book, its author and prints a list of stores in the area close to you where you can buy it right now".); - providing images, block schemes, diagrams, and other illustrative data that graphically shows what's going on (e.g. a diagram that shows the implemented neural network architecture); - writing README file containing all the necessary information about code utilization (e.g. README to Kotlin); - providing additional information in the form of PDF or HTML files, guides, handbooks, etc. - creating a wiki for managing your documentation (e. g. wiki for Python); - writing release notes/changelogs (e.g. Slack release notes) To get started and get more information on this, check this beginner's guide to writing documentation from, as stated, "a global community of people who care about documentation". 3. README README is usually a plain text file (if it's called README.md though, it's using markdown markup like the provided example) which serves as an introduction to a project explaining what it does and why. There is no strict content policy for a README file, which, to some extent, will also depend on whom you're writing it for (users or developers). However, you may notice some commonly-used sections like: - Name - Description - Installation - Usage - FAQ - Support - Contributing - Contact Information - License - Acknowledgment Section names are quite self-explanatory, but you can read more instructions on how to write README files. When the file is ready, put it in the same directory as your project. 4. Wiki Wiki is basically a website that can be edited by multiple users, so it's especially convenient for team projects. Writing a whole wiki is not gonna be easy, so you'll need some tools and recommendations on how to do it. One of the most popular software used for this type of work is MediaWiki. It's a free and open documentation platform that helps you organize the information about your project in a customizable way. Obviously, this format of documentation gives you much more freedom in design and functionality than a simple README file, so you can create a complete and clickable table of contents, add media files, embed code snippets with correct indentation, etc. 5. Release notes/changelogs Release notes and changelogs are specific types of documentation used for informing and keeping track of updates, so it's particularly useful for version control. When something's been changed in the project, you write a release note to inform users and colleagues how exactly this new version differs from the previous one: what's new, what's been fixed, what's deprecated, etc. By writing release notes you will always know at what moment something went wrong or broke, and ask for feedback. Even if you're not planning on publishing these files, it's good practice just to keep these notes for yourself. 6. Auto-generated documentation There are also external tools that have different functionality that at their core are aimed at helping you with generating documentation for your code. It's important to notice that, for the most part, these tools do not automatically write documentation for you. Since their work is mostly based on the comments embedded in the source code in the first place, they simply convert what's already been written into a more structured and readable form. For example, Doxygen supports many languages and can generate HTML files from source code. Sphinx was originally developed for Python documentation and is ready to help you with building tables, defining document trees, etc. Dr. Explain is a tool for frontend developers, it analyzes the user interface of an application and generates documentation in a fast and user-friendly manner. This list is by no means complete, so you can find something for yourself. 7. Pros and cons Documenting your code one way or another is quite an important skill to master on your journey to becoming a good programmer (which is our goal!). So let's one more time summarize why it's important and what pitfalls might be there: Pros: - helps to refresh your memory - guides you through the code - explains how the code works and how to use it - helps to fix bugs - allows for version control Cons: - takes a lot of time - takes practice to make it structured and clear - easily becomes outdated, so needs to be updated all the time - it's a common practice to write documentation in English which might be a foreign language for you - constant feedback is needed to make it genuinely good ######################################## kotlin 1. addition.files 1.1. ReadingFiles package addition.files import java.io.File import kotlin.io.path.* fun readTextMethod() { var fileName = "D:\\Learning\\Kotlin\\reading.txt" var file = File(fileName) var lines = file.readText() println(lines) // If there is no such file in the file system, then FileNotFoundException is thrown. You can catch and process it // as a usual Kotlin's exception, it's totally up to you. fileName = "D:\\Learning\\Kotlin\\read.txt" file = File(fileName) if (file.exists()) { lines = file.readText() println(lines) } else { println("No such file") } // Actually, our file variable didn't open the file, it just provided a reference with the path to it. What's more, // readText() automatically opened and closed the file! // We can also provide specific charset for reading the file: val line = File("D:\\Learning\\Kotlin\\reading.txt").readText(Charsets.US_ASCII) println(File("D:\\Learning\\Kotlin\\reading.txt").length()) // Just keep it in mind that by default readText() has UTF-8 charset encoding, but you can change it whenever you // want. Also, Kotlin doesn't recommend using this function with files larger than 2 Gb, because this may cause // OutOfMemoryError. // Actually, you can use any method from File, for example, length() or delete(). } fun readLinesMethod() { // Function readLines() provides the functionality of reading all lines from a file and storing them in a List: var fileName = "D:\\Learning\\Kotlin\\reading.txt" val lines = File(fileName).readLines() for (line in lines){ println(line) } // This method has the same size limitations and charset specification as readText(). } fun readBytesMethod() { // Function readBytes() may be helpful if you need to store file contents as an array of bytes: var fileName = "D:\\Learning\\Kotlin\\reading.txt" val lines = File(fileName).readBytes() // Note, this function returns the ByteArray. // It's still not recommended using this option with large files (2 Gb or more). This method is used as an // implementation of readText() function with conversion to String in Kotlin source files. } fun forEachLineMethod() { // forEachLine() is the recommended way of reading large files: var fileName = "D:\\Learning\\Kotlin\\reading.txt" val lines = File(fileName).forEachLine { println(it) } // This lambda reading approach provides an action (println() in our case) for each line. // It's always a possibility that the file you're going to read has already been opened in another process, or it // might have access restrictions. In such cases, AccessDeniedException is thrown. } fun fileSeparator() { val separator = File.separator val src = "D:\\Learning\\Kotlin" val fileName = "${src}${separator}reading.txt" val lines = File(fileName).readLines() lines.forEach { println(it) } } fun usingPath() { // Kotlin also offer Path object to work with files. You can use this object by importing import kotlin.io.path.*. // This object offers us various functions to handle directories and files. Including reading and writing. val textFile = Path("D:\\Learning\\Kotlin\\reading.txt") // path to the file println(textFile.exists()) // check if the file exists val text = textFile.readText() // read the text from the file as a string val lines = textFile.readLines() // read the lines from the file as a collection text.lines().forEach { println(it) } val bytesFile = Path("D:\\Learning\\Kotlin\\reading.txt") // path to the file val bytes = bytesFile.readBytes() // read the bytes from the file as a byte array bytes.forEach { println(it) } // read the bytes from the file as a sequence } fun main() { usingPath() } ######################################### 1.2. WritingFiles package addition.files import java.io.File import kotlin.io.path.* fun writingFiles() { // Suppose we want to create a text file called MyFile with the following text: //"It is awfully hard work doing nothing!" // To do that, we need to create a File(pathName: String) object, and use the writeText(text: String) function which // sets the text content of a file: var myFile = File("D:\\Learning\\Kotlin\\writing.txt") myFile.writeText("It is awfully hard work doing nothing!") // Notice, if you just create a File object, you will not create a file, it's just an object. // Suppose we didn't write the file in the current directory but elsewhere. In that case, simply indicate the path // to it like this: // myFile = File("D:\\Learning\\Kotlin\\KotlinCore\\writing.txt") // myFile.writeText("It is awfully hard work doing nothing!") // If you run the code, you can see that our file with the specified text was created on the corresponding disk // following this path. // If the file has access restrictions or has already been opened in another process, AccessDeniedException is thrown. // Also, you can determine the current directory with System.getProperty ("user.dir"): val workingDirectory = System.getProperty("user.dir") // D:\Learning\Kotlin\KotlinCore // Last but not least, let’s talk about code style: the file name should be put into a separate string variable. You // can also put the text into a variable. Take a look: val fileName = "myFile.txt" val textToFile = "If we learn to process our code carefully, we’ll grow as programmers." File(fileName).writeText(textToFile) } fun formattingAndProcessing() { // Sometimes we need to apply formatting to improve the layout of the text. For example, we may need to move the // text to a new line. In this case, the \n comes in handy: val fileName = "myFile.txt" File(fileName).writeText("Just \nlook\n at\n that!") // If you need to make an indented paragraph, use \t: File(fileName).writeText("\tThere’s a tab") } fun overwritingAndAppending() { val fileName = "D:\\Learning\\Kotlin\\writing.txt" File(fileName).writeText("Beware of bugs in the above code; I have only proved it correct, not tried it") // You can open the file and make sure it worked! //Logically, if the specified file did not exist on this path, it will be created. //Okay, and what if you wanted to retain the current content of the file and add something else to it? Then we’ll // need the appendText(text: String) function: File(fileName).appendText(", Donald E. Knuth said.") } fun writingByteArrays() { // writeBytes(array: ByteArray) – with this function, we can write an array of bytes to this file. val arrayOfBytes = byteArrayOf(1, 2, 3) // create an array // another way: // val arrayOfBytes = mutableListOf<Byte>(1, 2, 3).toByteArray() val fileName = "D:\\Learning\\Kotlin\\writing.txt" File(fileName).writeBytes(arrayOfBytes) } fun usingPaths() { val textFile = Path("/path/to/textFile.txt") // path to the file textFile.writeText("This is some text.") // write text to the file textFile.appendText("This is some text.") // append text to the file val bytesFile = Path("/path/to/bytesFile.txt") // path to the file bytesFile.writeBytes(byteArrayOf(1, 2, 3)) // write bytes to the file bytesFile.appendBytes(byteArrayOf(1, 2, 3)) // append bytes to the file } fun main() { writingByteArrays() } ########################################## 1.3. FileHierarchies package addition.files import java.io.File import java.nio.file.Paths import kotlin.io.path.* /** * You can organize the data you store on a disk by arranging files into directories. A parent directory can include * other directories, also known as subdirectories, resulting in a file hierarchy. For instance, consider the file system * hierarchy in Linux: the root directory / includes all other files and directories, even those stored on different * physical devices. */ fun filesAndDirectories() { // A file is a named data section on a storage medium. It's the fundamental unit of data interaction in operating // systems. // A directory is an entity in a file system that helps organize files. Given that a typical file system contains a // massive number of files, directories assist in organization by grouping them. You can also interact with them as // File entities. // Kotlin offers various methods to work with directories and files. For instance, you might use the java.io library. // - File.isDirectory checks if the File is a directory. // - File.isFile verifies if the File is indeed a file. // - File.exists() checks for the file's existence. // - File.resolve(String) returns a file with the name String in a directory. // - File.resolve(File) returns a file File from a directory. // - File.createNewFile() creates a new file. // - File.mkdir() crafts a new directory. val outDir = File("D:\\Learning\\Kotlin\\Files\\outDir") println(outDir.exists()) // false outDir.mkdir() println(outDir.exists()) // true println(outDir.isDirectory) // true val file = outDir.resolve("newFile.txt") // outDir/newFile.txt println(file.exists()) // false file.createNewFile() println(file.exists()) // true println(file.isFile) // true } fun methodsForIteratingThroughFileHierarchies() { // You can navigate through file hierarchy using the java.io.File methods: // - File.getParentFile() returns an instance of java.io.File that represents the parent directory of a file, or // null if the file doesn't have a parent (indicating it is the root). // - File.getParent() returns a string representation of the parent directory of a file, or null if the file doesn't // have a parent. // - File.listFiles() returns an array of files located in a given directory, or null if the instance is not a directory. // - File.list() returns an array of files and directories in the directory specified by this abstract pathname. // The kotlin.io provides special methods that enable you to go through the entire file hierarchy. // - File.walk(direction): FileTreeWalk provides the files and directories in this directory that you can visit; you // need to specify the way you'll iterate (up or down the hierarchy structure). // - File.walkBottomUp(): FileTreeWalk offers the directories and files in this directory that you can visit. It // employs a depth-first search, visiting directories after all their files have been visited. // - File.walkTopDown(): FileTreeWalk offers the directories and files in this directory for you to visit. A depth-first // search is used, and the directories are visited before all their files. // The FileTreeWalk class is designed to iterate through a given file system. It lets you traverse all the files within // a directory. The iterator method returns an iterator that traverses the files. You can iterate over this structure // or convert it to a list with the toList() function. val dir = File("D:\\Learning\\Kotlin\\Files") dir.walk().forEach { println(it) } // Output: // D:\Learning\Kotlin\Files // D:\Learning\Kotlin\Files\outDir // D:\Learning\Kotlin\Files\outDir\newFile.txt // D:\Learning\Kotlin\Files\reading.txt // D:\Learning\Kotlin\Files\writing.txt dir.walkBottomUp().forEach { println(it) } // Output: // D:\Learning\Kotlin\Files\outDir\newFile.txt // D:\Learning\Kotlin\Files\outDir // D:\Learning\Kotlin\Files\reading.txt // D:\Learning\Kotlin\Files\writing.txt // D:\Learning\Kotlin\Files dir.walk().toList() } @OptIn(ExperimentalPathApi::class) fun path() { // In kotlin Path converts the name sequence specified with the base path string and a number of subpaths additional // names to a Path object of the default filesystem to perform basic operations on files and directories easily. // - Path.listDirectoryEntries(): we can obtain the list of files in a given directory. // - Path.copyTo(): copy a source to a destination. // - Path.moveTo(): move a file to a new destination. // - Path.writeText(): create a new text file. // - Path.readText(): read the full file as string. // - Path.readLines(): read a full file as a collection of lines. // - Path.walk(): for visiting directories recursively in an efficient manner. // - Path.copyToRecursively(): for copying a whole directory and its content to another path. val sourceDirName = "D:\\Learning\\Kotlin\\Files\\sourceDir" val destinationDirName = "D:\\Learning\\Kotlin\\Files\\destinationDir" val copyDirName = "D:\\Learning\\Kotlin\\Files\\copyDir" val sourcePath = Path("$sourceDirName\\file.txt") val destinationPath = Path("$destinationDirName\\file.txt") // List all files in the 'sourceDir' directory val filesInDir = Path(sourceDirName).listDirectoryEntries() println("Files in sourceDir: $filesInDir") // Copy the file from 'sourceDir' to 'destDir' sourcePath.copyTo(destinationPath, overwrite = true) println("File copied from sourceDir to destDir.") // Move the file within 'destDir' to a new file val movedPath = destinationPath.moveTo(Paths.get("$destinationDirName\\movedFile.txt"), overwrite = true) println("File moved to: $movedPath") // Write text to the moved file movedPath.writeText("Hello, world!") println("Text written to file.") // Read the text from the file val text = movedPath.readText() println("Text read from file: $text") // Read the lines from the file as a collection val lines = movedPath.readLines() println("Lines read from file: $lines") // Walk all files and directories within 'destDir' val walkedPaths = Path(destinationDirName).walk().toList() println("Walked paths: $walkedPaths") // Copy 'sourceDir' to 'copyDir' recursively Path(sourceDirName).copyToRecursively( target = Paths.get(copyDirName), overwrite = true, followLinks = true, ) println("Directory copied recursively.") // Output: // Files in sourceDir: [D:\Learning\Kotlin\Files\sourceDir\file.txt] // File copied from sourceDir to destDir. // File moved to: D:\Learning\Kotlin\Files\destinationDir\movedFile.txt // Text written to file. // Text read from file: Hello, world! // Lines read from file: [Hello, world!] // Walked paths: [D:\Learning\Kotlin\Files\destinationDir\movedFile.txt] // Directory copied recursively. } fun main() { path() } ########################################## 1.4. WorksWithFileHierarchies package addition.files import java.io.File import java.nio.file.Files import kotlin.io.path.* fun gettingDirectoryContentsAndParentWithFileAndPath() { // The Files.listFiles() function prints the contents (files and directories) of a chosen directory also you can use // Path.listDirectoryEntries(). val rootFileName = "D:\\Learning\\Kotlin\\Files\\FileHierarchy" val rootFile = File(rootFileName) // with File val helloWorldDirection = File("$rootFileName\\CompletedProjects\\HelloWorld") var helloWorldFileNames = helloWorldDirection.listFiles()?.map { it.name } val reviewsDirection = File("$rootFileName\\CompletedProjects\\HelloWorld\\Reviews.txt") var reviewsFiles = reviewsDirection.listFiles()?.map { it.name }// null val soundtracksDirection = File("$rootFileName\\Music\\SoundTracks") var soundtracksFiles1 = soundtracksDirection.listFiles() // [] // with Path val helloWorldPath = Path("$rootFileName\\CompletedProjects\\HelloWorld") helloWorldFileNames = helloWorldPath.listDirectoryEntries().map { it.name } // [Doc.pdf, Reviews.txt] val reviewPath = Path("$rootFileName\\CompletedProjects\\HelloWorld\\Reviews.txt") reviewsFiles = if (reviewPath.isDirectory()) { reviewPath.listDirectoryEntries().map { it.name } } else { emptyList() // [] } val soundtracksPath = Path("/Files/Music/SoundTracks") val soundtracksFiles2 = if (soundtracksPath.isDirectory()) soundtracksPath.listDirectoryEntries() else emptyList() // [] } @OptIn(ExperimentalPathApi::class) fun iteratingInBothDirections() { // with File val files: File = File("D:\\Learning\\Kotlin\\Files\\FileHierarchy") println("TOP_DOWN: ") files.walk(FileWalkDirection.TOP_DOWN).forEach { println(it) } println("BOTTOM_UP: ") files.walk(FileWalkDirection.BOTTOM_UP).forEach { println(it) } // with Path val dir = Path("D:\\Learning\\Kotlin\\Files\\FileHierarchy") println("TOP_DOWN: ") dir.walk().forEach { println(it) } println("BOTTOM_UP: ") dir.walk().asIterable().reversed().forEach { println(it) } //TOP_DOWN: //D:\Learning\Kotlin\Files\FileHierarchy //D:\Learning\Kotlin\Files\FileHierarchy\CompletedProjects //D:\Learning\Kotlin\Files\FileHierarchy\CompletedProjects\HelloWorld //D:\Learning\Kotlin\Files\FileHierarchy\CompletedProjects\HelloWorld\Doc.pdf //D:\Learning\Kotlin\Files\FileHierarchy\CompletedProjects\HelloWorld\Reviews.txt //D:\Learning\Kotlin\Files\FileHierarchy\CompletedProjects\JCalculator //D:\Learning\Kotlin\Files\FileHierarchy\CompletedProjects\JCalculator\Doc.pdf //D:\Learning\Kotlin\Files\FileHierarchy\Music //D:\Learning\Kotlin\Files\FileHierarchy\Music\SoundTracks //BOTTOM_UP: //D:\Learning\Kotlin\Files\FileHierarchy\CompletedProjects\HelloWorld\Doc.pdf //D:\Learning\Kotlin\Files\FileHierarchy\CompletedProjects\HelloWorld\Reviews.txt //D:\Learning\Kotlin\Files\FileHierarchy\CompletedProjects\HelloWorld //D:\Learning\Kotlin\Files\FileHierarchy\CompletedProjects\JCalculator\Doc.pdf //D:\Learning\Kotlin\Files\FileHierarchy\CompletedProjects\JCalculator //D:\Learning\Kotlin\Files\FileHierarchy\CompletedProjects //D:\Learning\Kotlin\Files\FileHierarchy\Music\SoundTracks //D:\Learning\Kotlin\Files\FileHierarchy\Music //D:\Learning\Kotlin\Files\FileHierarchy // You can reach the same result using these methods: // - File.walkBottomUp() (same as File.walk(FileWalkDirection.BOTTOM_UP)); // - File.walkTopDown() (same as File.walk(FileWalkDirection.TOP_DOWN)). } fun workingWithHierarchies() { // Assuming we have an instance of java.io.File named completedProjects, which refers to the CompletedProjects // directory. We will now get the two subdirectories containing project data. // with File val completedProjects1: File = File("D:\\Learning\\Kotlin\\Files\\FileHierarchyCompletedProjects") val projects1 = completedProjects1.walk() projects1.maxDepth(1) // HelloWorld and JCalculator // with Path val completedProjects2 = Path("D:\\Learning\\Kotlin\\Files\\FileHierarchyCompletedProjects") val projects2 = Files.walk(completedProjects2, 1) // The specific order of files in the array is not promised. To find the HelloWorld project, we will traverse the // file tree: var helloWorldProject: File? = projects1.first { it.name == "HelloWorld" } // You can also obtain the HelloWorld directory by using the File.listFiles() method: helloWorldProject = completedProjects1.listFiles()?.first { it.name == "HelloWorld" } } @OptIn(ExperimentalPathApi::class) fun fileCopying() { fun copyToWithFile() { val fileIn = File("newFile.txt") val fileOut = File("copyNewFile") fileIn.copyTo(fileOut) // Be aware that if you need to overwrite the file, you should include an overwrite parameter: fileIn.copyTo(fileOut, overwrite = true) } fun copyToWithPath() { val fileIn = Path("newFile.txt") val fileOut = Path("copyNewFile.txt") fileIn.copyTo(fileOut) // Be aware that if you need to overwrite the file, you should include an overwrite parameter: fileIn.copyTo(fileOut, overwrite = true) } fun copyRecursivelyWithFile() { val fileIn = File("outDir") val fileOut = File("newDir") fileIn.copyRecursively(fileOut) fileIn.copyRecursively(fileOut, overwrite = true) } fun copyRecursivelyWithPath() { val pathIn = Path("outDir") val pathOut = Path("newDir") pathIn.copyToRecursively(pathOut, overwrite = true, followLinks = true) // Remember that if you need to overwrite folders and files, you should also introduce an overwrite parameter or // follow the links (only in Path) with followLinks option: } } fun main() { iteratingInBothDirections() } ########################################### 1.5. ZipFile package addition.files import java.io.* import java.util.* import java.util.zip.ZipEntry import java.util.zip.ZipFile fun intro() { // The java.util.zip.ZipFile class in the Java API is meant for reading entries from ZIP files. It acts as an access // point to the compressed data within a ZIP archive, enabling developers to conveniently extract files and // directories while efficiently managing resources. // The ZipFile class works by reading the ZIP file contents, including compressed files and directories. It offers // methods for retrieving metadata about the ZIP file, such as the number of entries, and retrieving the actual data // in ZipEntry object form. /* import java.util.zip.ZipFile; import java.util.Enumeration; import java.util.zip.ZipEntry; public class ZipFileExample { public static void main(String[] args) { try (ZipFile zipFile = new ZipFile("example.zip")) { Enumeration<? extends ZipEntry> entries = zipFile.entries(); while (entries.hasMoreElements()) { ZipEntry entry = entries.nextElement(); System.out.println("Entry Name: " + entry.getName()); } } catch (IOException e) { System.err.println("Error opening ZIP file: " + e.getMessage()); } } } */ // In this snippet, you create a ZipFile object by specifying the ZIP file path. Then, you get an enumeration of the // ZIP file entries and run through them, printing out each entry's name. The try-with-resources statement ensures // that the ZipFile closes properly, even if an exception happens. // The ZipFile class is a vital tool for working with ZIP archives in Java, featuring a straightforward API to // interact with compressed data. } fun openingAndAccessingZipFileInJava() { fun openZipFile(filePath: String) { val zipFile = ZipFile(filePath) // Once you have the ZipFile instance, you can get an enumeration of the ZIP file entries: val entries: Enumeration<*> = zipFile.entries() // Then, you can cycle through the entries using a while loop: while (entries.hasMoreElements()) { val entry = entries.nextElement() as ZipEntry // Process the entry println("Entry name: ${entry.name}") // Now you can extract the entry or read its contents } zipFile.close() } val zipFilePath = "D:\\Learning\\Kotlin\\Files\\example.zip" openZipFile(zipFilePath) } fun readingZipEntryContents() { // Getting input streams for individual ZipEntry objects within a ZIP file is crucial for reading their contents. // The java.util.zip.ZipFile class provides necessary methods for these entries' access. // To read the contents of a ZipEntry, you must first obtain an InputStream for the entry using the getInputStream // method of ZipFile. This allows you to read bytes from the entry just like any other input stream in Java. // In this snippet, you open a ZipFile and retrieve a ZipEntry by its name. Then, you get an InputStream for the // entry and wrap it in a BufferedReader to read the content line by line. // Remember to handle exceptions properly, as working with I/O operations can lead to various errors. Also, ensure // that you close the ZipFile and other I/O resources to prevent resource leaks, which is usually achieved by using // a try-with-resources statement, as shown above. } object ZipEntryReader { @JvmStatic fun main(args: Array<String>) { val zipFilePath = "D:\\Learning\\Kotlin\\Files\\example.zip" val entryName = "reading.txt" // Name of the ZipEntry you want to read try { ZipFile(zipFilePath).use { zipFile -> val entry = zipFile.getEntry(entryName) if (entry != null) { zipFile.getInputStream(entry).use { stream -> BufferedReader(InputStreamReader(stream)).use { reader -> var line: String? while ((reader.readLine().also { line = it }) != null) { println(line) } } } } else { println("Entry not found in the zip file") } } } catch (e: Exception) { // e.printStackTrace() println(e.localizedMessage) println(e.cause) println(e.stackTrace) println(e.suppressed) } } } fun extractingFilesFromZipFile() { // To extract files from a ZipFile to a given directory in Java, follow these steps: val zipFile = ZipFile("D:\\Learning\\Kotlin\\Files\\example.zip") val destDir = File("D:\\Learning\\Kotlin\\Files\\extractingZipFile") if (!destDir.exists()) { destDir.mkdir() } val entries = zipFile.entries() while(entries.hasMoreElements()) { val entry = entries.nextElement() as ZipEntry val entryDestination = File(destDir, entry.name) if (entry.isDirectory) { entryDestination.mkdirs() } else { entryDestination.parentFile.mkdirs() val inputStream = zipFile.getInputStream(entry) val outputStream = FileOutputStream(entryDestination) // IOUtils.copy(inputStream, outputStream) // Apache Commons IO inputStream.close(); outputStream.close(); } } // 1. Open the zip file: Utilize the ZipFile class from the java.util.zip package to open the zip file. // 2. Create Output Directory: Ensure the directory where you want to extract files exists or create it. // 3. Iterate through the entries: Get the ZIP file entries and iterate through them. // 4. Extract each entry: For each entry, create a file on the disk and write the content. // 5. Handle IOException: Use try-catch blocks to handle potential IOExceptions. // 6. Close the zip file: Lastly, remember to close the ZipFile. // Note: Don't forget to properly handle resources to avoid leaks. You can use the try-with-resources statement for // automatic resource management. } fun closingZipFileResources() { val zipFileName = "example.zip" // try-with-resources statement for automatic ZipFile resource management try { ZipFile(zipFileName).use { zipFile -> // Work with the zip file here } } catch (e: IOException) { System.err.println("Error opening zip file: " + e.message) } // No need for finally block to close the ZipFile, it's handled automatically // The ZipFile will automatically close at the end of the try block, even if an exception is thrown, thus ensuring // that system resources are properly released. This approach eliminates the need for a finally block and makes the // code cleaner and more robust. } fun main() { } ############################################# 2. controlflow.exception 2.1. exceptiontype 2.1.1. CustomExceptions package controlflow.exception.exceptiontype fun throwingAnException() { // 1. Throwing an exception without any parameters. This isn't a really useful case because there is no information // about the error here. // throw Exception() // 2. Throwing an exception with a message. // throw Exception("My error message") // 3. Throwing an exception with a message and a cause, which usually is another exception. // throw Exception("My error message", cause = Exception("My cause")) // 4. Throwing an exception with the cause parameter only. // throw Exception(Exception("My cause")) // 5. Throwing a subtype of the exception object. In the following example, we throw NullPointerException with our // custom message. // throw NullPointerException("NPE at Alpha point") } fun creatingCustomExceptions() { // We can create our own exception classes as mere subclasses of the Exception class. In the following example, we // create 2 custom exceptions named LessThanZero and GreaterThanTen. class LessThanZero : Exception("Parameter less than zero") class GreaterThanTen : Exception("Parameter greater than ten") fun myFunction(par: Int) { if (par < 0) throw LessThanZero() else if (par > 10) throw GreaterThanTen() println(par) } myFunction(-3) // Note that a custom exception can be a subclass of any preexistent exception subclass, like in the following example. class MyException: ArithmeticException("My message") // Also, if we want to be able to create subclasses of our custom exception, then it should be defined as open open class MyParentException : ArithmeticException("My parent message") class MyChildException : MyParentException() } fun catchingCustomExceptions() { // Custom exceptions are handled in exactly the same way as normal exceptions. In the following example, 2 custom // exceptions are created and a try-catch statement is used for handling them. class BetweenOneAndFive: Exception("Value between 1 and 5") class BetweenSixAndTen: Exception("Value between 6 and 10") fun myFunction() { val randomInteger = (1..10).shuffled().first() // Get a random integer between 1 and 10 if (randomInteger <= 5) throw BetweenOneAndFive() else throw BetweenSixAndTen() } try { myFunction() } catch (e: BetweenOneAndFive) { println("BetweenOneAndFive was thrown") } catch (e: BetweenSixAndTen) { println("BetweenSixAndTen was thrown") } } fun multipleConstructor() { // the Exception class has 4 different constructors class MyCustomException: Exception { constructor() : super() // No parameters constructor(message: String?) : super(message) // Only the String parameter constructor(message: String?, cause: Throwable?) : super(message, cause) // Both parameters constructor(cause: Throwable?) : super(cause) // Only the exception parameter } // Our new exception has 4 constructors and can be thrown in the following ways: // throw MyCustomException() // throw MyCustomException("My message") // throw MyCustomException("My message", Exception("My cause")) // throw MyCustomException(Exception("My cause")) // In the following example, we create a custom exception derived from ArithmeticException. It has only 2 constructors: class MyArithmeticException: ArithmeticException { constructor() : super() constructor(message: String?) : super(message) } } fun main() { catchingCustomExceptions() } ############################################### 2.1.2. CatchingSupertype package controlflow.exception.exceptiontype fun catchingMultipleExceptions() { // Imagine a situation where you encounter different types of exceptions and want to handle them differently. The // straightforward approach is to use separate catch blocks for each exception. val input = readln() try { println(100 / input.toInt()) } catch (e: Exception) { println("What else can go wrong!") } catch (e: NumberFormatException) { println("You didn't type an INT number!") } catch (e: ArithmeticException) { println("You typed 0!") } // if we input a non-numeric character, the code will print: // "What else can go wrong!" // This is what we will get // Meanwhile, the output we were expecting is: // "You didn't type an INT number!" // That happens because when dealing with multiple catch blocks, if a supertype exception is caught first, all // subsequent exceptions inheriting from it will be ignored. } fun orderMatters() { // To achieve the desired output, we need to order the catch blocks appropriately: val input = readln() try { println(100 / input.toInt()) } catch (e: NumberFormatException) { println("You didn't type an INT number!") } catch (e: ArithmeticException) { println("You typed 0!") } catch (e: Exception) { println("What else can go wrong!") } // By rearranging the catch blocks and placing the more specific exceptions first, we ensure that the corresponding // catch block executes correctly. } fun catchingAllInOne() { val input = readln() try { println(100 / input.toInt()) } catch (e: Exception) { when (e) { is NumberFormatException -> println("You didn't type an INT number!") is ArithmeticException -> println("You typed 0!") else -> println("What else can go wrong!") } } // Notice that even when using the when statement, you still have to order the catch blocks correctly so that the // more specific ones are always handled first. } fun catchingTheExceptionType() { // It is always considered a best practice to catch the supertype Exception when handling multiple exceptions. // It helps us gracefully handle unknown or unexpected exceptions. By catching the supertype, we can handle such // exceptions without causing our program to crash or behave unpredictably. } fun main() { val a = readln().toDouble() val b = readln().toDouble() val c = readln().toDouble() val d = readln().toDouble() } ############################################## 2.1.3. StackTrace package controlflow.exception.exceptiontype import java.util.Scanner fun stackTraceInDetail() { val scanner = Scanner(System.`in`) val input: String = scanner.nextLine() val number = input.toInt() // an exception is possible here! println(number + 1) // If we enter a word instead of a number (for instance, "Kotlin"), the application throws an error and shows the // following stack trace message: // Exception in thread "main" java.lang.NumberFormatException: For input string: "Kotlin" // at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:67) // at java.base/java.lang.Integer.parseInt(Integer.java:668) // at java.base/java.lang.Integer.parseInt(Integer.java:784) // at MainKt.main(Main.kt:6) // at MainKt.main(Main.kt) // First, we need to read the top line, where we have three important hints: // 1. The thread in which the exception was thrown. If you remember, when the application starts, it creates a main // thread. // 2. The class responsible for the type of error. In our case, it is the NumberFormatException class from the // java.lang package. // 3. A message indicating why the exception was thrown (here, entering the string "Kotlin"). Further on, you will // see how this message was generated. // Basically, this stack trace message shows the call stack from the main method up to the place where the exception // was thrown. // Since the call stack is a LIFO data structure, the main() method that was called when the application was launched // is at the bottom, and it will be the last printed element of the stack trace. } fun gettingAStackTraceOnDemand() { // We have discussed an example of getting a stack trace after your application throws an error. What if you need to // get a stack trace at some specific point? It can be obtained without throwing an error by calling the // Thread.currentThread().stackTrace method. This way, it returns a StackTraceElement array, and you can print the // stack trace using a loop. for (element in Thread.currentThread().stackTrace) { println(element) } // There are also other ways of getting a stack trace, such as calling the Throwable().stackTrace or // Throwable().printStackTrace() methods. } fun stackTraceElementClass() { // According to the official Java Documentation, the StackTraceElement class is described as an element in a stack // trace representing a single stack frame. That is, each element returned by Thread.currentThread().getStackTrace() // is a stack frame, where the element printed at the top represents the execution point where the stack trace was // generated. fun demo(input: String) { for (element in Thread.currentThread().stackTrace) { println(element) } val number = input.toInt() // an exception is possible here! println(number + 1) } val scanner = Scanner(System.`in`) val input = scanner.nextLine() demo(input) // If you print println(element.className) inside the mentioned loop, you will get a stack trace message in the // following form: // java.lang.Thread // MainKt // MainKt // MainKt for (element in Thread.currentThread().stackTrace) { println(element.className) } println() for (element in Thread.currentThread().stackTrace) { println(element.methodName) } println() for (element in Thread.currentThread().stackTrace) { println(element.lineNumber) } } fun main() { stackTraceElementClass() } ############################################### 2.2. handling 2.2.1. TryWithResources package controlflow.exception.handling import java.io.File import java.io.FileReader import java.io.IOException import java.io.Reader import java.util.* /** * Why close? * * When an input stream is created, the JVM notifies the OS about its intention to work with a file. If the JVM process * has enough permissions and everything is fine, the OS returns a file descriptor — a special indicator used by a process * to access the file. * The problem is that the number of file descriptors is limited. This is the reason why it is important to notify the * OS that the job is done and the file descriptor that is held can be released for further reuse. * In previous examples, we invoked the method close for this purpose. Once it is called, the JVM releases all system * resources associated with the stream. */ fun pitfalls() { // Resource releasing works if the JVM calls the close method, but it is possible that the method will not be called // at all. val reader: Reader = FileReader("file.txt") // code which may throw an exception reader.close() // Suppose something goes wrong before the close invocation and an exception is thrown. It leads to a situation in // which the method will never be called and system resources won't be released. It is possible to solve the problem // by using the try-catch-finally construction: var reader2: Reader? = null try { reader2 = FileReader("file.txt") // code which may throw an exception } finally { reader2!!.close() } // In this and the following examples, we assume that file.txt exists and do not check the instance of Reader for // null in the finally block. We do it to keep the code snippet as simple as possible, but it is not safe in the // case of a real application. // Unfortunately, this solution still has some problems. That is, the close method can potentially raise exceptions // itself. Suppose there are two exceptions: the first was raised inside the try section, and the second was thrown // by the finally section. It leads to the loss of the first exception. Let's see why this happens: @Throws(IOException::class) fun readFile() { var reader: Reader? = null try { reader = FileReader("D:\\Learning\\Kotlin\\file.txt") throw RuntimeException("Exception1") } finally { reader?.close() // throws new RuntimeException("Exception2") } } // First, the try block throws an exception. As we know, the finally block is invoked anyway. Next, in our example, // the close method throws an exception. When two exceptions occur, which one is thrown outside the method? It will // be the latter one: Exception2 in our case. It means we will never know that the try block raised an exception at all. // Ok, we don't want to lose the first exception, so we upgrade the code a little bit and handle Exception2 right // after it was thrown: @Throws(IOException::class) fun readFile2() { var reader: Reader? = null try { reader = FileReader("D:\\Learning\\Kotlin\\file.txt") throw RuntimeException("Exception1") } finally { try { reader!!.close() // throws new RuntimeException("Exception2") } catch (e: Exception) { // handle Exception2 } } } // Now, the piece of code throws Exception1 outside. It may be correct, but we still do not save information on both // exceptions, and sometimes we don't want to lose it. So now, let's see how we can handle this situation nicely. } fun solution() { // A simple and reliable way called try-with-resources was introduced in Java 7. In Kotlin, there is no implementation // of try-with-resources, but we can apply the use extension method: FileReader("file.txt").use { reader -> // some code } // It is possible to create several objects as well. The code below is also fine: FileReader("file1.txt").use { reader1 -> FileReader("file2.txt").use { reader2 -> // some code } } // As you see, there are no explicit calls of the close method at all. It is implicitly invoked for all objects // declared in the first part. The construction guarantees closing all resources in a proper way. // You can initialize the input stream outside the design and then utilize the use method: val reader: Reader = FileReader("file.txt") reader.use { // some code } } fun closeableResources() { // We have dealt with a file input stream to demonstrate how try-with-resources is used. However, not only the // resources based on files should be released. Closing is also crucial for other outside sources, like web or // database connections. Classes that handle them have a close method and, therefore, can be wrapped by the // try-with-resources statement. // For example, let's consider java.util.Scanner. Earlier, we used Scanner for reading data from the standard input, // but it can read data from a file as well. Scanner has a close method for releasing outside sources. Scanner(File("file.txt")).use { scanner -> val first: Int = scanner.nextInt() val second: Int = scanner.nextInt() println("arguments: $first $second") } // Suppose something went wrong and the file content is 123 not_number, where the second argument is a String. It // leads to a java.util.InputMismatchException while parsing the second argument. The use method guarantees that // file-related resources are released properly. // Ham ready() dung de kiem tra file da san sang de doc chua (tra ve true hoac false) // Neu file da duoc close() thi se goi ra Exception khi dung file.ready() val file = FileReader("D:\\Learning\\Kotlin\\Files\\reading.txt") file.use { val iterator = it.readLines().iterator() while (iterator.hasNext()) { println(iterator.next()) } } try { println(file.ready()) } catch (e: Exception) { println(e.message) // Stream closed } } fun main() { }
Editor is loading...
Leave a Comment