文章目录
流
流的理解
在计算机科学中,流(Stream)是数据处理的一种抽象模型。数据流可以被看作是数据项的序列,这些数据项可以逐个地被处理,而无需一次性将所有数据项加载到内存中。
流的概念常用于输入/输出(I/O)系统,如文件读写、网络通信等。例如,当我们从文件中读取数据时,可以将文件看作是一个输入流,我们可以逐个地读取文件中的数据项,而无需一次性将整个文件加载到内存中。同样,当我们向文件中写入数据时,可以将文件看作是一个输出流,我们可以逐个地向文件中写入数据项。
在gRPC中,流式RPC允许我们在一个RPC调用中发送或接收多个消息。例如,服务器流式RPC允许服务端连续发送多个响应给客户端,客户端可以在任何时候从这个“流”中读取响应。同样,客户端流式RPC允许客户端连续发送多个请求给服务端,服务端可以在任何时候从这个“流”中读取请求。
总的来说,流提供了一种处理大量数据的有效方式,特别是对于那些无法一次性加载到内存中的大数据集。
流怎么处理大数据量?
流(Stream)处理大数据集的方式主要是通过逐个处理数据项,而不是一次性加载整个数据集到内存中。这种方式可以有效地处理大数据集,因为它不需要大量的内存,并且可以立即开始处理数据,而不需要等待整个数据集加载完成。
流的处理过程通常包括以下几个步骤:
-
读取数据:从数据源(如文件、网络等)读取一部分数据到内存中。这部分数据通常被称为缓冲区(Buffer)。
-
处理数据:在内存中处理缓冲区中的数据。处理方式取决于具体的应用,可能包括解析、转换、过滤、聚合等操作。
-
输出数据:将处理后的数据输出到目标地方(如文件、网络等)。如果数据需要进一步处理,也可以输出到另一个流中。
-
重复步骤1-3,直到所有数据都被处理。
流的处理过程可以是同步的,也可以是异步的。在同步模式下,每个步骤都是按顺序执行的,即必须等待一个步骤完成后才能开始下一个步骤。在异步模式下,多个步骤可以同时执行,例如,可以在读取数据的同时处理数据,这可以进一步提高处理效率。
流的底层实现取决于具体的系统和语言。在许多系统和语言中,流是通过操作系统的文件或网络I/O接口实现的。例如,在Unix和Linux系统中,流可以通过文件描述符(File Descriptor)实现,文件描述符是一个用于表示打开的文件或网络连接的整数。在Java和Python等语言中,流可以通过类似于InputStream和OutputStream的类实现,这些类提供了读取和写入数据的方法。
流每次处理的数据量大小
流每次读取数据的数量,也就是缓冲区的大小,取决于具体的应用和系统。缓冲区的大小可以影响到数据处理的效率和内存使用。
如果缓冲区太小,那么流可能需要频繁地从数据源读取数据,这可能导致大量的I/O操作,从而降低处理效率。另一方面,如果缓冲区太大,那么可能会占用大量的内存,这可能导致内存不足的问题。
在许多系统和语言中,缓冲区的大小通常可以配置。例如,在Java的BufferedReader类中,可以在构造函数中指定缓冲区的大小。在Python的open函数中,可以通过buffering参数指定缓冲区的大小。
在实际应用中,缓冲区的大小通常需要根据数据的特性和系统的性能进行调整。例如,如果数据源是一个慢速的网络连接,那么可能需要使用一个较大的缓冲区来减少I/O操作。如果数据是大量的小文件,那么可能需要使用一个较小的缓冲区来减少内存使用。
Java IO流
Java的IO流是实现输入/输出的基础,在Java中把不同的输入/输出源里的有序数据抽象表述为“流”(Stream),简化了输入输出处理。流是指一连串的流动的字符,是以先进先出的方式发送和接收数据的通道,通过流的方式允许Java程序使用相同的方式来访问不同的输入/输出源。Java把所有的流操作相关的类都放在java.io包中,用来实现输入/输出功能。
Java IO流的分类
按流向来分,流可分为输入流和输出流:
- 输入流:只能从中读取数据,不能向其写入数据,主要由InputStream和Reader作为基类
- 输出流:只能向其写入数据,不能从中读取数据,主要由OutputStream和Writer作为基类
按所操作的数据单元可分为字节流和字符流:
- 字节流:操作的数据单元是8位的字节,主要由InputStream类和OutputStream作为基类
- 字符流:操作的数据单元是16位的字符,主要由Reader和Writer作为基类
输入流与输出流
流分为输入流和输出流,输入/输出流是相对于计算机内存来说的,如果数据输入到内存,则称为输入流,如果是从内存中输出则称为输出流。Java的输入流主要由InputStream类和Reader类作为基类而输出流则主要由OutputStream类和Write类作为基类。构造流对象时往往会和数据源(如文件)联系起来。
输入/输出流又分为字节流和字符流两种形式。
InputStream类常用方法
方法 | 说明 |
---|---|
int read() | 从输入流中读取下一个字节数据 |
int read(byte[] b) | 从输入流中读取数据,并将数据存储在缓冲区数组b中,返回实际读取的字节数 |
int read(byte[] b,int off,int len) | 从输入流中读取最多len长度的字节,保存到字节数组b中保存的位置从off开始 |
void close() | 关闭输入流 |
- InputStream类的常用子类有FileInputStream,用于从文件中读取数据
OutputStream类常用方法:
方法 | 说明 |
---|---|
int write() | 将指定的字节数据写入到此输出流中 |
int write(byte[] buf) | 将数组buf中的所有字节写入到此输出流中 |
int write(byte[] buf,int off,int len) | 将字节数组中从偏移量off开始的长度为len的字节数据输出到输出流中 |
void close() | 关闭输出流 |
- OutputStream类的常用子类有FileOutputStream,用于向文件中写入数据
字节流和字符流
字节流是8位通用字节流,其基本单位是字节。字节流的基类是InputStream类和OutputStream类,它们是抽象类。
字符流是16位Unicode字符流,基本单位是Unicode字符。字符流最适合用来处理字符串和文本,因为它们支持国际上的大多数字符集和语言。字符流的基类是Reader类和Writer类,它们也是抽象类。
Reader类常用方法
方法 | 说明 |
---|---|
int read() | 从输入流中读取单个字符 |
int read(char[] b) | 从输入流中读取最多c.length个字符,保存到字符数组c中,返回实际读取的字符数 |
int read(char[] b,int off,int len) | 从输入流中读取最多length个字符,保存到字符数组c中,保存的位置从off位置开始,返回实际读取的字节数 |
void close() | 关闭字符输入流 |
- Reader类的常用子类为BufferedReader,接受Reader对象作为参数,并对其添加字符缓冲器
Writer类常用方法
方法 | 说明 |
---|---|
int write(String str) | 将str字符串里包含的字符输出到指定的输出流中 |
int write(String str,int off,int len) | 将str字符串里从off位置开始,长度为len的多个字符输出到输出流中 |
void close() | 关闭输出流 |
void flush() | 刷新输出流 |
void close() | 关闭字符输入流 |
- Writer类的常用子类为BufferedWriter,用于将数据缓冲到字符输出流
注:
- 在操作字节流与字符流时有一个区别,字符流在操作时使用了缓冲区(内部存储器),而字节流在操作时直接操作文件,不会使用缓冲区。
- 所有的这些方法在出现错误时都会抛出IOException异常
OkHttp3
OkHttp3是一个开源的HTTP客户端,用于发送网络请求。它的API设计简洁,易于使用,同时提供了强大的功能,如连接池、GZIP压缩、HTTP/2支持等。
使用OkHttp3发送GET请求
以下是一个使用OkHttp3发送GET请求的基本示例:
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
public class OkHttpExample {
public static void main(String[] args) throws Exception {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("http://www.example.com")
.build();
try (Response response = client.newCall(request).execute()) {
System.out.println(response.body().string());
}
}
}
在这个示例中,我们首先创建了一个OkHttpClient实例,然后创建了一个Request实例,指定了请求的URL。然后,我们调用OkHttpClient的newCall方法创建一个Call实例,然后调用execute方法发送请求并获取响应。最后,我们打印出响应的内容。
使用OkHttp3发送POST请求
OkHttp3还支持其他类型的请求(默认是GET),如POST、PUT、DELETE等,可以通过Request.Builder的method方法或者对应的快捷方法(如post、put)来设置。例如,以下是一个发送POST请求的示例:
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.MediaType;
public class OkHttpPostExample {
public static void main(String[] args) throws Exception {
OkHttpClient client = new OkHttpClient();
MediaType JSON = MediaType.get("application/json; charset=utf-8");
RequestBody body = RequestBody.create(JSON, "{\"name\":\"John Doe\"}");
Request request = new Request.Builder()
.url("http://www.example.com")
.post(body)
.build();
try (Response response = client.newCall(request).execute()) {
System.out.println(response.body().string());
}
}
}
在这个示例中,我们首先创建了一个RequestBody实例,指定了请求的内容类型和内容。然后,我们在创建Request实例时调用了post方法,将RequestBody实例传入,从而创建了一个POST请求。其他步骤与前面的示例相同。
使用OkHttp3需要注意的点
在生产环境中使用OkHttp3时,有几个关键点需要注意:
-
错误处理:网络请求可能会失败,你需要准备好处理这些失败的情况。例如,你可能需要重新尝试请求,或者在用户界面上显示错误消息。
-
超时设置:OkHttp的默认超时可能不适合你的应用。你应该根据你的应用的需求和网络条件来设置合适的超时。
-
OkHttpClient实例的复用:OkHttpClient实例是设计用来被复用的,而不是为每个请求创建一个新的实例。复用OkHttpClient实例可以提高性能,因为它可以共享同一个连接池和线程池。
-
线程管理:OkHttp的请求是同步的,也就是说,当你调用execute方法时,它会阻塞当前线程直到请求完成。如果你在主线程中执行网络请求,这可能会导致应用界面冻结。你应该在后台线程中执行网络请求,或者使用OkHttp的异步API。
-
安全性:如果你的应用需要处理敏感信息,如用户密码或者信用卡信息,你需要确保你的请求是通过HTTPS发送的,而不是HTTP。你也需要验证你的服务器的SSL证书,以防止中间人攻击。
-
关闭响应:当你处理完响应后,你需要关闭它,以释放网络资源。你可以通过调用Response的close方法来关闭响应,或者使用Java 7的try-with-resources语句来自动关闭响应。
-
请求/响应日志:在生产环境中,可能需要记录请求和响应的详细信息,以便于问题排查。可以通过实现和设置OkHttp的Interceptor来完成。
-
处理大文件或流媒体:如果你需要下载大文件或流媒体,你应该使用流的方式来处理响应,而不是一次性加载整个响应到内存中。