Highlight Bookmark Manager 為網站加入劃重點、討論和書籤功能

如果你還有印象,三個月前我介紹過「HiLight 為網站加入螢光筆標記、群組討論及收藏分享內容等互動式功能」服務,只要在網站加入程式碼,輕鬆整合劃重點、摘要和留言討論、收藏內容等工具,對於內容或媒體網站來說非常實用!尤其想讓內容能更有效被討論或分享使用,就無須花大錢開發不知道好不好用的功能,HiLight 是一項最快最方便的解決方案,而且完全免費。

HiLight 由台灣團隊開發製作,相較於一些類似的國外服務來說,更符合中文使用者的操作習慣以及對中文字元支援。近期針對 WordPress 開發了全新外掛「Highlight Bookmark Manager」,已在 WordPress 官方外掛目錄上架,如果你是自己架站的朋友,可參考以下教學快速將 HiLight 整合到自己網站。

簡單來說,當安裝完「Highlight Bookmark Manager」外掛後,網站左下角會多出三個新按鈕,分別是筆記、討論和書籤功能。使用者只要選取網頁內的文字段落就能輕鬆劃上螢光筆高亮效果(Highlight),加入自己的觀點,這麼一來將有助於拉長使用者停留在網站時間,亦提供了一個更好的討論渠道,也能將內容再次分享至其他社群平台。

網站名稱:Highlight Bookmark Manager
網站鏈結:http://www.hilight.cc/installation/wordpress

使用教學

STEP 1

HiLight 網站首頁提供兩種安裝方法,如果你不是使用 WordPress 架站的話,可以直接建立帳號來取得程式碼,將它加入網站就能使用;若你的網站是 WordPress,那麼就更簡單方便了,直接從 HiLight 官方網站的 WordPress 外掛頁面來下載安裝即可。

另一種安裝方式是登入你的 WordPress 控制台,點選左側「外掛 -> 安裝外掛」,接著在右上角輸入 hilight 搜尋。

搜尋結果第一個就是「Highlight Bookmark Manager」,點選右上角「立即安裝」就能將它下載、安裝到 WordPress。從 WordPress 4.6 版開始,安裝及啟用外掛和佈景主題變得更快更簡潔了,直接在原頁面就能下載、啟用,節省時間。

STEP 2

安裝後 Highlight Bookmark Manager 還無法運作,我們必須先註冊以取得自己網站專屬的 API 資訊,將網頁拉到最下方,就能找到連接至 Highlight Bookmark 註冊鏈結。

STEP 3

點選「Log in With Facebook」以你的 Facebook 帳戶登入註冊。

輸入你的網站網址、名稱後,點選「Submit」為你的網站建立一個新帳戶。

最後就能獲取 Highlight Bookmark Manager 的 Web Id 和 Web Secret,這兩項資訊非常重要,記得不要將它外流出去,以避免安全問題。

STEP 4

回到 Highlight Bookmark Manager 外掛的設定選項,將 Web Id 和 Web Secret 兩項數值填入後點選「Submit」,出現確認訊息就完成連接和基本設定。

STEP 5

設定好 Highlight Bookmark Manager 後,我們就可以回到網站前台來測試功能,試著複製文章任一段文字內容,會跳出 HiLight 工具列,能夠劃線做筆記,或者開啟討論留言功能。

使用 Highlight Bookmark 來保存內容也是一個相當方便的作法,可惜它目前僅能在安裝這項工具的網站使用,跟一般我們可能使用過筆記工具不同(大部分都是 Google Chrome 或 Firefox 外掛),使用時一樣得先登入你的 Facebook 帳戶,用於存取你保存或討論的相關內容記錄。

我相當推薦 HiLight 的原因是它的劃線及討論功能非常好用,而且可以取代的免費服務並不多(尤其又是中文服務相對更少),若你覺得只提供文章迴響或 Facebook 留言框無法讓讀者聚焦於內容討論的話,我認為 Highlight Bookmark Manager 更是一個不可多得的網站工具!

STEP 6

如同前面提到的,使用 HiLight 標記內容後,還能透過分享功能將它傳送給其他人,或者分享至 Facebook、Twitter、Google+ 等社群網站!不用再擔心其他人找不到討論功能因而使寶貴的意見石沈大海,

本篇文章側重於 Highlight Bookmark Manager 外掛在 WordPress 網站的安裝及設定教學,沒有對 HiLight 有更深入的報導或討論,若你想更詳細了解這項功能,可參考我之前寫的 HiLight 為網站加入螢光筆標記、群組討論及收藏分享內容等互動式功能。

整體來說,這項服務非常深得我心,無論在網站或影片介紹甚至程式方面都具備中英文介面,看得出 HiLight 積極向外發展的野心,期待未來能有更多發展空間。

  • 按一下以分享至 Facebook(在新視窗中開啟)
  • 分享到 Twitter(在新視窗中開啟)
  • 分享到 LinkedIn(在新視窗中開啟)
  • 分享到 Pinterest(在新視窗中開啟)
  • 分享到 Pocket(在新視窗中開啟)

相關文章

本站聲明:網站內容來源免費資源網路社群https://free.com.tw,如有侵權,請聯繫我們,我們將及時處理

【精選推薦文章】

自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

WordPress 無敵架站手冊:架站新手都想擁有,參加活動免費帶回家

免費資源網路社群跟著 WordPress 一起長大,可以這麼說,我在 2006 年接觸 WordPress 後隨即就架設了第一個部落格,迄今十年,也就是大家正在瀏覽的網站。這些年持續不斷在 WordPress Taiwan 正體中文社群盡自己最大努力,除了程式中文語系維護,我也嘗試翻譯、撰寫部分教學文件以及在外掛功能上推動中文化工作,本著自己也有在使用就順手做一下,讓後來加入的人可以少碰一些問題的想法,我做得很開心。

這幾年台灣使用 WordPress 架站的朋友越來越多,全世界市佔率超越 25%(數據來自 W3Tech),到處都能看到 WordPress 蹤跡,想架設網站幾乎沒有第二個選項。但回到台灣書店一望,WordPress 書籍仍舊寥寥可數(當然相較於幾年前來說已經算活躍許多),即使網路上已有豐富的教學文件,卻沒有一本非常完整的中文手冊。

對於學習 WordPress 架站的人來說,想從頭到尾、穩紮穩打很難找到學習路徑,於是我這幾年就興起想要寫一本由淺入深、全面的 WordPress 工具書,恰巧得知英國出版商 Packt Publishing 的《WordPress 4.x Complete》一書,二話不說就決定將它翻譯成中文書,讓想學 WordPress 架站的朋友有個更完整的參考資料。

《WordPress 無敵架站手冊》是一本從 WordPress 介紹、入門、安裝操作,到進階開發外掛、佈景主題及小工具等主題皆有涵蓋的完整手冊!後半章節還會教你如何利用 WordPress 建立非網誌型態網站。

雖然無法在有限篇幅內詳細說明所有範圍,但讀者可將本書做為學習的起點,從基礎延伸出去,繼續學習。對於有心想要了解 WordPress 架站的朋友來說,這是一本易於閱讀及上手的工具書,也希望透過這本書讓你認識更方便、安全可靠的免費開放原始碼架站程式 WordPress。

如何購買這本書,網上購書更優惠

如果你對架站有興趣,或者對於 WordPress 好奇、有興趣、想學習甚至是希望搭建一個自己的個人網站或部落格,你可以在實體店面或從網路通路買到這本書,我僅在文章內提供幾個購書鏈結,透過 Google 搜尋可以找到更多管道。

購書鏈結:http://www.books.com.tw/products/0010730008 (博客來)

久違的抽獎活動,還是要免費送書

若你一路以來都有在關注或追蹤免費資源網路社群的文章,依照慣例我都會提供一些贈品來送讀者,感謝博碩文化贊助本次贈書。

活動方式一如往昔,只要到免費資源網路社群 Facebook 粉絲專頁按個讚、在本文下方留言,就有機會在免費得到《WordPress 無敵架站手冊》!我們會在活動結束後抽出幸運讀者,在最短時間內將新書寄到您手上。

STEP 1

前往免費資源網路社群 Facebook 按個讚,相信大家都已經按過(也請多多幫我們宣傳分享,謝謝);如果你有使用其他社群網站,例如 Twitter、Google+,也一併將免費資源網路社群加入追蹤。網站右側還有一些免費資源網路社群使用的社群服務。

STEP 2

在本文下方留言(或直接在粉絲頁面同一則貼文留言)回覆以下文字:

WordPress 無敵架站手冊:架站新手都想擁有,參加活動免費帶回家

這樣就可以參加抽獎活動,是不是非常簡單呢?

活動時間五天,請多多分享轉貼

如果方便的話,也歡迎將本文鏈結分享、轉貼到你的 Facebook 或其他網站。若活動夠踴躍,我們也會視情況增加更多贈書名額喔!我會在下週三(9/28)抽出幸運的朋友,屆時在本文更新及 Facebook 等社群網站公告得獎情形,請大家把握這次難得機會多多支持!

得獎名單(9/28 晚間 22:00 更新)

本次活動一共有 816 位讀者參加,扣掉重複留言,以「粉絲團留言抽籤小助手」小工具隨機抽取 10 位留言者贈送新書《WordPress 無敵架站手冊》一本,幸運得獎的朋友名單如下(依照抽獎結果順序排列):

黃楚翔、王凱蓓、Yu-Wei Chen、Stella Lin、Cheng Lin、尹翠軒、盧嘉皓、黃豊益、胡倫、黃謙。

