1. 程式人生 > 實用技巧 >恕我直言你可能真的不會java第6篇:Stream效能差?不要人云亦云

恕我直言你可能真的不會java第6篇:Stream效能差?不要人云亦云

概念介紹:

單連結串列是一種鏈式存取的資料結構,用一組地址任意的儲存單元存放線性表中的資料元素

連結串列中的資料是以結點來表示的,每個結點的構成:元素(資料元素的映象) + 指標(指示後繼元素儲存位置),元素就是儲存資料的儲存單元,指標就是連線每個結點的地址資料

由圖可知:

  1. 連結串列在進行新增/刪除時,只需要修改 當前節點和相鄰節點 的next,更新效率高
  2. 遍歷資料,需要根據節點按順序訪問,導致查詢速度慢,時間複雜度為O(n)
  3. 每個節點中都儲存了下一個節點的指標【Next,最後一個節點的next為null】,所以長度是動態變化的,且不是連續記憶體空間

相關程式碼:

MyLinkedListNode:自定義連結串列節點類

     /// <summary>
/// 自定義連結串列節點類: 單連結串列
/// </summary>
public class MyLinkedListNode<T>
{
/// <summary>
/// 當前節點
/// </summary>
public T Node { get; set; } /// <summary>
/// 下一個節點
/// </summary>
public MyLinkedListNode<T> Next { get; set; } /// <summary>
/// 建構函式: 無參建構函式
/// </summary>
/// <param name="Node"></param>
public MyLinkedListNode()
{
this.Node = default;
this.Next = null;
} /// <summary>
/// 建構函式: 指定當前節點,常用於 新增根節點和最後一個節點
/// </summary>
/// <param name="node"></param>
public MyLinkedListNode(T node)
{
this.Node = node;
this.Next = null;
} /// <summary>
/// 建構函式: 指定當前節點和下一節點,常用於 新增內部節點/確定下一節點 的情況
/// </summary>
/// <param name="next"></param>
/// <param name="Next"></param>
public MyLinkedListNode(T node, MyLinkedListNode<T> next)
{
this.Node = node;
this.Next = next;
}
}

MyLinkedList:自定義連結串列

     /// <summary>
