读写文件
读写 CSV 文件
CSV: Comma-Separated Values 逗号分隔的文本文件 读写csv文件和读写普通文件类似;写的时候给数据之间添加上逗号。
设定存储路径和文件名:
private static final String FILE_FOLDER =
Environment.getExternalStorageDirectory().getAbsolutePath()
+ File.separator + "AboutView" + File.separator + "data";
private static final String FILE_CSV = "about_data.csv";
获取应用内的存储路径
File appDir = getFilesDir();
if (appDir.exists()) {
Log.d(TAG, "appDir.exists(): " + appDir.exists());
Log.d(TAG, "appDir.isDirectory(): " + appDir.isDirectory());
Log.d(TAG, "getFilesDir().getAbsolutePath(): " + getFilesDir().getAbsolutePath());
for (File c : appDir.listFiles()) {
if (c.isDirectory()) {
Log.d(TAG, c.getAbsolutePath() + File.separator);
} else {
Log.d(TAG, c.getAbsolutePath());
}
}
}
// appDir.exists(): true
// appDir.isDirectory(): true
// getFilesDir().getAbsolutePath(): /data/data/com.rustfisher.anmediacodec/files
// /data/data/com.rustfisher.anmediacodec/files/a.log
// /data/data/com.rustfisher.anmediacodec/files/log_folder/
写CSV文件
写文本文件。使用 FileOutputStream
来向文件尾部添加数据
FileOutputStream.write(byte[] data)
向文件流写入字节数据。
class WriteData2CSVThread extends Thread {
short[] data;
String fileName;
String folder;
StringBuilder sb;
public WriteData2CSVThread(short[] data, String folder, String fileName) {
this.data = data;
this.folder = folder;
this.fileName = fileName;
}
private void createFolder() {
File fileDir = new File(folder);
boolean hasDir = fileDir.exists();
if (!hasDir) {
fileDir.mkdirs();// 这里创建的是目录
}
}
@Override
public void run() {
super.run();
createFolder();
File eFile = new File(folder + File.separator + fileName);
if (!eFile.exists()) {
try {
boolean newFile = eFile.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
try {
FileOutputStream os = new FileOutputStream(eFile, true);
sb = new StringBuilder();
for (int i = 0; i < data.length; i++) {
sb.append(data[i]).append(",");
}
sb.append("\n");
os.write(sb.toString().getBytes());
os.flush();
os.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
读CSV文件
使用 BufferedReader
读取每一行内容;读出来的数据带有逗号分隔符
class ReadCSVThread extends Thread {
String fileName;
String folder;
public ReadCSVThread(String folder, String fileName) {
this.folder = folder;
this.fileName = fileName;
}
@Override
public void run() {
super.run();
File inFile = new File(folder + File.separator + fileName);
final StringBuilder cSb = new StringBuilder();
String inString;
try {
BufferedReader reader =
new BufferedReader(new FileReader(inFile));
while ((inString = reader.readLine()) != null) {
cSb.append(inString).append("\n");
}
reader.close();
} catch (Exception e) {
e.printStackTrace();
}
runOnUiThread(new Runnable() {
@Override
public void run() {
mCSVTv.setText(cSb.toString());// 显示
}
});
}
}
读写二进制文件
以流的形式来读写文件
写文件
主要代码:
DataOutputStream os = new DataOutputStream(new FileOutputStream(file, true));
这里的 true 表示在文件尾追加数据。
写二进制文件时,文件路径和文件名分开传递。如果路径不存在,则用 mkdirs()
创建;若 文件不存在,用 createNewFile()
来创建新文件。
DataOutputStream
的写数据方法各有不同(以 Galaxy Note4 为例)
writeByte
写入1个字节writeInt
写入4个字节 big-endianwriteFloat
写入4个字节 使用 Float 类中的 floatToIntBits 方法将 float 参数转换 为一个 int 值,然后将该 int 值以 4-byte 值形式写入基础输出流中,先写入高字节。 如果没有抛出异常,则计数器 written 增加 4。writeDouble
使用 Double 类中的 doubleToLongBits 方法将 double 参数转换为一个 long 值,然后将该 long 值以 8-byte 值形式写入基础输出流中,先写入高字节。如果没有抛出异常,则计数器 written 增加 8。
其他方法可以参见 DataOutputStream
的API文档
class SaveHexFileThread extends Thread {
private int mmValue1;
private int mmValue2;
private int mmValue3;
private String mmDir;
private String mmName;
private HEX_TYPE type;
// 文件路径和文件名分开传送
public SaveHexFileThread(HEX_TYPE type, int v1, int v2, int v3,
String dir, String name) {
this.mmValue1 = v1;
this.mmValue2 = v2;
this.mmValue3 = v3;
mmDir = dir;
mmName = name;
this.type = type;
}
public void saveData() {
try {
File file = new File(mmDir + File.separator + mmName);
if (!file.exists()) {
boolean newFile = file.createNewFile();
Log.d(TAG, "创建新文件 " + file.getName() + " " + newFile);
}
DataOutputStream os = new DataOutputStream(new FileOutputStream(file, true));
switch (type) {
case BYTE:
os.writeByte(mmValue1);
os.writeByte(mmValue2);
os.writeByte(mmValue3);
break;
case INT:
os.writeInt(mmValue1);
os.writeInt(mmValue2);
os.writeInt(mmValue3);
break;
case FLOAT:
os.writeFloat(mmValue1);
os.writeFloat(mmValue2);
os.writeFloat(mmValue3);
break;
case DOUBLE:
os.writeDouble(mmValue1);
os.writeDouble(mmValue2);
os.writeDouble(mmValue3);
break;
}
os.close();
} catch (IOException ioe) {
Log.e(TAG, "write data error ", ioe);
}
}
private void createFiles() {
File fileDir = new File(mmDir);
boolean hasDir = fileDir.exists();
if (!hasDir) {
fileDir.mkdirs();// 这里创建的是目录
}
}
@Override
public void run() {
super.run();
createFiles();
saveData();
new ReadAllHexThread(mmDir).start();
}
}
读取文件
以什么形式写的文件,就以什么形式读取。以免弄错数据。
使用 DataInputStream
来读取数据,拼接后显示出来。
关键代码:
File file = new File(mmDir + File.separator + fileName);
try {
if (file.exists()) {
DataInputStream is = new DataInputStream(new FileInputStream(file));
int readRes = is.read(dataBuffer);
StringBuilder contentBuilder = new StringBuilder(type.toString());
contentBuilder.append(": \n");
for (int i = 0; i < readRes; i++) {
contentBuilder.append(Integer.toHexString(dataBuffer[i] & 0xff));
contentBuilder.append(", ");
}
final String finalContent = contentBuilder.toString();
runOnUiThread(new Runnable() {
@Override
public void run() {
// update UI
}
});
} else {
runOnUiThread(new Runnable() {
@Override
public void run() {
// update UI
}
});
}
} catch (Exception e) {
e.printStackTrace();
}
可以把二进制文件复制到电脑上,用软件(例如Hex Editor Neo)来查看里面的数据。
读写 json 文件
这里讲的 json 是指 key-value 类型的文本文件。
例如
{
"key1": 1,
"key2": "Rust Fisher"
}
接下来的例子会分为几个步骤:
- 定义数据类
- 写文件
- 读文件
定义数据类
为了更直观,我们定义一个类 Info1 来存放数据
public class Info1 {
private String name;
private long time;
// 构造器和get set方法省略
@NonNull
@Override
public String toString() {
return name + ", " + time;
}
}
重写了 toString
方法,打 log 的时候可以更方便地看到感兴趣的信息。
现在这个类是用来模拟业务数据的。实际工程中可能是后台传回的具体数据。
写文件
为了更方便操作 json,我们使用 Google 的 gson 库来操作 json。
dependencies {
implementation 'com.google.code.gson:gson:2.8.5'
}
目标文件存放在 app 内部存储中。
mFile1Path = getFilesDir().getAbsolutePath() + File.separator + "rwFile"
+ File.separator + "jsonFile1.json";
读写文件等 io 操作,应该放在子线程内。
关键代码:
private void write1(Info1 info1) {
Gson gson = new Gson();
String jsonText = gson.toJson(info1);
Log.d(TAG, "write1: jsonText: " + jsonText);
File file = new File(mFile1Path);
try {
if (file.exists()) {
boolean d1 = file.delete();
Log.d(TAG, "delete old file: " + d1);
}
File dir = file.getParentFile();
if (dir != null && !dir.exists()) {
boolean m1 = dir.mkdirs();
Log.d(TAG, "write1: create dir: " + m1);
}
boolean c1 = file.createNewFile();
Log.d(TAG, "create new file: " + c1);
OutputStream os = new FileOutputStream(file);
os.write(jsonText.getBytes());
os.flush();
os.close();
} catch (IOException e) {
e.printStackTrace();
Log.e(TAG, "write1: ", e);
}
}
使用 Gson.toJson
方法,把一个对象转换为 json 格式的字符串。 然后用 FileOutputStream 写入文件中。
执行这个方法后,我们可以用 as 的 Device File Explorer
面板找到这个文件。
本例中,文件路径是
/data/data/com.rustfisher.tutorial2020/files/rwFile/jsonFile1.json
用 as 双击它可以看到内容。例如:
{"name":"RustFisher","time":1605266868256}
读文件
从文件中读出文本,然后转换为相应的对象。
private Info1 read1() {
File file = new File(mFile1Path);
if (file.exists() && file.isFile()) {
try {
BufferedReader reader = new BufferedReader(new FileReader(file));
String inString;
StringBuilder sb = new StringBuilder();
while ((inString = reader.readLine()) != null) {
sb.append(inString);
}
Gson gson = new Gson();
return gson.fromJson(sb.toString(), Info1.class);
} catch (Exception e) {
e.printStackTrace();
}
} else {
Log.d(TAG, "read1: 文件不存在 " + file);
}
return null;
}
用 BufferedReader 读出文件的字符串。 用 Gson.fromJson
方法将其转换成目标对象。
小结一下:
- 首先自定义一个类,用来模拟业务数据
- 确定文件存放路径,例如存放在app内部路径
Context.getFilesDir()
- 将对象转换为json格式字符串,使用
Gson.toJson
方法 - json格式字符串转为对象,使用
Gson.fromJson
方法 - 文件操作,把文本写入文件FileOutputStream
- 读取文件中的文本,BufferedReader
IO 相关面试题
1. java中有几种类型的流?jdk为每种类型的流提供了一些抽象类以供继承,请说出他们分别是那些类?
- 字节流:继承自 InputStream 和 OutputStream
- 字符流:继承自 InputStreamReader 和 OutputStreamWriter
2. 字节流有字符流的区别?
- 字节流操作的最基本的单元是字节,字符流操作的基本单位是字符,也就是Unicode码元。
- 字节流默认不使用缓冲区,字符流使用缓冲区。
- 在硬盘上的所有文件都是以字节形式存在的(图片,声音,视频),而字符只在内存中才会形成。
3. 简单介绍一下 io 流?
- IO 流是用来进行文件的数据传输,java对数据操作主要通过流的方式,操作的类都在IO包中,按流的走向可以分为输入流和输出流两种。
- 常见的应用是:文件复制,文件上传、文件下载。
4. 输入流和输出流?
- 读数据是输入流
- 写数据是输出流
- 此时对象代表的是 java 程序。
5. IO流的分类?
6. 什么叫缓冲区? 有什么作用?
- 缓冲区就是一片特殊的内存
- 在程序频繁的操作内存或者数据库时,为了提升性能,可以将程序先读到内存中的一块区域中,以后可以从内存中直接读取,这样读取速度较块,提升性能。
- 在字符流的操作中,所有字符都是在内存中形成的,在输出前会将所有的内容都保存在内存之中,所以使用了缓冲区暂存数据。
7.什么是 java 的序列化,如何实现 java 的序列化,请解释一下 serializable 接口的作用?
- java 对象的序列化是指将 java 对象写入 io 流中,与此对应的是,对象的反序列化,则从 io 流中恢复该 java 对象。
- 如果要让某个对象支持序列化机制,则必须让它的类是可序列化的,必须实现serializable接口或者Externalizable接口。
- 序列化:把对象转换为字节序列的过程称为对象的序列化。
- 反序列化:把字节序列恢复为对象的过程称为对象的反序列化。
8. io流中常见方法?
- flush,字符流中刷新缓存区的方法。
- close,关闭io流。
- read,读字节,write,写字节。