Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 150 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
150
Dung lượng
1,86 MB
Nội dung
Ensuring a File Exists Let’s suppose that you want to append data to a file if it exists and create a new file if it doesn’t. Either way, you want to end up with a file output stream to work with. You will need to go through several checks to achieve this: ❑ Use the File object to verify that it actually represents a file rather than a directory. If it doesn’t, you can’t go any further, so output an error message. ❑ Use the File object to decide whether the file exists. If it doesn’t, ensure that you have a File object with an absolute path. You need this to obtain and check out the parent directory. ❑ Get the path for the parent directory and create another File object using this path. Use the new File object to check whether the parent directory exists. If it doesn’t, create it using the mkDirs() method for the new File object. Let’s look at how that might be done in practice. Try It Out Ensuring That a File Exists You could guarantee a file is available with the following code: import java.io.File; import java.io.FileOutputStream; import java.io.FileNotFoundException; public class GuaranteeAFile { public static void main(String[] args) { String filename = “C:/Beg Java Stuff/Bonzo/Beanbag/myFile.txt”; File aFile = new File(filename); // Create the File object // Verify the path is a file if (aFile.isDirectory()) { // Abort after a message // You could get input from the keyboard here and try again System.out.println(“The path “ + aFile.getPath() + “ does not specify a file. Program aborted.”); System.exit(1); } // If the file doesn’t exist if (!aFile.isFile()) { // Check the parent directory aFile = aFile.getAbsoluteFile(); File parentDir = new File(aFile.getParent()); if (!parentDir.exists()) { // and create it if necessary parentDir.mkdirs(); } } FileOutputStream outputFile = null;// Place to store the stream reference try { 421 Accessing Files and Directories 12_568744 ch09.qxd 11/23/04 9:31 PM Page 421 // Create the stream opened to append data outputFile = new FileOutputStream(aFile, true); } catch (FileNotFoundException e) { e.printStackTrace(System.err); } System.exit(0); } } Don’t forget to change the file name and path if the filename string isn’t convenient in your environ- ment. After executing this code, you should find that all the necessary directories and the file have been created if they don’t already exist. You can try this out with paths with a variety of directory levels. Delete them all when you are done, though. How It Works You call isDirectory() in the if statement to see whether the path is just a directory. Instead of aborting at this point, you could invite input of a new path from the keyboard, but I’ll leave that for you to try. Next, you check whether the file exists. If it doesn’t, you call getAbsoluteFile() to ensure that our File object has a parent path. If you don’t do this and you have a file specified with a parent, in the current directory, for example, then getParent() will return null. Having established the File object with an absolute path, you create a File object for the directory containing the file. If this directory does not exist, calling mkdirs() will create all the directories required for the path so that you can then safely create the file stream. The FileOutputStream constructor can in theory throw a FileNotFoundException, although not in our situation here. In any event, you must put the try and catch block in for the exception. A further possibility is that you might start with two strings defining the directory path and the file name separately. You might then want to be sure that you had a valid directory before you created the file. You could do that like this: String dirname = “C:/Beg Java Stuff”; // Directory name String filename = “charData.txt”; // File name File dir = new File(dirname); // File object for directory if (!dir.exists()) { // If directory does not exist if (!dir.mkdirs()) { // create it System.out.println(“Cannot create directory: “ + dirname); System.exit(1); } } else if (!dir.isDirectory()) { System.err.println(dirname + “ is not a directory”); System.exit(1); } // Now create the file If the directory doesn’t exist, you call mkdirs() inside the nested if to create it. Since the method returns false if the directory was not created, this will determine whether or not you have indeed managed to create the directory. 422 Chapter 9 12_568744 ch09.qxd 11/23/04 9:31 PM Page 422 Avoiding Overwriting a File In some situations when the file does exist, you may not want it to be overwritten. Here is one way you could avoid overwriting a file if it already exists: String filename = “C:/Beg Java Stuff/myFile.txt”; File aFile = new File(filename); FileOutputStream outputFile = null; // Place to store the stream reference if (aFile.isFile()) { System.out.println(“myFile.txt already exists.”); } else { // Create the file stream try { // Create the stream opened to append data outputFile = new FileOutputStream(aFile); System.out.println(“myFile.txt output stream created”); } catch (FileNotFoundException e) { e.printStackTrace(System.err); } } Of course, if you want to be sure that the path will in fact result in a new file being created when it doesn’t already exist, you would need to put in the code from the previous example that checks out the parent directory. The preceding fragment avoids overwriting the file, but it is not terribly helpful. If the file exists, you create the same FileOutputStream object as before, but if it doesn’t, you just toss out an error mes- sage. In practice, you are more likely to want the program to take some action so that the existing file is protected but the new file still gets written. One solution would be to rename the original file in some way if it exists, and then create the new one with the same name as the original. This takes a little work though. Try It Out Avoiding Overwriting a File Without worrying about plugging in the code that ensures that the file directory exists, here is how you could prevent an existing file from being overwritten. As always, you should change the file name and path to suit your environment if necessary. import java.io.File; import java.io.FileOutputStream; import java.io.FileNotFoundException; public class AvoidOverwritingFile { public static void main(String[] args) { String filepath = “C:/Beg Java Stuff/myFile.txt”; File aFile = new File(filepath); FileOutputStream outputFile = null; // Stores the stream reference if (aFile.isFile()) { File newFile = aFile; // Start with the original file 423 Accessing Files and Directories 12_568744 ch09.qxd 11/23/04 9:31 PM Page 423 that represents a file stream. Once you have closed the stream, you can no longer obtain the FileDescriptor object for it since the connection to the file will have been terminated. You can use a FileDescriptor object to create other stream objects when you want to have several con- nected to the same file concurrently. Since a FileDescriptor object represents an existing connection, you can only use it to create streams with read and/or write permissions that are consistent with the original stream. You can’t use the FileDescriptor object from a FileOutputStream to create a FileInputStream, for example. If you look at the documentation for the FileDescriptor class, you’ll see that it also defines three pub- lic static data members: in, out, and err, which are themselves of type FileDescriptor. These corre- spond to the standard system input, the standard system output, and the standard error stream, respectively, and they are there as a convenience for when you want to create byte or character stream objects corresponding to the standard streams. Summary In this chapter, I’ve discussed the facilities for inspecting physical files and directories and for writing basic types of data to a file. The important points I have discussed include the following: ❑ An object of the class File can encapsulate a file or directory path. The path encapsulated by a File object does not necessarily correspond to a physical file or directory. ❑ You can use a File object to test whether the path it encapsulates refers to a physical file or directory. If it does not, there are methods available to create it together with any directories that are part of the path that may also be required. ❑ The File class defines static methods for creating temporary files. ❑ An object of type FileDescriptor can also identify a physical file. ❑ A FileOutputStream object can be created from a File object, and the file will be opened for writing. If the file does not exist, it will be created where possible. Exercises You can download the source code for the examples in the book and the solutions to the following exer- cises from http://www.wrox.com. Don’t confuse the data members of the FileDescriptor class with the data mem- bers of the same name defined by the System class in the java.lang package. The in, out, and err data members of the System class are of type PrintStream, so they have the print(), println(), and printf() methods. The FileDescriptor data members do not. A PrintStream object is a stream, whereas a FileDescriptor object is not. 425 Accessing Files and Directories 12_568744 ch09.qxd 11/23/04 9:31 PM Page 425 // Append “_old” to the file name repeatedly until it is unique do { String name = newFile.getName(); // Get the name of the file int period = name.indexOf(‘.’); // Find the separator for the extension newFile = new File(newFile.getParent(), name.substring(0, period) + “_old” + name.substring(period)); } while(newFile.exists()); // Stop when no such file exists aFile.renameTo(newFile); // Rename the file } // Now we can create the new file try { // Create the stream opened to append data outputFile = new FileOutputStream(aFile); System.out.println(“myFile.txt output stream created”); } catch (FileNotFoundException e) { e.printStackTrace(System.err); } System.exit(0); } } If you run this a few times, you should see some _old_old files created. How It Works If the file exists, the code in the if block executes. This stores the reference to the original File object in newFile as a starting point for the do-while loop that follows. Each iteration of the loop appends the string “ _old” to the name of the file and creates a new File object using this name in the original direc- tory. The expression in the loop condition tests whether the new File object referenced by newFile corresponds to an existing file. If it does, the original file cannot be renamed to this name so the loop continues and adds a further occurrence of _old to the file name. Eventually, this process should arrive at a name that does not correspond to an existing file as long as the permitted file name length of the system in not exceeded. At this point the loop ends and the original file is renamed to the name corre- sponding to newFile. I use the getParent() method in the loop to obtain the parent directory for the file, and the getName() method returns the file name. I have to split the file name into the name part and the extension to append the “ _old” string, and the charAt() method for the String object gives the index position of the period separating the name from the file extension. Of course, this code presumes the existence of a file extension since I define my original file name with one. It is quite possible to deal with files that don’t have an extension, but I’ll leave that as a little digression for you. FileDescriptor Objects A FileOutputStream object has a method getFD() that returns an object of type FileDescriptor that represents the current connection to the physical file. You cannot create a FileDescriptor object yourself. You can only obtain a FileDescriptor object by calling the getFD() method for an object 424 Chapter 9 12_568744 ch09.qxd 11/23/04 9:31 PM Page 424 1. Modify the example that avoids overwriting a file to permit the file path to be entered as a command-line argument and to allow for file names that do not have extensions. 2. File names on many systems are not of unlimited length, so appending _old to file names may break down at some point. Modify the example that avoids overwriting a file to append a three- digit numerical value to the file name to differentiate it from the existing file instead of just adding _old. The program should check for the presence of three digits at the end of the name for the existing file and replace this with a value incremented by an amount to make it unique. (That is, increment the last three digits by 1 until a unique file name is created.) 3. Write a program that will list all the directories in a directory defined by a path supplied as a command-line argument, or all the directories on a system if no command-line argument is pre- sent. (Hint: The listRoots() method will give you the roots on a system and the listFiles() method will give you an array of File objects for the files and directories in any given directory— including a root.) 426 Chapter 9 12_568744 ch09.qxd 11/23/04 9:31 PM Page 426 10 Writing Files In this chapter, you’ll be looking at ways in which basic data can be written to a file using the new file input/output capability that was introduced in the 1.4 release of the JDK and that continues in JDK 5.0. This mechanism for file I/O largely superseded the read/write capability provided by readers and writers from the java.io package when applied to file streams. Since the new file I/O does everything that the old capability does, and does it better, I’ll focus just on that. In this chapter you’ll learn: ❑ The principles of reading and writing files using the new I/O capability ❑ How you obtain a file channel for a file ❑ How you create a buffer and load it with data ❑ What view buffers are and how you use them ❑ How you use a channel object to write the contents of a buffer to a file File I/O Basics If you are new to programming file operations, there are a couple of things about how they work that may not be apparent to you and can be a source of confusion so I’ll clarify these before I go any further. If you already know how input and output for disk files work, you can skip this section. First, let’s consider the nature of a file. Once you have written data to a file, what you have is just a linear sequence of bytes. The bytes in a file are referenced by their offset from the beginning, so the first byte is byte 0, the next byte is byte 1, the third byte is byte 2, and so on through to the end of the file. If there are n bytes in a file, the last byte will be at offset n-1. There is no specific informa- tion in the file about how the data originated or what it represents unless you explicitly put it there. Even if there is, you need to know that it’s there and read and interpret the data accordingly. 13_568744 ch10.qxd 11/23/04 9:29 PM Page 427 For example, if you write a series of 25 binary values of type int to a file, it will contain 100 bytes. Nothing in the file will indicate that the data consists of 4-byte integers so there is nothing to prevent you from reading the data back as 50 Unicode characters or 10 long values followed by a string, or any other arbitrary collection of data items that corresponds to 100 bytes. Of course, the result is unlikely to be very meaningful unless you interpret the data in the form in which it was originally written. This implies that to read data from a file correctly, you need to have prior knowledge of the structure and for- mat of the data that is in the file. The form of the data in the file may be recorded or implied in many ways. For example, one way that the format of the data in a file can be communicated is to use an agreed file name extension for data of a par- ticular kind, such as .java for a Java source file or .jpg for a graphical image file or .wav for a sound file. Each type of file has a predefined structure, so from the file extension you know how to interpret the data in the file. Of course, another way of transferring data so that it can be interpreted correctly is to use a generalized mechanism for communicating data and its structure, such as XML. You will be looking into how you can work with XML in your Java applications in Chapters 22 and 23. You can access an existing file to read it or write it in two different ways, described as sequential access or random access. The latter is sometimes referred to as direct access. Sequential access to a file is quite straightforward and works pretty much as you would expect. Sequential read access involves reading bytes from the file starting from the beginning with byte 0. Of course, if you are interested only in the file contents starting at byte 100, you can just read and ignore the first 100 bytes. Sequential write access involves writing bytes to the file starting at the beginning if you are replacing the existing data or writ- ing a new file, and writing bytes starting at the end if you are appending new data to an existing file. The term random access is sometimes misunderstood initially. Just like sequential access, random access is just a way of accessing data in a file and has nothing to do with how the data in the file is structured or how the physical file was originally written. You can access any file randomly for reading and/or writing. When you access a file randomly, you can read one or more bytes from the file starting at any point. For example, you could read 20 bytes starting at the 13th byte in the file (which will be the byte at offset 12, of course) and then read 50 bytes starting at the 101st byte or any other point that you choose. Similarly, you can update an existing file in random access mode by writing data starting at any point in the file. In random access mode, the choice of where to start reading or writing and how many bytes you read or write is entirely up to you. You just need to know the offset for the byte where a read or write operation should start. Of course, for these to be sensible and successful operations, you have to have a clear idea of how the data in the file is structured. First a note of caution: Before running any of the examples in this chapter, be sure to set up a separate directory for storing the files that you are using when you are test- ing programs. It’s also not a bad idea to back up any files and directories on your system that you don’t want to risk losing. But of course, you do back up your files regularly anyway — right? The old adage “If anything can go wrong, it will,” applies particularly in this con- text, as does the complementary principle “If anything can’t go wrong, it will.” Remember also that the probability of something going wrong increases in propor- tion to the inconvenience it is likely to cause. 428 Chapter 10 13_568744 ch10.qxd 11/23/04 9:29 PM Page 428 File Input and Output The new file I/O capabilities that were introduced in Java 1.4 provided the potential for substantially improved performance over the I/O facilities of previous releases, the only cost being some slight increase in complexity. Three kinds of objects are involved in reading and writing files using the new I/O capability: ❑ A file stream object that encapsulates the physical file that you are working with. You saw how to create FileOutputStream objects at the end of the previous chapter, and you use these for files to which you want to write. In the next chapter, you will be using FileInputStream objects for files that you want to read. ❑ One or more buffer objects in which you put the data to be written to a file, or from which you get the data that has been read. You’ll learn about buffer objects in the next section. ❑ A channel object that provides the connection to the file and does the reading or writing of the data using one or more buffer objects. You’ll see how to obtain a channel from a file stream object later in this chapter. The way in which these types of objects work together is illustrated in Figure 10-1. Figure 10-1 The process for writing and reading files is basically quite simple. To write to a file, you load data into one or more buffers that you have created and then call a method for the channel object to write the data to the file that is encapsulated by the file stream. To read from a file, you call a method for the channel object to read data from the file into one or more buffers, and then retrieve the data from the buffers. You will be using four classes defined in the java.io package when you are working with files. As I’ve said, the FileInputStream and FileOutputStream classes define objects that provide access to a file for reading or writing, respectively. You use an object of type RandomAccessFile when you want to access a file randomly, or when you want to use a single channel to both read from and write to a file. You’ll be exploring this, along with the FileInputStream class, in the next chapter. You will see from The channel transfers data between the buffers and the file stream File Stream Object Buffer Objects Channel Object 429 Writing Files 13_568744 ch10.qxd 11/23/04 9:29 PM Page 429 the JDK documentation for the FileInputStream, FileOutputStream, and RandomAccessFile classes that they each provide methods for I/O operations. However, I’ll ignore these, as you’ll be using the services of a file channel to perform operations with objects of these stream classes. The only method from these classes that you will be using is the close() method, which closes the file and any associated channel. Channels Channels were introduced in the 1.4 release of Java to provide a faster capability for input and output operations with files, network sockets, and piped I/O operations between programs than the methods provided by the stream classes. I will be discussing channels only in the context of files, not because the other uses for channels are difficult, but just to keep the book focused on the essentials so that the poten- tial for a hernia is minimized. The channel mechanism can take advantage of buffering and other capa- bilities of the underlying operating system and therefore is considerably more efficient than using the operations provided directly within the file stream classes. As I said earlier, a channel transfers data between a file and one or more buffers. I’ll first introduce the overall relationships between the various classes that define channels and buffers, and then look into the details of how you use channels with file streams. A considerable number of classes and interfaces define both channels and buffers. They also have similar names such as ByteBuffer and ByteChannel. Of course, File and file stream objects are also involved in file I/O operations, so you will be using at least four different types of objects working together when you read from or write to files. Just to clarify what they all do, here’s a summary of the essential role of each of them in file operations: ❑ A File object encapsulates a path to a file or a directory, and such an object encapsulating a file path can be used to construct a file stream object. ❑ A FileInputStream object encapsulates a file that can be read by a channel. A FileOutputstream object encapsulates a file that can be written by a channel. As you will see in the next chapter, a RandomAccessFile object can encapsulate a file that can be both read from and written to by a channel. ❑ A buffer just holds data in memory. You load the data that you want to write to a file into a buffer using the buffer’s put() methods. You use a buffer’s get() methods to retrieve data that has been read from a file. ❑ You obtain a FileChannel object from a file stream object or a RandomAccessFile object. You use a FileChannel object to read and/or write a file using the read() and write() methods for the FileChannel object, with a buffer or buffers as the source or destination of the data. The channel interfaces and classes that you will be using are in the java.nio.channels package. The classes that define buffers are defined in the java.nio package. In a program that reads or writes files, you will therefore need import statements for class names from at least three packages, the two packages I have just introduced plus the java.io package. 430 Chapter 10 13_568744 ch10.qxd 11/23/04 9:29 PM Page 430 [...]... this buffer with the following statements: double[] data = { 1.0, 1 .41 4, 1.7 32, 2.0, 2.236, 2 .44 9 }; doubleBuf.put(data); // Transfer the array elements to the buffer The put() operation automatically increments the position for the view buffer Now the buffer will be as shown in Figure 10-11 capacity = 10 1.0 1 .41 4 1.732 2.0 2.236 2 .44 9 empty position = 6 empty empty empty limit = 10 Figure 10-11 The... 1, 1, 2, 3, 5, 8, 13, 21, 34, 55 , 89}; LongBuffer numBuf = LongBuffer.wrap(numbers); The buffer of type LongBuffer that you create here will have a capacity of array.length, which will be 11 The buffer position will be set to 0 and the limit will be set to the capacity In a similar manner you can create buffers from arrays of any of the other basic types with the exception of type boolean 44 4 Writing... multiple of 4 will be, since intBuf stores elements of type int that require 4 bytes each The view buffer will have an initial position of 0, and a capacity and limit of 256 This is because 256 elements of type int completely fill the 10 24 bytes remaining in buf If you had allocated buf with 1023 bytes, then intBuf would have mapped to 1020 bytes of buf and would have a capacity and limit of 255 You could... charData.txt that you will create in the directory Beg Java Stuff on your C: drive If you want to write to a different drive and/or directory, just change the program accordingly Here is the code: import import import import import import java. io.File; java. io.FileOutputStream; java. io.IOException; java. io.FileNotFoundException; java. nio.ByteBuffer; java. nio.channels.FileChannel; public class WriteAString... the view buffer holds Figure 10 -5 illustrates a view buffer of type IntBuffer that is created after the initial position of the byte buffer has been incremented by 2, possibly after inserting a value of type char into the byte buffer: capacity = 20 position = 2 limit = 12 ByteBuffer buf buf.asIntBuffer() buf asIntBuffer IntBuffer position = 0 capacity = 2 Figure 10 -5 44 0 limit = 2 Writing Files You... example: ByteBuffer buf = 43 8 ByteBuffer.allocate(10 24) ; // Buffer of 10 24 bytes capacity Writing Files When you create a new buffer using the allocate() method for the buffer class, it will have a position of zero, and its limit will be set to its capacity The buffer that the preceding statement creates will therefore have a position of 0, and a limit and capacity of 10 24 You can also create other... This method also returns a reference of type Buffer so you could chain it with the methods for setting the limit and position: buf.limit (51 2).position( 256 ).mark(); This will set the mark to 256 , the same as the position, which is set after the limit has been set to 51 2 After a series of operations that alter the position, you can reset the buffer’s position to the mark that you have set previously by... that contains the same sequence of characters as the original string You can now modify the buffer, but of course this won’t affect the original String object, only the underlying array that you created 4 45 Chapter 10 You could also create a StringBuilder or StringBuffer object from the String object and wrap that For example: String wisdom = “Many a mickle makes a muckle.”; CharBuffer charBuf = CharBuffer.wrap(new... enables you to chain calls to these methods together in a single statement For example, given a buffer reference buf, you could set both the position and the limit with the statement: buf.limit (51 2).position( 256 ); 43 7 Chapter 10 file For a ByteBuffer used for file input, the position identifies where the next byte that is read from the file will be stored in the buffer When you transfer one or more values... bytes 44 8 Writing Files Note that you are transferring the string characters to the buffer as bytes in the local character encoding in the previous code fragment, not as Unicode characters To transfer them as the original Unicode characters, you could code the operations like this: char[] array = text.toCharArray(); // Create char[] array from the string ByteBuffer buf = ByteBuffer.allocate (50 ); // . object is a stream, whereas a FileDescriptor object is not. 4 25 Accessing Files and Directories 12 _56 8 744 ch09.qxd 11/23/ 04 9:31 PM Page 4 25 // Append “_old” to the file name repeatedly until it. FileDescriptor object by calling the getFD() method for an object 42 4 Chapter 9 12 _56 8 744 ch09.qxd 11/23/ 04 9:31 PM Page 42 4 1. Modify the example that avoids overwriting a file to permit the. inconvenience it is likely to cause. 42 8 Chapter 10 13 _56 8 744 ch10.qxd 11/23/ 04 9:29 PM Page 42 8 File Input and Output The new file I/O capabilities that were introduced in Java 1 .4 provided the potential