/// 自定義連結串列
/// 功能:
/// 1.新增: 新增到集合最後面
/// 2.新增: 新增到集合最前面
/// 3.新增: 新增索引後面
/// 4.新增: 新增索引前面
/// 5.刪除: 刪除T
/// 6.刪除: 刪除指定索引
/// 7.刪除: 刪除第一個
/// 8.刪除: 刪除最後一個
/// 9.刪除: 刪除所有
/// </summary>
public class MyLinkedList<T>
{
/// <summary>
/// 儲存連結串列集合-根節點:
/// 框架自帶了雙向連結串列 System.Collections.Generic.LinkedList,連結串列集合儲存在 MyLinkedListNode 中
/// 考慮到存在clear方法,連結串列預設值為null更方便
/// </summary>
private MyLinkedListNode<T> _rootNode = null; // { get; set; } /// <summary>
/// 索引索引器,從0開始,根據索引返回指定索引節點資訊
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public T this[int index]
{
get
{
var node = GetNodeAt(index).Node;
Console.WriteLine($"this[int {index}] = {node}\r\n");
return node;
}
} #region 查詢方法 /// <summary>
/// 根據索引返回指定索引節點資訊
/// </summary>
/// <param name="index">索引,從0開始</param>
/// <returns></returns>
private MyLinkedListNode<T> GetNodeAt(int index) => GetNodeTupleAt(index)?.Item1; /// <summary>
/// 根據索引返回指定索引:節點元組
/// </summary>
/// <param name="index">索引,從0開始</param>
/// <returns>item1:當前節點;item2:上一節點;item3:下一節點;</returns>
private Tuple<MyLinkedListNode<T>, MyLinkedListNode<T>, MyLinkedListNode<T>> GetNodeTupleAt(int index)
{
if (index < ) return null;
if (_rootNode == null) throw new Exception("自定義連結串列為空!"); var num = ;
// 當前節點
MyLinkedListNode<T> currentNode = _rootNode;
// 上一節點
MyLinkedListNode<T> prevNode = _rootNode;
// while迴圈會在 currentNode == 倒數第二個節點時就會停止迴圈,所以while後面需要再做一次判斷
while (currentNode.Next != null)
{
// 如果當前索引 和 查詢索引相同,則返回擔負起節點
if (num == index) return GetValidNodeTuple(index, currentNode, prevNode);
// 重置:上一節點
prevNode = currentNode;
// 重置:當前節點
currentNode = currentNode.Next;
num++;
}
if (num < index) throw new Exception("索引超過連結串列長度!");
return num == index ? GetValidNodeTuple(index, currentNode, prevNode) : null;
} /// <summary>
/// 獲取有效的節點元組
/// </summary>
/// <param name="index">索引</param>
/// <param name="currentNode">當前節點</param>
/// <param name="prevNode">上一節點【如果索引 == 0 ? null :上一節點 】</param>
/// <returns>item1:當前節點;item2:上一節點;item3:下一節點;</returns>
private Tuple<MyLinkedListNode<T>, MyLinkedListNode<T>, MyLinkedListNode<T>> GetValidNodeTuple(int index, MyLinkedListNode<T> currentNode, MyLinkedListNode<T> prevNode)
{
return new Tuple<MyLinkedListNode<T>, MyLinkedListNode<T>, MyLinkedListNode<T>>(currentNode, index == ? null : prevNode, currentNode.Next);
} #endregion #region 新增方法 /// <summary>
/// 1.新增: 新增到集合最後面
/// </summary>
/// <param name="item"></param>
public void Append(T item)
{
MyLinkedListNode<T> node = new MyLinkedListNode<T>(item);
// 如果連結串列集合為空,則講當前 元素當作跟節點
if (_rootNode == null)
{
_rootNode = node;
return;
} // 迴圈得到最末節點
MyLinkedListNode<T> currentNode = _rootNode;
while (currentNode.Next != null) currentNode = currentNode.Next; // 新增到集合最後面
currentNode.Next = node;
} /// <summary>
/// 2.新增: 新增到集合最前面
/// </summary>
/// <param name="item"></param>
public void AddFirst(T item)
{
MyLinkedListNode<T> node = new MyLinkedListNode<T>(item);
// 如果連結串列集合為空,則講當前 元素當作跟節點
if (_rootNode == null)
{
_rootNode = node;
return;
} _rootNode = new MyLinkedListNode<T>(item, _rootNode); // 顯示連結串列中的所有資料
Console.Write($"AddFirst({item})\t");
Show();
} /// <summary>
/// 3.新增: 在索引後面新增
/// 3.1.獲取到當前索引的節點
/// 3.2.根據item建立新節點,把 當前節點的 下一節點指給 新節點的下一節點
/// 3.3.把新節點當作當前節點的下一節點
/// </summary>
/// <param name="index"></param>
/// <param name="item"></param>
public void AddAtAfter(int index, T item)
{
MyLinkedListNode<T> node = new MyLinkedListNode<T>(item);
// 如果連結串列集合為空,則講當前 元素當作跟節點
if (_rootNode == null)
{
_rootNode = node;
return;
}
// 3.1.獲取到當前索引的節點
var currentNode = GetNodeAt(index);
// 如果連結串列集合為空,則講當前 元素當作跟節點
if (currentNode == null)
{
_rootNode = node;
return;
} // 3.2.根據item建立新節點
var newNode = new MyLinkedListNode<T>(item, currentNode.Next); // 3.3.把新節點當作當前節點的下一節點
currentNode.Next = newNode; // 顯示連結串列中的所有資料
Console.Write($"AddAtAfter(int {index},T {item})\t");
Show();
} /// <summary>
/// 4.新增: 在索引前面新增
/// 4.1.獲取到 當前索引 和 上一索引 的節點
/// 4.2.根據item建立新節點,把當前節點當作新節點的下一節點
/// 4.3.把新節點當作上一節點的下一節點
/// </summary>
/// <param name="index"></param>
/// <param name="item"></param>
public void AddAtBefore(int index, T item)
{
var nodeTuple = GetNodeTupleAt(index);
if (nodeTuple == null) throw new Exception("索引超過連結串列長度!"); // 4.1.獲取到 當前索引 和 上一索引 的節點
var currentNode = nodeTuple.Item1;
var prevtNode = nodeTuple.Item2; // 4.2.根據item建立新節點,把當前節點當作新節點的下一節點
var newNode = new MyLinkedListNode<T>(item, currentNode); // 4.3.把新節點當作上一節點的下一節點:如果索引是0,則新節點作為連結根節點
if (index == ) _rootNode = newNode;
else prevtNode.Next = newNode; // 顯示連結串列中的所有資料
Console.Write($"AddAtBefore(int {index},T {item})\t");
Show();
} #endregion #region 刪除方法 /// <summary>
/// 5.刪除: 刪除T
/// 5.1.得到 當前節點/上一節點/下一節點
/// 5.2.把 上一節點的下一節點 更新為 下一節點
/// </summary>
/// <param name="item"></param>
public void Remove(T item)
{
if (_rootNode == null) return;
// 當前節點
var currentNode = _rootNode;
// 上一節點
MyLinkedListNode<T> prevNode = null;
while (currentNode.Next != null)
{
if (currentNode.Node.Equals(item))
{
// 根據 當前節點 的 上一節點和下一節點 刪除 當前節點
Remove(prevNode, currentNode.Next); // 顯示連結串列中的所有資料
Console.Write($"Remove({item})\t");
Show();
return;
}
// 重置 上一節點 和 當前節點
prevNode = currentNode;
currentNode = currentNode.Next;
}
// 如果需要刪除的是最後一個節點,則while迴圈在 currentNode == 倒數第二個節點時就會停止迴圈
Remove(prevNode, null); // 顯示連結串列中的所有資料
Console.Write($"Remove({item})\t");
Show();
} /// <summary>
/// 根據 當前節點 的 上一節點和下一節點 刪除 當前節點:把上一節點的next 指向 下一節點
/// </summary>
/// <param name="prevNode"></param>
/// <param name="nextNode"></param>
private void Remove(MyLinkedListNode<T> prevNode, MyLinkedListNode<T> nextNode)
{
if (prevNode == null) _rootNode = nextNode;
else prevNode.Next = nextNode;
} /// <summary>
/// 6.刪除: 刪除指定索引
/// 6.1.得到 當前/上一/下一節點
/// 6.2.把當前節點的下一節點 更新為 下一節點
/// </summary>
/// <param name="index"></param>
public void RemoveAt(int index)
{
var nodeTuple = GetNodeTupleAt(index); // 上一節點
var prevNode = nodeTuple.Item2;
// 判斷上一節點是不是null ? 當前節點為根節點,需要把下一節點更新為更節點
if (prevNode == null) _rootNode = nodeTuple.Item3;
else prevNode.Next = nodeTuple.Item3; // 顯示連結串列中的所有資料
Console.Write($"RemoveAt({index})\t");
Show();
} /// <summary>
/// 7.刪除: 刪除第一個
/// </summary>
public void RemoveFirst()
{
if (_rootNode == null) return;
_rootNode = _rootNode.Next; // 顯示連結串列中的所有資料
Console.Write($"RemoveFirst()\t");
Show();
} /// <summary>
/// 8.刪除: 刪除最後一個
/// </summary>
public void RemoveLast()
{
if (_rootNode == null) return;
// 如果連結串列只存在根節點,則把根節點刪除
if (_rootNode.Next == null)
{
_rootNode = null;
return;
}
// while迴圈獲得最後一個節點
var currentNode = _rootNode;
// 上一節點/倒數第二個節點
MyLinkedListNode<T> prevNode = null;
while (currentNode.Next != null)
{
prevNode = currentNode;
currentNode = currentNode.Next;
}
prevNode.Next = null; // 顯示連結串列中的所有資料
Console.Write($"RemoveLast()\t");
Show();
} /// <summary>
/// 9.刪除: 刪除所有
/// </summary>
public void Clear()
{
_rootNode = null; // 顯示連結串列中的所有資料
Console.Write($"Clear()\t");
Show();
} #endregion /// <summary>
/// 顯示連結串列中的所有資料
/// </summary>
public void Show()
{
if (_rootNode == null)
{
Console.WriteLine($"連結串列中的資料為空!\r\n");
return;
}
StringBuilder builder = new StringBuilder(); MyLinkedListNode<T> currentNode = _rootNode;
while (currentNode.Next != null)
{
builder.Append($"{currentNode.Node}\t");
currentNode = currentNode.Next;
}
// 最後一個節點next為null,不會進入while
builder.Append($"{currentNode.Node}\t"); Console.WriteLine($"連結串列中的資料為:\r\n{builder.ToString()}\r\n");
}
}