請幸運中獎的讀者儘速與 Pseric 聯繫,我也會到留言下方 tag 通知你,直接私訊告知我收件人資料即可。確認完畢就會陸續將熱騰騰的新書寄到你們手上囉!本書已在各大書局、網路書店上架,有興趣的朋友也能直接從網上購得,謝謝大家熱烈參與支持本次活動!

為了公平起見,下方是本次抽獎影片紀錄:

  • 按一下以分享至 Facebook(在新視窗中開啟)
  • 分享到 Twitter(在新視窗中開啟)
  • 分享到 LinkedIn(在新視窗中開啟)
  • 分享到 Pinterest(在新視窗中開啟)
  • 分享到 Pocket(在新視窗中開啟)

相關文章

本站聲明:網站內容來源免費資源網路社群https://free.com.tw,如有侵權,請聯繫我們,我們將及時處理

【精選推薦文章】

智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

想知道網站建置、網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計及後台網頁設計

帶您來看台北網站建置台北網頁設計,各種案例分享

廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

Cloudflare 免費 WordPress 外掛教學,一鍵為網站快速套用最佳化設定


(Photo credit: TechCrunch via Visualhunt / CC BY)

最近 CloudFlare 在台灣網路圈吵蠻兇的,起因是 CloudFlare 八月在自家網站公布服務在全世界各地頻寬支出成本(參考資料:Bandwidth Costs Around the World),其中點名的六家超高成本 ISP 業者在亞洲就有台灣中華電信 HiNet 及韓國電信,相較於歐洲或北美支出的頻寬成本來說多出 15 倍!CloudFlare 為了提出抗議,更大動作將連線到使用 CloudFlare 服務網站的中華電信使用者導向頻寬成本較低的美國西岸節點。

其實連到美西頻寬夠大,速度倒也不是非常糟,畢竟很多網站都選擇放在開銷較低的國外機房,那麼這段距離對於台灣使用者開啟網站的速度並沒有太大影響。但恰巧中華電信近期發生海底電纜異常,尖峰時段連線壅塞造成很多網站無法打開,才會有好像取消台灣節點造成網站變慢的問題。

撇開這些狀況不說,我還是推薦架站時搭配 CloudFlare 提供的免費 CDN 來加速網站連線速度,同時遏止任何可能的攻擊或安全問題,類似服務還有 Incapsula,但沒有它來得這麼全面(目前 CF 節點數也是同類服務之冠)。

關於 CloudFlare 使用教學我在「CloudFlare 架站者必備免費 CDN、DNS 託管服務設定教學,網站載入加速、節省流量防止惡意攻擊」一文有完整說明,若你是使用 WordPress 架站,我建議你務必安裝全新 CloudFlare 外掛,它為 WP 使用者提供更多功能。

早先 WordPress 的 CloudFlare 外掛只有解析 IP 功能,讓無法在控制台迴響顯示使用者真實 IP 位址的問題得以解決;近期 CloudFlare 外掛更新,將設定選項帶進 WordPress 控制台,往後將可以在控制台進行一些基本的設定或操作,也能查看分析報表。

網站名稱:CloudFlare for WordPress
網站鏈結:https://tw.wordpress.org/plugins/cloudflare/

使用教學

STEP 1

登入 WordPress 控制台後,點選左側選單的「外掛 -> 安裝外掛」,右上角填入 CloudFlare 會跳出相關結果,第一個就是來自 CloudFlare 官方的外掛功能,點選「立即安裝」即可將它下載安裝到網站。

在外掛列表中將 CloudFlare 啟用,設定裡就會多一個 CF 設定頁面。

STEP 2

不過要在控制台使用 CloudFlare 功能,必須要連結到你 CF 帳戶,驗證方法是 Email 加上你的 API 密鑰,這組密鑰在哪呢?只要開啟 CloudFlare 控制台並點選右上角的 My Settings 就能找到(網頁下方的 Global API Key,點選 View API Key 就會顯示)。

這組密鑰非常重要,等同於你的密碼,切勿將它告訴其他人。

回到 WordPress 的 CloudFlare 設定頁面,輸入你的 CloudFlare 帳號(Email)及 API Key 就能成功串連,基本上登入畫面只需要設定一次,之後就不會再次出現。

STEP 3

接著會看到 CloudFlare 在全新 3.0 版加入的所有功能,介面也變得跟以前不同。

如果你剛開始接觸 CF,沒有太多時間來詳細研究各項設定,推薦你直接使用第一項設定的「Apply Default Settings」一鍵套用最適合網站效能的建議設定值,這也是此次更新後我認為最喜愛的功能。

STEP 4

CloudFlare 在新的 WordPress 外掛程式上已經加入清理快取(Purge Cache)自動化完整快取管理(Automatic Full Cache Management)功能,若開啟後當你更新網站選項時就會自動清理 CloudFlare 的快取內容。不過預設情況下其實不太需要打開,因為網站頁面並不會被 CloudFlare 快取,只有圖片等靜態元件才會。

STEP 5

另外,設定頁右上角的「Enable “Im Under Attack” Mode」是一項很特殊的功能,如果你認為你的網站正遭遇攻擊,或者有人想嘗試登入網站(例如使用暴力破解),點選後就能使網站獲得更高安全性的保護措施。

正常情況下並不建議開啟這項功能,因為很可能會使一般訪客在瀏覽時發生問題。

STEP 6

其他設定選項在「More Settings」可以找到,完整設定項目建議回到 CloudFlare 調整修改,外掛裡沒有涵蓋所有選項。但還是要補充一下,從 Analytics 裡可以看到 CF 端的分析圖表了!這也是此次更新中非常令人振奮的全新功能。

  • 按一下以分享至 Facebook(在新視窗中開啟)
  • 分享到 Twitter(在新視窗中開啟)
  • 分享到 LinkedIn(在新視窗中開啟)
  • 分享到 Pinterest(在新視窗中開啟)
  • 分享到 Pocket(在新視窗中開啟)

相關文章

本站聲明:網站內容來源免費資源網路社群https://free.com.tw,如有侵權,請聯繫我們,我們將及時處理

【精選推薦文章】

如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

想要讓你的商品在網路上成為最夯、最多人討論的話題?

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師"嚨底家"!!

Java集合 ArrayList原理及使用

ArrayList是集合的一種實現,實現了接口List,List接口繼承了Collection接口。Collection是所有集合類的父類。ArrayList使用非常廣泛,不論是數據庫表查詢,excel導入解析,還是網站數據爬取都需要使用到,了解ArrayList原理及使用方法顯得非常重要。

一. 定義一個ArrayList

//默認創建一個ArrayList集合
List<String> list = new ArrayList<>();
//創建一個初始化長度為100的ArrayList集合
List<String> initlist = new ArrayList<>(100);
//將其他類型的集合轉為ArrayList
List<String> setList = new ArrayList<>(new HashSet());

我們讀一下源碼,看看定義ArrayList的過程到底做了什麼?

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    /**
     * Default initial capacity.
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * Shared empty array instance used for empty instances.
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
     * Shared empty array instance used for default sized empty instances. We
     * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
     * first element is added.
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
     * The array buffer into which the elements of the ArrayList are stored.
     * The capacity of the ArrayList is the length of this array buffer. Any
     * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
     * will be expanded to DEFAULT_CAPACITY when the first element is added.
     */
    transient Object[] elementData; // non-private to simplify nested class access

    /**
     * The size of the ArrayList (the number of elements it contains).
     *
     * @serial
     */
    private int size;

    /**
     * Constructs an empty list with the specified initial capacity.
     *
     * @param  initialCapacity  the initial capacity of the list
     * @throws IllegalArgumentException if the specified initial capacity
     *         is negative
     */
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

    /**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

    /**
     * Constructs a list containing the elements of the specified
     * collection, in the order they are returned by the collection's
     * iterator.
     *
     * @param c the collection whose elements are to be placed into this list
     * @throws NullPointerException if the specified collection is null
     */
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }
}

其實源碼裏面已經很清晰了,ArrayList非線程安全,底層是一個Object[],添加到ArrayList中的數據保存在了elementData屬性中。

  • 當調用new ArrayList<>()時,將一個空數組{}賦值給了elementData,這個時候集合的長度size為默認長度0;

  • 當調用new ArrayList<>(100)時,根據傳入的長度,new一個Object[100]賦值給elementData,當然如果玩兒的話,傳了一個0,那麼將一個空數組{}賦值給了elementData;

  • 當調用new ArrayList<>(new HashSet())時,根據源碼,我們可知,可以傳遞任何實現了Collection接口的類,將傳遞的集合調用toArray()方法轉為數組內賦值給elementData;

注意:在傳入集合的ArrayList的構造方法中,有這樣一個判斷

if (elementData.getClass() != Object[].class),

給出的註釋是:c.toArray might (incorrectly) not return Object[] (see 6260652),即調用toArray方法返回的不一定是Object[]類型,查看ArrayList源碼

public Object[] toArray() {    return Arrays.copyOf(elementData, size);}

我們發現返回的確實是Object[],那麼為什麼還會有這樣的判斷呢?

如果有一個類CustomList繼承了ArrayList,然後重寫了toArray()方法呢。。

public class CustomList<E> extends ArrayList {
    @Override
    public Integer [] toArray() {
        return new Integer[]{1,2};
    };
    
    public static void main(String[] args) {
        Object[] elementData = new CustomList<Integer>().toArray();
        System.out.println(elementData.getClass());
        System.out.println(Object[].class);
        System.out.println(elementData.getClass() == Object[].class);
    }
}

執行結果:

