从Okhttp的Head中学习一种key,value的存储方式

在用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实现,这就是学习框架的最大意义,学习框架就要学习它精妙的思想,抽丝剥茧,理解实现背景。如果只是能说出运行流程,基本对提升自己没用。

 

 

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值