學習之路九:深入剖析Web分頁原理


    說一下今天天氣很好,心情也非常的不錯,寫起來也非常舒暢,也希望園友們每天好心情,為自己的人生目標努力着!

  這段時間因為項目需要,要做一個分頁的功能,說實話這類的文章在園子里面可以說是滿天飛了,為什么要寫呢?沒什么高深的技術,只是做個總結,把那些零零碎碎的問題整合起來,好給大家一個完整的參考吧!

  這是我第一次自動動手寫分頁,所以這樣的文章適合跟我一樣的小菜閱讀....

 1.分頁的最基本參數                          

      總頁數                   →  PageTotalCount                         //查詢的結果分頁之后有多少頁
      總記錄數                 →  RecordTotalCount                       //查詢的結果包含多少條記錄
      每一頁的記錄數            →  DisplayRecordCount                     //每一頁顯示多少條記錄數
      一次顯示多少頁數         →  DisplayPageCount                       //一次加載顯示多少個頁數,五個或者十個等等
      首頁和末頁                →  FirstPage ,LastPage                   //快速回到第一頁以及最后一頁的按鈕
      上一頁和下一頁            →  UpPage,NextPage                       //下一頁和上一頁
      頁索引號                → IndexPage                             //用戶當前點擊的頁號碼

  Note:這只是最基本的參數,還有很多的查詢參數需要我們按照自己的項目需求來定義!

  Note:對於總記錄數的獲取我的方案是使用輸出參數來獲取!

   

 2.在URL地址中傳入分頁參數實現分頁              

   2.1 原理

      主要是通過獲取URL中的參數值來判斷用戶點擊的是第幾頁!

   2.2 機制

      通過查看HTML代碼,發現每一個分頁按鈕的超鏈接都是這樣寫的,如:    

    

    博客園分頁源代碼:

    所以這種的方式還是比較常用的!

   2.3 原理分析

             ①所有的HTML的代碼在后台進行組裝,然后在頁面加載的時候進行輸出!

             ②每個超鏈接的href屬性都在后台進行賦值!

             ③對一些復雜的業務邏輯進行有效處理!

   2.4 解決點擊“...”按鈕的問題,說實話這里面的邏輯還是有點復雜的,我搞了好久才弄清楚了:    

    

      這邊涉及到一個層的概念(不是標准術語),所謂的層就是一個頁面一次性顯示的的總頁數!    

       Note     層的層次關系:

                              1,2,3 … 10;              第一層   索引號為“0”

                              11,12,13 … 20;        第二層              “1”

                              21,22,23 … 30;        第三層              “2”    

       如果說你的總頁數有98頁,每一次顯示10頁,那么分頁的總層數就為 → int pageLevelCount = 98 % 10 ==0 ? 98 / 10 : 98 / 10 + 1

       有如下幾個核心代碼:    

1       int currentPageLevel = pageIndex / 10;        //當前頁所在的層數
2       //過濾一下整數,如10,20,30 本來他們應該屬於自己的層數,但是通過上面的計算會增加一個層數,所以要過濾下
3 currentPageLevel = pageIndex % 10 == 0 ? currentPageLevel - 1 : currentPageLevel;

4 int pageTotalLevelCount = pageTotalCount / 10; //總層數,如果不是10的倍數,就會少一層,主要用於后面“…”做判斷的
5       //計算后面三個點“...”處於什么樣的頁索引號,如果當前頁+1 乘以10大於總頁數,那么說明后面沒有了!
6       //下面的變量判斷當前的頁層數是不是最后一個頁層數!
7 int currentPageLastLevel = 10 * (currentPageLevel + 1) > pageTotalCount ? pageTotalCount : 10 * (currentPageLevel + 1);

      下面的代碼是判斷頁面上時候有“...”的按鈕:  

 1         //對前面“...”按鈕的判斷
2         if (pageIndex > 10) //對也按鈕的設置
3 {
4 strBuilder.AppendFormat(GetAHtml("", string.Format(url, 10 * pagecount), "..."));
5 }
6         //后面顯示“...”按鈕的判斷
7        if (pageIndex <= 10 * pageTotalLevelCount) //當當前頁索引號小於倒數第二頁頁碼時顯示在后端...
8 {
9 strBuilder.AppendFormat(GetAHtml("", string.Format(url, 10 * (pagecount + 1) + 1), ""));
10 }    

       Note:上面的代碼和下面的代碼不是一起的,上面的是我做演示的,下面的是一個完整的方案,寫的還不怎么好,明天我貼一個修改后的方案!

      如果能夠把這些邏輯搞清楚了,分頁也就不算太難了!

   下面是全部代碼:  

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Data.SqlClient;
using System.Data;
using System.Text.RegularExpressions;
using System.Text;