class [Ljava.lang.Integer;
class [Ljava.lang.Object;
false

接着說,如果傳入的集合類型和我們定義用來保存添加到集合中值的Object[]類型不一致時,ArrayList做了什麼處理?讀源碼看到,調用了Arrays.copyOf(elementData, size, Object[].class);,繼續往下走

public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {    
    T[] copy = ((Object)newType == (Object)Object[].class)
        ? (T[]) new Object[newLength]
        : (T[]) Array.newInstance(newType.getComponentType(), newLength); 
    System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
    return copy;
}

我們發現定義了一個新的數組,將原數組的數據拷貝到了新的數組中去。

二. ArrayList常用方法

ArrayList有很多常用方法,add,addAll,set,get,remove,size,isEmpty等

首先定義了一個ArrayList,

List<String> list = new ArrayList<>(10);
list.add('牛魔王');
list.add('蛟魔王');
...
list.add('美猴王');

Object[] elementData中數據如下:

1. add(E element)

我們通過源碼來看一下add(“白骨精”)到底發生了什麼

public boolean add(E e) {
    ensureCapacityInternal(size + 1);
    // Increments modCount!!
    elementData[size++] = e;
    return true;
}

首先通過 ensureCapacityInternal(size + 1) 來保證底層Object[]數組有足夠的空間存放添加的數據,然後將添加的數據存放到數組對應的位置上,我們看一下是怎麼保證數組有足夠的空間?

private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

這裏首先確定了Object[]足夠存放添加數據的最小容量,然後通過 grow(int minCapacity) 來進行數組擴容

private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);
}

擴容規則為“數組當前足夠的最小容量 + (數組當前足夠的最小容量 / 2)”,即數組當前足夠的最小容量 * 1.5,當然有最大值的限制。

因為最開始定義了集合容量為10,故而本次不會進行擴容,直接將第8個位置(從0開始,下標為7)設置為“白骨精”,這時Object[] elementData中數據如下:

還有和add()類似的方法。空間擴容原理都是一樣,如:

add("鐵扇", 0); //將數組中的元素各自往後移動一位,再將“鐵扇”放到第一個位置上;

addAll(list..七個葫蘆娃); //將集合{七個葫蘆娃}放到”白骨精”后,很明顯當前數組的容量已經不夠,需要擴容了,不執行該句代碼了;

addAll(list..哪吒三兄弟, 4);//從第五個位置將“哪吒三兄弟”插進去,那麼數組第五個位置后的元素都需往後移動三位,數組按規則擴容為18。

指定了插入位置的,會通過rangeCheckForAdd(int index)方法判斷是否數組越界

2. set(int index, E element)

因為ArrayList底層是由數組實現的,set實現非常簡單,調用 set(8, "豬八戒") 通過傳入的数字下標找到對應的位置,替換其中的元素,前提也需要首先判斷傳入的數組下標是否越界。將“獼猴王”替換為“豬八戒”

public E set(int index, E element) {
    rangeCheck(index);
    E oldValue = elementData(index);
    elementData[index] = element;
    return oldValue;
}

//返回值“獼猴王”,當前數組中數據:

3. get(int index)

ArrayList中get方法也非常簡單,通過下標查找即可,同時需要進行了類型轉換,因為數組為Object[],前提是需要判斷傳入的數組下標是否越界。

public E get(int index) {
    rangeCheck(index);
    return elementData(index);
}
E elementData(int index) {
    return (E) elementData[index];
}

調用get(6)返回”哪吒“。

4. remove(int index)

首先說一下ArrayList通過下標刪除的方法,我們看一下源碼

public E remove(int index) {
    rangeCheck(index);
    modCount++;
    E oldValue = elementData(index);
    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index, numMoved);
    elementData[--size] = null; // clear to let GC do its work
    return oldValue;
}

通過源碼我們可以看到首先獲取了待刪除的元素,並最終返回了。其次計算了數組中需要移動的位數 size – index – 1,那麼很明顯我們可以得出待刪除的是最後一個元素的話,移到位數為0,否則移動位數大於0,那麼通過數組元素的拷貝來實現往前移動相應位數。

如remove(10),找到的元素為“美猴王”,那麼移動位數 = 12-10-1 = 1;此時將原本在第12個位置上(數組下標為11)的“白骨精”往前移動一位,同時設置elementData[11] = null;這裏通過設置null值讓GC起作用。

5. remove(Object o)

刪除ArrayList中的值對象,其實和通過下標刪除很相似,只是多了一個步驟,遍歷底層數組elementData,通過equals()方法或 == (特殊情況下)來找到要刪除的元素,獲取其下標,調用remove(int index)一樣的代碼即可。

public boolean remove(Object o) {
    if (o == null) {
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                fastRemove(index);
                return true;
            }
    } else {
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
    }
    return false;
}

6. 其他方法

size() : 獲取集合長度,通過定義在ArrayList中的私有變量size得到

isEmpty():是否為空,通過定義在ArrayList中的私有變量size得到

contains(Object o):是否包含某個元素,通過遍歷底層數組elementData,通過equals或==進行判斷

clear():集合清空,通過遍歷底層數組elementData,設置為null

三. 總結

本文主要講解了ArrayList原理,從底層數組着手,講解了ArrayList定義時到底發生了什麼,再添加元素時,擴容規則如何,刪除元素時,數組的元素的移動方式以及一些常用方法的用途,若有不對之處,請批評指正,望共同進步,謝謝!

【精選推薦文章】

自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

JS數據結構第三篇—雙向鏈表和循環鏈表之約瑟夫問題

一、雙向鏈表

在上文《JS數據結構第二篇—鏈表》中描述的是單向鏈表。單向鏈表是指每個節點都存有指向下一個節點的地址,雙向鏈表則是在單向鏈表的基礎上,給每個節點增加一個指向上一個節點的地址。然後頭結點的上一個節點,和尾結點的下一個節點都指向null。同時LinkedList類中再增加一個last內部屬性,一直指向鏈表中最後一個節點。結構模擬如圖:

同樣對外暴露的方法和單向鏈表一樣,只是內部實現稍有變化

雙向鏈表完整設計代碼:

/**
 * 自定義雙向鏈表:對外公開的方法有
 * append(element) 在鏈表最後追加節點
 * insert(index, element) 根據索引index, 在索引位置插入節點
 * remove(element)  刪除節點
 * removeAt(index)  刪除指定索引節點
 * removeAll(element) 刪除所有匹配的節點
 * set(index, element) 根據索引,修改對應索引的節點值
 * get(index)  根據索引獲取節點信息
 * indexOf(element) 獲取某個節點的索引位置
 * clear()  清空所有節點
 * length()   返回節點長度
 * print() 打印所有節點信息
 * toString() 打印所有節點信息,同print
 * */
const LinkedListDouble = function(){
    let head = null; //鏈表中第一個LinkNode
    let last = null;  //鏈表中最後一個LinkNode
    let size = 0;   //記錄鏈表元素個數

    //Node模型
    function LinkNode(prev, element, next){
        this.prev = prev; //當前節點的上一個node
        this.element = element;  //當前節點的元素
        this.next = next; //當前節點的下一個node
    }

    //元素越界檢查, 越界拋出異常
    function outOfBounds(index){
        if (index < 0 || index >= size){
            throw("抱歉,目標位置不存在!");
        }
    }

    //根據索引,獲取目標對象
    function node(index){
        outOfBounds(index);

        //判斷index是靠近前半部分,還是後半部分,以求最少次數找到目標節點
        if (index <= size>>1){
            //說明從頭往後找,次數少一點
            let obj = head;
            for (let i = 0;i < index; i++){
                obj = obj.next;
            }
            return obj;
        }
        else{
            //說明從后往前找,次數少一點
            let obj = last;
            for (let i = size-1; i > index; i--){
                obj = obj.prev;
            }
            return obj;
        }
    }

    //新增一個元素
    function append(element){
        if (size == 0){
            head = new LinkNode(null, element, null);
            last = head;
        }
        else{
            let obj = node(size-1);
            obj.next = new LinkNode(obj, element, null);
            last = obj.next;
        }
        size++;
    }

    //插入一個元素
    function insert(index, element){
        if (index == 0){
            head = new LinkNode(null, element, head);

            if (size == 0){
                last = head;
            }
        }
        else{
            let obj = node(index-1);
            obj.next = new LinkNode(obj, element, obj.next);

            if (index == size){
                last = obj.next;
            }
        }
        size++;
    }

    //修改元素
    function set(index, element){
        let obj = node(index);
        obj.element = element;
    }

    //移除節點(內部使用,不對外暴露)
    function removeNode(node){
        let prev = node.prev, next = node.next; //當前節點的前一個,和后一個

        //判斷head臨界點
        if (prev == head){
            head = next;
        }
        else{
            prev.next = next;
        }

        //判斷last臨界點
        if (next == last){
            last = prev;
        }
        else{
            next.prev = prev;
        }
        size--;
        return node.element;
    }

    //根據值移除節點元素
    function remove(element){
        let temp = head;
        while(temp){
            if (temp.element == element){
                return removeNode(temp);
            }
            else{
                temp = temp.next;
            }
        }
        return null;
    }

    //根據索引移除節點
    function removeAt(index){
        return removeNode(node(index));
    }

    //移除鏈表裡面的所有匹配值element的元素
    function removeAll(element){
        let newFirst = new LinkNode(null, 0, head), ele = null;
        let virHead = newFirst;

        while(virHead.next){
            if (virHead.next.element == element){

                ele = element;

                if (virHead.next.next){
                    virHead.next.next.prev = virHead.next.prev;
                }
                else{ //刪除了最後一個節點
                    last = virHead.next.prev;
                }
                virHead.next = virHead.next.next;
                size--;
            }
            else{
                virHead = virHead.next;
            }
        }

        //重新賦值
        head = newFirst.next;

        return ele;
    }

    //獲取某個元素
    function get(index){
        return node(index).element;
    }

    //獲取元素索引
    function indexOf(element){
        let obj = head, index = -1;

        for (let i = 0; i < size; i++){
            if (obj.element == element){
                index = i;
                break;
            }
            obj = obj.next;
        }
        return index;
    }

    //清除所有元素
    function clear(){
        head = null;
        last = null;
        size = 0;
    }

    //屬性轉字符串
    function getObjString(obj){

        let str = "";

        if (obj instanceof Array){
            str += "[";
            for (let i = 0; i < obj.length; i++){
                str += getObjString(obj[i]);
            }
            str = str.substring(0, str.length - 2);
            str += "], "
        }
        else if (obj instanceof Object){
            str += "{";
            for (var key in obj){
                let item = obj[key];
                str += "\"" + key + "\": " + getObjString(item);
            }
            str = str.substring(0, str.length-2);
            str += "}, "
        }
        else if (typeof obj == "string"){
            str += "\"" + obj + "\"" + ", ";
        }
        else{
            str += obj + ", ";
        }

        return str;
    }
    function toString(){
        let str = "", obj = head;
        for (let i = 0; i < size; i++){
            str += getObjString(obj.element);
            obj = obj.next;
        }
        if (str.length > 0) str = str.substring(0, str.length -2);
        return str;
    }
    //打印所有元素
    function print(){
        console.log(this.toString())
    }

    //對外公開方法
    this.append = append;
    this.insert = insert;
    this.remove = remove;
    this.removeAt = removeAt;
    this.removeAll = removeAll;
    this.set = set;
    this.get = get;
    this.indexOf = indexOf;
    this.length = function(){
        return size;
    }
    this.clear = clear;
    this.print = print;
    this.toString = toString;
}


