io是什么
io是输入输出流,他的作用就是对外部进行数据交互使用的,内部和外部分别表示的是内存以及内存以外的,外部包括手机文件,电脑文件和网络, 服务器等都称为外部 ,外部统称为文件和网络
什么叫输入输出
输入就是从文件或者网络上的数据读取到内存里面,输出就是把内存的数据写入到文件或者发送到网络上。
java io的api作用就是为了和外部进行读写操作
先上一个简单例子
public class Main {
public static void main(String[] args[]){
try {
OutputStream outputStream = new FileOutputStream("./app/text.txt");
outputStream.write('a');
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
文件输出流的方式在文件中写入a,我们运行这段代码,就能够在app文件目录下找到text文件,打开文件能够看到文件内容 "a"
new FileOutputStream 通过文件存放的路径,就能够在文件路径下新建txt文件,通过outputStream.write "a"这个字符就写到文件中
再上一个输入流的例子
private static void io2(){
try {
InputStream inputStream = new FileInputStream("./app/text.txt");
System.out.println((char) inputStream.read());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
这样一个简单的输入流就算写好了,简单讲解下,刚才我们定义了输出流也就是新建一个text的文件并在文件上写入一个a字符,通过输入流把文件存放到内存中,这里我们通过打印的方式把文件的内容打印到控制台 截图如下:
这样一个简单的文件输入输出流就算完成了
但我们在实际的开发中面对的问题要比这个复杂的多,并且在实际开发中输入输出流读写完数据后需要关闭掉输入输出流。
java7有一个新特性可以不用关闭流,也能实现关闭流的效果也就是在try块中写相关代码,代码如下:
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
private static void io1(){
try (OutputStream outputStream = new FileOutputStream("./app/text.txt")){
outputStream.write('a');
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
字符 Reader 和Writer
这两者可以配合输入输出流一起使用,Reader和Writer又是什么呢,他们可以自动的把InputStream和OutputStream中的字节数据转换成字符数据
一般情况下InputStream和Reader,OutputStream和Writer结合使用
我们还可以在上面的基础上再套一层BufferReader或者BufferWriter,这又是什么呢?这个就是相当于给你的字符加一个buffer(缓冲),这样能够让我们读取或者写入的内容更加的稳定高效,主要的功能就是提高读写性能。
try(InputStream inputStream = new FileInputStream("./app/text.txt");
Reader reader = new InputStreamReader(inputStream);
BufferedReader bufferedReader = new BufferedReader(reader)){
System.out.println(bufferedReader.readLine());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
这里我在text文件中外加了一些字符组合成子长串的字符再次运行,这样就可以不用使用单个的字符读取就能很方便的读取数据了
outputStream.flush()
这又是什么呢,flush使用的主要场景就是在输出流中用的比较常见,作用就是把没有到文件管道的数据flush到文件中,也就不会因为管道中一直存在一个比较难处理的文件数据而一直运行,这样就避免程序出问题的情况,从而提高性能。
文件复制原理
文件复制就集合了输入输出流相关的知识,就是通过输入流的数据通过输出流把数据copy到另外一个文件的过程,这里要注意的就是这里是使用的字节的方式输入输出的,如果文件比较大的话可以给他外加一个buffer,还有一点就是判断readCount不为-1,这是什么呢,我这里是使用了一个循环,其实任何的读取或者写入文件都可以使用这种方式来操作,这也是比较通用的。如果不等于-1也就是管道的数据还有那么就继续读写数据直到为-1管道数据没有,也就是读写操作完成。
private static void io4(){
try(InputStream inputStream = new FileInputStream("./app/text.txt");
OutputStream outputStream = new FileOutputStream("./app/text_copy.txt")){
byte[] bytes = new byte[1024];
int readCount;
while ((readCount = inputStream.read(bytes))!=-1){
outputStream.write(bytes,0,readCount);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
执行结果:
nio的简单使用
nio使用的是RandomAccessFile来创建文件对象,这里需要注意的是他有两个关键参数,这个也比io流要简洁一些。根据上面的讲解,我们知道io流有输入和输出流。而nio的输入输出流通过参数就可以配置。第一个参数是文件路径的配置,第二个参数就是配置你的这个文件是输入的还是输出的"r"读,"w"写这个其实也很好理解。
ByteBuffer的原理
ByteBuffer的读写操作,中学我们了解了游标卡尺,ByteBuffer的原理就有点类似于游标卡尺,
position和capacitylimit,他们分别表示的是读或写的位置以及我们自己设置的容器大小(这个参数是在new 构造器的一个参数),通过游标position读或写一个字符,游标position向右移动一个字符长度的位置,读或写的思路如下:
注意:为了更好的理解NIO中的Bytebuffer,可以把ByteBuffer看着是内存,如果相对于ByteBuffer自身而言是读的操作,反之是写的操作。
NIO在网络中实现通信
private static void nio7(){
try {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(8080));
SocketChannel socketChannel = serverSocketChannel.accept();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
while (socketChannel.read(byteBuffer)!=-1){
byteBuffer.flip();
socketChannel.write(byteBuffer);
byteBuffer.clear();
}
} catch (IOException e) {
e.printStackTrace();
}
}
注意:大部分的网络io操作都是阻塞的方式进行网络通信的,阻塞处serverSocketChannel.accept()
什么意思,也就是如果没有网络通信这段代码是阻塞的,当进行网络通信这段代码就开始正常工作,实现正常网络通信操作。
有些读者会问了,为什么要用阻塞的不用阻塞可以吗?NIO中也有这样的api,可以通过socketChannel.configureBlocking(false);
但是我不建议大家使用这个api,为什么。如果没有阻塞,那么在没有通信的情况下也能正常执行代码,就会出现我们拿不到socketChannel,导致阻塞后面的代码因为socketChannel为null而使我们的程序crash ,异常也就是空指针异常,大家可以自行测试下
Okio
由于okio配置不是androidstudio自带的所以我们这里需要添加依赖
okio依赖链接
okio也是通过输入输出流的方式对文件进行io操作,只不过okio的输入输出名称不一样,我们可以对比理解下,通过上面的讲解,io的输入使用的InputStream 可以对比okio的source ,io的输出使用的是OutputStream可以对比与okio的sink,这样我们就可以更快速的了解到okio了,okio也是支持buffer功能的。
还是上面的例子,在控制台通过okio读取text的内容
一种方式是通过实例 buffer也就是通过new Buffer() ,然后通过source.read(buffer) 这样我们就可以把数据缓冲到buffer中了
当然okio提供了带有buffer的BufferedSource,完成对文件的读操作
private static void okio9(){
try(BufferedSource source = Okio.buffer(Okio.source(new File("./app/text.txt")))){
System.out.println(source.readUtf8());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
针对buffer,值得注意的是在buffer中写入数据使用的是输出而不是使用的输入buffer.outputStream()
传统io和okio进行交互
private static void okio10(){
Buffer buffer = new Buffer();
try(BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(buffer.outputStream()));) {
bufferedWriter.write("hahahhaha");
bufferedWriter.flush();
System.out.println("read:"+buffer.readUtf8());
} catch (IOException e) {
e.printStackTrace();
}
}
通过一个简单的例子来了解下io和okio的交互,如上,在我们实际的开发中也会遇到这样的情况,就是两者结合起来用有可能会更好些
上面我们也讲到从buffer缓冲中写入数据使用的是outputstream输出的方式,从这个例子也能够感受到 然后通过buffer.readUtf8()把数据读取出来,这里也可以把buffer理解为外部对象
这里我把结果打印出来,满足下我小小小的虚荣心,这也能让我们感受到对原理的理解通过实践来验证这些理论知识其实也能让我们更好的理解这些理论知识。
讲到这里我们就把io和okio相关的知识点讲完了,如果有疑问的地方或者比我理解的更深入或好的可以在评论区留言。如有写错误地地方欢迎指正。
代码传送门