namespace Web分頁原理學習Demo
{
    public partial class _Default : System.Web.UI.Page
    {
        public int totalCount = 0;
        public int pageIndex = 1;
        public string pageHTML = "";
        string url = HttpContext.Current.Request.Url.AbsoluteUri;//當前頁面絕對路徑
        protected void Page_Load(object sender, EventArgs e)
        {
            string QueryStringName = Request.QueryString["page"];
            //QueryStringName = QueryStringName == null ? "1" : QueryStringName;
            //首先先獲取URL
            //"http://localhost:1033/Default.aspx"

            pageIndex = QueryStringName == null ? 1 : Convert.ToInt32(QueryStringName);

            //對URL進行一些設置
            if (url.Contains("aspx?"))
            {
                if (Regex.IsMatch(url, @"page=[0-9]*$", RegexOptions.IgnoreCase))//如果存在page=*的字符串
                {
                    url = Regex.Replace(url, @"page=[0-9]*$", "", RegexOptions.IgnoreCase);//替換掉page=*的字符串
                }
                url += "page" + "={0}";
            }
            else
            {
                url += "?" + "page" + "={0}";
            }
            //if (!url.Contains("page"))
            //{
            //    url += "?page={0}";
            //}

            this.Repeater1.DataSource = GetData(pageIndex);
            this.Repeater1.DataBind();

            pageHTML = GetPageHTML(totalCount, pageIndex, url);
        }

        public DataTable GetData(int pageIndex)
        {
            DataTable dt = new DataTable();
            string connString = @"server=22610800E100468;integrated security = SSPI;database=Northwind";
            using (SqlConnection conn = new SqlConnection(connString))
            {
                SqlCommand cmd = conn.CreateCommand();
                cmd.CommandText = "MyPage";
                cmd.Parameters.Add(new SqlParameter("@pageIndex", pageIndex));
                SqlParameter para = new SqlParameter();
                para.ParameterName = "@recordTotalCount";
                para.DbType = DbType.Int32;   //必須指明參數類型
                para.Direction = ParameterDirection.Output;
                cmd.Parameters.Add(para);
                cmd.CommandType = System.Data.CommandType.StoredProcedure;
                SqlDataAdapter da = new SqlDataAdapter(cmd);
                da.Fill(dt);

                totalCount = (int)da.SelectCommand.Parameters["@recordTotalCount"].Value;
            }

            return dt;
        }

        //開始填充HTML代碼
        public string GetPageHTML(int recordTotalCount, int pageIndex, string url)
        {
            StringBuilder strBuilder = new StringBuilder(1000);
            string attr = "";
            int pagecount = 0;//當前頁面的總層數
            int floorcount = 0;//分頁的總層數
            int currentLastPage = 0;//當前最后一頁的頁碼,用來保存最后一頁的號碼

            //總頁數
            int pageTotalCount = recordTotalCount / 10 + 1;
            strBuilder.Append("<div>");
            attr = pageIndex == 1 ? "visible=\"" + "false\"" : ""; //標志當前頁第一頁是否相等 來控制前倆個按鈕的有效性
            strBuilder.AppendFormat(GetAHtml(attr, string.Format(url, 1), "首頁"));
            strBuilder.AppendFormat(GetAHtml(attr, string.Format(url, pageIndex - 1), "上一頁"));

            pagecount = pageIndex / 10;//當前頁數 0~1~2
            pagecount = pageIndex % 10 == 0 ? pagecount - 1 : pagecount;//清除當 當前頁數為分頁頁碼數的整數倍頁時除數多一的狀況
            floorcount = pageTotalCount / 10;//頁面層數 0~1~2
            currentLastPage = pageTotalCount < 10 * (pagecount + 1) ? pageTotalCount : 10 * (pagecount + 1);

            if (pageIndex > 10)  //對也按鈕的設置
            {
                strBuilder.AppendFormat(GetAHtml("", string.Format(url, 10 * pagecount), "..."));
            }
            for (int i = 10 * pagecount + 1; i < currentLastPage; i++)
            {
                if (i == pageIndex)
                {
                    strBuilder.AppendFormat(GetSpanHtml(i, ""));
                }
                else
                {
                    strBuilder.AppendFormat(GetAHtml("", string.Format(url, i), i.ToString())); //設置超鏈接
                }
            }
            if (pageIndex <= 10 * floorcount) //當當前序號小於倒數第二頁頁碼時顯示在后端...
            {
                strBuilder.AppendFormat(GetAHtml("", string.Format(url, 10 * (pagecount + 1) + 1), ""));
            }

            attr = pageIndex == pageTotalCount ? "visible=\"" + "false\"" : "";//標志當前頁最后一頁是否相等 來控制后倆個按鈕的有效性
            strBuilder.AppendFormat(GetAHtml(attr, string.Format(url, pageIndex + 1), "下一頁"));
            strBuilder.AppendFormat(GetAHtml(attr, string.Format(url, pageTotalCount), "末頁"));

            strBuilder.AppendFormat("</div>");

            return strBuilder.ToString();
        }

        /// <summary>
        /// get the html of a label
        /// </summary>
        /// <param name="title">a's title</param>
        /// <param name="url">the url of a</param>
        /// <param name="attr">the attribute</param>
        /// <returns>return html string</returns>
        private static string GetAHtml(string attr, string url, string title)
        {
            return "<a " + attr + " href=\"" + url + "\" style=\"margin-right:5px;\">" + title + "</a>\n";
        }