////測試
// let obj = new LinkedListDouble();
// let obj1 = { title: "全明星比賽", stores: [{name: "張飛vs岳飛", store: "2:3"}, { name: "關羽vs秦瓊", store: "5:5"}]};
// obj.append(99);
// obj.append("hello")
// obj.append(true)
// obj.insert(3, obj1);
// obj.insert(0, [12, false, "Good", 81]);
// obj.print();
// console.log("obj1.index: ", obj.indexOf(obj1));
// obj.remove(0);
// obj.removeAll(obj1);
// obj.print();

////測試2
console.log("\n\n......test2.....")
var obj2 = new LinkedListDouble();
obj2.append(8); obj2.insert(1, 99); obj2.append('abc'); obj2.append(8); obj2.append(false);
obj2.append(12); obj2.append(8); obj2.append('123'); obj2.append(8);
obj2.print();
obj2.removeAll(8); //刪除所有8
obj2.print();

View Code

 

二、循環鏈表

在鏈表的基礎上,再稍稍修改一下,讓鏈表中的尾結點和頭節點鏈接起來,形成一個循環生生不息。單向循環鏈表是尾結點的下一個地址指向頭結點,而不是null;雙向循環鏈表則是last節點的next指向head節點,head節點的prev指向last節點。結構模擬如下兩個圖:

 

 對於單個節點的循環鏈表,頭結點和尾節點為同一個節點,則自己指向自己。結構模擬如圖:

 循環鏈表的代碼這裏就不貼出來了,代碼放在Github那裡,有興趣可以點進去看看。

 

三、循環鏈表的應用—約瑟夫問題模擬

據說著名猶太歷史學家 Josephus有過以下的故事:在羅馬人佔領喬塔帕特后,39 個猶太人與Josephus及他的朋友躲到一個洞中,39個猶太人決定寧願死也不要被敵人抓到,於是決定了一個自殺方式,41個人排成一個圓圈,由第1個人開始報數,每報數到第3人該人就必須自殺,然後再由下一個重新報數,直到所有人都自殺身亡為止。然而Josephus 和他的朋友並不想遵從。首先從一個人開始,越過k-2個人(因為第一個人已經被越過),並殺掉第k個人。接着,再越過k-1個人,並殺掉第k個人。這個過程沿着圓圈一直進行,直到最終只剩下一個人留下,這個人就可以繼續活着。問題是,給定了和,一開始要站在什麼地方才能避免被處決?Josephus要他的朋友先假裝遵從,他將朋友與自己安排在第16個與第31個位置,於是逃過了這場死亡遊戲。

這裏我做了一個效果圖,模擬下約瑟夫問題:

接下來我們如何用鏈表來模擬約瑟夫問題。

在上面循環鏈表的基礎上,我們給LinkedList再添加一個內部屬性current,指向當前節點,默認指向頭結點;再增加三個對外方法next()、removeCurrent()、reset(),分別表示當前節點指向下一個節點,移除當前節點,重置當前節點為頭結點。修改后,新的LinkedList對外方法有:

 新的循環雙向鏈表完整設計代碼:

/**
 * 在循環雙向鏈表的基礎上,增加1個屬性,3個方法(屬性內部使用,方法對外開放),讓循環鏈表發揮更大的效果:
 * current: 指向當前節點,默認指向首節點head
 * next():讓current每次移動一次,移上下一個節點, 返回元素
 * removeCurrent(): 每次刪除當前current指向的節點,刪除后,current指向下一個節點
 * reset(): 重置current指向首節點head
 *
 * 自定義雙向循環鏈表:對外公開的方法有
 * append(element) 在鏈表最後追加節點
 * insert(index, element) 根據索引index, 在索引位置插入節點
 * remove(element)  刪除節點
 * removeAt(index)  刪除指定索引節點
 * removeAll(element) 刪除所有匹配的節點
 * set(index, element) 根據索引,修改對應索引的節點值
 * get(index)  根據索引獲取節點信息
 * indexOf(element) 獲取某個節點的索引位置
 * clear()  清空所有節點
 * length()   返回節點長度
 * print() 打印所有節點信息
 * toString() 打印所有節點信息,同print
 * */
const CircleLinkedListDouble = function(){
    let head = null; //鏈表中第一個LinkNode
    let last = null;  //鏈表中最後一個LinkNode
    let size = 0;   //記錄鏈表元素個數
    let current = null; //當前指向的節點

    //Node模型
    function LinkNode(prev, element, next){
        this.prev = prev; //當前節點的上一個node
        this.element = element;  //當前節點的元素
        this.next = next; //當前節點的下一個node
    }

    //元素越界檢查, 越界拋出異常
    function outOfBounds(index){
        if (index < 0 || index >= size){
            throw("抱歉,目標位置不存在!");
        }
    }

    //根據索引,獲取目標對象
    function node(index){
        outOfBounds(index);

        //判斷index是靠近前半部分,還是後半部分,以求最少次數找到目標節點
        if (index <= size>>1){
            //說明從頭往後找,次數少一點
            let obj = head;
            for (let i = 0;i < index; i++){
                obj = obj.next;
            }
            return obj;
        }
        else{
            //說明從后往前找,次數少一點
            let obj = last;
            for (let i = size-1; i > index; i--){
                obj = obj.prev;
            }
            return obj;
        }
    }

    //新增一個元素
    function append(element){
        if (size == 0){
            head = new LinkNode(null, element, null);
            head.next = head;
            head.prev = head;
            last = head;
            current = head;
        }
        else{
            let obj = node(size-1);
            obj.next = new LinkNode(obj, element, head);
            last = obj.next;
            head.prev = last;
        }
        size++;
    }

    //插入一個元素
    function insert(index, element){
        //表示插入到第一個
        if (index == 0){

            let last = null;
            if (size > 0) last = node(size-1);

            head = new LinkNode(last, element, head);

            if (size < 1){
                head.next = head;
                head.prev = head;
                last = head;
                current = head;
            }
            else{
                last.prev = head;
            }
        }
        else{
            let prev = node(index-1);
            prev.next = new LinkNode(prev, element, prev.next);

            //表示插入到最後一個
            if (index == size){
                last = prev.next;
                head.prev = last;
            }
        }
        size++;
    }

    //修改元素
    function set(index, element){
        let obj = node(index);
        obj.element = element;
    }

    //移除節點(內部使用,不對外暴露)
    function removeNode(node){

        if (size == 1){
            current = head = last = null;
        }
        else{
            let prev = node.prev, next = node.next; //當前節點的前一個,和后一個

            //判斷head臨界點
            if (prev == last){
                head = next;
                head.prev = last;
                last.next = head;
            }
            else{
                prev.next = next;
            }

            //判斷last臨界點
            if (next == head){
                last = prev;
                last.next = head;
                head.prev = last;
            }
            else{
                next.prev = prev;
            }

            if (current == node){
                current = next;
            }
        }

        size--;
        return node.element;
    }

    //根據值移除節點元素
    function remove(element){
        let temp = head;
        while(temp){
            if (temp.element == element){
                return removeNode(temp);
            }
            else{
                temp = temp.next;
            }
        }
        return null;
    }

    //根據索引移除節點
    function removeAt(index){
        return removeNode(node(index));
    }

    //移除鏈表裡面的所有匹配值element的元素
    function removeAll(element){
        let newFirst = new LinkNode(null, 0, head), delNode = null;
        let virHead = newFirst;

        //為了避免無限循環,先把循環鏈表斷開
        head.prev = null, last.next = null;

        while(virHead.next){
            if (virHead.next.element == element){

                delNode = virHead.next;

                if (virHead.next.next){
                    virHead.next.next.prev = virHead.next.prev;
                }
                else{
                    last = virHead.next.prev;
                }

                if (virHead.next == current){
                    current = current.next;
                    virHead.next = current;
                }
                else{
                    virHead.next = virHead.next.next;
                }

                size--;
            }
            else{
                virHead = virHead.next;
            }
        }

        //重新賦值
        head = newFirst.next;
        last = size > 0 ? node(size-1) : null;
        if (size > 0){
            last.next = head;
            head.prev = last;
        }
        else{
            current = head = last = null;
        }

        return delNode.element;
    }

    //獲取某個元素
    function get(index){
        return node(index).element;
    }

    //獲取元素索引
    function indexOf(element){
        let obj = head, index = -1;

        for (let i = 0; i < size; i++){
            if (obj.element == element){
                index = i;
                break;
            }
            obj = obj.next;
        }
        return index;
    }

    //清除所有元素
    function clear(){
        current = head = last = null;
        size = 0;
    }

    //這次新增加的三個方法next, removeCurrent, reset
    //調用重置current指向head節點
    function reset(){
        current = head;
    }
    function next(){
        if (!current) return null;

        current = current.next;
        return current.element;
    }

    //每調用一次,刪除current指向的節點
    function removeCurrent(){
        if (size < 1) return null;
        return removeNode(current);
    }

    //屬性轉字符串
    function getObjString(obj){

        let str = "";

        if (obj instanceof Array){
            str += "[";
            for (let i = 0; i < obj.length; i++){
                str += getObjString(obj[i]);
            }
            str = str.substring(0, str.length - 2);
            str += "], "
        }
        else if (obj instanceof Object){
            str += "{";
            for (var key in obj){
                let item = obj[key];
                str += "\"" + key + "\": " + getObjString(item);
            }
            str = str.substring(0, str.length-2);
            str += "}, "
        }
        else if (typeof obj == "string"){
            str += "\"" + obj + "\"" + ", ";
        }
        else{
            str += obj + ", ";
        }

        return str;
    }
    function toString(){
        let str = "", obj = head;
        for (let i = 0; i < size; i++){
            str += getObjString(obj.element);
            obj = obj.next;
        }
        if (str.length > 0) str = str.substring(0, str.length -2);
        return str;
    }
    //打印所有元素
    function print(){
        console.log(this.toString())
    }

    //對外公開方法
    this.append = append;
    this.insert = insert;
    this.remove = remove;
    this.removeAt = removeAt;
    this.removeAll = removeAll;
    this.set = set;
    this.get = get;
    this.indexOf = indexOf;
    this.length = function(){
        return size;
    }
    this.clear = clear;
    this.print = print;
    this.toString = toString;

    //新增加的方法
    this.next = next;
    this.removeCurrent = removeCurrent;
    this.reset = reset;
}


