整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

2020年文档相似性算法:初学者教程

2020年文档相似性算法:初学者教程

果你想知道2020年文档相似性任务的最佳算法,你来对了地方。

在33914篇《纽约时报》文章中,我测试了5种常见的文档相似性算法。从传统的统计方法到现代的深度学习方法。

每个实现少于50行代码。所有使用的模型都来自互联网。因此,你可以在没有数据科学知识的情况下,开箱即用,并且得到类似的结果。

在这篇文章中,你将学习如何实现每种算法以及如何选择最佳算法。内容如下:

  1. 最佳的定义
  2. 实验目标陈述
  3. 数据设置
  4. 比较标准
  5. 算法设置
  6. 选出赢家
  7. 对初学者的建议

你想深入自然语言处理和人工智能。你想用相关的建议来增加用户体验。你想升级旧的现有算法。那么你会喜欢这个文章的。

数据科学家主张绝对最好

你可能会搜索术语“最佳文档相似性算法”(best document similarity algorithms)。

然后你将从学术论文,博客,问答中得到搜索结果。一些侧重于特定算法的教程,而另一些则侧重于理论概述。

在学术论文中,一个标题说,这种算法的准确率达到了80%,而其他算法的准确率仅为75%。好啊。但是,这种差异是否足以让我们的眼睛注意到它呢?增加2%怎么样?实现这个算法有多容易?科学家倾向于在给定的测试集中追求最好,而忽略了实际意义。

