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));
sink.writeByte(bitmap.green(x, y));
sink.writeByte(bitmap.red(x, y));
}
// Padding for 4-byte alignment.
for (int p = rowByteCountWithoutPadding; p < rowByteCount; p++) {
sink.writeByte(0);
}
}
}
public static void main(String[] args) throws Exception {
BitmapEncoder encoder = new BitmapEncoder();
Bitmap bitmap = encoder.generateGradient();
encoder.encode(bitmap, new File(“gradient.bmp”));
}
}
代码中对文件按照BMP的格式写入二进制数据,这会生成一个bmp格式的图片文件,BMP格式要求每行以4字节开始,所以代码中加了很多0来做字节对齐。
编码其他二进制的格式非常相似。一些值得注意的点:
-
使用Golden values编写测试,对于确认程序的预期结果可以使调试更容易。
-
使用
Utf8.size()
方法计算编码字符串的字节长度。这对于length-prefixed
格式必不可少。 -
使用
Float.floatToIntBits()
和Double.doubleToLongBits()
来编码浮点型的数值。
使用Socket进行通信
通过网络发送和接收数据有点像文件的读写。Okio使用BufferedSink
对输出进行编码,使用BufferedSource
对输入进行解码。与文件一样,网络协议可以是文本、二进制或两者的混合。但是网络和文件系统之间也有一些实质性的区别。
当你有一个文件对象,你只可以选择读或者写,但是网络与之不同的是可以同时进行读和写!在有一些协议中,处理这个问题的方式是轮流的进行:写入请求、读取响应、重复以上操作。你可以用一个单线程来实现这种协议。而在其他协议中,你可以同时进行读写。通常你需要一个专门的线程来读取数据。对于写入数据,你可以使用专门线程或者使用synchronized
,以便多个线程可以共享一个Sink
。Okio的流在并发情况下使用是不安全的。
对于Okio的Sinks缓冲区,必须手动调用flush()
来传输数据,以最小化I/O操作。通常,面向消息的协议会在每条消息之后刷新。注意,当缓冲数据超过某个阈值时,Okio将自动刷新。但这只是为了节省内存,不能依赖它进行协议交互。
Okio是基于java.io.socket
建立连接的,当你通过socket创建服务器或客户端后,可以使用Okio.source(Socket)
进行读取,使用Okio.sink(Socket)
进行写入。这些API也同样适用于SSLSocket
。
在任意线程中想要取消socket连接可以调用Socket.close()
方法,这将导致 sources 和 sinks 对象立即抛出IOException
而失败。Okio中可以为所有的socket操作配置超时限制,但并不需要你去调用Socket的方法来设置超时:Source
和 Sink
会提供超时的接口。即使流对象被装饰,这个API也能工作。
Okio官方Demo中编写了一个 简单的Socket代理服务 来示例完整的网络交互操作,下面是其中的部分代码截取:
private void handleSocket(final Socket fromSocket) {
try {
final BufferedSource fromSource = Okio.buffer(Okio.source(fromSocket));
final BufferedSink fromSink = Okio.buffer(Okio.sink(fromSocket));
//…
//…
} catch (IOException e) {
…
}
}
可以看到通过Socket创建sources 和 sinks的方式与通过文件创建的方式一样,都是先通过Okio.source()
拿到Socket对应的Source
或Sink
对象,然后通过Okio.buffer()
获取对应的装饰者缓冲对象。在Okio中,一旦你为Socket
对象创建了Source
或者 Sink
,那么你就不能再使用InputStream
或 OutputStream
了。
Buffer buffer = new Buffer();
for (long byteCount; (byteCount = source.read(buffer, 8192L)) != -1; ) {
sink.write(buffer, byteCount);
sink.flush();
}
以上代码中,循环从source中读取数据写入到sink当中,并调用flush()
进行刷新,如果你不需要每次写数据都进行flush()
,那么for循环里的两句可以使用BufferedSink.writeAll(Source)
一行代码来代替。
你会发现,在read()
方法中传递了一个8192
作为读取的字节数,其实这里可以传任何数字,但是Okio更喜欢用8 kib
,因为这是Okio在单个系统调用中所能处理的最大值。大多数时候应用程序代码不需要处理这样的限制!
int addressType = fromSource.readByte() & 0xff;
int port = fromSource.readShort() & 0xffff;
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
最后
其实Android开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。
上面分享的百度、腾讯、网易、字节跳动、阿里等公司2021年的高频面试题,博主还把这些技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,上面只是以图片的形式给大家展示一部分。
【Android思维脑图(技能树)】
知识不体系?这里还有整理出来的Android进阶学习的思维脑图,给大家参考一个方向。
【Android高级架构视频学习资源】
较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频**
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
[外链图片转存中…(img-qkUNcFY8-1710670451371)]
最后
其实Android开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。
上面分享的百度、腾讯、网易、字节跳动、阿里等公司2021年的高频面试题,博主还把这些技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,上面只是以图片的形式给大家展示一部分。
【Android思维脑图(技能树)】
知识不体系?这里还有整理出来的Android进阶学习的思维脑图,给大家参考一个方向。
[外链图片转存中…(img-oYzTpQXX-1710670451371)]
【Android高级架构视频学习资源】
**Android部分精讲视频领取学习后更加是如虎添翼!**进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!