////測試
// let obj = new CircleLinkedListDouble();
// let obj1 = { title: "全明星比賽", stores: [{name: "張飛vs岳飛", store: "2:3"}, { name: "關羽vs秦瓊", store: "5:5"}]};
//
// obj.append(99);
// obj.append("hello")
// obj.append(true)
// obj.insert(3, obj1);
// obj.insert(0, [12, false, "Good", 81]);
// obj.print();
// console.log("obj1.index: ", obj.indexOf(obj1));
// obj.remove(0);
// obj.removeAll(obj1);
// obj.print();

////測試2
// console.log("\n\n......test2.....")
// var obj2 = new CircleLinkedListDouble();
// obj2.append(8); obj2.append(99); obj2.append('abc'); obj2.append(8); obj2.append(false);
// obj2.append(12); obj2.append(8); obj2.append('123'); obj2.append(8);
// obj2.print();
// obj2.removeAll(8); //刪除所有8
// obj2.print();


/**
 * 測試3,來做一個有意思的題目:約瑟夫題目
 據說著名猶太歷史學家 Josephus有過以下的故事:在羅馬人佔領喬塔帕特后,39 個猶太人與Josephus及他的朋友躲到一個洞中,
 39個猶太人決定寧願死也不要被敵人抓到,於是決定了一個自殺方式,41個人排成一個圓圈,
 由第1個人開始報數,每報數到第3人該人就必須自殺,然後再由下一個重新報數,直到所有人都自殺身亡為止。
 然而Josephus 和他的朋友並不想遵從。
 首先從一個人開始,越過k-2個人(因為第一個人已經被越過),並殺掉第k個人。
 接着,再越過k-1個人,並殺掉第k個人。這個過程沿着圓圈一直進行,直到最終只剩下一個人留下,這個人就可以繼續活着。
 問題是,給定了和,一開始要站在什麼地方才能避免被處決?
 Josephus要他的朋友先假裝遵從,他將朋友與自己安排在第16個與第31個位置,於是逃過了這場死亡遊戲。

 用循環鏈表來模擬這個遊戲
 */
console.log("\n\n........test3約瑟夫題目。。。....");
var obj3 = new CircleLinkedListDouble();
for (let i = 0; i < 41; i++){
    obj3.append(i+1);
}
obj3.print();
console.log("*************約瑟夫遊戲開始**********")
for (let i = 0; i < 39; i++){
    obj3.next(); obj3.next(); //移動兩次
    obj3.removeCurrent(); //刪除當前節點
    console.log("第", (i+1), "次,剩餘的為:", obj3.toString())
}
console.log("***************game over***************")
console.log("最後生存下來的是:", obj3.toString());

View Code

看下用鏈表模擬約瑟夫問題過程的效果:

 

其餘單向循環鏈表、單向循環鏈表增強、雙向循環鏈表等代碼Demo見github地址:https://github.com/xiaotanit/Tan_DataStruct

【精選推薦文章】

智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

想知道網站建置、網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計及後台網頁設計

帶您來看台北網站建置台北網頁設計,各種案例分享

廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

【算法隨記三】小半徑中值模糊的急速實現(16MB圖7.5ms實現) + Photoshop中蒙塵和划痕算法解讀。,任意半徑中值濾波(擴展至百分比濾波器)O(1)時間複雜度算法的原理、實現及效果,任意半徑中值濾波(擴展至百分比濾波器)O(1)時間複雜度算法的原理、實現及效果

  在本人的博客里,分享了有關中值模糊的O(1)算法,詳見:任意半徑中值濾波(擴展至百分比濾波器)O(1)時間複雜度算法的原理、實現及效果 ,這裏的算法的執行時間和參數是無關的。整體來說,雖然速度也很快,但是在某些特殊情況下我們還是需要更快的速度。特別是對於小半徑的中值,我們有理由去對其進一步的優化的。本文我們進一步探討這個問題。

  一、3*3中值模糊

  首先我們來看看半徑為1的中值,此時涉及到的領域為3*3,共9個像素,那麼最傳統的實現方式就是對9個像素直接進行排序,這裏我們直接使用系統的排序函數qsort,一種簡單的代碼如下所示:

int __cdecl ComparisonFunction(const void *X, const void *Y)        // 一定要用__cdecl這個標識符
{ unsigned char Dx = *(unsigned char *)X; unsigned char Dy = *(unsigned char *)Y; if (Dx < Dy) return -1; else if (Dx > Dy) return +1; else
        return 0; } void MedianBlur3X3_Ori(unsigned char *Src, unsigned char *Dest, int Width, int Height, int Stride) { int Channel = Stride / Width; if (Channel == 1) { unsigned char Array[9]; for (int Y = 1; Y < Height - 1; Y++) { unsigned char *LineP0 = Src + (Y - 1) * Stride + 1; unsigned char *LineP1 = LineP0 + Stride; unsigned char *LineP2 = LineP1 + Stride; unsigned char *LinePD = Dest + Y * Stride + 1; for (int X = 1; X < Width - 1; X++) { Array[0] = LineP0[X - 1];        Array[1] = LineP0[X];    Array[2] = LineP0[X + 1]; Array[3] = LineP1[X - 1];        Array[4] = LineP1[X];    Array[5] = LineP2[X + 1]; Array[6] = LineP2[X - 1];        Array[7] = LineP2[X];    Array[8] = LineP2[X + 1]; qsort(Array, 9, sizeof(unsigned char), &ComparisonFunction); LinePD[X] = Array[4]; } } } else { unsigned char ArrayB[9], ArrayG[9], ArrayR[9]; for (int Y = 1; Y < Height - 1; Y++) { unsigned char *LineP0 = Src + (Y - 1) * Stride + 3; unsigned char *LineP1 = LineP0 + Stride; unsigned char *LineP2 = LineP1 + Stride; unsigned char *LinePD = Dest + Y * Stride + 3; for (int X = 1; X < Width - 1; X++) { ArrayB[0] = LineP0[-3];       ArrayG[0] = LineP0[-2];       ArrayR[0] = LineP0[-1]; ArrayB[1] = LineP0[0];        ArrayG[1] = LineP0[1];        ArrayR[1] = LineP0[2]; ArrayB[2] = LineP0[3];        ArrayG[2] = LineP0[4];        ArrayR[2] = LineP0[5]; ArrayB[3] = LineP1[-3];       ArrayG[3] = LineP1[-2];       ArrayR[3] = LineP1[-1]; ArrayB[4] = LineP1[0];        ArrayG[4] = LineP1[1];        ArrayR[4] = LineP1[2]; ArrayB[5] = LineP1[3];        ArrayG[5] = LineP1[4];        ArrayR[5] = LineP1[5]; ArrayB[6] = LineP2[-3];       ArrayG[6] = LineP2[-2];       ArrayR[6] = LineP2[-1]; ArrayB[7] = LineP2[0];        ArrayG[7] = LineP2[1];        ArrayR[7] = LineP2[2]; ArrayB[8] = LineP2[3];        ArrayG[8] = LineP2[4];        ArrayR[8] = LineP2[5]; qsort(ArrayB, 9, sizeof(unsigned char), &ComparisonFunction); qsort(ArrayG, 9, sizeof(unsigned char), &ComparisonFunction); qsort(ArrayR, 9, sizeof(unsigned char), &ComparisonFunction); LinePD[0] = ArrayB[4]; LinePD[1] = ArrayG[4]; LinePD[2] = ArrayR[4]; LineP0 += 3; LineP1 += 3; LineP2 += 3; LinePD += 3; } } } }

  代碼很簡潔和清晰,我們沒有處理邊緣的那一圈像素,這無關精要,我們編譯后測試,結果如下所示:

  1920*1080大小的24位圖像,平均用時1280ms,灰度圖像平均用時460ms,這相當的慢,無法接受。

  下面我們稍微對其進行下提速。

  對於9個數據的排序,我們可以對其進行特殊的處理,因為數據的個數是確定的,按照理論分析,沒有必要進行大規模的比較,實際只需要進行19次比較就可以了。修改后算法如下所示:

