只需检查一次,就可以在列表中找到正确的第一项,并返回检查结果



有一个url列表,如:

val urls = List("http://....", "http://....", "http://....", "http://....", ...)

有些url不可访问,有些会返回状态404, 500

我有一个函数,可以检查url是否可访问,也有状态码200,并获得ContentType。这个函数开销很大,因为它将访问网络并且读取超时为10秒。

case class GoodSite(url:String, contentType: String)
def checkUrl(url:String): Option[GoodSite] = {
    // visit the url, and get the response status code and content type
    if(responseCode==200) Some(GoodSite(url, contentType))
    else None
}

现在我想从列表中逐一检查url,并且只获得第一个将返回代码200的url,然后返回内容类型。

我有两个解决方案:

urls.flatMap(checkUrl).headOption

urls.find(url => checkUrl(url)!=None).map(checkUrl(_))

但是它们都不是很好,因为它们会执行一些不必要的checkUrl函数调用。

有什么好的解决方法可以只调用一次吗?

从列表中取出迭代器后,所有操作都是惰性求值:

  val urls = List("http://google.com", "http://amazon.com", "http::/yahoo.com")
  case class GoodSite(url:String, contentType: String)
  val cnt = new AtomicInteger(0)
  def checkUrl(url:String): Option[GoodSite] = {
    println(s"Check url: $url")
    val (responseCode, contentType) = getResponseCodeAndContentType(url)
    if(responseCode==200 && cnt.getAndIncrement == 1)
      Some(GoodSite(url, contentType))
    else None
  }
  val firstGood = urls.iterator.map(checkUrl).collectFirst {
    case Some(good) => good
  }
  println(firstGood)

你也可以将你的url列表转换为一个流,然后在你没有一个好的网站时删除:

  urls.toStream.map(checkUrl).dropWhile(_.isEmpty).headOption

Eugene Zhulenev和Kai Sternad的回答都满足了你最初的要求。但我认为你应该重新考虑你的问题。

想象urls的长度是N,第k个url需要T(k)的时间来检查,顺序代码找到GoodSite的时间是多少?在最好的情况下,如果第一个url是好的,它将是T(0)。在最坏的情况下,无论是T(N-1)是唯一的GoodSite,还是不存在GoodSite,它都将是T(0) + T(1) + ... + T(N-1)

我的建议是同时开始检查所有url, 异步,并在发现第一个GoodSite后立即完成检查。如果存在GoodSite,则取min(T(0), T(1), ..., T(N-1)),如果不存在GoodSite,则取max(T(0), T(1), ..., T(N-1))

下面是一段工作代码,使用spray-client(1.3.1-20140423_2.11)来演示我的观点。

  import akka.actor.ActorSystem
  import scala.util.{Failure, Success}
  import spray.http.HttpHeaders.`Content-Type`
  import scala.concurrent.Future
  import spray.http._
  import spray.client.pipelining._
  implicit val system = ActorSystem()
  import system.dispatcher
  val pipeline = sendReceive
  case class GoodSite(url: String, contentType: Option[`Content-Type`])
  def url2GoodSite(url: String): Future[GoodSite] = {
    pipeline(Get(url)).map { response =>
        response.status match {
          case StatusCodes.OK => GoodSite(url, response.header[`Content-Type`])
          case _ => throw new RuntimeException("bad site")
        }
    }
  }
  val data = List("http://www.yahoo.com", "http://www.sina.com.cn", "http://www.oschina.net")
  val result = Future.find(data.map(url2GoodSite))(_ => true)
  result.onComplete {
    case Success(good) =>
      // use the fastest GoodSite
      println(good)
      system.shutdown()
    case Failure(e) =>
      // no GoodSite found
      println("No good site found.")
      system.shutdown()
  }

使用spray-client或其他库并不重要。def url2GoodSite(url: String): Future[GoodSite]的实现细节并不重要。关键是它返回Future[GoodSite]而不是Option[GoodSite]。无论何时处理网络io,都要考虑这种转换。把这看作是赶上被动趋势的第一步。