測試程式碼:

         /// <summary>
/// 測試單連結串列
/// </summary>
public static void RunLinkedList()
{
Console.WriteLine("======= 連結串列測試 Start ======="); MyLinkedList<string> myLinkedList = new MyLinkedList<string>();
myLinkedList.Append("張三");
myLinkedList.Append("李四");
myLinkedList.Append("王五");
myLinkedList.Append("六麻子");
myLinkedList.Append("田七");
myLinkedList.Show(); // 張三 李四 王五 六麻子 田七 // 異常測試
var a = myLinkedList[]; // 張三
a = myLinkedList[]; // 六麻子
//a = myLinkedList[11]; // 測試新增功能
myLinkedList.AddFirst("郭大爺"); // 郭大爺 張三 李四 王五 六麻子 田七
myLinkedList.AddAtAfter(, "海大爺"); // 郭大爺 海大爺 張三 李四 王五 六麻子 田七
myLinkedList.AddAtBefore(, "Robot"); // Robot 郭大爺 海大爺 張三 李四 王五 六麻子 田七
myLinkedList.AddAtBefore(, "Robot"); // Robot 郭大爺 Robot 海大爺 張三 李四 王五 六麻子 田七
myLinkedList.AddAtBefore(, "Robot"); // Robot 郭大爺 Robot 海大爺 Robot 張三 李四 王五 六麻子 田七 // 測試刪除功能
myLinkedList.Remove("Robot"); // 郭大爺 Robot 海大爺 Robot 張三 李四 王五 六麻子 田七
myLinkedList.Remove("Robot"); // 郭大爺 海大爺 Robot 張三 李四 王五 六麻子 田七
myLinkedList.Remove("田七"); // 郭大爺 海大爺 Robot 張三 李四 王五 六麻子
myLinkedList.RemoveAt(); // 海大爺 Robot 張三 李四 王五 六麻子
myLinkedList.RemoveAt(); // 海大爺 張三 李四 王五 六麻子
myLinkedList.RemoveFirst(); // 張三 李四 王五 六麻子
myLinkedList.RemoveFirst(); // 李四 王五 六麻子
myLinkedList.RemoveLast(); // 李四 王五
myLinkedList.RemoveLast(); // 李四
myLinkedList.Clear(); // 連結串列中的資料為空! Console.WriteLine("======= 連結串列測試 End =======");
}