inline void Swap(int &X, int &Y) { X ^= Y; Y ^= X; X ^= Y; } void MedianBlur3X3_Faster(unsigned char *Src, unsigned char *Dest, int Width, int Height, int Stride) { int Channel = Stride / Width; if (Channel == 1) { for (int Y = 1; Y < Height - 1; Y++) { unsigned char *LineP0 = Src + (Y - 1) * Stride + 1; unsigned char *LineP1 = LineP0 + Stride; unsigned char *LineP2 = LineP1 + Stride; unsigned char *LinePD = Dest + Y * Stride + 1; for (int X = 1; X < Width - 1; X++) { int Gray0, Gray1, Gray2, Gray3, Gray4, Gray5, Gray6, Gray7, Gray8; Gray0 = LineP0[X - 1];        Gray1 = LineP0[X];    Gray2 = LineP0[X + 1]; Gray3 = LineP1[X - 1];        Gray4 = LineP1[X];    Gray5 = LineP2[X + 1]; Gray6 = LineP2[X - 1];        Gray7 = LineP2[X];    Gray8 = LineP2[X + 1]; if (Gray1 > Gray2) Swap(Gray1, Gray2); if (Gray4 > Gray5) Swap(Gray4, Gray5); if (Gray7 > Gray8) Swap(Gray7, Gray8); if (Gray0 > Gray1) Swap(Gray0, Gray1); if (Gray3 > Gray4) Swap(Gray3, Gray4); if (Gray6 > Gray7) Swap(Gray6, Gray7); if (Gray1 > Gray2) Swap(Gray1, Gray2); if (Gray4 > Gray5) Swap(Gray4, Gray5); if (Gray7 > Gray8) Swap(Gray7, Gray8); if (Gray0 > Gray3) Swap(Gray0, Gray3); if (Gray5 > Gray8) Swap(Gray5, Gray8); if (Gray4 > Gray7) Swap(Gray4, Gray7); if (Gray3 > Gray6) Swap(Gray3, Gray6); if (Gray1 > Gray4) Swap(Gray1, Gray4); if (Gray2 > Gray5) Swap(Gray2, Gray5); if (Gray4 > Gray7) Swap(Gray4, Gray7); if (Gray4 > Gray2) Swap(Gray4, Gray2); if (Gray6 > Gray4) Swap(Gray6, Gray4); if (Gray4 > Gray2) Swap(Gray4, Gray2); LinePD[X] = Gray4; } } } else { for (int Y = 1; Y < Height - 1; Y++) { unsigned char *LineP0 = Src + (Y - 1) * Stride + 3; unsigned char *LineP1 = LineP0 + Stride; unsigned char *LineP2 = LineP1 + Stride; unsigned char *LinePD = Dest + Y * Stride + 3; for (int X = 1; X < Width - 1; X++) { int Blue0, Blue1, Blue2, Blue3, Blue4, Blue5, Blue6, Blue7, Blue8; int Green0, Green1, Green2, Green3, Green4, Green5, Green6, Green7, Green8; int Red0, Red1, Red2, Red3, Red4, Red5, Red6, Red7, Red8; Blue0 = LineP0[-3];        Green0 = LineP0[-2];    Red0 = LineP0[-1]; Blue1 = LineP0[0];        Green1 = LineP0[1];        Red1 = LineP0[2]; Blue2 = LineP0[3];        Green2 = LineP0[4];        Red2 = LineP0[5]; Blue3 = LineP1[-3];        Green3 = LineP1[-2];    Red3 = LineP1[-1]; Blue4 = LineP1[0];        Green4 = LineP1[1];        Red4 = LineP1[2]; Blue5 = LineP1[3];        Green5 = LineP1[4];        Red5 = LineP1[5]; Blue6 = LineP2[-3];        Green6 = LineP2[-2];    Red6 = LineP2[-1]; Blue7 = LineP2[0];        Green7 = LineP2[1];        Red7 = LineP2[2]; Blue8 = LineP2[3];        Green8 = LineP2[4];        Red8 = LineP2[5]; if (Blue1 > Blue2) Swap(Blue1, Blue2); if (Blue4 > Blue5) Swap(Blue4, Blue5); if (Blue7 > Blue8) Swap(Blue7, Blue8); if (Blue0 > Blue1) Swap(Blue0, Blue1); if (Blue3 > Blue4) Swap(Blue3, Blue4); if (Blue6 > Blue7) Swap(Blue6, Blue7); if (Blue1 > Blue2) Swap(Blue1, Blue2); if (Blue4 > Blue5) Swap(Blue4, Blue5); if (Blue7 > Blue8) Swap(Blue7, Blue8); if (Blue0 > Blue3) Swap(Blue0, Blue3); if (Blue5 > Blue8) Swap(Blue5, Blue8); if (Blue4 > Blue7) Swap(Blue4, Blue7); if (Blue3 > Blue6) Swap(Blue3, Blue6); if (Blue1 > Blue4) Swap(Blue1, Blue4); if (Blue2 > Blue5) Swap(Blue2, Blue5); if (Blue4 > Blue7) Swap(Blue4, Blue7); if (Blue4 > Blue2) Swap(Blue4, Blue2); if (Blue6 > Blue4) Swap(Blue6, Blue4); if (Blue4 > Blue2) Swap(Blue4, Blue2); if (Green1 > Green2) Swap(Green1, Green2); if (Green4 > Green5) Swap(Green4, Green5); if (Green7 > Green8) Swap(Green7, Green8); if (Green0 > Green1) Swap(Green0, Green1); if (Green3 > Green4) Swap(Green3, Green4); if (Green6 > Green7) Swap(Green6, Green7); if (Green1 > Green2) Swap(Green1, Green2); if (Green4 > Green5) Swap(Green4, Green5); if (Green7 > Green8) Swap(Green7, Green8); if (Green0 > Green3) Swap(Green0, Green3); if (Green5 > Green8) Swap(Green5, Green8); if (Green4 > Green7) Swap(Green4, Green7); if (Green3 > Green6) Swap(Green3, Green6); if (Green1 > Green4) Swap(Green1, Green4); if (Green2 > Green5) Swap(Green2, Green5); if (Green4 > Green7) Swap(Green4, Green7); if (Green4 > Green2) Swap(Green4, Green2); if (Green6 > Green4) Swap(Green6, Green4); if (Green4 > Green2) Swap(Green4, Green2); if (Red1 > Red2) Swap(Red1, Red2); if (Red4 > Red5) Swap(Red4, Red5); if (Red7 > Red8) Swap(Red7, Red8); if (Red0 > Red1) Swap(Red0, Red1); if (Red3 > Red4) Swap(Red3, Red4); if (Red6 > Red7) Swap(Red6, Red7); if (Red1 > Red2) Swap(Red1, Red2); if (Red4 > Red5) Swap(Red4, Red5); if (Red7 > Red8) Swap(Red7, Red8); if (Red0 > Red3) Swap(Red0, Red3); if (Red5 > Red8) Swap(Red5, Red8); if (Red4 > Red7) Swap(Red4, Red7); if (Red3 > Red6) Swap(Red3, Red6); if (Red1 > Red4) Swap(Red1, Red4); if (Red2 > Red5) Swap(Red2, Red5); if (Red4 > Red7) Swap(Red4, Red7); if (Red4 > Red2) Swap(Red4, Red2); if (Red6 > Red4) Swap(Red6, Red4); if (Red4 > Red2) Swap(Red4, Red2); LinePD[0] = Blue4; LinePD[1] = Green4; LinePD[2] = Red4; LineP0 += 3; LineP1 += 3; LineP2 += 3; LinePD += 3; } } } }

  看上去代碼的行數多了,但是實際上執行速度回更快,我們測試的結果如下:

  1920*1080大小的24位圖像,平均用時155ms,灰度圖像平均用時45ms,比之前的原始實現速度要快了近10倍。

   而在任意半徑中值濾波(擴展至百分比濾波器)O(1)時間複雜度算法的原理、實現及效果一文中的算法,採用了SSE優化,同樣大小的圖耗時為:

       1920*1080大小的24位圖像,平均用時260ms,灰度圖像平均用時160ms,比上述的C語言版本要慢。

  早期有朋友曾提示我在手機上使用Neon可以做到16MB的圖像半徑為1的中值模糊可以做到20ms,我真的一點也不敢相信。總覺得不太可思議。16MB可是4000*4000的大小啊,我用上述C的代碼處理起來要242ms,比手機端還慢了10倍。

  經過朋友提醒,在https://github.com/ARM-software/ComputeLibrary/blob/master/src/core/NEON/kernels/NEMedian3x3Kernel.cpp#L113上看到了相關的Neon代碼,驚奇的發現他和我上面的C代碼幾乎完全一樣。但是就是這一點代碼提醒了我。

