Java 文件流

java 文件流整理

1. 字节流文件操作(读写)的代码

public static void readFileByByte(String filePath) {
File file = new File(filePath);
InputStream inputStream = null;
OutputStream outputStream = null;
try {
inputStream = new FileInputStream(file);
outputStream = new FileOutputStream("d:/work/readFileByByte.txt");
int temp;
while ((temp = inputStream.read()) != -1) {
outputStream.write(temp);
}
} catch (Exception e) {
e.getStackTrace();
} finally {
if (inputStream != null && outputStream != null) {
try {
inputStream.close();
outputStream.close();
} catch (IOException e) {
e.getStackTrace();
}
}
}
}
2. 字符流文件操作(读写)的代码
public static void readFileByCharacter(String filePath) {
File file = new File(filePath);
FileReader reader = null;
FileWriter writer = null;
try {
reader = new FileReader(file);
writer = new FileWriter("d:/work/readFileByCharacter.txt");
int temp;
while ((temp = reader.read()) != -1) {
writer.write((char)temp);
}
} catch (IOException e) {
e.getStackTrace();
} finally {
if (reader != null && writer != null) {
try {
reader.close();
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
3. 按行(读写)的代码
public static void readFileByLine(String filePath) {
File file = new File(filePath);
BufferedReader bufReader = null;
BufferedWriter bufWriter = null;
try {
bufReader = new BufferedReader(new FileReader(file));
bufWriter = new BufferedWriter(new FileWriter("d:/work/readFileByLine.txt"));
String temp = null;
while ((temp = bufReader.readLine()) != null) {
bufWriter.write(temp+"\n");
}
} catch (Exception e) {
e.getStackTrace();
} finally {
if (bufReader != null && bufWriter != null) {
try {
bufReader.close();
bufWriter.close();
} catch (IOException e) {
e.getStackTrace();
}
}
}
}
4. 将字节流要转换成字符流的代码
private static String getOuterIp() throws IOException {
InputStream inputStream = null;
BufferedReader bufferedReader = null;
try {
URL url = new URL("http://1212.ip138.com/ic.asp");
URLConnection urlconnnection = url.openConnection();
inputStream = urlconnnection.getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "GB2312"); //字节流转字符流,并且设置编码格式
bufferedReader = new BufferedReader(inputStreamReader);
StringBuffer webContent = new StringBuffer();
String str = null;
while ((str = bufferedReader.readLine()) != null) {
webContent.append(str);
}
int ipStart = webContent.indexOf("[") + 1;
int ipEnd = webContent.indexOf("]");
return webContent.substring(ipStart, ipEnd);
} finally {
if (inputStream != null && bufferedReader != null) {
inputStream.close();
bufferedReader.close();
}
}
}
Java 随机访问文件
使用随机访问文件,我们可以从文件读取以及写入文件。
使用文件输入和输出流的读取和写入是顺序过程。
使用随机访问文件,我们可以在文件中的任何位置读取或写入。
RandomAccessFile 类的一个对象可以进行随机文件访问。我们可以读/写字节和所有原始类型的值到一个文件。
RandomAccessFile 可以使用其 readUTF() 和 writeUTF() 方法处理字符串。
RandomAccessFile 类不在 InputStream 和 OutputStream 类的类层次结构中。
模式
可以在四种不同的访问模式中创建随机访问文件。访问模式值是一个字符串。
模式
含义
"r"
文件以只读模式打开。
"rw"
该文件以读写模式打开。 如果文件不存在,则创建该文件。
"rws"
该文件以读写模式打开。 对文件的内容及其元数据的任何修改立即被写入存储设备。
"rwd"
该文件以读写模式打开。 对文件内容的任何修改立即写入存储设备。
读和写
我们通过指定文件名和访问模式来创建RandomAccessFile类的实例。
RandomAccessFile raf = new RandomAccessFile("randomtest.txt", "rw");
随机访问文件具有文件指针,当我们从其读取数据或向其写入数据时,该文件指针向前移动。
文件指针是我们下一次读取或写入将开始的光标。
其值指示光标与文件开头的距离(以字节为单位)。
我们可以通过使用其getFilePointer()方法来获取文件指针的值。
当我们创建一个RandomAccessFile类的对象时,文件指针被设置为零。
我们可以使用seek()方法将文件指针设置在文件中的特定位置。
RandomAccessFile的length()方法返回文件的当前长度。我们可以通过使用其setLength()方法来扩展或截断文件。
实例
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
public class Main {
public static void main(String[] args) throws IOException {
String fileName = "randomaccessfile.txt";
File fileObject = new File(fileName);
if (!fileObject.exists()) {
initialWrite(fileName);
}
readFile(fileName);
readFile(fileName);
}
public static void readFile(String fileName) throws IOException {
RandomAccessFile raf = new RandomAccessFile(fileName, "rw");
int counter = raf.readInt();
String msg = raf.readUTF();
System.out.println(counter);
System.out.println(msg);
incrementReadCounter(raf);
raf.close();
}
public static void incrementReadCounter(RandomAccessFile raf)
throws IOException {
long currentPosition = raf.getFilePointer();
raf.seek(0);
int counter = raf.readInt();
counter++;
raf.seek(0);
raf.writeInt(counter);
raf.seek(currentPosition);
}
public static void initialWrite(String fileName) throws IOException {
RandomAccessFile raf = new RandomAccessFile(fileName, "rw");
raf.writeInt(0);
raf.writeUTF("Hello world!");
raf.close();
}
}
Java 序列化的高级认识
序列化 ID 问题
情境:两个客户端 A 和 B 试图通过网络传递对象数据,A 端将对象 C 序列化为二进制数据再传给 B,B 反序列化得到 C。
问题:C 对象的全类路径假设为 com.inout.Test,在 A 和 B 端都有这么一个类文件,功能代码完全一致。也都实现了 Serializable 接口,但是反序列化时总是提示不成功。
解决:虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化 ID 是否一致(就是 private static final long serialVersionUID = 1L)。清单 1 中,虽然两个类的功能代码完全一致,但是序列化 ID 不同,他们无法相互序列化和反序列化。
静态变量序列化
public class Test implements Serializable {
private static final long serialVersionUID = 1L;
public static int staticVar = 5;
public static void main(String[] args) {
try {
//初始时staticVar为5
ObjectOutputStream out = new ObjectOutputStream(
new FileOutputStream("result.obj"));
out.writeObject(new Test());
out.close();
//序列化后修改为10
Test.staticVar = 10;
ObjectInputStream oin = new ObjectInputStream(new FileInputStream(
"result.obj"));
Test t = (Test) oin.readObject();
oin.close();
//再读取,通过t.staticVar打印新的值
System.out.println(t.staticVar);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
代码中的 main 方法,将对象序列化后,修改静态变量的数值,再将序列化对象读取出来,然后通过读取出来的对象获得静态变量的数值并打印出来。依照代码,这个 System.out.println(t.staticVar) 语句输出的是 10 还是 5 呢?
最后的输出是 10,对于无法理解的读者认为,打印的 staticVar 是从读取的对象里获得的,应该是保存时的状态才对。之所以打印 10 的原因在于序列化时,并不保存静态变量,这其实比较容易理解,序列化保存的是对象的状态,静态变量属于类的状态,因此 序列化并不保存静态变量。
父类的序列化与 Transient 关键字
情境:一个子类实现了 Serializable 接口,它的父类都没有实现 Serializable 接口,序列化该子类对象,然后反序列化后输出父类定义的某变量的数值,该变量数值与序列化时的数值不同。
解决:要想将父类对象也序列化,就需要让父类也实现Serializable 接口。如果父类不实现的话的,就 需要有默认的无参的构造函数。在父类没有实现 Serializable 接口时,虚拟机是不会序列化父对象的,而一个 Java 对象的构造必须先有父对象,才有子对象,反序列化也不例外。所以反序列化时,为了构造父对象,只能调用父类的无参构造函数作为默认的父对象。因此当我们取父对象的变量值时,它的值是调用父类无参构造函数后的值。如果你考虑到这种序列化的情况,在父类无参构造函数中对变量进行初始化,否则的话,父类变量值都是默认声明的值,如 int 型的默认是 0,string 型的默认是 null。
Transient 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。
对敏感字段加密
情境:服务器端给客户端发送序列化对象数据,对象中有一些数据是敏感的,比如密码字符串等,希望对该密码字段在序列化时,进行加密,而客户端如果拥有解密的密钥,只有在客户端进行反序列化时,才可以对密码进行读取,这样可以一定程度保证序列化对象的数据安全。
解决:在序列化过程中,虚拟机会试图调用对象类里的 writeObject 和 readObject 方法,进行用户自定义的序列化和反序列化,如果没有这样的方法,则默认调用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。用户自定义的 writeObject 和 readObject 方法可以允许用户控制序列化的过程,比如可以在序列化的过程中动态改变序列化的数值。
序列化存储规则
ObjectOutputStream out = new ObjectOutputStream(
new FileOutputStream("result.obj"));
Test test = new Test();
//试图将对象两次写入文件
out.writeObject(test);
out.flush();
System.out.println(new File("result.obj").length());
out.writeObject(test);
out.close();
System.out.println(new File("result.obj").length());
ObjectInputStream oin = new ObjectInputStream(new FileInputStream(
"result.obj"));
//从文件依次读出两个文件
Test t1 = (Test) oin.readObject();
Test t2 = (Test) oin.readObject();
oin.close();
//判断两个引用是否指向同一个对象
System.out.println(t1 == t2);
解答:Java 序列化机制为了节省磁盘空间,具有特定的存储规则,当写入文件的为同一对象时,并不会再将对象的内容进行存储,而只是再次存储一份引用,上面增加的 5 字节的存储空间就是新增引用和一些控制信息的空间。反序列化时,恢复引用关系,使得代码中的 t1 和 t2 指向唯一的对象,二者相等,输出 true。该存储规则极大的节省了存储空间。
SQLite详解
首先创建一个继承在 SQLiteOpenHelper 的类,并重写onCreate()和onUpgrade()方法。这个类主要用于建数据库和建表用。
public class OrderDBHelper extends SQLiteOpenHelper{
private static final int DB_VERSION = 1;
private static final String DB_NAME = "myTest.db";
public static final String TABLE_NAME = "Orders";
public OrderDBHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
@Override
public void onCreate(SQLiteDatabase sqLiteDatabase) {
// create table Orders(Id integer primary key, CustomName text, OrderPrice integer, Country text);
String sql = "create table if not exists " + TABLE_NAME + " (Id integer primary key, CustomName text, OrderPrice integer, Country text)";
sqLiteDatabase.execSQL(sql);
}
@Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {
String sql = "DROP TABLE IF EXISTS " + TABLE_NAME;
sqLiteDatabase.execSQL(sql);
onCreate(sqLiteDatabase);
}
}
再创建一个 OrderDao 类用于处理所有的数据操作方法。在 OrderDao 中实例化
OrderDBHelper:
public OrderDao(Context context) {
this.context = context;
ordersDBHelper = new OrderDBHelper(context);
}
数据库操作无外乎:“增删查改”。
对于“增删改”这类对表内容变换的操作,我们需先调用getWritableDatabase(),在执行的时候可以调用通用的execSQL(String sql)方法或对应的操作API:insert()、delete()、update()。而对“查”,需要调用getReadableDatabase(),这时就不能使用execSQL方法了,得使用query()或rawQuery()方法。
增
db = ordersDBHelper.getWritableDatabase();
db.beginTransaction();
// insert into Orders(Id, CustomName, OrderPrice, Country) values (7, "Jne", 700, "China");
ContentValues contentValues = new ContentValues();
contentValues.put("Id", 7);
contentValues.put("CustomName", "Jne");
contentValues.put("OrderPrice", 700);
contentValues.put("Country", "China");
db.insertOrThrow(OrderDBHelper.TABLE_NAME, null, contentValues);
db.setTransactionSuccessful();
删
public int delete(String table, String whereClause, String[] whereArgs) {
acquireReference();
try {
SQLiteStatement statement = new SQLiteStatement(this, "DELETE FROM " + table +
(!TextUtils.isEmpty(whereClause) ? " WHERE " + whereClause : ""), whereArgs);
try {
return statement.executeUpdateDelete();
} finally {
statement.close();
}
} finally {
releaseReference();
}
}
改
db = ordersDBHelper.getWritableDatabase();
db.beginTransaction();
// update Orders set OrderPrice = 800 where Id = 6
ContentValues cv = new ContentValues();
cv.put("OrderPrice", 800);
db.update(OrderDBHelper.TABLE_NAME,
cv,
"Id = ?",
new String[]{String.valueOf(6)});
db.setTransactionSuccessful();
查
db = ordersDBHelper.getReadableDatabase();
// select count(Id) from Orders where Country = 'China'
cursor = db.query(OrderDBHelper.TABLE_NAME,
new String[]{"COUNT(Id)"},
"Country = ?",
new String[] {"China"},
null, null, null);
if (cursor.moveToFirst()) {
count = cursor.getInt(0);
}