在相关的问题问答中,狂热的支持者占据了整个话题。有人说现在最好的算法是BERT。这个算法概念是如此具有革命性,它打败了一切。另一方面,愤世嫉俗者称一切都取决于工作。有些答案早在深度学习之前就有了。看看这个Stackoverflow(https://stackoverflow.com/questions/8897593/how-to-compute-the-similarity-between-two-text-documents)。2012年是投票最多的一年,很难判断它对我们到底意味着什么。

谷歌会很乐意投入数百万美元购买工程师的能力和最新的计算能力,仅仅是为了将他们的搜索能力提高1%。这对我们来说可能既不现实也没有意义。

性能增益和实现所需的技术专业知识之间有什么权衡?它需要多少内存?它以最少的预处理可以运行多快?

你想知道的是一种算法在实际意义上是如何优于另一种算法的。

这篇文章将为你提供一个指导方针,指导你在文档相似性问题应该实现哪种算法。

各种算法,通篇流行文章,预训练模型

本实验有4个目标:

  1. 通过在同一个数据集上运行多个算法,你将看到算法与另一个算法的公平性以及公平程度。
  2. 通过使用来自流行媒体的全文文章作为我们的数据集,你将发现实际应用程序的有效性。
  3. 通过访问文章url,你将能够比较结果质量的差异。
  4. 通过只使用公开可用的预训练模型,你将能够设置自己的文档相似性并得到类似的输出。

“预训练模型是你的朋友。-Cathal Horan”

数据设置-5篇基础文章

本实验选取了33914篇《纽约时报》的文章。从2018年到2020年6月。数据主要是从RSS中收集的,文章的平均长度是6500个字符。

从这些文章中选择5个作为相似性搜索的基础文章。每一个代表一个不同的类别。

在语义类别的基础上,我们还将度量书面格式。更多的描述在下面。

  1. Lifestyle, Human Interest:How My Worst Date Ever Became My Best(https://www.nytimes.com/2020/02/14/style/modern-love-worst-date-of-my-life-became-best.html)
  2. Science, Informational:A Deep-Sea Magma Monster Gets a Body Scan(https://www.nytimes.com/2019/12/03/science/axial-volcano-mapping.html)
  3. Business, News:Renault and Nissan Try a New Way After Years When Carlos Ghosn Ruled(https://www.nytimes.com/2019/11/29/business/renault-nissan-mitsubishi-alliance.html)
  4. Sports, News:Dominic Thiem Beats Rafael Nadal in Australian Open Quarterfinal(https://www.nytimes.com/2020/01/29/sports/tennis/thiem-nadal-australian-open.html)
  5. Politics, News:2020 Democrats Seek Voters in an Unusual Spot: Fox News(https://www.nytimes.com/2019/04/17/us/politics/fox-news-democrats-2020.html)

判断标准

我们将使用5个标准来判断相似性的性质。如果你只想查看结果,请跳过此部分。

  1. 标签的重叠
  2. 小节
  3. 文风
  4. 主题

标签是最接近人类判断内容相似性的工具。记者自己亲手写下标签。你可以在HTML标题中的news_keywords meta标记处检查它们。使用标签最好的部分是我们可以客观地测量两个内容有多少重叠。每个标签的大小从1到12不等。两篇文章的标签重叠越多,就越相似。

第二,我们看这个部分。这就是《纽约时报》在最高级别对文章进行分类的方式:科学、政治、体育等等。在网址的域名后面会进行显示,例如nytimes.com/…

第二部分是小节。例如,一个版块可以细分为world,或者world可以细分为Australia。并不是所有的文章都包含它,它不像以上那2个那么重要。

第四是文风。大多数文档比较分析只关注语义。但是,由于我们是在实际用例中比较推荐,所以我们也需要类似的写作风格。例如,你不想在学术期刊的“跑鞋和矫形术”之后,从商业角度阅读“十大跑鞋”。我们将根据杰斐逊县学校的写作指导原则对文章进行分组。该列表包括人类兴趣、个性、最佳(例如:产品评论)、新闻、操作方法、过去的事件和信息。

5个候选算法

这些是我们将要研究的算法。

  1. Jaccard
  2. TF-IDF
  3. Doc2vec
  4. USE
  5. BERT

每一个算法对33914篇文章运行,以找出得分最高的前3篇文章。对于每一篇基础文章,都会重复这个过程。

输入的是文章的全文内容。标题被忽略。

请注意,有些算法并不是为文档相似性而构建的。但是在互联网上有如此不同的意见,我们将亲眼看到结果。

我们将不关注概念理解,也不关注详细的代码审查。相反,其目的是展示问题的设置有多简单。如果你不明白以下算法的细节,不要担心,你可以阅读其他优秀博客进行理解

你可以在Github repo中找到整个代码库:https://github.com/massanishi/document_similarity_algorithms_experiments

如果你只想查看结果,请跳过此部分。

Jaccard

Jaccard 在一个多世纪前提出了这个公式。长期以来,这一概念一直是相似性任务的标准。

幸运的是,你会发现jaccard是最容易理解的算法。数学很简单,没有向量化。它可以让你从头开始编写代码。

而且,jaccard是少数不使用余弦相似性的算法之一。它标记单词并计算交集。

我们使用NLTK对文本进行预处理。

步骤:

  1. 小写所有文本
  2. 标识化
  3. 删除停用词
  4. 删除标点符号
  5. 词根化
  6. 计算两个文档中的交集/并集
import string
import nltk

nltk.download('stopwords')
nltk.download('wordnet')
nltk.download('punkt')

from nltk.corpus import stopwords
from nltk.tokenize import word_tokenize
from nltk.stem import WordNetLemmatizer

lemmatizer=WordNetLemmatizer()

base_document="This is an example sentence for the document to be compared"
documents=["This is the collection of documents to be compared against the base_document"]

def preprocess(text):
    # 步骤:
    # 1. 小写字母
    # 2. 词根化
    # 3. 删除停用词
    # 4. 删除标点符号
    # 5. 删除长度为1的字符

    lowered=str.lower(text)

    stop_words=set(stopwords.words('english'))
    word_tokens=word_tokenize(lowered)

    words=[]
    for w in word_tokens:
        if w not in stop_words:
            if w not in string.punctuation:
                if len(w) > 1:
                    lemmatized=lemmatizer.lemmatize(w)
                    words.append(lemmatized)

    return words

def calculate_jaccard(word_tokens1, word_tokens2):
    # 结合这两个标识来找到并集。
    both_tokens=word_tokens1 + word_tokens2
    union=set(both_tokens)

    # 计算交集
    intersection=set()
    for w in word_tokens1:
        if w in word_tokens2:
            intersection.add(w)

    jaccard_score=len(intersection)/len(union)
    return jaccard_score

def process_jaccard_similarity():

    # 标记我们要比较的基本文档。
    base_tokens=preprocess(base_document)

    # 标记每一篇文档
    all_tokens=[]
    for i, document in enumerate(documents):
        tokens=preprocess(document)
        all_tokens.append(tokens)

        print("making word tokens at index:", i)

    all_scores=[]
    for tokens in all_tokens:
        score=calculate_jaccard(base_tokens, tokens)

        all_scores.append(score)

    highest_score=0
    highest_score_index=0
    for i, score in enumerate(all_scores):
        if highest_score < score:
            highest_score=score
            highest_score_index=i

    most_similar_document=documents[highest_score_index]

    print("Most similar document by Jaccard with the score:", most_similar_document, highest_score)

process_jaccard_similarity()

TF-IDF

这是自1972年以来出现的另一种成熟算法。经过几十年的测试,它是Elasticsearch的默认搜索实现。

Scikit learn提供了不错的TF-IDF的实现。TfidfVectorizer允许任何人尝试此操作。

利用scikit-learn的余弦相似度计算TF-IDF词向量的结果。我们将在其余的例子中使用这种余弦相似性。余弦相似性是许多机器学习任务中使用的一个非常重要的概念,可能值得你花时间熟悉一下。

多亏了scikit learn,这个算法产生了最短的代码行。

from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity

base_document="This is an example sentence for the document to be compared"
documents=["This is the collection of documents to be compared against the base_document"]

def process_tfidf_similarity():
    vectorizer=TfidfVectorizer()

    # 要生成统一的向量,首先需要将两个文档合并。
    documents.insert(0, base_document)
    embeddings=vectorizer.fit_transform(documents)

    cosine_similarities=cosine_similarity(embeddings[0:1], embeddings[1:]).flatten()

    highest_score=0
    highest_score_index=0
    for i, score in enumerate(cosine_similarities):
        if highest_score < score:
            highest_score=score
            highest_score_index=i


    most_similar_document=documents[highest_score_index]

    print("Most similar document by TF-IDF with the score:", most_similar_document, highest_score)

process_tfidf_similarity()

Doc2vec

Word2vec于2014年面世,这让当时的开发者们刮目相看。你可能听说过非常有名的一个例子:

国王 - 男性=女王

Word2vec非常擅长理解单个单词,将整个句子向量化需要很长时间。更不用说整个文件了。

相反,我们将使用Doc2vec,这是一种类似的嵌入算法,将段落而不是每个单词向量化。你可以看看这个博客的介绍:https://medium.com/wisio/a-gentle-introduction-to-doc2vec-db3e8c0cce5e

不幸的是,对于Doc2vec来说,没有官方预训练模型。我们将使用其他人的预训练模型。它是在英文维基百科上训练的(数字不详,但模型大小相当于1.5gb):https://github.com/jhlau/doc2vec

Doc2vec的官方文档指出,输入可以是任意长度。一旦标识化,我们输入整个文档到gensim库。

from gensim.models.doc2vec import Doc2Vec
from sklearn.metrics.pairwise import cosine_similarity

import string
import nltk

nltk.download('stopwords')
nltk.download('wordnet')
nltk.download('punkt')

from nltk.corpus import stopwords
from nltk.tokenize import word_tokenize
from nltk.stem import WordNetLemmatizer

lemmatizer=WordNetLemmatizer()

base_document="This is an example sentence for the document to be compared"
documents=["This is the collection of documents to be compared against the base_document"]

def preprocess(text):
    # 步骤:
    # 1. 小写字母
    # 2. 词根化
    # 3. 删除停用词
    # 4. 删除标点符号
    # 5. 删除长度为1的字符

    lowered=str.lower(text)

    stop_words=set(stopwords.words('english'))
    word_tokens=word_tokenize(lowered)

    words=[]
    for w in word_tokens:
        if w not in stop_words:
            if w not in string.punctuation:
                if len(w) > 1:
                    lemmatized=lemmatizer.lemmatize(w)
                    words.append(lemmatized)

    return words

def process_doc2vec_similarity():

    # 这两种预先训练的模型都可以在jhlau的公开仓库中获得。
    # URL: https://github.com/jhlau/doc2vec

    # filename='./models/apnews_dbow/doc2vec.bin'
    filename='./models/enwiki_dbow/doc2vec.bin'

    model=Doc2Vec.load(filename)

    tokens=preprocess(base_document)

    # 只处理出现在doc2vec预训练过的向量中的单词。enwiki_ebow模型包含669549个词汇。
    tokens=list(filter(lambda x: x in model.wv.vocab.keys(), tokens))

    base_vector=model.infer_vector(tokens)

    vectors=[]
    for i, document in enumerate(documents):

        tokens=preprocess(document)
        tokens=list(filter(lambda x: x in model.wv.vocab.keys(), tokens))
        vector=model.infer_vector(tokens)
        vectors.append(vector)

        print("making vector at index:", i)

    scores=cosine_similarity([base_vector], vectors).flatten()

    highest_score=0
    highest_score_index=0
    for i, score in enumerate(scores):
        if highest_score < score:
            highest_score=score
            highest_score_index=i

    most_similar_document=documents[highest_score_index]
    print("Most similar document by Doc2vec with the score:", most_similar_document, highest_score)

process_doc2vec_similarity()

Universal Sentence Encoder (USE)

这是Google最近在2018年5月发布的一个流行算法。 实现细节:https://www.tensorflow.org/hub/tutorials/semantic_similarity_with_tf_hub_universal_encoder。

我们将使用谷歌最新的官方预训练模型:Universal Sentence Encoder 4(https://tfhub.dev/google/universal-sentence-encoder/4).

顾名思义,它是用句子来构建的。但官方文件并没有限制投入规模。没有什么能阻止我们将它用于文档比较任务。

整个文档按原样插入到Tensorflow中。没有进行标识化。

from sklearn.metrics.pairwise import cosine_similarity

import tensorflow as tf
import tensorflow_hub as hub

base_document="This is an example sentence for the document to be compared"
documents=["This is the collection of documents to be compared against the base_document"]

def process_use_similarity():
    filename="./models/universal-sentence-encoder_4"

    model=hub.load(filename)

    base_embeddings=model([base_document])

    embeddings=model(documents)

    scores=cosine_similarity(base_embeddings, embeddings).flatten()

    highest_score=0
    highest_score_index=0
    for i, score in enumerate(scores):
        if highest_score < score:
            highest_score=score
            highest_score_index=i

    most_similar_document=documents[highest_score_index]
    print("Most similar document by USE with the score:", most_similar_document, highest_score)

process_use_similarity()

BERT

这可是个重量级选手。2018年11月谷歌开源BERT算法。第二年,谷歌搜索副总裁发表了一篇博文,称BERT是他们过去5年来最大的飞跃。

它是专门为理解你的搜索查询而构建的。当谈到理解一个句子的上下文时,BERT似乎比这里提到的所有其他技术都要出色。

最初的BERT任务并不打算处理大量的文本输入。对于嵌入多个句子,我们将使用UKPLab(来自德国大学)出版的句子转换器开源项目(https://github.com/UKPLab/sentence-transformers),其计算速度更快。它们还为我们提供了一个与原始模型相当的预训练模型(https://github.com/UKPLab/sentence-transformers#performance)

所以每个文档都被标记成句子。并对结果进行平均,以将文档表示为一个向量。

import numpy as np
from sklearn.metrics.pairwise import cosine_similarity
from nltk import sent_tokenize

from sentence_transformers import SentenceTransformer

base_document="This is an example sentence for the document to be compared"
documents=["This is the collection of documents to be compared against the base_document"]

def process_bert_similarity():
    # 这将下载和加载UKPLab提供的预训练模型。
    model=SentenceTransformer('bert-base-nli-mean-tokens')

    # 虽然在句子转换器的官方文件中并没有明确的说明,但是原来的BERT是指一个更短的句子。我们将通过句子而不是整个文档来提供模型。
    sentences=sent_tokenize(base_document)
    base_embeddings_sentences=model.encode(sentences)
    base_embeddings=np.mean(np.array(base_embeddings_sentences), axis=0)

    vectors=[]
    for i, document in enumerate(documents):

        sentences=sent_tokenize(document)
        embeddings_sentences=model.encode(sentences)
        embeddings=np.mean(np.array(embeddings_sentences), axis=0)

        vectors.append(embeddings)

        print("making vector at index:", i)

    scores=cosine_similarity([base_embeddings], vectors).flatten()

    highest_score=0
    highest_score_index=0
    for i, score in enumerate(scores):
        if highest_score < score:
            highest_score=score
            highest_score_index=i

    most_similar_document=documents[highest_score_index]
    print("Most similar document by BERT with the score:", most_similar_document, highest_score)

process_bert_similarity()

算法评估

让我们看看每种算法在我们的5篇不同类型的文章中的表现。我们根据得分最高的三篇文章进行比较。

在这篇博文中,我们将只介绍五种算法中性能最好的算法的结果。有关完整的结果以及个别文章链接,请参阅仓库中的算法目录:https://github.com/massanishi/document_similarity_algorithms_experiments

1. How My Worst Date Ever Became My Best

BERT胜利

这篇文章是一个人类感兴趣的故事,涉及一个50年代离婚妇女的浪漫约会。

这种写作风格没有像名人名字这样的特定名词。它对时间也不敏感。2010年的一个关于人类兴趣的故事在今天可能也同样重要。在比较中没有一个算法性能特别差。

BERT和USE的比赛千钧一发。USE把故事绕到了社会问题,BERT关注浪漫和约会。其他算法则转向了家庭和孩子的话题,可能是因为看到了“ex husband 前夫”这个词。

2. A Deep-Sea Magma Monster Gets a Body Scan

TF-IDF获胜。

这篇科学文章是关于海洋中活火山的三维扫描。

3D扫描、火山和海洋是罕见的术语。所有算法都很好地实现了公平。

TF-IDF正确地选择了那些只谈论地球海洋内火山的人。USE与它相比也是一个强大的竞争者,它的重点是火星上的火山而不是海洋。另一些算法则选择了有关俄罗斯军用潜艇的文章,这些文章与科学无关,与主题无关。

3. Renault and Nissan Try a New Way After Years When Carlos Ghosn Ruled

TF-IDF获胜。

文章谈到了前首席执行官卡洛斯·戈恩越狱后雷诺和日产的遭遇。

理想的匹配将讨论这3个实体。与前两篇相比,本文更具有事件驱动性和时间敏感性。相关新闻应与此日期或之后发生(从2019年11月开始)。

TF-IDF正确地选择了关注日产CEO的文章。其他人则选择了一些谈论通用汽车行业新闻的文章,比如菲亚特克莱斯勒(Fiat Chrysler)和标致(Peugeot)的结盟。

值得一提的是,Doc2vec和USE生成了完全相同的结果。

4. Dominic Thiem Beats Rafael Nadal in Australian Open Quarterfinal

Jaccard、TF-IDF和USE结果相似。

这篇文章是关于网球选手多米尼克·蒂姆在2020年澳大利亚网球公开赛(网球比赛)上的文章。

新闻是事件驱动的,对个人来说非常具体。所以理想的匹配是多米尼克和澳大利亚公开赛。

不幸的是,这个结果由于缺乏足够的数据而受到影响。他们都谈论网球。但有些比赛是在谈论2018年法国网球公开赛的多米尼克。或者,在澳大利亚网球公开赛上对费德勒的看法。

结果是三种算法的结果。这说明了关键的重要性:我们需要尽最大努力收集、多样化和扩展数据池,以获得最佳的相似性匹配结果。

5. 2020 Democrats Seek Voters in an Unusual Spot: Fox News

USE胜利。

这篇文章是关于民主党人的,特别关注伯尼·桑德斯在福克斯新闻(Fox News)上为2020年大选出镜。

每一个话题都有自己的大问题。关于民主党候选人和选举的文章很多。因为这个故事的主旨是新颖的,所以我们优先讨论民主党候选人和福克斯的关系。

旁注:在实践中,你要小心对待政治上的建议。把自由和保守的新闻混合在一起很容易让读者不安。既然我们是单独和《纽约时报》打交道,那就不必担心了。

USE找到了一些关于伯尼·桑德斯和福克斯、微软全国广播公司等电视频道的文章。其他人则选择了一些讨论2020年大选中其他民主党候选人的文章。

速度之王

在结束赢家之前,我们需要谈谈运行时间。每种算法在速度方面表现得非常不同。

结果是,TF-IDF的实施比任何其他方法都快得多。要在单个CPU上从头到尾计算33914个文档(标识化、向量化和比较),需要:

  • TF-IDF:1.5分钟。
  • Jaccard:13分钟。
  • Doc2vec:43分钟。
  • USE:62分钟。
  • BERT:50多小时(每个句子都被向量化了)。

TF-IDF只花了一分半钟。这是USE的2.5%。当然,你可以合并多种效率增强。但潜在收益需要讨论。这将使我们有另一个理由认真审视相关的利弊权衡。

以下是5篇文章中的每一篇的赢家算法。

  1. BERT
  2. TF-IDF
  3. TF-IDF
  4. Jaccard, TF-IDF和USE
  5. USE

从结果可以看出,对于新闻报道中的文档相似性,TF-IDF是最佳候选。如果你使用它的最小定制,这一点尤其正确。考虑到TF-IDF是发明的第二古老的算法,这也令人惊讶。相反,你可能会失望的是,现代先进的人工智能深度学习在这项任务中没有任何意义。

当然,每种深度学习技术都可以通过训练自己的模型和更好地预处理数据来改进。但所有这些都伴随着开发成本。你想好好想想,相对于TF-IDF方法,这种努力会带来额外多大的好处。

最后,可以说我们应该完全忘记Jaccard和Doc2vec的文档相似性。与今天的替代品相比,它们没有带来任何好处。

新手推荐

假设你决定从头开始在应用程序中实现相似性算法,下面是我的建议。

1.先实施TF-IDF

最快的文档相似性匹配是TF-IDF,尽管有深度学习的各种宣传,例如深度学习给你一个高质量的结果。但是TFIDF最棒的是,它是闪电般的快。

正如我们所看到的,将其升级到深度学习方法可能会或不会给你带来更好的性能。在计算权衡时,必须事先考虑很多问题。

2.积累更好的数据

Andrew Ng给出了一个类似的建议。你不能指望你的车没有油就跑。油必须是好的。

文档相似性依赖于数据的多样性,也依赖于特定的算法。你应该尽你最大的努力找到唯一的数据来增强你的相似性结果。

3.升级到深度学习

仅当你对TF-IDF的结果不满意时,才迁移到USE或BERT以升级模型。你需要考虑计算时间。你可能会预处理词嵌入,因此你可以在运行时更快地处理相似性匹配。谷歌为此写了一篇教程:https://cloud.google.com/solutions/machine-learning/building-real-time-embeddings-similarity-matching-system

4.调整深度学习算法

你可以慢慢升级你的模型。训练你自己的模型,将预训练好的知识融入特定的领域,等等。今天也有许多不同的深度学习模式。你可以一个一个的来看看哪一个最适合你的具体要求。

文档相似性是许多NLP任务之一

你可以使用各种算法实现文档的相似性:一些是传统的统计方法,另一些是尖端的深度学习方法。我们已经在纽约时报的文章中看到了它们之间的比较。

使用TF-IDF,你可以在本地笔记本电脑上轻松启动自己的文档相似性。不需要昂贵的GPU。不需要大内存。你仍然可以得到高质量的数据。

诚然,如果你想做情绪分析或分类等其他任务,深入学习应该适合你的工作。但是,当研究人员试图突破深度学习效率和成绩界限时,我们要意识到生活在炒作的圈子里是不健康的。它给新来的人带来巨大的焦虑和不安全感。

本篇文章主要介绍了前端HTML5几种存储方式的总结 ,主要包括本地存储localstorage,本地存储sessionstorage,离线缓存(application cache),Web SQL,IndexedDB。有兴趣的可以了解一下。

正文开始~

总体情况

h5之前,存储主要是用cookies。cookies缺点有在请求头上带着数据,大小是4k之内。主Domain污染。

主要应用:购物车、客户登录

对于IE浏览器有UserData,大小是64k,只有IE浏览器支持。

目标

  1. 解决4k的大小问题
  2. 解决请求头常带存储信息的问题
  3. 解决关系型存储的问题
  4. 跨浏览器

1.本地存储localstorage

存储方式:

以键值对(Key-Value)的方式存储,永久存储,永不失效,除非手动删除。

大小:

每个域名5M

支持情况:

注意:IE9 localStorage不支持本地文件,需要将项目署到服务器,才可以支持!

if(window.localStorage){
 
 alert('This browser supports localStorage');
 
}else{
 
 alert('This browser does NOT support localStorage');
 
} 

常用的API:

getItem //取记录

setIten//设置记录

removeItem//移除记录

key//取key所对应的值

clear//清除记录

存储的内容:

数组,图片,json,样式,脚本。。。(只要是能序列化成字符串的内容都可以存储)

2.本地存储sessionstorage

HTML5 的本地存储 API 中的 localStorage 与 sessionStorage 在使用方法上是相同的,区别在于 sessionStorage 在关闭页面后即被清空,而 localStorage 则会一直保存。

3.离线缓存(application cache)

本地缓存应用所需的文件

使用方法:

①配置manifest文件

页面上:

<!DOCTYPE HTML>
 
<html manifest="demo.appcache">
 
...
 
</html> 

Manifest 文件:

manifest 文件是简单的文本文件,它告知浏览器被缓存的内容(以及不缓存的内容)。

manifest 文件可分为三个部分:

①CACHE MANIFEST - 在此标题下列出的文件将在首次下载后进行缓存

②NETWORK - 在此标题下列出的文件需要与服务器的连接,且不会被缓存

③FALLBACK - 在此标题下列出的文件规定当页面无法访问时的回退页面(比如 404 页面)

完整demo:

CACHE MANIFEST
 
# 2016-07-24 v1.0.0
 
/theme.css
 
/main.js
 
 
 
NETWORK:
 
login.jsp
 
 
 
FALLBACK:
 
/html/ /offline.html 

服务器上:manifest文件需要配置正确的MIME-type,即 "text/cache-manifest"。

如Tomcat:

<mime-mapping>
 
 <extension>manifest</extension>
 
 <mime-type>text/cache-manifest</mime-type>
 
</mime-mapping> 

常用API:

核心是applicationCache对象,有个status属性,表示应用缓存的当前状态:

0(UNCACHED) : 无缓存, 即没有与页面相关的应用缓存

1(IDLE) : 闲置,即应用缓存未得到更新

2 (CHECKING) : 检查中,即正在下载描述文件并检查更新

3 (DOWNLOADING) : 下载中,即应用缓存正在下载描述文件中指定的资源

4 (UPDATEREADY) : 更新完成,所有资源都已下载完毕

5 (IDLE) : 废弃,即应用缓存的描述文件已经不存在了,因此页面无法再访问应用缓存

相关的事件:

表示应用缓存状态的改变:

checking : 在浏览器为应用缓存查找更新时触发

error : 在检查更新或下载资源期间发送错误时触发

noupdate : 在检查描述文件发现文件无变化时触发

downloading : 在开始下载应用缓存资源时触发

progress:在文件下载应用缓存的过程中持续不断地下载地触发

updateready : 在页面新的应用缓存下载完毕触发

cached : 在应用缓存完整可用时触发

Application Cache的三个优势:

① 离线浏览

② 提升页面载入速度

③ 降低服务器压力

注意事项:

1. 浏览器对缓存数据的容量限制可能不太一样(某些浏览器设置的限制是每个站点 5MB)

2. 如果manifest文件,或者内部列举的某一个文件不能正常下载,整个更新过程将视为失败,浏览器继续全部使用老的缓存

3. 引用manifest的html必须与manifest文件同源,在同一个域下

4. 浏览器会自动缓存引用manifest文件的HTML文件,这就导致如果改了HTML内容,也需要更新版本才能做到更新。

5. manifest文件中CACHE则与NETWORK,FALLBACK的位置顺序没有关系,如果是隐式声明需要在最前面

6. FALLBACK中的资源必须和manifest文件同源

7. 更新完版本后,必须刷新一次才会启动新版本(会出现重刷一次页面的情况),需要添加监听版本事件。

8. 站点中的其他页面即使没有设置manifest属性,请求的资源如果在缓存中也从缓存中访问

9. 当manifest文件发生改变时,资源请求本身也会触发更新

离线缓存与传统浏览器缓存区别:

1. 离线缓存是针对整个应用,浏览器缓存是单个文件

2. 离线缓存断网了还是可以打开页面,浏览器缓存不行

3. 离线缓存可以主动通知浏览器更新资源

4.Web SQL

关系数据库,通过SQL语句访问

Web SQL 数据库 API 并不是 HTML5 规范的一部分,但是它是一个独立的规范,引入了一组使用 SQL 操作客户端数据库的 APIs。

支持情况:

Web SQL 数据库可以在最新版的 Safari, Chrome 和 Opera 浏览器中工作。

核心方法:

①openDatabase:这个方法使用现有的数据库或者新建的数据库创建一个数据库对象。

②transaction:这个方法让我们能够控制一个事务,以及基于这种情况执行提交或者回滚。

③executeSql:这个方法用于执行实际的 SQL 查询。

打开数据库:

var db=openDatabase('mydb', '1.0', 'Test DB', 2 * 1024 * 1024,fn);
 
//openDatabase() 方法对应的五个参数分别为:数据库名称、版本号、描述文本、数据库大小、创建回调

执行查询操作:

var db=openDatabase('mydb', '1.0', 'Test DB', 2 * 1024 * 1024);
 
db.transaction(function (tx) { 
 
 tx.executeSql('CREATE TABLE IF NOT EXISTS WIN (id unique, name)');
 
}); 

插入数据: 

var db=openDatabase('mydb', '1.0', 'Test DB', 2 * 1024 * 1024);
 
db.transaction(function (tx) {
 
 tx.executeSql('CREATE TABLE IF NOT EXISTS WIN (id unique, name)');
 
 tx.executeSql('INSERT INTO WIN (id, name) VALUES (1, "winty")');
 
 tx.executeSql('INSERT INTO WIN (id, name) VALUES (2, "LuckyWinty")');
 
}); 

读取数据:

db.transaction(function (tx) {
 
 tx.executeSql('SELECT * FROM WIN', [], function (tx, results) {
 
 var len=results.rows.length, i;
 
 msg="<p>查询记录条数: " + len + "</p>";
 
 document.querySelector('#status').innerHTML +=msg; 
 
 for (i=0; i < len; i++){
 
 alert(results.rows.item(i).name );
 
 }
 
 }, null);
 
}); 

由这些操作可以看出,基本上都是用SQL语句进行数据库的相关操作,如果你会MySQL的话,这个应该比较容易用。

5.IndexedDB

索引数据库 (IndexedDB) API(作为 HTML5 的一部分)对创建具有丰富本地存储数据的数据密集型的离线 HTML5 Web 应用程序很有用。同时它还有助于本地缓存数据,使传统在线 Web 应用程序(比如移动 Web 应用程序)能够更快地运行和响应。

异步API:

在IndexedDB大部分操作并不是我们常用的调用方法,返回结果的模式,而是请求——响应的模式,比如打开数据库的操作

这样,我们打开数据库的时候,实质上返回了一个DB对象,而这个对象就在result中。由上图可以看出,除了result之外。还有几个重要的属性就是onerror、onsuccess、onupgradeneeded(我们请求打开的数据库的版本号和已经存在的数据库版本号不一致的时候调用)。这就类似于我们的ajax请求那样。我们发起了这个请求之后并不能确定它什么时候才请求成功,所以需要在回调中处理一些逻辑。

关闭与删除:

function closeDB(db){
 
 db.close();
 
}
 
function deleteDB(name){
 
 indexedDB.deleteDatabase(name);
 
} 

数据存储:

indexedDB中没有表的概念,而是objectStore,一个数据库中可以包含多个objectStore,objectStore是一个灵活的数据结构,可以存放多种类型数据。也就是说一个objectStore相当于一张表,里面存储的每条数据和一个键相关联。

我们可以使用每条记录中的某个指定字段作为键值(keyPath),也可以使用自动生成的递增数字作为键值(keyGenerator),也可以不指定。选择键的类型不同,objectStore可以存储的数据结构也有差异。

学习从来不是一个人的事情,要有个相互监督的伙伴,想要学习或交流前端问题的小伙伴可以私信“学习”小明获取web前端入门资料,一起学习,一起成长!

基本概念下手,让我们来创建一个简单的表,并在表中创建几条记录。


创建测试数据

接下来我们在 MySQL 中创建 RUNOOB 数据库,并创建 websites 数据表,表结构如下:

CREATE TABLE `websites` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `name` char(20) NOT NULL DEFAULT '' COMMENT '站点名称',
 `url` varchar(255) NOT NULL DEFAULT '',
 `alexa` int(11) NOT NULL DEFAULT '0' COMMENT 'Alexa 排名',
 `country` char(10) NOT NULL DEFAULT '' COMMENT '国家',
 PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8;

插入一些数据:

INSERT INTO `websites` VALUES ('1', 'Google', 'https://www.google.cm/', '1', 'USA'), ('2', '淘宝', 'https://www.taobao.com/', '13', 'CN'), ('3', '菜鸟教程', 'http://www.runoob.com', '5892', ''), ('4', '微博', 'http://weibo.com/', '20', 'CN'), ('5', 'Facebook', 'https://www.facebook.com/', '3', 'USA');



访问数据库

下面的实例演示了如何使用 Servlet 访问 RUNOOB 数据库。

package com.runoob.test;import java.io.IOException;import java.io.PrintWriter;import java.sql.*;import javax.servlet.ServletException;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;/**
* Servlet implementation class DatabaseAccess
*/@WebServlet("/DatabaseAccess")public class DatabaseAccess extends HttpServlet { private static final long serialVersionUID=1L; // JDBC 驱动名及数据库 URL static final String JDBC_DRIVER="com.mysql.jdbc.Driver"; static final String DB_URL="jdbc:mysql://localhost:3306/RUNOOB"; 
 // 数据库的用户名与密码,需要根据自己的设置 static final String USER="root"; static final String PASS="123456";
 /**
 * @see HttpServlet#HttpServlet()
 */
 public DatabaseAccess() {
 super();
 // TODO Auto-generated constructor stub
 } /**
 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
 */ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Connection conn=null; Statement stmt=null; // 设置响应内容类型 response.setContentType("text/html;charset=UTF-8"); PrintWriter out=response.getWriter(); String title="Servlet Mysql 测试 - 菜鸟教程"; String docType="<!DOCTYPE html>\n"; out.println(docType + "<html>\n" + "<head><title>" + title + "</title></head>\n" + "<body bgcolor=\"#f0f0f0\">\n" + "<h1 align=\"center\">" + title + "</h1>\n"); try{ // 注册 JDBC 驱动器 Class.forName("com.mysql.jdbc.Driver"); 
 // 打开一个连接 conn=DriverManager.getConnection(DB_URL,USER,PASS); // 执行 SQL 查询 stmt=conn.createStatement(); String sql; sql="SELECT id, name, url FROM websites"; ResultSet rs=stmt.executeQuery(sql); // 展开结果集数据库 while(rs.next()){ // 通过字段检索 int id=rs.getInt("id"); String name=rs.getString("name"); String url=rs.getString("url"); 
 // 输出数据 out.println("ID: " + id); out.println(", 站点名称: " + name); out.println(", 站点 URL: " + url); out.println("<br />"); } out.println("</body></html>"); // 完成后关闭 rs.close(); stmt.close(); conn.close(); } catch(SQLException se) { // 处理 JDBC 错误 se.printStackTrace(); } catch(Exception e) { // 处理 Class.forName 错误 e.printStackTrace(); }finally{ // 最后是用于关闭资源的块 try{ if(stmt!=null) stmt.close(); }catch(SQLException se2){ } try{ if(conn!=null) conn.close(); }catch(SQLException se){ se.printStackTrace(); } } 
 } /**
 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
 */ protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // TODO Auto-generated method stub doGet(request, response); }}

现在让我们来编译上面的 Servlet,并在 web.xml 文件中创建以下条目:

.... <servlet> <servlet-name>DatabaseAccess</servlet-name> <servlet-class>com.runoob.test.DatabaseAccess</servlet-class> </servlet> <servlet-mapping> <servlet-name>DatabaseAccess</servlet-name> <url-pattern>/TomcatTest/DatabaseAccess</url-pattern> </servlet-mapping>....