inline void sort(uint8x8_t &a, uint8x8_t &b) { const uint8x8_t min = vmin_u8(a, b); const uint8x8_t max = vmax_u8(a, b); a = min; b = max; }

  真是一語驚醒夢中人啊,這麼簡單的優化我怎麼沒想到呢。 

  我們自己看看上面的C代碼,每個像素的9次比較雖然不能用SIMD指令做,但是多個像素的比較之間是相互不關聯的,因此,這樣我就可以一次性處理16個像素了,改成SSE優化的方式也就很簡單了:

inline void _mm_sort_ab(__m128i &a, __m128i &b) { const __m128i min = _mm_min_epu8(a, b); const __m128i max = _mm_max_epu8(a, b); a = min;        b = max; } void MedianBlur3X3_SSE(unsigned char *Src, unsigned char *Dest, int Width, int Height, int Stride) { int Channel = Stride / Width; int BlockSize = 16, Block = ((Width - 2)* Channel) / BlockSize; for (int Y = 1; Y < Height - 1; Y++) { unsigned char *LineP0 = Src + (Y - 1) * Stride + Channel; unsigned char *LineP1 = LineP0 + Stride; unsigned char *LineP2 = LineP1 + Stride; unsigned char *LinePD = Dest + Y * Stride + Channel; for (int X = 0; X < Block * BlockSize; X += BlockSize, LineP0 += BlockSize, LineP1 += BlockSize, LineP2 += BlockSize, LinePD += BlockSize) { __m128i P0 = _mm_loadu_si128((__m128i *)(LineP0 - Channel)); __m128i P1 = _mm_loadu_si128((__m128i *)(LineP0 - 0)); __m128i P2 = _mm_loadu_si128((__m128i *)(LineP0 + Channel)); __m128i P3 = _mm_loadu_si128((__m128i *)(LineP1 - Channel)); __m128i P4 = _mm_loadu_si128((__m128i *)(LineP1 - 0)); __m128i P5 = _mm_loadu_si128((__m128i *)(LineP1 + Channel)); __m128i P6 = _mm_loadu_si128((__m128i *)(LineP2 - Channel)); __m128i P7 = _mm_loadu_si128((__m128i *)(LineP2 - 0)); __m128i P8 = _mm_loadu_si128((__m128i *)(LineP2 + Channel)); _mm_sort_ab(P1, P2); _mm_sort_ab(P4, P5); _mm_sort_ab(P7, P8); _mm_sort_ab(P0, P1); _mm_sort_ab(P3, P4); _mm_sort_ab(P6, P7); _mm_sort_ab(P1, P2); _mm_sort_ab(P4, P5); _mm_sort_ab(P7, P8); _mm_sort_ab(P0, P3); _mm_sort_ab(P5, P8); _mm_sort_ab(P4, P7); _mm_sort_ab(P3, P6); _mm_sort_ab(P1, P4); _mm_sort_ab(P2, P5); _mm_sort_ab(P4, P7); _mm_sort_ab(P4, P2); _mm_sort_ab(P6, P4); _mm_sort_ab(P4, P2); _mm_storeu_si128((__m128i *)LinePD, P4); } for (int X = Block * BlockSize; X < (Width - 2) * Channel; X++, LinePD++) { // DO Something
 } } }

  注意到上面我已經把灰度和彩色圖像的代碼寫成同一個方式處理了,這是因為對於彩色圖像,3個通道之間的處理時毫無聯繫的。同時我們前面的Swap2個變量的過程時完全可以通過Min和Max兩個算子實現的,我們按下F5測試運行,驚人的速度出現了:

       1920*1080大小的24位圖像,平均用時3ms,灰度圖像平均用時1ms,比上述的C語言版本快了近40倍。

       順便也測試了下16MB的圖像,結果平均只需要7.5ms。真是太厲害了。

、5*5中值模糊

      對於5*5的中值模糊,優化的方式還是一樣的,但是5*5共計25個像素,理論上需要131次比較,其他的過程類似,測試基於SSE的方式,5*5的中值1920*1080大小的24位圖像,平均用時40ms,灰度圖像平均用時20ms,雖慢了很多,但是還是O(1)那裡的速度快。

三、蒙塵和划痕

     在這裏提Photoshop的這個算法也許並不是很合適,但是我也是在研究中值模糊時順便把這個算法給攻破的,當我們打開蒙塵和划痕界面時,發現其有半徑和閾值兩個參數,細心比價,如果閾值設置為0,則相同半徑設置時其結果圖像和雜色里的中間值算法的結果一模一樣,這也可以從蒙塵和划痕算法和中間值同樣都放在雜色菜單下可以看出端倪。   

                     

    通過上述分析,我們可以肯定蒙塵和划痕算法是基於中值模糊的,實際上,PS里很多算法都是基於中值模糊的,特別是那些有平滑度參數的算法^_^。經過多次測試,我們得到的該算法的結果就是如下:

    if  (Median – Src) > Threshold 

    Dest = Median

    else 

    Dest = Src

     對於彩色圖像,不是用彩色圖的中值,而是用其亮度值作為唯一的判斷標準,如果用彩色的中值作為標準來判斷每個分量的,很容易出現過多的噪點,因為有可能會出現Blue分量改變,而Red不變的情況,或其他類似現象。

          蒙塵和划痕的一個作用是去除噪點,特別的,我覺得他在小半徑的時候更為有用,小半徑中值不會改變原圖太多,加上這個閾值則可以很容易去除噪點,同時,基本不會出現新的模糊問題。比如下面這個圖。

        

                           原圖                                                                                                                                                   半徑為1的中值模糊 

        

                                   半徑為1,閾值取20時的蒙塵和划痕                                                                                                            半徑為2,閾值取20時的蒙塵和划痕            

   由以上幾圖,可以明顯的看出,帶閾值的蒙塵和划痕在抑制了噪音的同時對原圖其他細節基本沒有破壞,因此,是一種比較合適的初級的預處理算法,既然是預處理,那麼其效率就非常重要了,因此本文的快速3*3模糊的作用也就是相當有用。

        本文相關算法代碼下載地址:https://files.cnblogs.com/files/Imageshop/MedianBlur3X3.rar。 

   本人的SSE算法優化合集DEMO:測試Demo:http://files.cnblogs.com/files/Imageshop/SSE_Optimization_Demo.rar。

       

      

【精選推薦文章】

如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

想要讓你的商品在網路上成為最夯、最多人討論的話題?

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師"嚨底家"!!

專業SEO網站“排名第一”上線

 

SEO自上線以來,就收到國內站長的廣泛熱愛,人數持續增加,截止到19年六月,SEO收錄用戶數量超過百萬,月均活躍用戶超過千萬,成為名副其實的站長最受歡迎網站話題。在人數火爆的背後。為了能夠快速擁有一個權重較高的網站,能夠有一套全面的優化,可以有更多的能夠自由選擇關鍵詞,從而在網站排名中獲勝,成為很多新站長迫切的需求,很多本身沒有時間SEO優化的站長來說,選擇SEO服務也就成了一種效率的選擇。

然而,當前的SEO優化市場十分的混亂,隨便登入一個論壇都能搜到一大片,也出現了很多交了錢不見人,給想要尋找SEO的網站站長帶來了很多困惑。

日前了解到,一個專業從事SEO的網站“排名第一 www.paimingdiyi.com”已經上線運作了,網站以統一平台、公平價格的模式,為需要SEO的站長提供可靠、合理的服務,並同時提供快速排名、SEO教程等SEO服務。在交易方式上,同時支持淘寶、支付寶、等多種支付方式,並有專一客服進行定點服務,能夠有效的避免SEO優化詐騙和服務不到位的現象,實實在在解決了網站站長在選擇SEO顧問時的各種擔心。

掌握最新排名算法,有想要通過SEO排名獲得網站流量的朋友,要抓緊時間了。如果本身又沒有太多時間的話,那就不妨到“SEO排名第一”來尋找SEO顧問吧,不必再為詐騙擔心,也不用害怕服務不到位,有了可靠的平台,自己網站排名快速提升,獲得網站流量的願望也就不再遙不可及了。

但話說回來,一個網站排名還不好,最重要的還是在持續的SEO優化中積累的排名經驗。單純依靠SEO顧問或者是買別人的服務,雖然能夠僥倖獲得排名提升,但卻不能夠真實的體現自己的SEO水平。對於想要通過自己練習來提高真實SEO技術的站長,還是更多的花點時間,多來學習SEO,當然如果你更多的是希望感受巔峰對決,和第一比拼,那排名第一的SEO顧問,可能就是你最合適的選擇。

本站聲明:網站內容來源搜狐https://www.admin5.com/,如有侵權,請聯繫我們,我們將及時處理

【精選推薦文章】

自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網絡營銷同行勸守護袁昆別天天聊SEO優化,原因何在?

 

(文/守護袁昆)最近一段時間守護袁昆聊網站SEO優化比較多,總有些朋友說現在還能做SEO優化嗎?SEO優化早就過時了,現在是移動互聯網時代,就應該做短視頻。

現在還能不能做網站SEO優化呢?

