在本文中,您将会了解到关于JavaIO详解的新资讯,同时我们还将为您解释七)------随机访问文件流的相关在本文中,我们将带你探索JavaIO详解的奥秘,分析七)------随机访问文件流的特点,并
在本文中,您将会了解到关于Java IO详解的新资讯,同时我们还将为您解释七)------随机访问文件流的相关在本文中,我们将带你探索Java IO详解的奥秘,分析七)------随机访问文件流的特点,并给出一些关于c – 随机访问文件无法正常工作、Java IO流详解(七)——对象流(序列化与反序列化)、java IO详解、Java IO详解那点事的实用技巧。
本文目录一览:- Java IO详解(七)------随机访问文件流(java随机访问文件功能)
- c – 随机访问文件无法正常工作
- Java IO流详解(七)——对象流(序列化与反序列化)
- java IO详解
- Java IO详解那点事
Java IO详解(七)------随机访问文件流(java随机访问文件功能)
1、什么是 随机访问文件流 RandomAccessFile?
该类的实例支持读取和写入随机访问文件。 随机访问文件的行为类似于存储在文件系统中的大量字节。 有一种游标,或索引到隐含的数组,称为文件指针 ; 输入操作读取从文件指针开始的字节,并使文件指针超过读取的字节。 如果在读/写模式下创建随机访问文件,则输出操作也可用; 输出操作从文件指针开始写入字节,并将文件指针提前到写入的字节。 写入隐式数组的当前端的输出操作会导致扩展数组。 文件指针可以通过读取getFilePointer方法和由设置seek方法。
通俗来讲:我们以前讲的 IO 字节流,包装流等都是按照文件内容的顺序来读取和写入的。而这个随机访问文件流我们可以再文件的任意地方写入数据,也可以读取任意地方的字节。
我们查看 底层源码,可以看到:
1
|
public
class
RandomAccessFile
implements
DataOutput, DataInput, Closeable {
|
实现了 DataOutput类,DataInput类,那么这两个类是什么呢?
2、数据流:DataOutput,DataInput
①、DataOutput:提供将数据从任何Java基本类型转换为一系列字节,并将这些字节写入二进制流。 还有一种将String
转换为modified UTF-8格式(这种格式会在写入的数据之前默认增加两个字节的长度)并编写结果字节系列的功能。
②、DataInput:提供从二进制流读取字节并从其中重建任何Java原语类型的数据。 还有,为了重建设施String从数据modified UTF-8格式。
下面我们以其典型实现:DataOutputSteam、DataInputStream 来看看它的用法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
//数据输出流
File file =
new
File(
"io"
+File.separator+
"a.txt"
);
DataOutputStream dop =
new
DataOutputStream(
new
FileOutputStream(file));
//写入三种类型的数据
dop.write(
65
);
dop.writeChar(
''哥''
);
dop.writeUTF(
"帅锅"
);
dop.close();
//数据输入流
DataInputStream dis =
new
DataInputStream(
new
FileInputStream(file));
System.out.println(dis.read());
//65
System.out.println(dis.readChar());
//哥
System.out.println(dis.readUTF());
//帅锅
dis.close();
|
3、通过上面的例子,我们可以看到因为 RandomAccessFile 实现了数据输入输出流,那么 RandomAccessFile 这一个类就可以完成 输入输出的功能了。
这里面第二个参数:String mode 有以下几种形式:(ps:为什么这里的值是固定的而不弄成枚举形式,不然很容易写错,这是因为随机访问流出现在枚举类型之前,属于Java 历史遗留问题)
第一种:用 随机流顺序读取数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
public
class
RandomAccessFileTest {
public
static
void
main(String[] args)
throws
Exception {
File file =
new
File(
"io"
+File.separator+
"a.txt"
);
write(file);
read(file);
}
/**
* 随机流读数据
*/
private
static
void
read(File file)
throws
Exception {
//以 r 即只读的方法读取数据
RandomAccessFile ras =
new
RandomAccessFile(file,
"r"
);
byte
b = ras.readByte();
System.out.println(b);
//65
int
i = ras.readInt();
System.out.println(i);
//97
String str = ras.readUTF();
//帅锅
System.out.println(str);
ras.close();
}
/**
* 随机流写数据
*/
private
static
void
write(File file)
throws
Exception{
//以 rw 即读写的方式写入数据
RandomAccessFile ras =
new
RandomAccessFile(file,
"rw"
);
ras.writeByte(
65
);
ras.writeInt(
97
);
ras.writeUTF(
"帅锅"
);
ras.close();
}
}
|
第二种:随机读取,那么我们先介绍这两个方法
这里所说的偏移量,也就是字节数。一个文件是有N个字节数组成,那么我们可以通过设置读取或者写入的偏移量,来达到随机读取或写入的目的。
我们先看看Java 各数据类型所占字节数:
下面是 随机读取数据例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
/**
* 随机流读数据
*/
private
static
void
read(File file)
throws
Exception {
//以 r 即只读的方法读取数据
RandomAccessFile ras =
new
RandomAccessFile(file,
"r"
);
byte
b = ras.readByte();
System.out.println(b);
//65
//我们已经读取了一个字节的数据,那么当前偏移量为 1
System.out.println(ras.getFilePointer());
//1
//这时候我们设置 偏移量为 5,那么可以直接读取后面的字符串(前面是一个字节+一个整型数据=5个字节)
ras.seek(
5
);
String str = ras.readUTF();
//帅锅
System.out.println(str);
//这时我们设置 偏移量为 0,那么从头开始
ras.seek(
0
);
System.out.println(ras.readByte());
//65
//需要注意的是:UTF 写入的数据默认会在前面增加两个字节的长度
ras.close();
}
|
随机流复制文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
/**
* 随机流复制文件
* @param fileA
* @param B
* @throws Exception
*/
private
static
void
copyFile(File fileA,File fileB)
throws
Exception{
RandomAccessFile srcRA =
new
RandomAccessFile(fileA,
"rw"
);
RandomAccessFile descRA =
new
RandomAccessFile(fileB,
"rw"
);
//向 文件 a.txt 中写入数据
srcRA.writeByte(
65
);
srcRA.writeInt(
97
);
srcRA.writeUTF(
"帅锅"
);
//获取 a.txt 文件的字节长度
int
len = (
int
) srcRA.length();
srcRA.seek(
0
);
System.out.println(srcRA.readByte()+srcRA.readInt()+srcRA.readUTF());
//开始复制
srcRA.seek(
0
);
//定义一个数组,用来存放 a.txt 文件的数据
byte
[] buffer =
new
byte
[len];
//将 a.txt 文件的内容读到 buffer 中
srcRA.readFully(buffer);
//再将 buffer 写入到 b.txt文件中
descRA.write(buffer);
//读取 b.txt 文件中的数据
descRA.seek(
0
);
System.out.println(descRA.readByte()+descRA.readInt()+descRA.readUTF());
//关闭流资源
srcRA.close();
descRA.close();
}
|
ps:一般多线程下载、断点下载都可以运用此随机流
c – 随机访问文件无法正常工作
我不是在寻找解决方案,只是在正确的方向上轻推.谢谢.
#include "stdafx.h" #include <iostream> #include <iomanip> #include <fstream> #include <sstream> #include "ccc_empl.h" using namespace std; const int NEWLINE_LENGTH = 2; const int RECORD_SIZE = 30 + 10 + NEWLINE_LENGTH; /** converts a string to a floating-point value @param s a string representing a floating-point value @return the equivalent floating-point value */ double string_to_double(string s) { istringstream instr(s); double x; instr >> x; return x; } /* reads an employee record from the input file @param e the employee @param in the file to read from */ Employee read_employee(istream& in) { string line; getline(in,line); string input_name = line.substr(0,30); double input_salary = string_to_double(line.substr(30,10)); Employee e(input_name,input_salary); return e; } /* gets input for an Employee object @param input_name the name of the employee @param input_salary the salary of the employee @param e the Employee object @return returns the Employee object */ Employee input_employee() { string input_name; cout << "Name: "; cin.ignore(); getline(cin,input_name); cout << "Salary: "; double input_salary; cin >> input_salary; Employee e(input_name,input_salary); return e; } /** adds an employee record to a file @param e the employee record to write @param out the file to write to */ void add_employee(Employee e,ostream& out) { out << e.get_name() << setw(30) << fixed << setprecision(2) << e.get_salary(); } /** removes an employee record from a file @param e the employee record to remove @param out the file to remove from */ void remove_employee(ostream& out) { out << " " << setw(42) << fixed << setprecision(2) << " \n"; } int main() { cout << "Please enter the data file name: "; string filename; cin >> filename; fstream fs; fs.open(filename); fs.seekg(0,ios::end); // Go to end of file int nrecord = fs.tellg() / RECORD_SIZE; // determine number of records in the file int menu_input = 0; string input_name; double input_salary = 0; while (menu_input != 4) { cout << "Please enter the record to update: (0 - " << nrecord - 1 << ") exit to quit "; int pos = 0; cin >> pos; if(cin.fail()) { cout << "Exiting..." << endl; system("pause"); return 0; } // menu for user input cout << "\nWhat action would you like to perform?" << endl; cout << "Add employee.....1" << endl; cout << "Remove employee..2" << endl; cout << "View employee....3" << endl; cin >> menu_input; switch(menu_input) { case 1: fs.seekg(pos * RECORD_SIZE,ios::beg); add_employee(input_employee(),fs); break; case 2: fs.seekg(pos * RECORD_SIZE,ios::beg); remove_employee(fs); break; case 3: fs.seekg(pos * RECORD_SIZE,ios::beg); cout << "\nName: " << read_employee(fs).get_name() << "Salary: " << read_employee(fs).get_salary() << endl << endl; break; default: cout << "Invalid entry" << endl; break; } } fs.close(); system("pause"); return 0; }
好的,这是新的和改进的(好吧,我想是的)代码.我唯一的问题是当添加一个员工时,我可以让程序将它添加到第一个打开的记录就好了,但是如果没有打开的记录,我似乎无法将它添加到最后文件没有搞乱添加到第一个空记录.我的意思是,如果有空记录,它会将员工添加到记录中,但如果没有空记录,则不会将员工添加到文件末尾.如果我添加代码添加到文件末尾,则会发生以下两种情况之一:要么添加到空记录,要么请求另一名员工并将其添加到结尾,或者只是跳过空记录并添加到文件的结尾.
不知道我在这里做错了什么,但任何提示都会受到赞赏.
#include "stdafx.h" #include <iostream> #include <iomanip> #include <fstream> #include <sstream> #include "ccc_empl.h" using namespace std; const int NEWLINE_LENGTH = 2; const int RECORD_SIZE = 30 + 10 + NEWLINE_LENGTH; /** converts a string to a floating-point value @param s a string representing a floating-point value @return the equivalent floating-point value */ double string_to_double(string s) { istringstream instr(s); double x; instr >> x; return x; } /* reads an employee record from the input file @param e the employee @param in the file to read from */ Employee read_employee(istream& in) { string line; getline(in,input_salary); return e; } /** adds an employee record to a file @param e the employee record to write @param out the file to write to */ void add_employee(Employee e,ostream& out) { out << e.get_name() << setw(10 + 30 - e.get_name().length()) << fixed << setprecision(2) << e.get_salary() << "\n"; } /** removes an employee record from a file @param e the employee record to remove @param out the file to remove from */ void remove_employee(ostream& out) { out << " " << setw(40) << fixed << setprecision(2) << " \n"; } int main() { cout << "Please enter the data file name: "; string filename; cin >> filename; fstream fs; fs.open(filename); fs.seekg(0,ios::end); // Go to end of file int nrecord = fs.tellg() / RECORD_SIZE; // determine number of records in the file int menu_input = 1; string input_name; double input_salary = 0; while (menu_input) { // menu for user input cout << "\nWhat action would you like to perform?" << endl; cout << "Add employee.....1" << endl; cout << "Remove employee..2" << endl; cout << "View employee....3" << endl; cout << "Exit.............4" << endl; cin >> menu_input; if (menu_input == 4) { cout << "\nExiting..." << endl << endl; system("pause"); return 0; } // switch statment to perform selected menu_input task switch(menu_input) { case 1: // adds an employee in the first empty record // or at the end of the file if no records are empty { int count = 0; string s; for (int i = 0; i < nrecord; i++) { fs.seekp(count,ios::beg); getline(fs,s); if (isspace(s[0])) { fs.seekp(count,ios::beg); add_employee(input_employee(),fs); nrecord++; break; }else count += 42; } } break; case 2: { cout << "Please enter the record to remove: (0 - " << nrecord -1 << ") "; int pos = 0; cin >> pos; fs.seekp(pos * RECORD_SIZE,ios::beg); remove_employee(fs); } break; case 3: { cout << "Please enter the record to view: (0 - " << nrecord -1 << ") "; int pos = 0; cin >> pos; fs.seekg(pos * RECORD_SIZE,ios::beg); Employee e(read_employee(fs)); cout << "\nName: " << e.get_name() << "Salary: " << e.get_salary() << endl << endl; } break; default: cout << "Invalid entry" << endl; break; } } fs.close(); system("pause"); return 0; }
解决方法
Java IO流详解(七)——对象流(序列化与反序列化)
对象流的主要用作是对Java对象的序列化和反序列化的操作。在Java IO流中提供了两个对象流:ObjectInputStream和ObjectOutputStream,这两个类都属于字节流。其中ObjectOutputStream将Java对象以字节序列的形式写出到文件,实现对象的永久存储,它继承自OutputStream。ObjectInputStream是将之前使用ObjectOutputStream序列化的字节序列恢复为Java对象,它继承自InputStream。
序列化与反序列化
序列化 : 把Java对象转换成字节序列的过程。
反序列化:把序列化成字节序列的数据恢复为Java对象的过程。
为什么需要序列化?
①、把对象的字节序列永久地保存到硬盘上:对于一个存在JVM中的对象来说,其内部的状态只是保存在内存中。当JVM退出之后,内存资源也就被释放,Java对象的内部状态也就丢失了。而在很多情况下,对象内部状态是需要被持久化的,将运行中的对象状态保存下来(最直接的方式就是保存到文件系统中),在需要的时候可以还原,即使是在Java虚拟机退出的情况下。
②、在网络上传送对象的字节序列:对象序列化机制是Java内建的一种对象持久化方式,可以很容易实现在JVM中的活动对象与字节数组(流)之间进行转换,使用得Java对象可以被存储,可以被网络传输,在网络的一端将对象序列化成字节流,经过网络传输到网络的另一端,可以从字节流重新还原为Java虚拟机中的运行状态中的对象。
1、ObjectOutputStream类
ObjectOutputStream代表对象输出流,即序列化,将Java对象以字节序列的形式写出到文件,实现对象的永久存储。
它的writeObject(Object obj)方法可对指定的obj参数对象进行序列化。
首先需要明确的一点是:一个对象要想序列化,该对象必须要实现Serializable接口,否则会抛出NotSerializableException异常。
定义一个Person类,实现Serializable接口:
package com.thr;
import java.io.Serializable;
/**
* @author Administrator
* @date 2020-02-28
* @desc Person对象
*/
public class Person implements Serializable {
private int id;
private String name;
private int age;
public Person() {
}
public Person(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
//getter、setter、toString方法省略(自己测试需要加上)
}
序列化操作:
package com.thr;
import java.io.*;
/**
* @author Administrator
* @date 2020-02-28
* @desc 使用ObjectOutputStream序列化对象
*/
public class ObjectOutputStreamTest {
public static void main(String[] args) {
//定义对象流
ObjectOutputStream oos = null;
try {
//创建对象流
oos = new ObjectOutputStream(new FileOutputStream("D:\\IO\\person.txt"));
//序列化对象
oos.writeObject(new Person(10001,"张三",20));
oos.writeObject(new Person(10002,"李四",21));
//刷新缓冲区
oos.flush();
System.out.println("序列化成功...");
} catch (IOException e) {
e.printStackTrace();
}finally {
//释放资源
if (oos!=null){
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
序列化之后打开的文件我们是看不懂的,因为它是字节序列文件,只有计算机懂。所以接下来需要将它反序列化成我们能看懂的。
2、ObjectInputStream类
ObjectOutputStream代表对象输入流,即反序列化,将之前使用ObjectOutputStream序列化的字节序列恢复为Java对象。
它的readObject()方法读取指定目录下的序列化对象。
反序列化操作:
package com.thr;
import java.io.*;
/**
* @author Administrator
* @date 2020-02-28
* @desc 使用ObjectInputStream反序列化对象
*/
public class ObjectInputStreamTest {
public static void main(String[] args) {
//定义对象流
ObjectInputStream ois = null;
try {
//创建对象输入流对象
ois = new ObjectInputStream(new FileInputStream("D:\\IO\\person.txt"));
//反序列化对象
Person person1 = (Person) ois.readObject();
Person person2 = (Person) ois.readObject();
System.out.println(person1);
System.out.println(person2);
System.out.println("反序列化成功...");
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
if (ois!=null){
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
在使用ObjectInputStream反序列化时需要注意一点:
①、在完成序列化操作后,如果对序列化对象进行了修改,比如增加某个字段,那么我们再进行反序列化就会抛出InvalidClassException异常,这种情况叫不兼容问题。
解决的方法是:在对象中手动添加一个 serialVersionUID 字段,用来声明一个序列化版本号,之后再怎么添加属性也能进行反序列化,凡是实现Serializable接口的类都应该有一个表示序列化版本标识符的静态变量。
public class Person implements Serializable {
//序列化版本号
private static final long serialVersionUID = 5687485987455L;
private int id;
private String name;
private int age;
//getter、setter、toString、构造方法省略(自己测试需要加上)
}
注意:ObjectInputStream和ObjectOutputStream不能序列化transient修饰的成员变量。
Transient 关键字
transient修饰符仅适用于变量,不适用于方法和类。在序列化时,如果我们不想序列化特定变量以满足安全约束,那么我们应该将该变量声明为transient。执行序列化时,JVM会忽略transient变量的原始值并将默认值保存到文件中。因此,transient意味着不要序列化。
假如Person类中的age属性不需要序列化,在age属性上添加transient关键字。private transient int age;


package com.thr;
import java.io.*;
/**
* @author Administrator
* @date 2020-02-29
* @desc transient的使用
*/
public class Test {
public static void main(String[] args) {
serialization(new Person(10001, "赵六", 20));
deserialization();
}
//序列化
public static void serialization(Person person){
ObjectOutputStream oos = null;
try {
//创建输出对象流
oos = new ObjectOutputStream(new FileOutputStream("D:\\IO\\object.txt"));
//序列化对象
oos.writeObject(person);
oos.flush();
System.out.println("序列化成功...");
} catch (IOException e) {
e.printStackTrace();
} finally {
//释放资源
try {
if (oos!=null){
oos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
//反序列化
public static void deserialization (){
ObjectInputStream ois = null;
try {
//创建输入对象流
ois = new ObjectInputStream(new FileInputStream("D:\\IO\\object.txt"));
//反序列化对象
Person person = (Person) ois.readObject();
System.out.println(person);
System.out.println("反序列化成功...");
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
transient的使用示例
运行的结果为:Person{id=10001, name=''赵六'', age=0},可以发现,尽管age属性没有序列化,但是它是有默认值的。
java IO详解
1. Abstract:
Java将I/O分为高阶I/O与低阶I/O,高阶I/O在使用上提供更多的读写方法,如读写int、double、String的资料型态,而低阶的I/O大部份只提供write、read的byte[]存取,因为程式大部份的资料都是以字串或其它主要型态资料来运算,因此低阶的I/O在使用上不利於程式设计,所以Java将许多好用的方法全部集合成高阶I/O; 换言之,低阶I/O的主要工作是负责与媒体资料作存取,高阶I/O类别主要作资料型态的转换及提供一些特殊的功能。在使用Java I/O时要谨记的一个重要原则是,在建立一个I/O之前必需先用低阶I/O类别来存取媒体资料(如档案或pipe),之後再使用高阶I/O来控制低阶I/O类别的动作,这种一层又一层的架构称I/O Chain。底下为Java的I/O架构图,第一个为以byte为单位的I/O,第二个则是以char为单位。
2. File I/O:
A. FileInputStream & FileOutputStream
FileInputStream是读取档案用的类别,其建构式有叁个:
public FileInputStream(String strFilename) throws FileNotFoundException
public FileInputStream(File fIn) throws FileNotFoundException
public FileInputStream(FileDescriptor fdObj)
在这里我只讲第一个,这是最直觉的方式,如下的范例1,会一次从e:\test.txt读10个bytes,将读入的结果输出到标准输出设备,直到档案结束。在这个范例中要注意的是,available会传回输入串流中还有多少个bytes,read则会根据buffer的大小来决定一次读几个bytes,并将实际读到的byte数传回。
===== 范例 1 =====
import java.io.*;
public class FIn {
public FIn() {
try {
FileInputStream fis = new FileInputStream("e:/in.txt");
while (fis.available() > 0) {
byte[] b = new byte[10];
int nResult = fis.read(b);
if (nResult == -1) break;
System.out.println(new String(b));
}
fis.close();
}
catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
FIn fIn = new FIn();
}
}
FileOutputStream是写入档案用的类别,其建构式有四个:
Public FileOutputStream(String strFilename) throws FileNotFoundException
Public FileOutputStream(File fOut) throws FileNotFound Exception
Public FileOutputStream(FileDescriptor fdObj)
public FileOutputStream(String name, boolean append) throws FileNotFoundException
第四个和第一个的差别只在於当档案存在时,第一个会将原来的档案内容覆盖,第四个则可以选择覆盖或将新内容接在原内容後面。范例2以建构式一讲解如何写入一个档案…在这个范例中要注意的是,fIn每个读10个bytes,但是最後一次不一定会读10个bytes,因此,fOut在write时,要指明要写几个bytes到档案中,否则最後一次仍会写入10个bytes,因Java在new byte时会先将内容先填0,所以後面的几个bytes会是0。
===== 范例2 =====
import java.io.*;
public class FOut {
public FOut() {
try {
FileInputStream fIn = new FileInputStream("e:/in.txt");
FileOutputStream fOut = new FileOutputStream("e:/out.txt");
while (fIn.available() > 0) {
byte[] b = new byte[10];
int nResult = fIn.read(b);
if (nResult == -1) break;
fOut.write(b, 0, nResult);
}
fIn.close();
fOut.close();
}
catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
FOut FOut1 = new FOut();
}
}
B. FileReader & FileWriter
FileReader和FileInputStream最大不同在於,FileInputStream读取的单位是byte,FileReader读取的单位是char。另外要注意的是,在FileInputStream中以available来判断是否还有资料可读取,在FileReader中是以ready来判断,
但是,available是传回还有多少个bytes可以读取,ready则传回true或false,当传回true时表示,下次read时保证不会停顿,当传回false时,表示下次read时”可能”停顿,所谓可能是指不保证不会停顿。
Ps. 测试时,in.txt里放些中文字就可以看出以byte和以char为单位有什麽不同。
===== 范例 3 =====
import java.io.*;
public class chFIn {
public chFIn() {
try {
FileReader rdFile = new FileReader("e:/in.txt");
while (rdFile.ready()) {
char[] chIn = new char[10];
int nResult = rdFile.read(chIn);
if (nResult == -1) break;
System.out.println(chIn);
}
rdFile.close();
}
catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
chFIn chFIn1 = new chFIn();
}
}
FileWriter和FileOutputStream的最大不同也在於写入单位的不同,FileOutputStream为byte,FileWriter为char。
===== 范例 4 =====
import java.io.*;
public class chFOut {
public chFOut() {
try {
FileReader rdFile = new FileReader("e:/in.txt");
FileWriter wrFile = new FileWriter("e:/out.txt");
while (rdFile.ready()) {
char[] chIn = new char[10];
int nResult = rdFile.read(chIn);
if (nResult == -1) break;
wrFile.write(chIn, 0, nResult);
}
rdFile.close();
wrFile.close();
}
catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
chFOut chFOut1 = new chFOut();
}
}
C. BufferedReader & BufferedWriter
File I/O是相当耗时的作业,通常电脑在做处理时,者会建立一个缓冲区,一次读取或写入一个区块,借由减少I/O次数,来节省时间 ,在Java中的BufferedReader和BufferedWriter就是提供这样的缓冲功能。
在范例5中,我们将FileReader导向BufferedReader,将FileWriter导向BufferedWriter,来达到区块读取、写入的目的。BufferedReader提供的readLine一次可以读取一行,当遇到档尾时,会传回null。BufferedWriter提供的newLine会产生列尾符号,这个列尾符号随作业系统的不同而不同,在Windows上为\r\n,在Unix上为\n,在Mac上为\r,这个符号是依据line.separator系统性质而来的。需注意的是,如果将BufferedWriter应用到网路程式时,绝对不要使用newLine,因为绝大多数的网路协定都是以\r\n为列尾,不会因作业系统不同而异。
===== 范例 5 =====
import java.io.*;
public class bufIn {
public bufIn() {
try {
FileReader rdFile = new FileReader("e:/in.txt");
BufferedReader brdFile = new BufferedReader(rdFile);
FileWriter wrFile = new FileWriter("e:/out.txt");
BufferedWriter bwrFile = new BufferedWriter(wrFile);
String strLine;
while ((strLine = brdFile.readLine()) != null) {
bwrFile.write(strLine);
bwrFile.newLine();
}
brdFile.close();
bwrFile.close();
}
catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
bufIn bufIn1 = new bufIn();
}
}
D. File
在档案处理方面,程式不只是要对档案做读、写,有时也需要得知档案的属性,或删除、移动、更名,有时则是要找出或列出某目录下的某些档案,针对这些运作,Java提供了File这个类别。底下的范例,说明如何使用File类别。
a. 如何得知档案属性:
在范例6中需注意的是lastModified传回的最後更改时间是自1970/1/1 00:00:00算起的时间,单位为毫秒,所以要用Date将它转换成日期、时间; 另外getCanonicalPath和getAbsolutePath得到的值在Windows上会是一样的,在Unix可能就会不一样。
===== 范例 6 =====
import java.io.*;
import java.util.*;
public class FileSpy {
public FileSpy(String strFilename) {
File fFile = new File(strFilename);
if (fFile.exists()) {
System.out.println("Name: " + fFile.getName());
System.out.println("Absolute path: " + fFile.getAbsolutePath());
try {
System.out.println("Canonical path: " + fFile.getCanonicalPath());
}
catch (IOException e) {
e.printStackTrace();
}
if (fFile.canWrite()) System.out.println(fFile.getName() + " is writable");
if (fFile.canRead()) System.out.println(fFile.getName() + " is readable");
if (fFile.isFile()) {
System.out.println(fFile.getName() + " is a file");
}
else if (fFile.isDirectory()) {
System.out.println(fFile.getName() + " is a directory");
}
else {
System.out.println("What is this?");
}
long lngMilliseconds = fFile.lastModified();
if (lngMilliseconds !=0) System.out.println("last modified at " + new Date(lngMilliseconds));
long lngLen = fFile.length();
if (lngLen !=0) System.out.println("size: " + lngLen);
}
else
System.out.println("file not found");
}
public static void main(String[] args) {
if (args.length == 1) {
FileSpy fileSpy1 = new FileSpy(args[0]);
}
else
System.out.println("Usage: java FileSpy Filename");
}
}
b. 建立、删除、移动、更名:
File类别提供了createNewFile、renameTo、delete作为建立(createNewFile)、删除(delete)、移动、更名(renameTo)之用,使用方式如下: (移动和更名都用renameTo,就如在Unix上档案搬移和更名都用mv一样)
===== 范例 7 =====
import java.io.*;
public class OperateFile {
public OperateFile() {
//create new file
File fNewFile = new File("C:/newfile.txt");
try {
if (fNewFile.exists() == false) {
if (fNewFile.createNewFile() == true) {
System.out.println("create c:/newfile.txt success");
}
else {
System.out.println("create c:/newfile.txt fail");
}
}
else {
System.out.println("file already exists");
}
}
catch (IOException e) {
e.printStackTrace();
}
//rename file
File fRenameFile = new File("c:/renamefile.txt");
fNewFile.renameTo(fRenameFile);
//remove file
File fRemoveFile = new File("d:/" + fRenameFile.getName());
fRenameFile.renameTo(fRemoveFile);
//delete file
try {
File fDelFile = new File(fRemoveFile.getCanonicalPath());
fDelFile.delete();
}
catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
OperateFile operateFile1 = new OperateFile();
}
}
c. 找出某特定目录里的所有档案:
File类别提供的list和listFiles都可以列出某特定目录里的所有档案,其中list传回的是String[],listFiles传回的是File[],这两个函式都会传回所有的档案和目录。
===== 范例 8 =====
import java.io.*;
public class ListAllFiles {
public ListAllFiles(String strDir) {
File fDir = new File(strDir);
File[] fAllFiles = fDir.listFiles();
for(int i=0; i
if (fAllFiles.isFile())
System.out.println("File: " + fAllFiles.getName());
else
System.out.println("Dir: " + fAllFiles.getName());
}
}
public static void main(String[] args) {
ListAllFiles listAllFiles1 = new ListAllFiles(args[0]);
}
}
3. Network I/O:
Java对网路的支援只有TCP/IP和UDP/IP,提供的类别有URL、URLConnection、Socket、ServerSocket,在这里我只打算用ServerSocket、Socket为例,来说明Network I/O。
基本上,Java的I/O不管在任何的媒体上都是将它们视为stream,所以,网路I/O和档案I/O原理也是一致的,底下的两个程式分别为server socket及client socket。在看范例之前,可以再复习一下前面的abstract…
===== 范例 9 =====
import java.net.*;
import java.io.*;
public class myServer {
public myServer(String strPort) {
int nPort = new Integer(strPort).intValue();
try {
ServerSocket ss = new ServerSocket(nPort);
Socket s = ss.accept();
OutputStream out = s.getOutputStream();
PrintStream psOut = new PrintStream(out);
String strResponse = "Hello " + s.getInetAddress() + " on port " + s.getPort() + "\r\n";
strResponse += "This is " + s.getLocalAddress() + " on port " + s.getLocalPort() + "\r\n";
psOut.print(strResponse);
s.close();
ss.close();
}
catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
myServer myServer1 = new myServer(args[0]);
}
}
===== 范例 10 =====
import java.net.*;
import java.io.*;
public class myClient {
public myClient(String strIP, String strPort) {
int nPort = new Integer(strPort).intValue();
try {
Socket s = new Socket(strIP, nPort);
InputStream in = s.getInputStream();
BufferedInputStream bisIn = new BufferedInputStream(in);
while (bisIn.available() > 0) {
byte[] b = new byte[30];
int nLen = bisIn.read(b);
System.out.println(new String(b, 0, nLen));
}
}
catch (UnknownHostException e) {
e.printStackTrace();
}
catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
myClient myClient1 = new myClient(args[0], args[1]);
}
}
4. Object Serialization:
A. 所谓Object Serialization就是把物件的”状态”储存成一系列的位元组,而这些位元组在稍候可用来恢复物件。更简单的说,Object Serialization是让物件可以以物件为储存单位。在Java中,任何物件要能Serialization,必须implements Serializable这个Interface,以下是一个简单的程式范例,可以将物件储存到e:\point.ser,或从e:\point.ser将物件恢复原值。
===== 范例 11 =====
import java.io.*;
public class ThreeDPoint implements Serializable
{
private double m_dblX, m_dblY, m_dblZ;
public ThreeDPoint(double x, double y, double z)
{
m_dblX = x;
m_dblY = y;
m_dblZ = z;
}
public void PrintXYZ()
{
System.out.println("X: " + m_dblX);
System.out.println("Y: " + m_dblY);
System.out.println("Z: " + m_dblZ);
}
public static void main(String[] args)
{
if (args[0].equalsIgnoreCase("w")) {
ThreeDPoint threeDPoint1 = new ThreeDPoint(10 ,20, 30);
try {
FileOutputStream fout = new FileOutputStream("e:\\point.ser");
ObjectOutputStream oout = new ObjectOutputStream(fout);
oout.writeObject(threeDPoint1);
oout.close();
System.out.println("write:");
threeDPoint1.PrintXYZ();
}
catch (Exception e) {
e.printStackTrace();
}
}
else if (args[0].equalsIgnoreCase("r")) {
try {
FileInputStream fin = new FileInputStream("e:\\point.ser");
ObjectInputStream oin = new ObjectInputStream(fin);
Object o = oin.readObject();
ThreeDPoint threeDPoint1 = (ThreeDPoint) o;
oin.close();
System.out.println("read:");
threeDPoint1.PrintXYZ();
}
catch (Exception e) {
}
}
} //end of main
}
B. 在Java中,一个实作某特定介面的类别,其子类别也因继承的原故而被视为实作了该介面,因此,许多没有明确宣告实作Serializable介面的类别,事实上也是可以被Serialization的。
C. 并非每个实作了Serializable介面的物件都可以被Serialization,如果这个物件继承图上的祖先,有其中一个是不可以被Serialization,那麽这个物件就不可以被Serialization。
5. Formated I/O:
在Java的I/O里,并没有所谓的型别,不管是int、long、double…最後都是以String输出,所以如果要让数字以特定格式输出,需透过Java提供的两个类别java.text.NumberFormat和java.text.DecimalFormat将数字格式化後再输出。
范例12简要说明NumberFormat如何使用,在开始使用NumberFormat时,应先用getInstance取得NumberFormat的实体,范例12中的setMaximumIntegerDigits和setMinimumFractionDigits是用来设定整数和小数的位数,另外还有setMinimumIntegerDigits和setMaximumFractionDigits也是同样功能。这些设定如有冲突,Java以最後设定的为准。
===== 范例 12 =====
import java.text.*;
public class myFormat {
public myFormat() {
NumberFormat nf = NumberFormat.getInstance();
double dblNum = Math.PI;
System.out.println(dblNum);
nf.setMaximumIntegerDigits(5);
nf.setMinimumFractionDigits(4);
System.out.println("PI: " + nf.format(dblNum));
}
public static void main(String[] args) {
myFormat myFormat1 = new myFormat();
}
}
另一个类别DecimalFormat是继承NumberFormat的子类别,它提供了更强的格式化功能,透过设定pattern,可以使我们的输出更多样化,至於Java提供的pattern有那些? 在API Document中有详细说明! 范例13仅举其一,说明DecimalFormat如何使用。
===== 范例 13 =====
import java.text.*;
public class myDecimalFormat {
public myDecimalFormat() {
DecimalFormat df = new DecimalFormat("0000.000");
double dblNum = 123.45;
System.out.println("dblNum: " + dblNum);
System.out.println("dblNum: " + df.format(dblNum));
}
public static void main(String[] args) {
myDecimalFormat myDecimalFormat1 = new myDecimalFormat();
}
}
Java IO详解那点事
阅读文本大概需要15分钟。
0x01:字节流
字节流基类
1. InputStream
InputStream:字节输入流基类,抽象类是表示字节输入流的所有类的超类。
常用方法:
// 从输入流中读取数据的下一个字节
abstract int read()
// 从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b中
int read(byte[] b)
// 将输入流中最多 len 个数据字节读入 byte 数组
int read(byte[] b, int off, int len)
// 跳过和丢弃此输入流中数据的 n个字节
long skip(long n)
// 关闭此输入流并释放与该流关联的所有系统资源
void close()
2. OutputStream
OutputStream:字节输出流基类,抽象类是表示输出字节流的所有类的超类。
常用方法:
// 将 b.length 个字节从指定的 byte 数组写入此输出流
void write(byte[] b)
// 将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此输出流
void write(byte[] b, int off, int len)
// 将指定的字节写入此输出流
abstract void write(int b)
// 关闭此输出流并释放与此流有关的所有系统资源
void close()
// 刷新此输出流并强制写出所有缓冲的输出字节
void flush()
字节文件操作流
1. FileInputStream
FileInputStream:字节文件输入流,从文件系统中的某个文件中获得输入字节,用于读取诸如图像数据之类的原始字节流。
构造方法:
// 通过打开一个到实际文件的连接来创建一个FileInputStream,该文件通过文件系统中的File对象file指定
FileInputStream(File file)
// 通过打开一个到实际文件的连接来创建一个FileInputStream,该文件通过文件系统中的路径name指定
FileInputStream(String name)
常用方法:覆盖和重写了父类的的常用方法。
// 读取f盘下该文件f://hell/test.txt
//构造方法1
InputStream inputStream = new FileInputStream(new File("f://hello//test.txt"));
int i = 0;
//一次读取一个字节
while ((i = inputStream.read()) != -1) {
// System.out.print(i + " ");// 65 66 67 68
//为什么会输出65 66 67 68?因为字符在底层存储的时候就是存储的数值。即字符对应的ASCII码。
System.out.print((char) i + " ");// A B C D
}
//关闭IO流
inputStream.close();
// 读取f盘下该文件f://hell/test.txt
//构造方法2
InputStream inputStream2 = new FileInputStream("f://hello/test.txt");
// 字节数组
byte[] b = new byte[2];
int i2 = 0;
// 一次读取一个字节数组
while ((i2 = inputStream2.read(b)) != -1) {
System.out.print(new String(b, 0, i2) + " ");// AB CD
}
//关闭IO流
inputStream2.close();
注: 一次读取一个字节数组,提高了操作效率,IO流使用完毕一定要关闭。
2. FileOutputStream
FileOutputStream:字节文件输出流是用于将数据写入到File,从程序中写入到其他位置。
构造方法:
// 创建一个向指定File对象表示的文件中写入数据的文件输出流
FileOutputStream(File file)
// 创建一个向指定File对象表示的文件中写入数据的文件输出流
FileOutputStream(File file, boolean append)
// 创建一个向具有指定名称的文件中写入数据的输出文件流
FileOutputStream(String name)
// 创建一个向具有指定name的文件中写入数据的输出文件流
FileOutputStream(String name, boolean append)
常用方法:覆盖和重写了父类的的常用方法。
OutputStream outputStream = new FileOutputStream(new File("test.txt"));
// 写出数据
outputStream.write("ABCD".getBytes());
// 关闭IO流
outputStream.close();
// 内容追加写入
OutputStream outputStream2 = new FileOutputStream("test.txt", true);
// 输出换行符
outputStream2.write("\r\n".getBytes());
// 输出追加内容
outputStream2.write("hello".getBytes());
// 关闭IO流
outputStream2.close();
注;输出的目的地文件不存在,则会自动创建,不指定盘符的话,默认创建在项目目录下;输出换行符时一定要写\r\n不能只写\n,因为不同文本编辑器对换行符的识别存在差异性。
字节缓冲流(高效流)
1. BufferedInputStream
BufferedInputStream:字节缓冲输入流,提高了读取效率。
构造方法:
// 创建一个 BufferedInputStream并保存其参数,即输入流in,以便将来使用。
BufferedInputStream(InputStream in)
// 创建具有指定缓冲区大小的 BufferedInputStream并保存其参数,即输入流in以便将来使用
BufferedInputStream(InputStream in, int size)
InputStream in = new FileInputStream("test.txt");
// 字节缓存流
BufferedInputStream bis = new BufferedInputStream(in);
byte[] bs = new byte[20];
int len = 0;
while ((len = bis.read(bs)) != -1) {
System.out.print(new String(bs, 0, len));
// ABCD
// hello
}
// 关闭流
bis.close();
2. BufferedOutputStream
BufferedOutputStream:字节缓冲输出流,提高了写出效率。
构造方法:
// 创建一个新的缓冲输出流,以将数据写入指定的底层输出流
BufferedOutputStream(OutputStream out)
// 创建一个新的缓冲输出流,以将具有指定缓冲区大小的数据写入指定的底层输出流
BufferedOutputStream(OutputStream out, int size)
常用方法:
// 将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此缓冲的输出流
void write(byte[] b, int off, int len)
// 将指定的字节写入此缓冲的输出流
void write(int b)
// 刷新此缓冲的输出流
void flush()
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("test.txt", true));
// 输出换行符
bos.write("\r\n".getBytes());
// 输出内容
bos.write("Hello Android".getBytes());
// 刷新此缓冲的输出流
bos.flush();
// 关闭流
bos.close();
0x02:字符流
字符流基类
1. Reader
Reader:读取字符流的抽象类.
常用方法:
// 读取单个字符
int read()
// 将字符读入数组
int read(char[] cbuf)
// 将字符读入数组的某一部分
abstract int read(char[] cbuf, int off, int len)
// 跳过字符
long skip(long n)
// 关闭该流并释放与之关联的所有资源
abstract void close()
2. Writer
Writer:写入字符流的抽象类.
常用方法:
// 写入字符数组
void write(char[] cbuf)
// 写入字符数组的某一部分
abstract void write(char[] cbuf, int off, int len)
// 写入单个字符
void write(int c)
// 写入字符串
void write(String str)
// 写入字符串的某一部分
void write(String str, int off, int len)
// 将指定字符添加到此 writer
Writer append(char c)
// 将指定字符序列添加到此 writer
Writer append(CharSequence csq)
// 将指定字符序列的子序列添加到此 writer.Appendable
Writer append(CharSequence csq, int start, int end)
// 关闭此流,但要先刷新它
abstract void close()
// 刷新该流的缓冲
abstract void flush()
字符转换流
1. InputStreamReader
InputStreamReader:字节流转字符流,它使用的字符集可以由名称指定或显式给定,否则将接受平台默认的字符集。
构造方法:
// 创建一个使用默认字符集的 InputStreamReader
InputStreamReader(InputStream in)
// 创建使用给定字符集的 InputStreamReader
InputStreamReader(InputStream in, Charset cs)
// 创建使用给定字符集解码器的 InputStreamReader
InputStreamReader(InputStream in, CharsetDecoder dec)
// 创建使用指定字符集的 InputStreamReader
InputStreamReader(InputStream in, String charsetName)
特有方法:
//返回此流使用的字符编码的名称
String getEncoding()
//使用默认编码
InputStreamReader reader = new InputStreamReader(new FileInputStream("test.txt"));
int len;
while ((len = reader.read()) != -1) {
System.out.print((char) len);//爱生活,爱Android
}
reader.close();
//指定编码
InputStreamReader reader = new InputStreamReader(new FileInputStream("test.txt"),"utf-8");
int len;
while ((len = reader.read()) != -1) {
System.out.print((char) len);//????????Android
}
reader.close();
注:Eclipse默认使用GBK编码,test.txt文件所以是GBK编码,当指定utf-8编码时所以会乱码。
2. OutputStreamWriter
OutputStreamWriter:字节流转字符流。
构造方法:
// 创建使用默认字符编码的 OutputStreamWriter
OutputStreamWriter(OutputStream out)
// 创建使用给定字符集的 OutputStreamWriter
OutputStreamWriter(OutputStream out, Charset cs)
// 创建使用给定字符集编码器的 OutputStreamWriter
OutputStreamWriter(OutputStream out, CharsetEncoder enc)
// 创建使用指定字符集的 OutputStreamWriter
OutputStreamWriter(OutputStream out, String charsetName)
特有方法:
//返回此流使用的字符编码的名称
String getEncoding()
字符缓冲流(高效流)
1. BufferedReader
BufferedReader:字符缓冲流,从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。
构造方法:
// 创建一个使用默认大小输入缓冲区的缓冲字符输入流
BufferedReader(Reader in)
// 创建一个使用指定大小输入缓冲区的缓冲字符输入流
BufferedReader(Reader in, int sz)
特有方法:
// 读取一个文本行
String readLine()
//生成字符缓冲流对象
BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream("test.txt")));
String str;
//一次性读取一行
while ((str = reader.readLine()) != null) {
System.out.println(str);// 爱生活,爱Android
}
//关闭流
reader.close();
2. BufferedWriter
BufferedWriter:字符缓冲流,将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。
构造方法:
// 创建一个使用默认大小输出缓冲区的缓冲字符输出流
BufferedWriter(Writer out)
// 创建一个使用给定大小输出缓冲区的新缓冲字符输出流
BufferedWriter(Writer out, int sz)
特有方法:
// 写入一个行分隔符
void newLine()
FileReader、FileWriter
FileReader:InputStreamReader类的直接子类,用来读取字符文件的便捷类,使用默认字符编码。
FileWriter:OutputStreamWriter类的直接子类,用来写入字符文件的便捷类,使用默认字符编码。
0x03: 高效流效率比对
读取f盘下的一个视频文件到项目中:文件大小29.5 MB
读取方式一:
FileInputStream inputStream = new FileInputStream("d://苍井空那点事.mp4");
FileOutputStream outputStream = new FileOutputStream("苍井空那点事.mp4");
int len;
// 开始时间
long begin = System.currentTimeMillis();
// 一次读取一个字节
while ((len = inputStream.read()) != -1) {
outputStream.write(len);
}
// 用时毫秒
System.out.println(System.currentTimeMillis() - begin);// 213195
//关闭流释放资源
inputStream.close();
outputStream.close();
读取方式二:
FileInputStream inputStream = new FileInputStream("d://苍井空那点事.mp4");
FileOutputStream outputStream = new FileOutputStream("苍井空那点事.mp4");
int len;
byte[] bs = new byte[1024];
// 开始时间
long begin = System.currentTimeMillis();
// 一次读取一个字节数组
while ((len = inputStream.read(bs)) != -1) {
outputStream.write(bs, 0, len);
}
// 用时毫秒
System.out.println(System.currentTimeMillis() - begin);// 281
inputStream.close();
outputStream.close();
读取方式三:
FileInputStream inputStream = new FileInputStream("d://苍井空那点事.mp4");
BufferedInputStream bis = new BufferedInputStream(inputStream);
FileOutputStream outputStream = new FileOutputStream("苍井空那点.mp4");
BufferedOutputStream bos = new BufferedOutputStream(outputStream);
int len;
byte[] bs = new byte[1024];
// 开始时间
long begin = System.currentTimeMillis();
while ((len = bis.read(bs)) != -1) {
bos.write(bs, 0, len);
}
// 用时毫秒
System.out.println(System.currentTimeMillis() - begin);// 78
bis.close();
bos.close();
注:由此可以看出高效缓冲流读写速度是非常快的,建议使用。
☆
往期精彩
☆
01 漫谈发版哪些事,好课程推荐
02 Linux的常用最危险的命令
03 互联网支付系统整体架构详解
04 优秀的Java程序员必须了解的GC哪些
05 IT大企业有哪些病,别被这些病毁了自己?
关注我每天进步一点点

你点的在看,我都当成了喜欢
本文分享自微信公众号 - JAVA乐园(happyhuangjinjin88)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。
关于Java IO详解和七)------随机访问文件流的介绍现已完结,谢谢您的耐心阅读,如果想了解更多关于c – 随机访问文件无法正常工作、Java IO流详解(七)——对象流(序列化与反序列化)、java IO详解、Java IO详解那点事的相关知识,请在本站寻找。
本文标签: