- 易于实施。
source
只声明了三个方法:read()
、close()
和timeout()
。没有像available()
或单字节读取这样会导致正确性和性能意外的危险操作。
- 使用方便。虽然
source
和sink
的实现只有三种方法可写,但是调用方可以实现Bufferedsource
和Bufferedsink
接口, 这两个接口提供了丰富API能够满足你所需的一切。
- 字节流和字符流之间没有人为的区别。都是数据。你可以以字节、UTF-8字符串、big-endian的32位整数、little-endian的短整数等任何你想要的形式进行读写;不再有
InputStreamReader
!
- 易于测试。
Buffer
类同时实现了BufferedSource
和BufferedSink
接口,因此测试代码简单明了。
Sources 和 Sinks分别与
InputStream
和OutputStream
交互操作。你可以将任何Source
看做InputStream
,也可以将任何InputStream
当做Source
。对于Sink
和Outputstream
也是如此。
读文本文件
public void readLines(File file) throws IOException {
Source fileSource = Okio.source(file);
BufferedSource bufferedSource = Okio.buffer(fileSource);
for (String line; (line = bufferedSource.readUtf8Line()) != null; ) {
System.out.println(line);
}
bufferedSource.close();
}
这个示例代码是用来读取文本文件的,Okio通过Okio.source(File)
的方式来读取文件流,它返回的是一个Source对象,但是Source对象的方法是比较少的(只有3个),因此Okio提供了一个装饰者对象接口BufferedSource
,通过Okio.buffer(fileSource)
来生成,这个方法内部实际会生成一个RealBufferedSource
类对象,RealBufferedSource
内部持有Buffer缓冲对象可使IO速度更快,该类实现了BufferedSource
接口,而BufferedSource
接口提供了大量丰富的接口方法:
可以看到,几乎你想从输入流中读取任何的数据类型都可以,而不需要你自己去转换,可以说是非常强大而且人性化了,除了read方法以外,还有一些别的方法:
在上面的示例代码中,打开输入流对象的方法需要负责关闭对象资源,调用close方法,okio官方推荐使用java的try-with-source
语法,上面示例代码可以写成下面这样:
public void readLines(File file) throws IOException {
try (BufferedSource bufferedSource = Okio.buffer(Okio.source(file))) {
for (String line; (line = bufferedSource.readUtf8Line()) != null; ) {
System.out.println(line);
}
}
}
try-with-source
是jdk1.7开始提供的语法糖,在try
语句()里面的资源对象,jdk最终会自动调用它的close
方法去关闭它, 即便try
里有多个资源对象也是可以的,这样就不用你手动去关闭资源了。但是在android里面使用的话,会提示你要求API level最低为19
才可以。
readUtf8Line()
方法适用于大多数文件。对于某些用例,还可以考虑使用readUtf8LineStrict()
。类似readUtf8Line()
,但它要求每一行都以\n
或\r\n
结尾。如果在这之前遇到文件结尾,它将抛出一个EOFException
。它还允许设置一个字节限制来防止错误的输入。
public void readLines(File file) throws IOException {
try (BufferedSource source = Okio.buffer(Okio.source(file))) {
while (!source.exhausted()) {
String line = source.readUtf8LineStrict(1024L);
System.out.println(line);
}
}
}
写文本文件
public void writeEnv(File file) throws IOException {
Sink fileSink = Okio.sink(file);
BufferedSink bufferedSink = Okio.buffer(fileSink);
for (Map.Entry<String, String> entry : System.getenv().entrySet()) {
bufferedSink.writeUtf8(entry.getKey());
bufferedSink.writeUtf8(“=”);
bufferedSink.writeUtf8(entry.getValue());
bufferedSink.writeUtf8(“\n”);
}
bufferedSink.close();
}
类似于读文件使用Source
和BufferedSource
, 写文件的话,则是使用的Sink
和 BufferedSink
,同样的在BufferedSink
接口中也提供了丰富的接口方法:
其中Okio.buffer(fileSink)
内部返回的实现对象是一个RealBufferedSink
类的对象, 跟RealBufferedSource
一样它也是一个装饰者对象,具备Buffer
缓冲功能。同样,以上代码可以使用jdk的try-with-source
语法获得更加简便的写法:
public void writeEnv(File file) throws IOException {
try (BufferedSink sink = Okio.buffer(Okio.sink(file))) {
sink.writeUtf8(“啊啊啊”)
.writeUtf8(“=”)
.writeUtf8(“aaa”)
.writeUtf8(“\n”);
}
}
其中的换行符\n
,Okio没有提供单独的api方法,而是要你手动写,因为这个跟操作系统有关,不过你可以使用System.lineSeparator()
来代替\n
,这个方法在Windows上返回的是"\r\n"
在UNIX上返回的是"\n"。
在上面的代码中,对writeUtf8()
进行了四次调用, 这样要比下面的代码更高效,因为虚拟机不必对临时字符串进行创建和垃圾回收。
sink.writeUtf8(entry.getKey() + “=” + entry.getValue() + “\n”);
Gzip压缩和读取
//zip压缩
GzipSink gzipSink = new GzipSink(Okio.sink(file));
BufferedSink bufferedSink = Okio.buffer(gzipSink);
bufferedSink.writeUtf8(“this is zip file”);
bufferedSink.flush();
bufferedSink.close();
//读取zip
GzipSource gzipSource = new GzipSource(Okio.source(file));
BufferedSource bufferedSource = Okio.buffer(gzipSource);
String s = bufferedSource.readUtf8();
bufferedSource.close();
UTF-8
在上面的代码中,可以看到使用的基本都是带UTF-8的读写方法。Okio推荐优先使用UTF-8的方法,why UTF-8? 这是因为UTF-8在世界各地都已标准化,而在早期的计算机系统中有许多不兼容的字符编码如:ISO-8859-1、ShiftJIS、 ASCII、EBCDIC等,使用UTF-8可以避免这些问题。
如果你需要使用其他编码的字符集,可以使用readString()
和 writeString()
,这两个方法可以指定字符编码参数,但在大多数情况下应该只使用带UTF-8的方法。
在编码字符串时,需要特别注意字符串的表达形式和编码方式。当字形有重音或其他装饰时,情况可能会有点复杂。尽管在I/O中读写字符串时使用的都是UTF-8,但是当在内存中,Java字符串使用的是已过时的UTF-16进行编码的。这是一种糟糕的编码格式,因为它对大多数字符使用 16-bit char
,但有些字符不适合。特别是大多数的表情符号使用的是两个Java字符, 这时就会出现一个问题: String.length()返回的结果是utf-16字符的数量,而不是字体原本的字符数量。
| | Café ? | Café ? |
| --: | :-- | :-- |
| Code Points | c a f é ␣ ?
| c a f e ´ ␣ ?
|
| UTF-8 bytes | 43 61 66 c3a9 20 f09f8da9
| 43 61 66 65 cc81 20 f09f8da9
|
| String.codePointCount | 6 | 7 |
| String.length | 7 | 8 |
| Utf8.size | 10 | 11 |
在大多数情况下,Okio无需你关心这些问题,从而可以将关注点放在数据本身的使用上。但是当你确实需要处理这些低级的UTF-8字符串问题时,也有一些方便的API来处理,如使用utf8.size()
可以计算字符串按钮UTF-8形式编码的字节数(但是并不会实际编码),使用bufferedsource.readutf8codepoint()
读取单个可变长度的Code Point
,使用bufferedsink.writeutf8codepoint()
写入一个Code Point
。
public final class ExploreCharsets {
public void run() throws Exception {
dumpStringData(“Café \uD83C\uDF69”); // NFC: é is one code point.
dumpStringData(“Café \uD83C\uDF69”); // NFD: e is one code point, its accent is another.
}
public void dumpStringData(String s) throws IOException {
System.out.println(" " + s);
System.out.println(" String.length: " + s.length());
System.out.println("String.codePointCount: " + s.codePointCount(0, s.length()));
System.out.println(" Utf8.size: " + Utf8.size(s));
System.out.println(" UTF-8 bytes: " + ByteString.encodeUtf8(s).hex());
readUtf8CodePoint(s);
System.out.println();
}
private void readUtf8CodePoint(String s) throws IOException {
Buffer buffer = new Buffer();
buffer.writeString(s, Charset.forName(“utf-8”));
Source source = Okio.source(buffer.inputStream());
BufferedSource bufferedSource = Okio.buffer(source);
int i = -1;
StringBuilder sb = new StringBuilder();
while (!bufferedSource.exhausted() && (i = bufferedSource.readUtf8CodePoint()) != -1) {
sb.append((char) i).append(“—”);
}
System.out.println(" readUtf8CodePoint: " + sb.toString());
bufferedSource.close();
}
public static void main(String… args) throws Exception {
new ExploreCharsets().run();
}
}
序列化和反序列化
将一个对象进行序列化并以ByteString
的形式返回:
private ByteString serialize(Object o) throws IOException {
Buffer buffer = new Buffer();
try (ObjectOutputStream objectOut = new ObjectOutputStream(buffer.outputStream())) {
objectOut.writeObject(o);
}
return buffer.readByteString();
}
这里使用Buffer
对象代替java的ByterrayOutputstream
,然后从buffer中获得输出流对象,并通过ObjectOutputStream
(来自java的Api)写入对象到buffer缓冲区当中,当你向Buffer
中写数据时,总是会写到缓冲区的末尾。
最后,通过buffer对象的readByteString()
从缓冲区读取一个ByteString
对象,这会从缓冲区的头部开始读取,readByteString()
方法可以指定要读取的字节数,如果不指定,则读取全部内容。
我们利用上面的方法将一个对象进行序列化,并得到的ByteString
对象按照base64
格式进行输出:
Point point = new Point(8.0, 15.0);
ByteString pointBytes = serialize(point);
System.out.println(pointBytes.base64());
这样我们会得到输出的一串字符串:
rO0ABXNyADVjb20uZXhhbXBsZS5hc3VzLm15YXBwbGljYXRpb24ub2tpby5Hb2xkZW5WYWx1ZSRQb2ludIRwF2M5CCK2AgACRAABeEQAAXl4cEAgAAAAAAAAQC4AAAAAAAA=
Okio将这个字符串称之为 Golden Value
接下来,我们尝试将这个字符串(Golden Value)反序列化为一个Point对象
首先转回ByteString
对象:
ByteString goldenBytes = ByteString.decodeBase64(“rO0ABXNyADVjb20uZXhhbXBsZS5hc3VzLm1”
+“5YXBwbGljYXRpb24ub2tpby5Hb2xkZW5WYWx1ZSRQb2ludIRwF2M5CCK2AgACRAABeEQAAXl4cEAgAAAAAAAAQC4AAAAAAAA=”);
然后将ByteString
对象反序列化:
private Object deserialize(ByteString byteString) throws IOException, ClassNotFoundException {
Buffer buffer = new Buffer();
buffer.write(byteString);
try (ObjectInputStream objectIn = new ObjectInputStream(buffer.inputStream())) {
Object result = objectIn.readObject();
if (objectIn.read() != -1) throw new IOException(“Unconsumed bytes in stream”);
return result;
}
}
public void run() throws Exception {
Point point = new Point(8.0, 15.0);
ByteString pointBytes = serialize(point);
System.out.println(pointBytes.base64());
ByteString goldenBytes = ByteString.decodeBase64(“rO0ABXNyADVjb20uZXhhbXBsZS5hc3VzLm15YXBwbGljYXRpb24ub2tpby5Hb2xkZW5WYWx1ZSRQb2ludIRwF2M5CCK2AgACRAABeEQAAXl4cEAgAAAAAAAAQC4AAAAAAAA=”);
Point decoded = (Point) deserialize(goldenBytes);
if (point.x == decoded.x || point.y == decoded.y) {
System.out.println(“Equals”);
}
}
输出:
这样我们可以在不破坏兼容性的情况下更改对象的序列化方式。
这个序列化与Java原生的序列化有一个明显的区别就是GodenValue
可以在不同客户端之间兼容(只要序列化和反序列化的Class是相同的)。什么意思呢,比如我在PC端使用Okio序列化一个User对象生成的GodenValue
字符串,这个字符串你拿到手机端照样可以反序列化出来User对象。
写二进制文件
编码二进制文件与编码文本文件没有什么不同,Okio使用相同的BufferedSink
和BufferedSource
字节。这对于同时包含字节和字符数据的二进制格式很方便。写二进制数据比写文本更容易出错,需要注意以下几点:
-
字段的宽度 ,即字节的数量。Okio没有释放部分字节的机制。如果你需要的话,需要自己在写操作之前对字节进行shift和mask运算。
-
字段的字节顺序 , 所有多字节的字段都具有结束符:字节的顺序是从最高位到最低位(大字节 big endian),还是从最低位到最高位(小字节 little endian)。Okio中针对小字节排序的方法都带有
Le
的后缀;而没有后缀的方法默认是大字节排序的。 -
有符号和无符号,Java没有无符号的基础类型(除了char!)因此,在应用程序层经常会遇到这种情况。为方便使用,Okio的
writeByte()
和writeShort()
方法可以接受int
类型。你可以直接传递一个“无符号”字节像255,Okio会做正确的处理。
| 方法 | 宽度 | 字节排序 | 值 | 编码后的值 |
| :-- | :-: | :-: | :-: | :-- |
| writeByte | 1 | | 3 | 03
|
| writeShort | 2 | big | 3 | 00 03
|
| writeInt | 4 | big | 3 | 00 00 00 03
|
| writeLong | 8 | big | 3 | 00 00 00 00 00 00 00 03
|
| writeShortLe | 2 | little | 3 | 03 00
|
| writeIntLe | 4 | little | 3 | 03 00 00 00
|
| writeLongLe | 8 | little | 3 | 03 00 00 00 00 00 00 00
|
| writeByte | 1 | | Byte.MAX_VALUE | 7f
|
| writeShort | 2 | big | Short.MAX_VALUE | 7f ff
|
| writeInt | 4 | big | Int.MAX_VALUE | 7f ff ff ff
|
| writeLong | 8 | big | Long.MAX_VALUE | 7f ff ff ff ff ff ff ff
|
| writeShortLe | 2 | little | Short.MAX_VALUE | ff 7f
|
| writeIntLe | 4 | little | Int.MAX_VALUE | ff ff ff 7f
|
| writeLongLe | 8 | little | Long.MAX_VALUE | ff ff ff ff ff ff ff 7f
|
下面的示例代码是按照 BMP文件格式 对文件进行编码:
public final class BitmapEncoder {
static final class Bitmap {
private final int[][] pixels;
Bitmap(int[][] pixels) {
this.pixels = pixels;
}
int width() {
return pixels[0].length;
}
int height() {
return pixels.length;
}
int red(int x, int y) {
return (pixels[y][x] & 0xff0000) >> 16;
}
int green(int x, int y) {
return (pixels[y][x] & 0xff00) >> 8;
}
int blue(int x, int y) {
return (pixels[y][x] & 0xff);
}
}
/**
-
Returns a bitmap that lights up red subpixels at the bottom, green subpixels on the right, and
-
blue subpixels in bottom-right.
*/
Bitmap generateGradient() {
int[][] pixels = new int[1080][1920];
for (int y = 0; y < 1080; y++) {
for (int x = 0; x < 1920; x++) {
int r = (int) (y / 1080f * 255);
int g = (int) (x / 1920f * 255);
int b = (int) ((Math.hypot(x, y) / Math.hypot(1080, 1920)) * 255);
pixels[y][x] = r << 16 | g << 8 | b;
}
}
return new Bitmap(pixels);
}
void encode(Bitmap bitmap, File file) throws IOException {
try (BufferedSink sink = Okio.buffer(Okio.sink(file))) {
encode(bitmap, sink);
}
}
/**
- https://en.wikipedia.org/wiki/BMP_file_format
*/
void encode(Bitmap bitmap, BufferedSink sink) throws IOException {
int height = bitmap.height();
int width = bitmap.width();
int bytesPerPixel = 3;
int rowByteCountWithoutPadding = (bytesPerPixel * width);
int rowByteCount = ((rowByteCountWithoutPadding + 3) / 4) * 4;
int pixelDataSize = rowByteCount * height;
int bmpHeaderSize = 14;
int dibHeaderSize = 40;
// BMP Header
sink.writeUtf8(“BM”); // ID.
sink.writeIntLe(bmpHeaderSize + dibHeaderSize + pixelDataSize); // File size.
sink.writeShortLe(0); // Unused.
sink.writeShortLe(0); // Unused.
sink.writeIntLe(bmpHeaderSize + dibHeaderSize); // Offset of pixel data.
// DIB Header
sink.writeIntLe(dibHeaderSize);
sink.writeIntLe(width);
sink.writeIntLe(height);
sink.writeShortLe(1); // Color plane count.
sink.writeShortLe(bytesPerPixel * Byte.SIZE);
sink.writeIntLe(0); // No compression.
sink.writeIntLe(16); // Size of bitmap data including padding.
sink.writeIntLe(2835); // Horizontal print resolution in pixels/meter. (72 dpi).
sink.writeIntLe(2835); // Vertical print resolution in pixels/meter. (72 dpi).
sink.writeIntLe(0); // Palette color count.
sink.writeIntLe(0); // 0 important colors.
// Pixel data.
for (int y = height - 1; y >= 0; y–) {
for (int x = 0; x < width; x++) {
sink.writeByte(bitmap.blue(x, y));
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
文末
初级工程师拿到需求会直接开始做,然后做着做着发现有问题了,要么技术实现不了,要么逻辑有问题。
而高级工程师拿到需求会考虑很多,技术的可行性?对现有业务有没有帮助?对现有技术架构的影响?扩展性如何?等等…之后才会再进行设计编码阶段。
而现在随着跨平台开发,混合式开发,前端开发之类的热门,Android开发者需要学习和掌握的技术也在不断的增加。
通过和一些行业里的朋友交流讨论,以及参考现在大厂面试的要求。我们花了差不多一个月时间整理出了这份Android高级工程师需要掌握的所有知识体系。你可以看下掌握了多少。
混合式开发,微信小程序。都是得学会并且熟练的
这些是Android相关技术的内核,还有Java进阶
高级进阶必备的一些技术。像移动开发架构项目实战等
Android前沿技术;包括了组件化,热升级和热修复,以及各种架构跟框架的详细技术体系
以上即是我们整理的Android高级工程师需要掌握的技术体系了。可能很多朋友觉得很多技术自己都会了,只是一些新的技术不清楚而已。应该没什么太大的问题。
而这恰恰是问题所在!为什么别人高级工程师能年限突破30万,而你只有十几万呢?
就因为你只需补充你自己认为需要的,但并不知道企业需要的。这个就特别容易造成差距。因为你的技术体系并不系统,是零碎的,散乱的。那么你凭什么突破30万年薪呢?
我这些话比较直接,可能会戳到一些人的玻璃心,但是我知道肯定会对一些人起到点醒的效果的。而但凡只要有人因为我的这份高级系统大纲以及这些话找到了方向,并且付出行动去提升自我,为了成功变得更加努力。那么我做的这些就都有了意义。
喜欢的话请帮忙转发点赞一下能让更多有需要的人看到吧。谢谢!
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
式开发,微信小程序。都是得学会并且熟练的
[外链图片转存中…(img-EqhBr1kF-1712790277449)]
这些是Android相关技术的内核,还有Java进阶
[外链图片转存中…(img-a1IQERFK-1712790277450)]
高级进阶必备的一些技术。像移动开发架构项目实战等
[外链图片转存中…(img-uYisQERI-1712790277450)]
Android前沿技术;包括了组件化,热升级和热修复,以及各种架构跟框架的详细技术体系
[外链图片转存中…(img-PP6di5Fw-1712790277450)]
以上即是我们整理的Android高级工程师需要掌握的技术体系了。可能很多朋友觉得很多技术自己都会了,只是一些新的技术不清楚而已。应该没什么太大的问题。
而这恰恰是问题所在!为什么别人高级工程师能年限突破30万,而你只有十几万呢?
就因为你只需补充你自己认为需要的,但并不知道企业需要的。这个就特别容易造成差距。因为你的技术体系并不系统,是零碎的,散乱的。那么你凭什么突破30万年薪呢?
我这些话比较直接,可能会戳到一些人的玻璃心,但是我知道肯定会对一些人起到点醒的效果的。而但凡只要有人因为我的这份高级系统大纲以及这些话找到了方向,并且付出行动去提升自我,为了成功变得更加努力。那么我做的这些就都有了意义。
喜欢的话请帮忙转发点赞一下能让更多有需要的人看到吧。谢谢!
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!