其實任何一個平台信息過量,那麼就會有搜索存在,守護袁昆認為有搜索就離不開SEO優化。大多數朋友認為SEO優化做不了,主要還是網站SEO優化,甚至百度SEO優化泛濫成災。

百度SEO優化、淘寶SEO優化、微博SEO優化、微信SEO優化、頭條SEO優化……哪一個平台不是用戶量大、信息量大?系統推薦的可能並不是你喜歡的,那麼搜索就是一種必然。

哪些人在說SEO優化做不了?

沒有玩好SEO優化的一群人,他們也許是真的做SEO優化沒什麼效果,最後不得不忍痛離開,還不忘抱怨一句:SEO優化不是人乾的事,SEO優化已經過時了。

沒有做過SEO優化的一群人,人云亦云的事太多了。很多朋友根本就沒做過SEO優化,聽到別人說SEO優化做不了,所以SEO優化不能做。

網絡營銷同行為什麼勸守護袁昆別談SEO優化?

SEO優化是網絡營銷的基礎,為什麼同行讓守護袁昆別談呢?因為大家都不做,才有更多的機會。其實完全沒這個必要,為什麼這麼說?

大多數SEO優化人員能力不足,所以完全不會對我們造成競爭,或者說競爭極小,沒必要擔心SEO優化市場。

SEO優化資深玩家更多的是資源,甚至做付費方面的營銷推廣,也看不起這低成本耗費時間的玩法。

守護袁昆認為不僅僅要談SEO優化,還要讓沒有多大營銷預算的中小企業看到SEO優化的效果,讓大家認可SEO優化在網絡營銷推廣中的地位,這樣才能更適合行業發展。

SEO優化過時了,應該做自媒體、做短視頻?

SEO優化並沒有過時,做自媒體也並沒有錯,自媒體寫好標題和描述不就是最基本的SEO優化技巧嗎?

中小企業是否要做短視頻?守護袁昆給大家分析過:做短視頻的投入成本應該是圖文的兩三倍。對於營銷預算嚴重不足的中小企業,能去玩短視頻嗎?

不管互聯網營銷怎麼變,只要涉及到信息過量就離不開搜索,那麼SEO優化還是基礎。不管互聯網營銷平台怎麼變,優質的原創內容已經越來越重要。

守護袁昆別天天聊SEO優化了,作為想玩好互聯網營銷推廣的中小企業主,大家認為是否應該繼續聊下去?

本站聲明:網站內容來源搜狐https://www.admin5.com/,如有侵權,請聯繫我們,我們將及時處理

【精選推薦文章】

智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

想知道網站建置、網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計及後台網頁設計

帶您來看台北網站建置台北網頁設計,各種案例分享

廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

想寫出一份完整的seo優化方案 你應該這麼做

 

作為一個seo從業人員,網站seo優化方案相信很多人都被老闆要求寫過。老唐今天來介紹一下如何寫出一份完整的seo優化方案,供seo新手朋友們參考。對於一份完整的網站優化方案,老唐這裏將從站內優化、站外優化以及競爭對手分析三個部分來詳細說下。

一、站內優化

一個網站的站內優化,是否出現了問題,大家可以從下面三點來分析查找。

1、代碼標籤優化

最基本的標籤就是我們通常所說的title、keywords以及Description這三個標籤。當然現在的keywords重要性大大降低,可以無視。其他的代碼標籤就是網站h1-h3標籤;圖片Alt、title標籤;外鏈nofollow;頁面元標記meta等等。

2、內容優化

網站SEO的內容優化指的就是網站內容的錨文本、圖片ALT屬性、Tag標籤、麵包屑導航以及原創或者偽原創文章更新之類的。這裏要說的就是站內盡量少用flash,視頻等,影響網頁打開速度不說,蜘蛛抓取也不友好。對於文章中的關鍵字密度以及關鍵字加粗強調等也要注意。

內容優化中內容頁最好設置一些相關欄目,例如:最新文章、推薦文章或者熱門文章等等,這樣可以為文章內容頁增加相關性、減少用戶跳出率以及方便蜘蛛根據鏈接抓取更多內容等等。

對於具體的文章內容,內鏈也是一個很重要的部分,我們需要控制文章內部鏈接數量,不宜太多;注意鏈接對象的相關性;內鏈最好使用使用絕對路徑;做好錨文本等等。

3、URL優化

所謂的網站的url優化就是指網站的url標準化,靜態化,相對/絕對地址,還有首選域,301重定向等等。使用絕對地址,可以防止網站被惡意鏡像。並且如果有人引用了你的網站內容,你會獲得來自外界的導入鏈接。至於首選域就是在www與沒有www的網址之間選擇一個主要網站,將另一個重定向到主要網址,有利於集中網站的權重。

4、網站地圖與robots.txt

我們的網站最好都設置兩個網站地圖,一份是針對訪客的html網站地圖與針對搜索引擎蜘蛛的XML網站地圖。這裏老唐要說一點,我們的網站地圖設置好了可以去對應的站長平台提交下。如百度站長工具、360站長等等。至於robots.txt就是告訴蜘蛛我們網站哪些頁面可以抓取,哪些頁面不能抓取。

二、站外優化

站外優化最重要的就是外鏈的建設,對於外鏈我們首要注重質量,在保證質量的同時可以適當的追求數量。而一般高質量的外鏈來源方式有軟文、目錄提交、獨立博客、論壇簽名、黃頁網站、提交收藏、分類信息、微博推廣、sns推廣等等。而這裏老唐seo特別推薦的就是百度系列的產品,如百度知道、百度貼吧、百度經驗等等。

另外一個就是友情鏈接了,交換友情鏈接是需要注意幾點:

1、交換行業相關的鏈接

2、注意不要被加上nofollow

3、選擇更新與收錄比較正常的網站

4、不要一次性交換大量友鏈

5、友鏈的數量不宜過多

6、定時檢查,刪除異常鏈接

三、競爭對手分析

針對我們自己的網站就是上面所說的這麼多內容了,然而一份完整的seo優化方案,不僅針對自己的網站,還需要對競爭對手的網站進行科學的分析。下面老唐就如何分析競爭對手來詳細說下。

分析競爭對手主要從下面幾點入手:

1、百度權重、PR值

2、域名年齡

3、快照更新

4、反鏈

5、收錄情況

6、品牌關鍵詞

7、長尾關鍵詞

一般來說可以通過一些站長工具如:站長之家、愛站以及5118等等來分析對手的網站。

不知道seo整體優化方案怎麼做?A5服務為企業提供SEO診斷方案,快速讓你了解問題所在。

本站聲明:網站內容來源搜狐https://www.admin5.com/,如有侵權,請聯繫我們,我們將及時處理

【精選推薦文章】

如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

想要讓你的商品在網路上成為最夯、最多人討論的話題?

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師"嚨底家"!!

網站seo優化方案書 seo專員必寫方案 學到就是錢

 

公司成立后,都希望自己公司有一個公司官網,但有了公司網站后,並不是每個網站都做得很好,所以需要專業的seo優化人員進行站內和站外優化,針對公司網站寫seo優化方案時,有哪些要點呢?以下就是seo方案書的框架,僅供參考!

一、網站概述

1,網站介紹

1)網站名稱:

2)網站域名:

3)網站定位:

目標用戶:

2,網站數據分析

1)網站基本信息

2)網站收錄和反鏈

3)網站百度快照

4)網站的權重及外鏈

5)標題、關鍵詞及描述數據(TKD三大標籤)

6)關鍵詞指數和排名

3,網站優化目標

1)權重:將網站權重提升為3

2)關鍵詞排名:優化關鍵詞並把核心關鍵詞優化到百度前3頁

3)收錄數:後期達到每天2-3的收錄數

二、行業和競爭對手分析

1,分析競爭對手

1)競爭者

2)競爭策略

2,行業分析

三、網站優化方案

1,關鍵詞優化

2,標題優化

3,描述優化

4,代碼優化

5,網站結構和頁面優化

6,301和404頁面

7,內容優化

8,內鏈優化

1)文章正文錨文本設計四大原則:

a,軟性植入,符合文章的需要,不要生硬嵌套;

b,錨文本對文章有促進作用,二者可以做到相得益彰;

c,延展用戶需求,挖掘用戶額外需求並滿足;

d,錨文本數量不宜太多,否則會降低當前頁面的權重。

2)Tag標籤優化:起到引導作用,增加文章被訪問的機會,添加tag標籤要體現自己的特色;

3)定期檢查死鏈以及處理掉,可以使用站長工具查詢是否有死鏈,及時刪除或更改鏈接。

9,外鏈優化

1)軟文:新聞報道型、學員體驗型、故事講述型、利用網絡事件型、網絡熱帖型及總結歸納型等;

2)外鏈優化方法:原創或偽原創高質量的軟文,平衡持續增加文章數量,並且文章之間要有相關性,進行深度鏈接;

3)外鏈發布平台:微信平台、微博平台、問答平台、百科平台、直播平台、視頻平台、音頻平台、社區平台、論壇平台及自媒體平台等;

4)外鏈日常工作安排:每天堅持發布10篇關於網站的外鏈。

四、總結

這就是以上針對公司網站進行seo優化,寫方案書時的框架參考,通過站長之家查詢該公司網站的數據進行填寫,並且對該公司主營產品或服務進行詳細了解,針對性寫出優化方案即可。

不知道seo整體優化方案怎麼做?A5服務為企業提供SEO診斷方案,快速讓你了解問題所在。

本站聲明:網站內容來源搜狐https://www.admin5.com/,如有侵權,請聯繫我們,我們將及時處理

【精選推薦文章】

自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"