       //這個方法的目的是固定住當前用戶點擊的頁索引
        private static string GetSpanHtml(int num, string className)
        {
            return "<span class=\"" + className + "\">" + num + "</span>\n";
        }
    }
}

  

  3.分頁存儲過程深入學習                        

   3.1 定義表變量來存儲數據,實現分頁    

  --使用表變量來讀取數據,注意表變量的語法,定義一個自動那個增長列用來以后的分頁
DECLARE @MyTable TABLE (myID INT IDENTITY(1,1),OrderID INT,CustomerID NCHAR(5))
--把你刪選的數據填充到表變量中去
INSERT INTO @MyTable(OrderID,CustomerID)
(
SELECT OrderID,CustomerID FROM dbo.Orders
)
SELECT myID,OrderID,CustomerID FROM @MyTable WHERE myID BETWEEN 1 AND 10

      Note:自SQL Server2005之后出來了“CTE”的語法,大家也可以使用這種方式來進行分頁! 

1   WITH MyTable(ID,Number)
2   AS
3   (
4   SELECT AppID,ROW_NUMBER() OVER(ORDER BY AppID) FROM Core
5   )
6
7   SELECT * FROM MyTable WHERE Number BETWEEN 10000 AND 10010

     3.2 使用“Top”和“In”    

1    --使用“TOP”跟“IN”語法
2 SELECT TOP 10 OrderID FROM dbo.Orders WHERE OrderID NOT IN
3 (
4 SELECT TOP 10 OrderID FROM dbo.Orders
5 )
6 //這個語句就可以查處表中第“11”條到“20”的數據了!

     3.3 使用“Row_Number() Over(Order By [字段名] DESC)”

1    --這是SQL Server2005新出來的函數,不僅操作簡單,而且還易於理解!
2 --所以了解這個函數的語法就變得很重要了!
3   SELECT OrderID,CustomerID FROM
4   (
5   SELECT OrderID,CustomerID, ROW_NUMBER() OVER(ORDER BY OrderID) AS number FROM dbo.Orders
6   ) AS T
7   WHERE T.number BETWEEN 1 AND 10

      Note:你可以指定你要排序的主鍵是按升序還是按降序排列!ASC → 升序,DESC → 降序!

    3.4 使用“Top”和“Max”

 1    --分頁思想:
2 --首先根據主鍵進行排序,刪選出前十條,默認是降序排列
3 --然后取出前十條的最大值,也就是前十條的最后一條記錄
4 --最后在刪選出這條記錄的后十條記錄,那么就是“11”到第“20”條的記錄了
5 SELECT TOP 10 OrderID,CustomerID FROM dbo.Orders AS T WHERE T.OrderID >
6 (
7 SELECT MAX(TempTable.OrderID) FROM
8 (SELECT TOP 10 OrderID FROM dbo.Orders ORDER BY OrderID) AS TempTable
9 )
10 //默認為升序 → ASC

     3.5 分頁查詢速度比較

      說實話這些比較園子里面也很多,我就做個總結了,不實際測試了!

      Top,Max  >  Row_Number  >  Top  >  表變量 !   

   3.6 比較“Top Max”和“Row Number”的性能差異所在

      如果對“SQL 執行計划”還沒有一定的理解,請先看這篇文章:看懂SqlServer查詢計划 ,值得一看的文章!      

      

      從上面的可以看出,在單表分頁的情況下,“Row_Number”比“Top ,Max”會多檢索出很多行,那么在性能上“Top Max”就比“Row_Number”好點!

      Note:這種情況只限於單表操作的情況下,如果說是多表查詢,感覺還是用“Row_Number”會比較好點,因為在多表的情況下,“Top,Max”或做出兩次的連接查詢,在大數據量的情況下,性能會比“Row_Number”差一點!如圖:      

      

 

 4.大數據的分頁思想                          

    前幾天看到一片文章是說關於百度,Google他們的分頁思想,找不到那篇文章了,找到了園友發個鏈接給我!

    我也是看到這些文章做個小總結,沒什么創新!    

  1. 每一張表設置固定的容量,達到一定程度后,把新紀錄轉移到另外一張表中,讓以前的那些表成為歷史數據!
  2. 做好查詢需要的索引,雖然不能濫用索引,但是適當的增加將加快查詢速度!
  3. 優化查詢,避免表掃描,少用模糊查詢,也就是在沒有索引的前提下,掃描整張表!
  4. 在服務端代碼中盡量考慮到數據緩沖和連接池的情況!
  5. 對於千萬級的數據,可以參考百度,Goole等網站的分頁技巧!
  6. 其實它們的分頁利用了客戶只關注前面重要的信息,越往后就不會太多的關注的思維定勢!
  7. 千萬級的數據,只取出前面幾十萬的信息,然后進行排序分頁! 

  

 5.思路很重要                             

  我總覺得在編程之前應該就把思路理清楚,清楚之后就能行如流水,但是現在的我還沒有到達這個境界,需要多多努力!

  好了,差不多就是這么多了,也算對分頁有了一點點的理解了,不至於以后工作需要而手忙腳亂的,寫在這邊與大家一起分享! 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM