Android File 文件流

Java 文件流

字节流和字符流
输入 / 输出(I / O)是内存和物理设备之间的操作。输入正在将数据从物理设备复制到内存。输出正在将数据从内存复制到设备。IO 操作通过流执行。流链接到物理设备,例如磁盘文件,键盘,网络套接字。

Java 读写文件的 IO 流分两大类:

  • 字节流:
    基类:Reader 和 Writer
  • 字符流:
    基类:InputStream 和 OutputStream

BufferedInputStream 是带缓冲区的输入流,默认缓冲区大小是 8M,能够减少访问磁盘的次数,提高文件读取性能;
BufferedOutputStream 是带缓冲区的输出流,能够提高文件的写入效率。
BufferedInputStream 与 BufferedOutputStream 分别是 FilterInputStream 类和 FilterOutputStream 类的子类,实现了装饰设计模式。

示例(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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
public class MainActivity extends AppCompatActivity {
/**
* 读写 放入指定位置
*/
private String inPath = Environment.getExternalStorageDirectory()+"/a.txt";
private String outPath = Environment.getExternalStorageDirectory()+"/text.txt";

/**
* 位于项目的 data 目录下
*/
private String dataPath ;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

initView();
// 运行时权限
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {

// Should we show an explanation?
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.WRITE_EXTERNAL_STORAGE)) {

// Show an expanation to the user *asynchronously* -- don't block
// this thread waiting for the user's response! After the user
// sees the explanation, try again to request the permission.
initData();

} else {

// No explanation needed, we can request the permission.

ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
11);

// MY_PERMISSIONS_REQUEST_READ_CONTACTS is an
// app-defined int constant. The callback method gets the
// result of the request.
}
}
}

/**
* 运行时权限回调
*/
@Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
switch (requestCode) {
case 11: {
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {

// permission was granted, yay! Do the
// contacts-related task you need to do.
initData();

} else {

// permission denied, boo! Disable the
// functionality that depends on this permission.
}
return;
}

// other 'case' lines to check for other
// permissions this app might request
}
}

private void initData() {
// dataPath = this.getFilesDir() + File.separator+"a.txt";
// flowPath(inPath);

// 写入测试文件
// boolean isSave=saveFile(inPath);
// if (isSave){
// Toast.makeText(this,"写入完成",Toast.LENGTH_LONG).show();
// }

// 读写文件
// characterStream(inPath);
// byteStream(inPath);
// readFileByLine(inPath);
// transform(inPath);

// 从指定路径文件读取内容
// String con=getString(inPath);
// 从assets或raw中读取内容
// String con=getString("a.txt");
// Log.e("TAG", con);

// 删除测试文件
// deleteFiles(new File(inPath));
// deleteFiles(new File(outPath));
}


