在用okhttp的时候,我们可能会自定义一些head,一般是这么写代码
Request.Builder builder = new Builder().url(url)
.addHeader(InstagramWebConstants.HEADER_ORIGIN,
.addHeader(InstagramWebConstants.HEADER_X_REQUESTED_WITH, InstagramWebConstants.XML_HTTP_REQUEST);
if(isCanAddHead()){
addHeadFromApi(builder);
}
Request request = builder
.post(getPostBody())
.build();
进入addHeader方法,
public Builder addHeader(String name, String value) {
headers.add(name, value);
return this;
}
headers的类型是Headers.Builder,说明用Headers来管理Head的增删改查,先留在这一步,我们思考,head是一个key,value的结构,Headers类用什么数据结构来存储呢,没有看过源码的人很自然的想到用map,但实际并不是,用的是数组.好的废话不说,直接看代码; Headers的代码有三百多行,不全贴出来了。先看下存储变量
public final class Headers {
private final String[] namesAndValues;
Headers(Builder builder) {
this.namesAndValues = builder.namesAndValues.toArray(new String[builder.namesAndValues.size()]);
}
...
namesAndValues从名字来看是自解释的,存储head的name和value,后面看下对这个数组的操作。首先从Headers.Build的add方法开始.
public Builder add(String name, String value) {
checkName(name);
checkValue(value, name);
return addLenient(name, value);
}
Builder addLenient(String name, String value) {
namesAndValues.add(name);
namesAndValues.add(value.trim());
return this;
}
Builder的namesAndValues是一个List变量。后面添加完后,使用到的时候调用build方法,创建Headers对象。
public Headers build() {
return new Headers(this);
}
Headers(Builder builder) {
this.namesAndValues = builder.namesAndValues.toArray(new String[builder.namesAndValues.size()]);
}
在Header中就是把Builder中的List调用toArray转为一个数组。从这个创建过程来看,我们可以得知一个结论,在数组中是如何存放的,就是偶数位存name,奇数位纯value,name,value相邻存储,索引从0开始的,因为构建好的headers不会在变化,不考虑扩容。怎么使用呢。在Http1ExchangeCodec中
/** Returns bytes of a request header for sending on an HTTP transport. */
public void writeRequest(Headers headers, String requestLine) throws IOException {
if (state != STATE_IDLE) throw new IllegalStateException("state: " + state);
sink.writeUtf8(requestLine).writeUtf8("\r\n");
for (int i = 0, size = headers.size(); i < size; i++) {
sink.writeUtf8(headers.name(i))
.writeUtf8(": ")
.writeUtf8(headers.value(i))
.writeUtf8("\r\n");
}
sink.writeUtf8("\r\n");
state = STATE_OPEN_REQUEST_BODY;
}
就是循环Headers的数组,调用name(i),value(i)
/** Returns the field at {@code position}. */
public String name(int index) {
return namesAndValues[index * 2];
}
/** Returns the value at {@code index}. */
public String value(int index) {
return namesAndValues[index * 2 + 1];
}
偶数位取name,然后下一位就是对应的value。再看一个方法,values(String name),取所有name对应values的方法,比如Set-Cookie的场景,可能会设置几个value,具体使用代码在,
/** Returns all of the cookies from a set of HTTP response headers. */
public static List<Cookie> parseAll(HttpUrl url, Headers headers) {
List<String> cookieStrings = headers.values("Set-Cookie");
List<Cookie> cookies = null;
for (int i = 0, size = cookieStrings.size(); i < size; i++) {
Cookie cookie = Cookie.parse(url, cookieStrings.get(i));
if (cookie == null) continue;
if (cookies == null) cookies = new ArrayList<>();
cookies.add(cookie);
}
return cookies != null
? Collections.unmodifiableList(cookies)
: Collections.emptyList();
}
查找时间复杂度为O(n).Headers的使用基本到这里结束了。
但不经让我们反思:key,value有必要搞这么复杂吗,查找存储用Map就好了呀,操作方便,替换的理由如果是因为数组比Map省内存,还是有点牵强的。刚开始是真的不理解这种写法,直到读了类注释之后,才豁然开朗,觉得设计者真的思路很精妙。
/** * The header fields of a single HTTP message. Values are uninterpreted strings; use {@code Request} * and {@code Response} for interpreted headers. This class maintains the order of the header fields * within the HTTP message. * * <p>This class tracks header values line-by-line. A field with multiple comma- separated values on * the same line will be treated as a field with a single value by this class. It is the caller's * responsibility to detect and split on commas if their field permits multiple values. This * simplifies use of single-valued fields whose values routinely contain commas, such as cookies or * dates. * * <p>This class trims whitespace from values. It never returns values with leading or trailing * whitespace. * * <p>Instances of this class are immutable. Use {@link Builder} to create instances. */
我们尝试翻译下这段注释。
这是一个简单http消息的head的一些field。values都是自解释的字符串;在request,response中用于诠释的headers。这个类保留了field的顺序。
这个类逐行检索header的values。一个field的values用逗号分隔来表示多个value的话,这这个类中会被当做只是一个value的field.请求调用者负责发现和用逗号分隔多个value的field。这样就简化了单value的field的使用,虽然这些field的value通常包含逗号,比如cookies 和 dates.
这个类会去除values的空格。绝不会返回前后带空格的values.这个类的实例是不可变的。用Builder来创建对象实例。
从这段注释中,我们可以得出几点。1 field要保留顺序。2一个field可能包含多个value,意思可以多个filed,value,但field是同一个。从第二个点来看,map类还真hold不住,因为如果field一样,value是会被覆盖的。
我们举个例子,nameAndValues[0] = "ig" namesAndValues[1] = "hello" ,nameAndvalues[2] = "ig" nameAndValues[3]="world"
基于此设计者就设计数组来实现这个需求,真的是太巧妙了,总体来看还是简单的。注释中还有其他的点,和设计数组的思路基本没多大关系,就不解释了。
思路在切换到自己,如果我自己来实现这个需求点,很可能会用Map<String,List<String>>这个结构来实现,对比Headers的设计,我觉得我这个结构太冗余了,很多field的value是single,用list来保存,有点浪费。
那在现实中,我们是否可以借鉴这种思想呢,可以说不太容易找到场景的。不过当有一天我们遇到的时候,如果就能想起okhttp的header实现,这就是学习框架的最大意义,学习框架就要学习它精妙的思想,抽丝剥茧,理解实现背景。如果只是能说出运行流程,基本对提升自己没用。