/**
* 流的一般使用流程
*/
private void flowPath(String filePath) {
// 1.创建文件对象
File file = new File(filePath);
FileReader fileReader ;
BufferedReader bufferedReader ;
StringBuffer stringBuffer = new StringBuffer();
try {
// 2.用流装载文件
fileReader = new FileReader(file);

// 如果遇到字节流要转换成字符流,则在缓冲区前加一步
// InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
// 或者需要编码转换的,则在缓冲区前加一步
// InputStreamReader inputStreamReader = new InputStreamReader(inputStream,"UTF-8");

// 3.如果用缓冲区,则用缓冲区装载流,用缓冲区是为了提高读写性能
bufferedReader = new BufferedReader(fileReader);
// 4.开始读写操作
String str ;
while ((str = bufferedReader.readLine()) != null) {
stringBuffer.append(str);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}

/**
* 字节流
*/
private void byteStream(String filePath) {
File file = new File(filePath);

InputStream inputStream = null;
OutputStream outputStream = null;

try {
inputStream = new FileInputStream(file);
outputStream = new FileOutputStream(outPath);
int temp ;
while ((temp = inputStream.read())!= -1){
outputStream.write(temp);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inputStream !=null && outputStream!=null){
try {
inputStream.close();
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

/**
* 字符流
*/
private void characterStream(String filePath) {
File file = new File(filePath);

FileReader fileReader = null;
FileWriter fileWriter = null ;

try {
fileReader = new FileReader(file);
fileWriter = new FileWriter(outPath);
int temp ;
while ((temp = fileReader.read())!=-1){
fileWriter.write((char)temp);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if (fileReader!=null && fileWriter!=null){
try {
fileReader.close();
fileWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

/**
* 按行读写
*/
private void readFileByLine(String inPath) {
File file = new File(inPath);

BufferedReader bufReader = null;
BufferedWriter bufWriter = null;
try {

bufReader = new BufferedReader(new FileReader(file));
bufWriter = new BufferedWriter(new FileWriter(outPath));

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();
}
}
}
}

/**
* 从 assets 中 或 raw 读取 txt 文件
*
* 一、共同点:
* 目录下的资源会被原封不动的拷贝到APK中,而不会像其它资源文件那样被编译成二进制的形式。
* 二、区别
* 1、最直观的就是获取它们的 InputStream 的API不一样。
* 获取assets资源:InputStream assets = getAssets().open("xxxx");
* 获取raw资源:InputStream raw = getResources().openRawResource(R.raw.xxxx)
* 2、assets 下可以创建目录结构,而 res/raw 不可以。
* 3、assets 能够动态的列出assets中的所有资源 getAssets().list(String path); ,而 res/raw 不可以。
* 4、raw 在 Android XML文件中也可以@raw/的形式引用到它,而 assets 不可以。
*
*/
public String getString(String inPath) {
InputStream inputStream =null;
BufferedReader bufferedReader = null;
try {
// 从指定文件读取内容
inputStream=new FileInputStream(new File(inPath));
// 从 main/assets 目录下读取 txt 文件
// inputStream = getAssets().open(inPath);
// 从 res/raw 目录下读取 txt 文件
// inputStream = getResources().openRawResource(R.raw.a);
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "UTF-8");
bufferedReader = new BufferedReader(inputStreamReader);
StringBuffer sb = new StringBuffer();
String line;
while ((line = bufferedReader.readLine()) != null) {
sb.append(line);
sb.append("\n");
}
return sb.toString();
} catch (UnsupportedEncodingException e1) {
e1.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if (inputStream != null && bufferedReader != null) {
try {
inputStream.close();
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}

/**
* 需要将字节流转换为字符流时
*/
private String transform(String inPath) {
InputStream inputStream = null;
BufferedReader bufferedReader = null;
try {
URL url = new URL("https://www.baidu.com/");
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);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inputStream != null && bufferedReader != null) {
try {
inputStream.close();
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}

/**
* 写入文件
*/
public boolean saveFile(String inPath){
File file = new File(inPath);
try {
//文件输出流
FileOutputStream fos = new FileOutputStream(file);
//写数据
fos.write(("zhouzhou 222").getBytes());
fos.flush();
//关闭文件流
fos.close();
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* flie:要删除的文件夹的所在位置
*/
public static void deleteFiles(File file) {
if (file.isDirectory()) {
File[] files = file.listFiles();
for (int i = 0; i < files.length; i++) {
File f = files[i];
deleteFiles(f);
}
// 如要保留文件夹,只删除文件,请注释这行
file.delete();
} else if (file.exists()) {
file.delete();
}}

private void initView() {

}
}

示例(Kotlin)

Kotlin 在执行 I / O 时继承 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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
/**
* (第一行代码第三版)
* 文件存储
*
* 它不对存储的内容进行任何格式化处理,所有数据都是原封不动地保存到文件当中的,
* 因而它比较适合存储一些简单的文本数据或二进制数据。
* 如果想保存一些较为复杂的结构化数据,就需要定义一套自己的格式规范,方便之后将数据从文件中重新解析出来。
*/
class FileActivity :BaseActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_file)

// 从文件中读取数据
val inputText = load()
if (inputText.isNotEmpty()){
// EditText
et.setText(inputText)
// 将光标移动到末尾
et.setSelection(inputText.length)
Toast.makeText(this,"Restoring succeeded",Toast.LENGTH_SHORT).show()
}
}

/**
* Context 类中提供了一个 openFileInput() 用于从文件中读取数据,它接收一个参数,即要读取的文件名。
*/
private fun load(): String {
val content = StringBuilder()
// FileInputStream -> InputStreamReader -> BufferedReader
try {
// 返回一个 FileInputStream 对象
val input = openFileInput("data")
// 借助 FileInputStream 构建出一个 InputStreamReader 对象,
// 接着使用 InputStreamReader 构建出一个 BufferedReader 对象,
// 然后通过 BufferedReader 将文件中的数据一行行读取出来,拼接到 SB 对象当中。
val reader = BufferedReader(InputStreamReader(input))
reader.use{
// forEachLine 函数也是 Kotlin 提供的一个内置扩展函数
// 它会将读取到的每行内容都回调到 Lambda 表达式中,然后在表达式中完成拼接即可。
reader.forEachLine {
content.append(it)
}
}
}catch (e: IOException){
e.printStackTrace()
}
return content.toString()
}

/**
* Context 类中提供了一个 openFileOutput(),可以用于将数据存储到指定的文件中。它接收两个参数:
* 第一个参数是文件名,在文件创建的时候使用,注意这里指定的文件名不可以包含路径,
* 因为所有的文件都默认存储到/data/data/<package name>/files/目录下。
* 第二个参数是文件的操作模式,主要有两种模式可选:(还有两种危险的模式已在 Android 4.2 版本中被废弃)
* MODE_PRIVATE:默认的,表示当指定相同文件名时,所写入的内容将会覆盖原文件中的内容。
* MODE_APPEND:表示如果该文件已存在,就往文件里面追加内容,不存在就创建新文件。
*/
private fun save(inputText:String){
// FileOutputStream -> OutputStreamWriter -> BufferedWriter -> write() 将内容写入文件
try {
// 返回一个 FileOutputStream 对象,得到这个对象后就可以使用 Java 流的方式将数据写入文件中了。
val output = openFileOutput("data",Context.MODE_PRIVATE)
// 借助 FileOutputStream 对象构建出一个 OutputStreamWriter 对象,
// 接着再使用 OutputStreamWriter 对象构建出一个 BufferedWriter 对象,
// 然后通过 BufferedWriter 对象将文本内容写入文件中。
val writer = BufferedWriter(OutputStreamWriter(output))
// use 函数是 Kotlin 提供的一个内置扩展函数,它会保证在 Lambda 表达式中的代码全部执行完之后自动将外层的流关闭,
// 这样就不需要写一个 finally 语句,手动关闭流了。
writer.use {
it.write(inputText)
}
}catch (e: IOException){
e.printStackTrace()
}
}

/**
* 退出时保存数据
*
* 可借助工具查看:Device File Explorer
* 一般在 AS 右下角,也可以通过快捷键(command 或 Ctrl + shift + A)打开搜索功能。
* 这个工具其实就相当于一个设备文件浏览器,比如此项目:
* /data/data/com.example.myapplication/files/data(双击此文件,就能看到保存的内容了。)
*/
override fun onDestroy() {
super.onDestroy()
val inputText = et.text.toString()
save(inputText)
}

companion object{
fun actionStart(context: Context){
val intent = Intent(context, FileActivity::class.java)
context.startActivity(intent)
}
}
}

Java 随机访问文件

使用文件输入和输出流的读取和写入是顺序过程。
使用随机访问文件,我们可以在文件中的任何位置读取或写入。

RandomAccessFile 类的一个对象可以进行随机文件访问。我们可以读 / 写字节和所有原始类型的值到一个文件。
RandomAccessFile 可以使用其 readUTF () 和 writeUTF () 方法处理字符串。
RandomAccessFile 类不在 InputStream 和 OutputStream 类的类层次结构中。

模式

可以在四种不同的访问模式中创建随机访问文件。访问模式值是一个字符串:

模式 含义
“r” 文件以只读模式打开。
“rw” 该文件以读写模式打开。
如果文件不存在,则创建该文件。
“rws” 该文件以读写模式打开。
对文件的内容及其元数据的任何修改立即被写入存储设备。
“rwd” 该文件以读写模式打开。
对文件内容的任何修改立即写入存储设备。

读和写

1
2
3
4

// 通过指定文件名和访问模式来创建RandomAccessFile类的实例
RandomAccessFile raf = new RandomAccessFile("randomtest.txt", "rw");

随机访问文件具有文件指针,当我们从其读取数据或向其写入数据时,该文件指针向前移动。
文件指针是我们下一次读取或写入将开始的光标。
其值指示光标与文件开头的距离(以字节为单位)。
我们可以通过使用其 getFilePointer () 方法来获取文件指针的值。
当我们创建一个 RandomAccessFile 类的对象时,文件指针被设置为零。
我们可以使用 seek () 方法将文件指针设置在文件中的特定位置。
RandomAccessFile 的 length()方法返回文件的当前长度。我们可以通过使用其 setLength () 方法来扩展或截断文件。

示例

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/**
* 随机访问文件
*/
private void readRandomAccessFile(String inPath) {
String fileName = inPath;
File fileObject = new File(inPath);

if (!fileObject.exists()) {
try {
initialWrite(fileName);
} catch (IOException e) {
e.printStackTrace();
}
}
try {
readFile(fileName);
readFile(fileName);
} catch (IOException e) {
e.printStackTrace();
}
}

public static void readFile(String fileName) throws IOException {
// "rw" 该文件以读写模式打开。 如果文件不存在,则创建该文件。
RandomAccessFile raf = new RandomAccessFile(fileName, "rw");
int counter = raf.readInt();
String msg = raf.readUTF();

Log.e("TAG","counter:"+counter);
Log.e("TAG","msg:"+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();
}

链接

传送门GitHub