Update from Sync Service
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
1. common join(shuffle join/reduce join)
|
||||
1. 概要:common join是hive的默认join类型,其在reduce阶段完成join工作,包含map、shuffle和reduce三个阶段;
|
||||
2. 执行过程
|
||||
1. map阶段:map阶段读取数据源表,以join中的条件列为key,若有多个条件列,则以多个条件列的组合作为key,value中包含tag和reduce阶段需要使用的列(如,select或where中需要使用的列);
|
||||
2. shuffle阶段:对key进行hash,推送至不同的reduce中,确保两个相同的key位于同一个reduce中;
|
||||
3. reduce阶段:根据key值完成reduce,在此期间使用tag识别不同的来源表;
|
||||
3. 实例:输入以下代码common join的流程如图
|
||||
```sql
|
||||
SELECT a.id,a.dept,b.age FROM a join b ON (a.id = b.id);
|
||||
```
|
||||

|
||||
1. map join:
|
||||
1. 概要:map join适用于小表join大表,hive在map阶段会先将小表读取到内存生成HashTableFiles上传至[Distributed Cache](obsidian://open?vault=%E7%9F%A5%E8%AF%86%E7%AE%A1%E7%90%86&file=%E7%BC%96%E7%A8%8B%E7%BB%8F%E9%AA%8C%2Fhadoop%2F%E5%88%86%E5%B8%83%E5%BC%8F%E7%BC%93%E5%AD%98),在map阶段,大表会直接读取小表的缓存完成join过程,不涉及shuffle;
|
||||
2. 执行过程:
|
||||
- Local Task:
|
||||
- hive将小表的数据发送到hive客户端;
|
||||
- 客户端将小表的数据转换为HashTable的数据结构,并创建一个.gz的压缩文件;
|
||||
- 客户端将HashTable的.gz压缩文件上传至hadoop的Distributed Cache中;
|
||||
- MR:hive执行一个没有reduce的MR任务,在map阶段根据大表的每条记录与Distributed Cache中小表对应的HashTable进行关联;
|
||||
- 返回:由于没有Reduce过程,map阶段直接返回结果,并且有多少个map就有多少个文件;
|
||||

|
||||
3. 适用范围:
|
||||
1. 小表与大表的连接,其中小表应小到存入内存而不影响性能,否则可能导致oom;
|
||||
2. 若连接中的大表存在数据倾斜,则更应该使用mapjoin;
|
||||
3. map join适合进行不等式连接,这是因为在hive2.2之前,hive不支持在on之后写不等式,必须写在where语句中,这会导致必须先求笛卡尔积之后再进行过滤,使性能大幅下降,而map join在map阶段直接拿另外一个表的数据和内存中表数据做匹配。这种情况下即使笛卡尔积也不会对任务运行速度造成太大的效率影响。 而且hive的where条件本身就是在map阶段进行的操作,所以在where里写入不等值比对的话,也不会造成额外负担;**但在hive2.2之后,hive支持在on语句中执行不等式操作,所以2.2之后没有这个问题**;
|
||||
|
||||
3. Bucket Map Join
|
||||
1. 概述:Bucket Map Join是对map join的改进,其解决了map join中要求一个表是小表的问题。其基本思想是利用分桶的表,只将符合连接条件的分桶数据发送到map端,之后再map段执行map join;
|
||||
2. 条件:
|
||||
1. 参与join的表均为大表;
|
||||
2. 参与join的表都是[分桶表](obsidian://open?vault=%E7%9F%A5%E8%AF%86%E7%AE%A1%E7%90%86&file=%E7%BC%96%E7%A8%8B%E7%BB%8F%E9%AA%8C%2FHIVE%2F%E5%88%86%E5%8C%BA%E3%80%81%E5%88%86%E6%A1%B6),并且都以join的key为分桶字段;
|
||||
3. 参与join的表分桶数相同,或一个表的分桶数是其他表的倍数;
|
||||
3. 执行过程:
|
||||
1. hive将较小的表根据分桶,生成多个HashTable;
|
||||
2. 根据较大表的分桶情况,将较小表中分桶情况对应的HashTable发送到较大表的map端,二者按map join的方式进行join,并返回结果;
|
||||
|
||||
4. 实例:
|
||||
假设有两个大表,table1和table2,其中table2为较table1小;执行下述查询语句:
|
||||
```sql
|
||||
SELECT /*+ MAPJOIN(table2) */ table1.emp_id, table1.emp_name, table2.job_title FROM table1 JOIN table2 ON table1.emp_id = table2.emp_id;
|
||||
```
|
||||

|
||||
|
||||

|
||||
|
||||
4. Sort Merge Bucket Join(SMB join)
|
||||
1. 概述:SMB join是对bucket join的进一步改进,在bucket join中,map端仍然至少需要存储较小表的一个桶的数据,同时,需要将较小的表转化为HashTable数据。而SMB join利用了排序且分桶的数据,每个map端只需要读取固定的对应key的row即可,且由于数据是排序的,不需要转换为HashTable结构,可以直接进行join;
|
||||
2. 条件:
|
||||
1. 参与join的表均为大表;
|
||||
2. 参与join的表均以join的key作为分桶字段分桶,
|
||||
3. 参与join的表均以join的key进行排序;
|
||||
4. **参与join的表的分桶数必须相同**;
|
||||
3. 执行过程:
|
||||
1. maper读取较大表的数据;
|
||||
2. maper读取较小表的数据;
|
||||
3. 在maper中完成join;
|
||||
4. 实例:
|
||||
假设有两个表,table1和table2,其中table2较table1小;
|
||||

|
||||
|
||||
***参考文献***
|
||||
1. [Implementation Limitations of MapJoin in Hive 0.13 on MR](http://dmtolpeko.com/2014/10/01/implementation-limitations-of-mapjoin-in-hive-0-13-on-mr/);
|
||||
2. [Map Join Limitations – Out of Memory in Local Task](http://dmtolpeko.com/2014/10/14/map-join-limitations-out-of-memory-in-local-task/);
|
||||
3. [LanguageManual Joins](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+Joins)(https://cwiki.apache.org/confluence/display/Hive/LanguageManual+Joins);
|
||||
4. [【Hive】Hive中的MapJoin,CommonJoin,SMBJoin详细介绍](https://blog.csdn.net/qq_32727095/article/details/107803199);
|
||||
5. [Bucket Map Join in Hive](https://www.clairvoyant.ai/blog/bucket-map-join-in-hive);
|
||||
6. [Optimizing Your Apache Hive Queries: Bucketing and Sort Merge Bucket Map Join](https://www.linkedin.com/pulse/optimizing-your-apache-hive-queries-bucketing-sort-merge-varun-rao);
|
||||
7. [Sort Merge Bucket Join in Hive – SMB Join](https://data-flair.training/blogs/hive-sort-merge-bucket-join/);
|
||||
@@ -0,0 +1,22 @@
|
||||
## 1. 问题
|
||||
|
||||
在hive中使用了过多的窗口函数导致了内存OOM
|
||||
|
||||
## 2. 原因
|
||||
|
||||
每一个窗口函数,hive都会在内存中执行一次窗口划分,消耗大量的内存,最终导致OOM;
|
||||
|
||||
## 3. 解决方法
|
||||
|
||||
使用window算子,复用窗口,如:
|
||||
```sql
|
||||
select
|
||||
id,
|
||||
ts,
|
||||
v,
|
||||
lead(v) over w as lead_v,
|
||||
lag(v) over w as lag_v
|
||||
from
|
||||
test
|
||||
WINDOW w as (partition by id order by ts)
|
||||
```
|
||||
@@ -0,0 +1,3 @@
|
||||
## 一、JDBC使用经验
|
||||
### 1.1 Statement
|
||||
1. Statement实例使用setstring等函数时,其下标是从1开始的,而不是从0开始;
|
||||
@@ -0,0 +1,77 @@
|
||||
## 1. 基本概念
|
||||
|
||||
1. 完全二叉树:若二叉树的高度是 h,除第 h 层之外,其他(1~h-1)层的节点数都达到了最大个数,并且第 h 层的节点都连续的集中在最左边。想到点什么没?实际上,完全二叉树和堆联系比较紧密。
|
||||
|
||||
2. 满二叉树:除最后一层外,每一层上的所有节点都有两个子节点,最后一层都是叶子节点。
|
||||
|
||||
3. 哈夫曼树:给定 n 个权值作为 n 的叶子结点,构造一棵二叉树,若带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树 (Huffman tree)。
|
||||
|
||||
4. 二叉排序树:又称二叉查找树(Binary Search Tree),亦称二叉搜索树。二叉排序树或者是一棵空树,或者是具有下列性质的二叉树:
|
||||
|
||||
1. 若左子树不空,则左子树上所有结点的值均小于它的根结点的值;
|
||||
2. 若右子树不空,则右子树上所有结点的值均大于或等于它的根结点的值;
|
||||
3. 左、右子树也分别为二叉排序树;
|
||||
4. 没有键值相等的节点
|
||||
5. 二分查找的时间复杂度是 O(log(n)),最坏情况下的时间复杂度是 O(n)(相当于顺序查找)
|
||||
|
||||
6. 平衡二叉树:又称 AVL 树。平衡二叉树是二叉搜索树的进化版,所谓平衡二叉树指的是,左右两个子树的高度差的绝对值不超过 1。
|
||||
|
||||
7. 红黑树:红黑树是每个节点都带颜色的树,节点颜色或是红色或是黑色,红黑树是一种查找树。红黑树有一个重要的性质,从根节点到叶子节点的最长的路径不多于最短的路径的长度的两倍。对于红黑树,插入,删除,查找的复杂度都是 O(log N)。
|
||||
|
||||
## 2. 面试题总结
|
||||
|
||||
1. 遍历问题
|
||||
1. 前序遍历
|
||||
```python
|
||||
def preOrderTraverse(node):
|
||||
if not node:
|
||||
return None
|
||||
print(node.val)
|
||||
preOrderTraverse(node.left)
|
||||
preOrderTraverse(node.right)
|
||||
```
|
||||
2. 中序遍历
|
||||
```python
|
||||
def inOrderTraverse(node):
|
||||
if node is None:
|
||||
return None
|
||||
inOrderTraverse(node.left)
|
||||
print(node.val)
|
||||
inOrderTraverse(node.right)
|
||||
```
|
||||
3. 后序遍历
|
||||
```python
|
||||
|
||||
def postOrderTraverse(node):
|
||||
if node is None:
|
||||
return None
|
||||
postOrderTraverse(node.left)
|
||||
postOrderTraverse(node.right)
|
||||
print(node.val)
|
||||
```
|
||||
4. 层次遍历
|
||||
[二叉树层次遍历](https://www.bilibili.com/video/BV1GY4y1u7b2/)
|
||||
```python
|
||||
class Solution:
|
||||
def levelOrder(self, root: TreeNode) -> List[List[int]]:
|
||||
if not root: return []
|
||||
res = []
|
||||
queue = [root]
|
||||
while queue:
|
||||
num = len(queue)
|
||||
tmp = []
|
||||
for _ in range(num):
|
||||
node = queue.pop(0)
|
||||
tmp.append(node.val)
|
||||
if node.left:
|
||||
queue.append(node.left)
|
||||
if node.right:
|
||||
queue.append(node.right)
|
||||
res.append(tmp)
|
||||
return res
|
||||
```
|
||||
|
||||
2.
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,503 @@
|
||||
> https://zhuanlan.zhihu.com/p/43142694
|
||||
|
||||
本文首发于我的个人博客:[尾尾部落](https://weiweiblog.cn/linkedlist_summary/)
|
||||
|
||||
链表是面试过程中经常被问到的,这里把剑指 offer 和 LeetCode 中的相关题目做一个汇总,方便复习。
|
||||
|
||||
## **1. 在 O(1) 时间删除链表节点**
|
||||
|
||||
**题目描述**:给定单向链表的头指针和一个节点指针这里的意思是这个要被删除的节点是已知的,不需要遍历找到该节点,定义一个函数在 O(1) 时间删除该节点。
|
||||
**解题思路**:常规的做法是从链表的头结点开始遍历,找到需要删除的节点的前驱节点,把它的 next 指向要删除节点的下一个节点,平均时间复杂度为 O(n),不满足题目要求。
|
||||
那是不是一定要得到被删除的节点的前一个节点呢?其实不用的。我们可以很方面地得到要删除节点的下一个节点,如果我们把下一个节点的内容复制到要删除的节点上覆盖原有的内容,再把下一个节点删除,那就相当于把当前要删除的节点删除了。举个栗子,我们要删除的节点 i,先把 i 的下一个节点 j 的内容复制到 i,然后把 i 的指针指向节点 j 的下一个节点。此时再删除节点 j,其效果刚好是把节点 i 给删除了。
|
||||
要注意两种情况:
|
||||
|
||||
1. 如果链表中只有一个节点,即头节点等于要删除的节点,此时我们在删除节点之后,还需要把链表的头节点设置为 NULL。
|
||||
2. 如果要删除的节点位于链表的尾部,那么它就没有下一个节点,这时我们就要从链表的头节点开始,顺序遍历得到该节点的前序节点,并完成删除操作。
|
||||
|
||||
**参考代码**
|
||||
|
||||
```
|
||||
public static ListNode deleteNode(ListNode head, ListNode toBeDeleted) {
|
||||
// 如果输入参数有空值就返回表头结点
|
||||
if (head == null || toBeDeleted == null) {
|
||||
return head;
|
||||
}
|
||||
// 如果删除的是头结点,直接返回头结点的下一个结点
|
||||
if (head == toBeDeleted) {
|
||||
return head.next;
|
||||
}
|
||||
// 下面的情况链表至少有两个结点
|
||||
// 在多个节点的情况下,如果删除的是最后一个元素
|
||||
if (toBeDeleted.next == null) {
|
||||
// 找待删除元素的前驱
|
||||
ListNode tmp = head;
|
||||
while (tmp.next != toBeDeleted) {
|
||||
tmp = tmp.next;
|
||||
}
|
||||
// 删除待结点
|
||||
tmp.next = null;
|
||||
}
|
||||
// 在多个节点的情况下,如果删除的是某个中间结点
|
||||
else {
|
||||
// 将下一个结点的值输入当前待删除的结点
|
||||
toBeDeleted.value = toBeDeleted.next.value;
|
||||
// 待删除的结点的下一个指向原先待删除引号的下下个结点,即将待删除的下一个结点删除
|
||||
toBeDeleted.next = toBeDeleted.next.next;
|
||||
}
|
||||
// 返回删除节点后的链表头结点
|
||||
return head;
|
||||
}
|
||||
```
|
||||
|
||||
## **2. 翻转单链表**
|
||||
|
||||
**题目描述**:输出一个单链表的逆序反转后的链表。
|
||||
**解题思路**:用三个临时指针 prev、cur、next 在链表上循环一遍即可。
|
||||
>
|
||||
> 外部引用:Python实现
|
||||
> [https://cloud.tencent.com/developer/article/1725642](https://cloud.tencent.com/developer/article/1725642)
|
||||
|
||||
[[剑指 offer] 从尾到头打印链表](https://weiweiblog.cn/printlistfromtailtohead/)
|
||||
[[剑指 offer] 反转链表](https://weiweiblog.cn/reverselist/)
|
||||
|
||||
## **3. 翻转部分单链表:**
|
||||
|
||||
**题目描述**:给定一个单向链表的头结点 head, 以及两个整数 from 和 to, 在单链表上把第 from 个节点和第 to 个节点这一部分进行反转
|
||||
|
||||
举例:1->2->3->4->5->null, from = 2, to = 4
|
||||
结果:1->4->3->2->5->null
|
||||
|
||||
```
|
||||
public ListNode reverseBetween(ListNode head, int m, int n) {
|
||||
if (head == null) return null;
|
||||
if (head.next == null) return head;
|
||||
int i = 1;
|
||||
ListNode reversedNewHead = null;// 反转部分链表反转后的头结点
|
||||
ListNode reversedTail = null;// 反转部分链表反转后的尾结点
|
||||
ListNode oldHead = head;// 原链表的头结点
|
||||
ListNode reversePreNode = null;// 反转部分链表反转前其头结点的前一个结点
|
||||
ListNode reverseNextNode = null;
|
||||
while (head != null) {
|
||||
if (i > n) {
|
||||
break;
|
||||
}
|
||||
if (i == m - 1) {
|
||||
reversePreNode = head;
|
||||
}
|
||||
if (i >= m && i <= n) {
|
||||
if (i == m) {
|
||||
reversedTail = head;
|
||||
}
|
||||
reverseNextNode = head.next;
|
||||
head.next = reversedNewHead;
|
||||
reversedNewHead = head;
|
||||
head = reverseNextNode;
|
||||
} else {
|
||||
head = head.next;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
reversedTail.next = reverseNextNode;
|
||||
if (reversePreNode != null) {
|
||||
reversePreNode.next = reversedNewHead;
|
||||
return oldHead;
|
||||
} else {
|
||||
return reversedNewHead;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## **4. 旋转单链表**
|
||||
|
||||
**题目描述**:给定一个单链表,设计一个算法实现链表向右旋转 K 个位置。
|
||||
举例: 给定 1->2->3->4->5->6->NULL, K=3
|
||||
则 4->5->6->1->2->3->NULL
|
||||
**解题思路**:
|
||||
|
||||
* **方法一** 双指针,快指针先走 k 步,然后两个指针一起走,当快指针走到末尾时,慢指针的下一个位置是新的顺序的头结点,这样就可以旋转链表了。
|
||||
>
|
||||
> 外部引用: Python实现
|
||||
> [https://blog.csdn.net/weixin_41958153/article/details/81258992](https://blog.csdn.net/weixin_41958153/article/details/81258992)
|
||||
|
||||
* **方法二** 先遍历整个链表获得链表长度 n,然后此时把链表头和尾链接起来,在往后走 n - k % n 个节点就到达新链表的头结点前一个点,这时断开链表即可。
|
||||
|
||||
方法二代码:
|
||||
|
||||
```
|
||||
public class Solution { {
|
||||
public ListNode rotateRight(ListNode head, int k) {
|
||||
if (!head) return null;
|
||||
int n = 1;
|
||||
ListNode cur = head;
|
||||
while (cur.next) {
|
||||
++n;
|
||||
cur = cur.next;
|
||||
}
|
||||
cur.next = head;
|
||||
int m = n - k % n;
|
||||
for (int i = 0; i < m; ++i) {
|
||||
cur = cur.next;
|
||||
}
|
||||
ListNode newhead = cur.next;
|
||||
cur.next = NULL;
|
||||
return newhead;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## **5. 删除单链表倒数第 n 个节点**
|
||||
|
||||
**题目描述**:删除单链表倒数第 n 个节点,1 <= n <= length,尽量在一次遍历中完成。
|
||||
**解题思路**:双指针法,找到倒数第 n+1 个节点,将它的 next 指向倒数第 n-1 个节点。要注意,本题中并没有给出被删除节点的指针,因此需要先找到被删除节点
|
||||
|
||||
|
||||
> 备注:
|
||||
> 要注意,本题中并没有给出被删除节点的指针,因此需要先找到被删除节点
|
||||
>
|
||||
> 外部引用:
|
||||
> [https://www.cnblogs.com/c-x-a/p/17028715.html](https://www.cnblogs.com/c-x-a/p/17028715.html)
|
||||
|
||||
[[剑指 offer] 链表中倒数第 k 个结点](https://weiweiblog.cn/findkthtotail/)
|
||||
|
||||
## **6. 求单链表的中间节点**
|
||||
|
||||
**题目描述**:求单链表的中间节点,如果链表的长度为偶数,返回中间两个节点的任意一个,若为奇数,则返回中间节点。
|
||||
**解题思路**:快慢指针,慢的走一步,快的走两步,当快指针到达尾节点时,慢指针移动到中间节点。快的速度是慢的两倍,因此快指针走到链表结尾时,慢指针走到链表中部,这个思路也可以用来求四分之一节点位置
|
||||
|
||||
```
|
||||
// 遍历一次,找出单链表的中间节点
|
||||
public ListNode findMiddleNode(ListNode head) {
|
||||
if (null == head) {
|
||||
return;
|
||||
}
|
||||
ListNode slow = head;
|
||||
ListNode fast = head;
|
||||
|
||||
while (null != fast && null != fast.next) {
|
||||
fast = fast.next.next;
|
||||
slow = slow.next;
|
||||
}
|
||||
return slow;
|
||||
}
|
||||
```
|
||||
|
||||
## **7. 链表划分**
|
||||
|
||||
**题目描述**: 给定一个单链表和数值 x,划分链表使得所有小于 x 的节点排在大于等于 x 的节点之前。新建两个链表,一个存储小于x的节点,一个存储大于等于x的节点。最后把两个链表连接起来。注意把最后一个节点的next置为None,否则可能出现死循环。
|
||||
|
||||
> 思路:
|
||||
> 新建两个链表,一个存储小于x的节点,一个存储大于等于x的节点。最后把两个链表连接起来。注意把最后一个节点的next置为None,否则可能出现死循环。
|
||||
>
|
||||
> 外部引用:
|
||||
> [https://blog.csdn.net/yurenguowang/article/details/77449206](https://blog.csdn.net/yurenguowang/article/details/77449206)
|
||||
|
||||
```
|
||||
public class Solution {
|
||||
/**
|
||||
* @param head: The first node of linked list.
|
||||
* @param x: an integer
|
||||
* @return: a ListNode
|
||||
*/
|
||||
public ListNode partition(ListNode head, int x) {
|
||||
// write your code here
|
||||
if(head == null) return null;
|
||||
ListNode leftDummy = new ListNode(0);
|
||||
ListNode rightDummy = new ListNode(0);
|
||||
ListNode left = leftDummy, right = rightDummy;
|
||||
|
||||
while (head != null) {
|
||||
if (head.val < x) {
|
||||
left.next = head;
|
||||
left = head;
|
||||
} else {
|
||||
right.next = head;
|
||||
right = head;
|
||||
}
|
||||
head = head.next;
|
||||
}
|
||||
|
||||
right.next = null;
|
||||
left.next = rightDummy.next;
|
||||
return leftDummy.next;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## **8. 链表求和其实只要将数据依次取出并按位相加即可,但是要注意进位的问题**
|
||||
|
||||
**题目描述**:你有两个用链表代表的整数,其中每个节点包含一个数字。数字存储按照在原来整数中相反的顺序,使得第一个数字位于链表的开头。写出一个函数将两个整数相加,用链表形式返回和。
|
||||
**解题思路**:做个大循环,对每一位进行操作:
|
||||
|
||||
当前位:(A[i]+B[i])%10
|
||||
进位:(A[i]+B[i])/10
|
||||
|
||||
```
|
||||
public class Solution {
|
||||
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
|
||||
ListNode c1 = l1;
|
||||
ListNode c2 = l2;
|
||||
ListNode sentinel = new ListNode(0);
|
||||
ListNode d = sentinel;
|
||||
int sum = 0;
|
||||
while (c1 != null || c2 != null) {
|
||||
sum /= 10;
|
||||
if (c1 != null) {
|
||||
sum += c1.val;
|
||||
c1 = c1.next;
|
||||
}
|
||||
if (c2 != null) {
|
||||
sum += c2.val;
|
||||
c2 = c2.next;
|
||||
}
|
||||
d.next = new ListNode(sum % 10);
|
||||
d = d.next;
|
||||
}
|
||||
if (sum / 10 == 1)
|
||||
d.next = new ListNode(1);
|
||||
return sentinel.next;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## **9. 单链表排序**
|
||||
|
||||
**题目描述**:在 O(nlogn) 时间内对链表进行排序。
|
||||
**快速排序**:
|
||||
|
||||
```
|
||||
public ListNode sortList(ListNode head) {
|
||||
//采用快速排序
|
||||
quickSort(head, null);
|
||||
return head;
|
||||
}
|
||||
public static void quickSort(ListNode head, ListNode end) {
|
||||
if (head != end) {
|
||||
ListNode node = partion(head, end);
|
||||
quickSort(head, node);
|
||||
quickSort(node.next, end);
|
||||
}
|
||||
}
|
||||
|
||||
public static ListNode partion(ListNode head, ListNode end) {
|
||||
ListNode p1 = head, p2 = head.next;
|
||||
|
||||
//走到末尾才停
|
||||
while (p2 != end) {
|
||||
|
||||
//大于key值时,p1向前走一步,交换p1与p2的值
|
||||
if (p2.val < head.val) {
|
||||
p1 = p1.next;
|
||||
|
||||
int temp = p1.val;
|
||||
p1.val = p2.val;
|
||||
p2.val = temp;
|
||||
}
|
||||
p2 = p2.next;
|
||||
}
|
||||
|
||||
//当有序时,不交换p1和key值
|
||||
if (p1 != head) {
|
||||
int temp = p1.val;
|
||||
p1.val = head.val;
|
||||
head.val = temp;
|
||||
}
|
||||
return p1;
|
||||
}
|
||||
```
|
||||
|
||||
**归并排序**:
|
||||
|
||||
```
|
||||
public ListNode sortList(ListNode head) {
|
||||
//采用归并排序
|
||||
if (head == null || head.next == null) {
|
||||
return head;
|
||||
}
|
||||
//获取中间结点
|
||||
ListNode mid = getMid(head);
|
||||
ListNode right = mid.next;
|
||||
mid.next = null;
|
||||
//合并
|
||||
return mergeSort(sortList(head), sortList(right));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取链表的中间结点,偶数时取中间第一个
|
||||
*
|
||||
* @param head
|
||||
* @return
|
||||
*/
|
||||
private ListNode getMid(ListNode head) {
|
||||
if (head == null || head.next == null) {
|
||||
return head;
|
||||
}
|
||||
//快慢指针
|
||||
ListNode slow = head, quick = head;
|
||||
//快2步,慢一步
|
||||
while (quick.next != null && quick.next.next != null) {
|
||||
slow = slow.next;
|
||||
quick = quick.next.next;
|
||||
}
|
||||
return slow;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* 归并两个有序的链表
|
||||
*
|
||||
* @param head1
|
||||
* @param head2
|
||||
* @return
|
||||
*/
|
||||
private ListNode mergeSort(ListNode head1, ListNode head2) {
|
||||
ListNode p1 = head1, p2 = head2, head;
|
||||
//得到头节点的指向
|
||||
if (head1.val < head2.val) {
|
||||
head = head1;
|
||||
p1 = p1.next;
|
||||
} else {
|
||||
head = head2;
|
||||
p2 = p2.next;
|
||||
}
|
||||
|
||||
ListNode p = head;
|
||||
//比较链表中的值
|
||||
while (p1 != null && p2 != null) {
|
||||
|
||||
if (p1.val <= p2.val) {
|
||||
p.next = p1;
|
||||
p1 = p1.next;
|
||||
p = p.next;
|
||||
} else {
|
||||
p.next = p2;
|
||||
p2 = p2.next;
|
||||
p = p.next;
|
||||
}
|
||||
}
|
||||
//第二条链表空了
|
||||
if (p1 != null) {
|
||||
p.next = p1;
|
||||
}
|
||||
//第一条链表空了
|
||||
if (p2 != null) {
|
||||
p.next = p2;
|
||||
}
|
||||
return head;
|
||||
}
|
||||
```
|
||||
|
||||
## **10. 合并两个排序的链表**
|
||||
|
||||
**题目描述**:输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
|
||||
|
||||
[[剑指 offer] 合并两个排序的链表](https://weiweiblog.cn/mergelinklist/)
|
||||
|
||||
## **11. 复杂链表的复制**
|
||||
|
||||
**题目描述**:输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的 head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)思路: 1. 在旧链表中创建新链表,此时不去创建随机指针; 2. 遍历链表,复制旧链表中的随机指针; 3. 从旧链表中拆分新链表;
|
||||
|
||||
|
||||
> 思路:
|
||||
> 1. 在旧链表中创建新链表,此时不去创建随机指针;
|
||||
> 2. 遍历链表,复制旧链表中的随机指针;
|
||||
> 3. 从旧链表中拆分新链表;
|
||||
>
|
||||
> 外部引用:
|
||||
> [https://zhuanlan.zhihu.com/p/38888164](https://zhuanlan.zhihu.com/p/38888164)
|
||||
|
||||
[[剑指 offer] 复杂链表的复制](https://weiweiblog.cn/clonelink/)
|
||||
|
||||
## **12. 删除链表中重复的结点**
|
||||
|
||||
**题目描述**:在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表 1->2->3->3->4->4->5 处理后为 1->2->5
|
||||
|
||||
[[剑指 offer] 删除链表中重复的结点](https://weiweiblog.cn/deleteduplication/)
|
||||
|
||||
## **13. 判断单链表是否存在环**
|
||||
|
||||
**题目描述**:判断一个单链表是否有环
|
||||
分析:快慢指针,慢指针每次移动一步,快指针每次移动两步,如果存在环,那么两个指针一定会在环内相遇。
|
||||
|
||||
## **14. 单链表是否有环扩展:找到环的入口点**
|
||||
|
||||
**题目描述**:判断单链表是否有环,如果有,找到环的入口点
|
||||
**解题思路**:在第 5 题两个指针相遇后,让其中一个指针回到链表的头部,另一个指针在原地,同时往前每次走一步,当它们再次相遇时,就是在环路的入口点。
|
||||
|
||||
[[剑指 offer] 链表中环的入口结点](https://weiweiblog.cn/entrynodeofloop/)
|
||||
|
||||
## **15. 判断两个无环单链表是否相交**
|
||||
|
||||
**题目描述**:给出两个无环单链表
|
||||
**解题思路**:
|
||||
|
||||
* **方法一** 最直接的方法是判断 A 链表的每个节点是否在 B 链表中,但是这种方法的时间复杂度为 O(Length(A) * Length(B))。
|
||||
* **方法二** 转化为环的问题。把 B 链表接在 A 链表后面,如果得到的链表有环,则说明两个链表相交。可以之前讨论过的快慢指针来判断是否有环,但是这里还有更简单的方法。如果 B 链表和 A 链表相交,把 B 链表接在 A 链表后面时,B 链表的所有节点都在环内,所以此时只需要遍历 B 链表,看是否会回到起点就可以判断是否相交。这个方法需要先遍历一次 A 链表,找到尾节点,然后还要遍历一次 B 链表,判断是否形成环,时间复杂度为 O(Length(A) + Length(B))。
|
||||
* **方法三** 除了转化为环的问题,还可以利用 “如果两个链表相交于某一节点,那么之后的节点都是共有的” 这个特点,如果两个链表相交,那么最后一个节点一定是共有的。所以可以得出另外一种解法,先遍历 A 链表,记住尾节点,然后遍历 B 链表,比较两个链表的尾节点,如果相同则相交,不同则不相交。时间复杂度为 O(Length(A) + Length(B)),空间复杂度为 O(1),思路比解法 2 更简单。
|
||||
|
||||
方法三的代码:
|
||||
|
||||
```
|
||||
public boolean isIntersect(ListNode headA, ListNode headB) {
|
||||
if (null == headA || null == headB) {
|
||||
return false;
|
||||
}
|
||||
if (headA == headB) {
|
||||
return true;
|
||||
}
|
||||
while (null != headA.next) {
|
||||
headA = headA.next;
|
||||
}
|
||||
while (null != headB.next) {
|
||||
headB = headB.next;
|
||||
}
|
||||
return headA == headB;
|
||||
}
|
||||
```
|
||||
|
||||
## **16. 两个链表相交扩展:求两个无环单链表的第一个相交点**
|
||||
|
||||
**题目描述**:找到两个无环单链表第一个相交点,如果不相交返回空,要求在线性时间复杂度和常量空间复杂度内完成。
|
||||
**解题思路**:
|
||||
|
||||
* **方法一** 如果两个链表存在公共结点,那么它们从公共结点开始一直到链表的结尾都是一样的,因此我们只需要从链表的结尾开始,往前搜索,找到最后一个相同的结点即可。但是题目给出的单向链表,我们只能从前向后搜索,这时,我们就可以借助栈来完成。先把两个链表依次装到两个栈中,然后比较两个栈的栈顶结点是否相同,如果相同则出栈,如果不同,那最后相同的结点就是我们要的返回值。
|
||||
* **方法二** 先找出 2 个链表的长度,然后让长的先走两个链表的长度差,然后再一起走,直到找到第一个公共结点。
|
||||
* **方法三** 由于 2 个链表都没有环,我们可以把第二个链表接在第一个链表后面,这样就把问题转化为求环的入口节点问题。
|
||||
* **方法四** 两个指针 p1 和 p2 分别指向链表 A 和链表 B,它们同时向前走,当走到尾节点时,转向另一个链表,比如 p1 走到链表 A 的尾节点时,下一步就走到链表 B,p2 走到链表 B 的尾节点时,下一步就走到链表 A,当 p1=p2 时,就是链表的相交点;
|
||||
|
||||
> 思路:
|
||||
> 这个办法的思路是,让两个指针走过的长度相同,从而讲问题转化为两个长度相同的链表,这样当两个指针相等时就是第一个公共节点了
|
||||
>
|
||||
> 外部引用:
|
||||
> [https://blog.csdn.net/Lynette_bb/article/details/75674765](https://blog.csdn.net/Lynette_bb/article/details/75674765)
|
||||
|
||||
方法四的代码:
|
||||
|
||||
```
|
||||
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
|
||||
if (null == headA || null == headB) {
|
||||
return null;
|
||||
}
|
||||
if (headA == headB) {
|
||||
return headA;
|
||||
}
|
||||
|
||||
ListNode p1 = headA;
|
||||
ListNode p2 = headB;
|
||||
while (p1 != p2) {
|
||||
// 遍历完所在链表后从另外一个链表再开始
|
||||
// 当 p1 和 p2 都换到另一个链表时,它们对齐了:
|
||||
// (1)如果链表相交,p1 == p2 时为第一个相交点
|
||||
// (2)如果链表不相交,p1 和 p2 同时移动到末尾,p1 = p2 = null,然后退出循环
|
||||
p1 = (null == p1) ? headB : p1.next;
|
||||
p2 = (null == p2) ? headA : p2.next;
|
||||
}
|
||||
return p1;
|
||||
}
|
||||
```
|
||||
|
||||
[[剑指 offer] 两个链表的第一个公共结点](https://weiweiblog.cn/findfirstcommonnode/)
|
||||
|
||||
## **17. 两个链表相交扩展:判断两个有环单链表是否相交**
|
||||
|
||||
**题目描述**:上面的问题是针对无环链表的,如果是链表有环呢?
|
||||
**解题思路**:如果两个有环单链表相交,那么它们一定共有一个环,即环上的任意一个节点都存在于两个链表上。因此可以先用之前快慢指针的方式找到两个链表中位于环内的两个节点,如果相交的话,两个节点在一个环内,那么移动其中一个节点,在一次循环内肯定可以与另外一个节点相遇。
|
||||
@@ -0,0 +1,3 @@
|
||||
|
||||
1. os.getcwd()获取的是目前python程序运行的目录;
|
||||
2. 想获取某个文件的完整目录可以使用__file__变量,但是这个变量仅在文件内有效;
|
||||
@@ -0,0 +1,47 @@
|
||||
|
||||
## 一、GRU模型的关键参数
|
||||
|
||||
1. input_size:输入的特征数,代表输入序列数据中每个值由多少个特征表示,比如NLP中,每个单次由几维的词向量表示;
|
||||
|
||||
> 个人经验:在氢燃料电池系统性能均值预测模型中,input_size代表了输入序列中每个值有多少个特征维度;
|
||||
|
||||
2. hidden_size:隐藏层特征数;
|
||||
3. num_layers:代表GRU的层数;
|
||||
4. bias:是否带有偏置, False则没有偏置,即$b_{ih}=0, b_{hh}=0$. 默认为True
|
||||
5. batch_first:输入序列中,第一个维度是否为batch. 默认为False;
|
||||
6. dropout:除最后一层,每一层的输出都进行dropout,默认为: 0
|
||||
7. bidirectional:是否为双向GRU,默认为False;
|
||||
|
||||
## 二、GRU模型的输入和输出及维度
|
||||
|
||||
### 2.1 输入
|
||||
|
||||
1. GRU模型的输入为input和$h_0$,其中input是输入的序列数据,$h_0$是第一个GRU cell的隐藏层在每个batch中的初始值,$h_0$可以不提供,系统会默认生成一个符合维度要求的全零tensor。
|
||||
2. 输入维度:
|
||||
1. input,当参数batch_first为False时,input的维度为(seq_size, batch_size, feature_size),当batch_first为True时,input的维度为(batch_size, seq_size, feature_size);
|
||||
2. $h_0$的维度为(num_layers×GRU方向数,batch_size,hidden_size);
|
||||
|
||||
### 2.2 输出
|
||||
|
||||
1. GRU模型的输出为output和$h_n$,其中output为模型输出值,$h_n$为模型最后一个GRU cell的隐藏层状态;
|
||||
2. 输出维度:
|
||||
1. output的维度为(seq_size,batch_size,num_layers×GRU方向数)
|
||||
2. $h_n$的维度为(num_layers×GRU方向数,batch_size,hidden_size)
|
||||
|
||||
> 1. GRU方向数:当bidirectional为True时,GRU方向数为2,当bidirectional为False时,GRU方向数为1;
|
||||
> 2. seq_size为输入的单个序列长度,对于NLP代表了每个句子的长度,对于氢燃料电池系统性能均值预测模型中,seq_size代表了序列的时间窗口长度;
|
||||
|
||||
## 三、对应关系
|
||||
|
||||
1. GRU设置参数中input_size与输入序列input的最后一个维度即feature_size对应;
|
||||
2. GRU设置参数中hidden_size与输入$h_0$、输出output和$h_n$的最后一个维度对应;
|
||||
3. GRU设置参数中num_layers与输入$h_0$、输出$h_n$的第一个维度中num_layers对应;
|
||||
4. GRU设置参数中bidirectional与输入$h_0$、输出output和$h_n$的第一个维度中GRU方向数对应;
|
||||
5. GRU输入和输出中seq_size、batch_size不变,但是最后一个维度由输入的feature_size变为输出的hidden_size;
|
||||
|
||||
> 由于上述对应关系,在encoder-decoder架构中,decoder和encoder的hidden_size必须保持一致,否则会出现维度不匹配的问题;
|
||||
|
||||
## 参考文献
|
||||
|
||||
1. [循环神经网络 - torch.nn.GRU()参数详解](https://blog.51cto.com/u_11299290/4727876)
|
||||
2. [RNN, LSTM, GRU中输入输出维度](https://blog.csdn.net/sophicchen/article/details/108005115)
|
||||
@@ -0,0 +1,6 @@
|
||||
1. 位置编码
|
||||
2. 自注意力
|
||||
3. 多头自注意力
|
||||
4. 全连接网络
|
||||
5. 残差连接
|
||||
6.
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
---
|
||||
doc_type: hypothesis-highlights
|
||||
url: 'https://blog.csdn.net/qq_38410428/article/details/101102075'
|
||||
---
|
||||
|
||||
## Metadata
|
||||
|
||||
- Reference: https://blog.csdn.net/qq_38410428/article/details/101102075
|
||||
|
||||
|
||||
## Highlights
|
||||
|
||||
## mode.train()和mode.eval()作用
|
||||
|
||||
### train
|
||||
|
||||
- 启用 Batch Normalization 和 Dropout,如果模型中有BN层(Batch Normalization)和 Dropout,需要在训练时添加model.train()
|
||||
|
||||
### eval
|
||||
|
||||
- 在这个特定的例子中,似乎每50次迭代就会降低准确度。 如果我们检查一下代码, 我们看到确实在train函数中设置了训练模式。
|
||||
|
||||
- 修复很简单,我们将model.train() 向下移动一行,让其在训练循环中。理想的模式设置是尽可能接近推理步骤,以避免忘记设置它。修正后,我们的训练过程看起来更合理,没有中间的峰值出现。
|
||||
|
||||
## mode.eval()和torch.no_grad()区别
|
||||
|
||||
- 主要用于通知dropout层和BN层在training和validation/test模式间切换: 在train模式下,dropout网络层会按照设定的参数p,设置保留激活单元的概率(保留概率=p)。BN层会继续计算数据的mean和var等参数并更新。在eval模式下,dropout层会让所有的激活单元都通过,而BN层会停止计算和更新mean和var,直接使用在训练阶段已经学出的mean和var值。
|
||||
|
||||
- 2.eval模式不会影响各层的gradient计算行为,即gradient计算和存储与training模式一样,只是不进行反向传播(back probagation)。
|
||||
|
||||
- 而with torch.no_grad()则主要是用于停止autograd模块的工作,以起到加速和节省显存的作用。它的作用是将该with语句包裹起来的部分停止梯度的更新,从而节省了GPU算力和显存,但是并不会影响dropout和BN层的行为。
|
||||
|
||||
- 如果不在意显存大小和计算时间的话,仅仅使用model.eval()已足够得到正确的validation/test的结果;而with torch.no_grad()则是更进一步加速和节省gpu空间(因为不用计算和存储梯度),从而可以更快计算,也可以跑更大的batch来测试。
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
|
||||
最近被 [pytorch](https://so.csdn.net/so/search?q=pytorch&spm=1001.2101.3001.7020) 的几种 Tensor 维度转换方式搞得头大,故钻研了一下,将钻研历程和结果简述如下
|
||||
|
||||
_**注意:torch.__version__ == '1.2.0’**_
|
||||
|
||||
## [torch](https://so.csdn.net/so/search?q=torch&spm=1001.2101.3001.7020).transpose() 和 torch.permute()
|
||||
|
||||
两者作用相似,都是用于交换不同维度的内容。但其中`torch.transpose()`是交换指定的两个维度的内容,`permute()`则可以一次性交换多个维度。具体情况如 code 所示:
|
||||
**transpose()**: 两个维度的交换
|
||||
|
||||
```
|
||||
>>> a = torch.Tensor([[[1,2,3,4,5], [6,7,8,9,10], [11,12,13,14,15]],
|
||||
[[-1,-2,-3,-4,-5], [-6,-7,-8,-9,-10], [-11,-12,-13,-14,-15]]])
|
||||
>>> a.shape
|
||||
torch.Size([2, 3, 5])
|
||||
>>> print(a)
|
||||
tensor([[[ 1., 2., 3., 4., 5.],
|
||||
[ 6., 7., 8., 9., 10.],
|
||||
[ 11., 12., 13., 14., 15.]],
|
||||
|
||||
[[ -1., -2., -3., -4., -5.],
|
||||
[ -6., -7., -8., -9., -10.],
|
||||
[-11., -12., -13., -14., -15.]]])
|
||||
>>> b = a.transpose(1,2) # 使用transpose,将维度1和2进行交换。这个很好理解。转换后tensor与其shape如下
|
||||
>>> print(b, b.shape)
|
||||
(tensor([[[ 1., 6., 11.],
|
||||
[ 2., 7., 12.],
|
||||
[ 3., 8., 13.],
|
||||
[ 4., 9., 14.],
|
||||
[ 5., 10., 15.]],
|
||||
|
||||
[[ -1., -6., -11.],
|
||||
[ -2., -7., -12.],
|
||||
[ -3., -8., -13.],
|
||||
[ -4., -9., -14.],
|
||||
[ -5., -10., -15.]]]),
|
||||
torch.Size([2, 5, 3])))
|
||||
```
|
||||
|
||||
**permute()**:一次性做任意维度的交换
|
||||
|
||||
```
|
||||
>>> c = a.permute(2, 0, 1)
|
||||
>>> print(c, c.shape) # 此举将原维度0,1,2的次序变为2,1,0,所以shape也发生了相应的变化。
|
||||
(tensor([[[ 1., 6., 11.],
|
||||
[ -1., -6., -11.]],
|
||||
|
||||
[[ 2., 7., 12.],
|
||||
[ -2., -7., -12.]],
|
||||
|
||||
[[ 3., 8., 13.],
|
||||
[ -3., -8., -13.]],
|
||||
|
||||
[[ 4., 9., 14.],
|
||||
[ -4., -9., -14.]],
|
||||
|
||||
[[ 5., 10., 15.],
|
||||
[ -5., -10., -15.]]]),
|
||||
torch.Size([5, 2, 3]))
|
||||
```
|
||||
|
||||
transpose() 和 permute() 之间的转化:
|
||||
|
||||
```
|
||||
>>> b = a.permute(2,0,1)
|
||||
>>> c = a.transpose(1,2).transpose(0,1)
|
||||
>>> print(b == c, b.shape)
|
||||
(tensor([[[True, True, True],
|
||||
[True, True, True]],
|
||||
|
||||
[[True, True, True],
|
||||
[True, True, True]],
|
||||
|
||||
[[True, True, True],
|
||||
[True, True, True]],
|
||||
|
||||
[[True, True, True],
|
||||
[True, True, True]],
|
||||
|
||||
[[True, True, True],
|
||||
[True, True, True]]]),
|
||||
torch.Size([5, 2, 3]))
|
||||
```
|
||||
|
||||
如代码所示,先将 [Tensor](https://so.csdn.net/so/search?q=Tensor&spm=1001.2101.3001.7020) a 的 1,2 维度进行交换,再将得到的 Tensor 的 0,1 维度再交换,得到的结果和 permute 是一样的。
|
||||
|
||||
## transpose() 和 view()
|
||||
|
||||
`view()`是个在 pytorch 中很常见的函数。该函数也起到转换 Tensor 维度的作用,但它转换的方式和 transpose()/permute() 截然不同。如果说`tranpose()`是按照 Tensor 的原有维度忠实地进行交换,那么`view()`就直接而且简单的多——首先,view() 函数会将 Tensor 所有维度拉平成一维,然后再根据传入的的维度信息重构出一个 Tensor。code 如下:
|
||||
|
||||
```
|
||||
# 还是上面的Tensor a
|
||||
>>> print(a.shape)
|
||||
torch.Size([2, 3, 5])
|
||||
>>> print(a.view(2,5,3))
|
||||
tensor([[[ 1., 2., 3.],
|
||||
[ 4., 5., 6.],
|
||||
[ 7., 8., 9.],
|
||||
[ 10., 11., 12.],
|
||||
[ 13., 14., 15.]],
|
||||
|
||||
[[ -1., -2., -3.],
|
||||
[ -4., -5., -6.],
|
||||
[ -7., -8., -9.],
|
||||
[-10., -11., -12.],
|
||||
[-13., -14., -15.]]])
|
||||
>>> c = a.transpose(1,2)
|
||||
>>> print(c, c.shape)
|
||||
(tensor([[[ 1., 6., 11.],
|
||||
[ 2., 7., 12.],
|
||||
[ 3., 8., 13.],
|
||||
[ 4., 9., 14.],
|
||||
[ 5., 10., 15.]],
|
||||
|
||||
[[ -1., -6., -11.],
|
||||
[ -2., -7., -12.],
|
||||
[ -3., -8., -13.],
|
||||
[ -4., -9., -14.],
|
||||
[ -5., -10., -15.]]]),
|
||||
torch.Size([2, 5, 3]))
|
||||
```
|
||||
|
||||
如代码所示。即使`view()`和`transpose()`最终得到的 Tensor 的 shape 是一样的,但二者内容并不相同。view 函数只是按照给定的 (2,5,3) 的 Tensor 维度,将元素**按顺序**一个个填进去;而 transpose 函数,则的确是在进行第一个第二维度的**转置**。
|
||||
|
||||
此外,有些情况下转置 (transpose) 后的 Tensor 是无法被 **view** 的,原因在于,转置后的 Tensor 不是 “连续”(non-contiguous)的。关于 contiguous array 的问题 numpy 里也一样,在这里有个[很棒的解释](https://stackoverflow.com/questions/26998223/what-is-the-difference-between-contiguous-and-non-contiguous-arrays/26999092#26999092)
|
||||
@@ -0,0 +1,36 @@
|
||||
---
|
||||
doc_type: hypothesis-highlights
|
||||
url: 'https://zhuanlan.zhihu.com/p/445009191'
|
||||
---
|
||||
|
||||
## Metadata
|
||||
|
||||
- Reference: https://zhuanlan.zhihu.com/p/445009191
|
||||
- Tags:
|
||||
|
||||
## Highlights
|
||||
|
||||
- loss.backward()故名思义,就是将损失loss 向输入侧进行反向传播,同时对于需要进行梯度计算的所有变量 xxx (requires_grad=True),计算梯度 $\frac{d}{dx}loss$,并将其累积到梯度 $x.grad$ 中备用,即: $x.grad=x.grad+ddxlossx.grad =x.grad +\frac{d}{dx}lossx.grad =x.grad +\frac{d}{dx}loss$
|
||||
|
||||
- optimizer.step()是优化器对 xxx 的值进行更新,以随机梯度下降SGD为例:学习率(learning rate, lr)来控制步幅,即:$x=x−lr∗x.gradx$,减号是由于要沿着梯度的反方向调整变量值以减少Cost。
|
||||
```python
|
||||
x = torch.tensor([1., 2.], requires_grad=True)
|
||||
# x: tensor([1., 2.], requires_grad=True)
|
||||
y = 100*x
|
||||
# y: tensor([100., 200.], grad_fn=<MulBackward0>)
|
||||
|
||||
loss = y.sum(). # tensor(300., grad_fn=<SumBackward0>)
|
||||
|
||||
# Compute gradients of the parameters respect to the loss
|
||||
print(x.grad) # None, 反向传播前,梯度不存在
|
||||
loss.backward()
|
||||
print(x.grad) # tensor([100., 100.]) loss对y的梯度为1, 对x的梯度为100
|
||||
|
||||
optim = torch.optim.SGD([x], lr=0.001) # 随机梯度下降, 学习率0.001
|
||||
print(x) # tensor([1., 2.], requires_grad=True)
|
||||
optim.step() # 更新x
|
||||
print(x) # tensor([0.9000, 1.9000], requires_grad=True) 变化量=梯度X学习率 0.1=100*0.001
|
||||
```
|
||||
|
||||
- optimizer.zero_grad()清除了优化器中所有 xxx 的 $x.grad$ ,在每次loss.backward()之前,不要忘记使用,否则之前的梯度将会累积,这通常不是我们所期望的( 也不排除也有人需要利用这个功能)。
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
|
||||
1. redis-py-cluster在连接集群时会自动创建连接池,并使用连接池进行连接;
|
||||
|
||||
2. 连接池的最大连接数必须大于redis集群的节点数量;
|
||||
|
||||
3. 可以使用python命令:redis_cli.client_setname命令对连接进行命名,以方便后续查找
|
||||
|
||||
4. 可以使用redis-cli命令:client setname对连接进行命名;
|
||||
|
||||
5. 使用redis-cli连接redis集群的命令为:redis-cli -h host -p port -c进行连接;
|
||||
|
||||
6. 完成连接后,可以使用auth “password”进行授权,可以在连接集群时使用-a “password”进行授权;
|
||||
|
||||
7. 可以使用CLIENT LIST命令列出所有的redis连接;
|
||||
|
||||
8. 可以使用redis_cli.connection_pool.\_available\_connections查看目前连接池中可用的连接;
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 6.6 KiB |
@@ -0,0 +1,225 @@
|
||||
## 一、官方JDBC使用
|
||||
|
||||
TDengine官方提供了JDBC,Spark读取和写入均可以直接使用
|
||||
|
||||
### 1.1 依赖问题
|
||||
|
||||
1. 需要引用taos-jdbcdriver-2.0.42.jar这个包,版本上强烈推荐2.0.42,其他版本会有各种问题;
|
||||
|
||||
2. taos-jdbc的依赖与南京spark集群存在冲突,需要手动引入依赖,方法为:
|
||||
1. 下载正确的依赖,并上传hadoop;
|
||||
2. 在提交spark任务的时候,手动制定依赖:
|
||||
|
||||
```shell
|
||||
--conf spark.driver.extraClassPath=guava-30.1.1-jre.jar:failureaccess-1.0.1.jar \
|
||||
--conf spark.executor.extraClassPath=guava-30.1.1-jre.jar:failureaccess-1.0.1.jar
|
||||
--jars path-to-jar/guava-30.1.1-jre.jar,path-to-jar/failureaccess-1.0.1.jar
|
||||
```
|
||||
|
||||
```pom
|
||||
<dependency>
|
||||
<groupId>com.taosdata.jdbc</groupId>
|
||||
<artifactId>taos-jdbcdriver</artifactId>
|
||||
<version>2.0.42</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
3. 对于scala可以在pom中引入依赖,并将所有依赖打包到jar包中,但是python做不到,所以对于pyspark程序,最好引用taos-jdbcdriver-2.0.42-dist.jar
|
||||
|
||||
```shell
|
||||
--conf spark.driver.extraClassPath=guava-30.1.1-jre.jar:failureaccess-1.0.1.jar:taos-jdbcdriver-2.0.42-dist.jar
|
||||
--conf spark.executor.extraClassPath=guava-30.1.1-jre.jar:failureaccess-1.0.1.jar:taos-jdbcdriver-2.0.42-dist.jar
|
||||
--jars path-to-jar/guava-30.1.1-jre.jar,path-to-jar/failureaccess-1.0.1.jar,path-to-jar/taos-jdbcdriver-2.0.42-dist.jar
|
||||
```
|
||||
|
||||
### 1.2 读取
|
||||
|
||||
可以使用下面的方法读取TDegine
|
||||
|
||||
1. 读取整个表
|
||||
```Python
|
||||
info = spark.read\
|
||||
.format("jdbc")\
|
||||
.option("driver", "com.taosdata.jdbc.rs.RestfulDriver")\
|
||||
.option("url", "jdbc:TAOS-RS://ip:port/db?user=user&password=password")\
|
||||
#这里是设置网络超时的,如果一次性读取的信息比较多可以把这里设置大一些,单位是ms,默认为5000
|
||||
.option("httpSocketTimeout", "100000") \
|
||||
.option("dbtable", "test.test")\
|
||||
.load()
|
||||
```
|
||||
|
||||
2. 读取部分内容
|
||||
```python
|
||||
info = spark.read\
|
||||
.format("jdbc")\
|
||||
.option("driver", "com.taosdata.jdbc.rs.RestfulDriver")\
|
||||
.option("url", "jdbc:TAOS-RS://ip:port/db?user=user&password=password")\
|
||||
#这里是设置网络超时的,如果一次性读取的信息比较多可以把这里设置大一些,单位是ms,默认为5000
|
||||
.option("httpSocketTimeout", "100000") \
|
||||
.option("query", "select * from test.test limit 100")\
|
||||
.load()
|
||||
```
|
||||
|
||||
### 1.3 存储
|
||||
|
||||
可以使用下面的方法向TDegine保存数据
|
||||
|
||||
```python
|
||||
toHbaseSave
|
||||
.write
|
||||
.format("jdbc")
|
||||
.option("url", url)
|
||||
.option("driver", driver)
|
||||
.mode(SaveMode.Append)
|
||||
.option("dbtable", "test.test")
|
||||
.save()
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 二、使用自定义数据源写入数据
|
||||
|
||||
对于spark来说,直接使用官方的jdbc进行连接只能实现向普通表中写入数据,无法利用TDengine超级表的特性,为了能向超级表中写入数据,需要自定义数据源。由于只需要写入特性,因此下面的内容中只实现了写入的逻辑,没有实现读取的逻辑。
|
||||
|
||||
1. extends DataSourceV2 with WriteSupport 并重写createWriter方法,创建自定义的数据源;
|
||||
```scala
|
||||
class TDSourceV2 extends DataSourceV2 with WriteSupport with Serializable {
|
||||
|
||||
override def createWriter(
|
||||
jobId: String,
|
||||
structType: StructType,
|
||||
saveMode: SaveMode,
|
||||
dataSourceOptions: DataSourceOptions): Optional[DataSourceWriter] = {
|
||||
Optional.of(new TDSourceWriter(
|
||||
//地址
|
||||
dataSourceOptions.get("url").get(),
|
||||
//用户名
|
||||
dataSourceOptions.get("user").get(),
|
||||
//密码
|
||||
dataSourceOptions.get("password").get(),
|
||||
//数据库名称
|
||||
dataSourceOptions.get("db").get(),
|
||||
//超级表名称
|
||||
dataSourceOptions.get("stable").get()))
|
||||
}
|
||||
```
|
||||
2. 继承 DataSourceWriter 重写 createWriterFactory 方法并返回自定义的 DataWriterFactory,重写 commit 方法,用来提交整个事务, 重写 abort 方法,用来做事务回滚;
|
||||
```scala
|
||||
class TDSourceWriter(
|
||||
url: String,
|
||||
user: String,
|
||||
password: String,
|
||||
db: String,
|
||||
stable: String) extends DataSourceWriter with Serializable{
|
||||
|
||||
override def createWriterFactory(): DataWriterFactory[InternalRow] = {
|
||||
new TDWriterFactory(url, user, password, db, stable)
|
||||
}
|
||||
// 2.6版本的TDengine不支持事务
|
||||
override def commit(writerCommitMessages: Array[WriterCommitMessage]): Unit = Unit
|
||||
// 2.6版本的TDengine不支持事务
|
||||
override def abort(writerCommitMessages: Array[WriterCommitMessage]): Unit = Unit
|
||||
}
|
||||
```
|
||||
3. 继承 DataWriterFactory, 重写 createDataWriter方法返回自定义的 DataWriter;
|
||||
```scala
|
||||
class TDWriterFactory(
|
||||
url: String,
|
||||
user: String,
|
||||
password: String,
|
||||
db: String,
|
||||
stable: String) extends DataWriterFactory[InternalRow] with Serializable {
|
||||
override def createDataWriter(
|
||||
partitionId: Int,
|
||||
taskId: Long,
|
||||
epochId: Long): DataWriter[InternalRow] = {
|
||||
new TDDataWriter(url, user, password, db, stable)
|
||||
}
|
||||
}
|
||||
```
|
||||
4. 继承 DataWriter 重写 write 方法实现具体的写入数据库逻辑,重写 commit 方法用来提交事务,重写 abort 方法用来做事务回滚 ;
|
||||
```scala
|
||||
class TDDataWriter(
|
||||
url: String,
|
||||
user: String,
|
||||
password: String,
|
||||
db: String,
|
||||
stable: String) extends DataWriter[InternalRow] with Serializable {
|
||||
|
||||
private val logger = LoggerFactory.getLogger(this.getClass)
|
||||
private var conn: Connection = null
|
||||
private var stmt: Statement = null
|
||||
|
||||
override def write(record: InternalRow): Unit = {
|
||||
|
||||
//在这里编写将数据插入数据库的逻辑
|
||||
|
||||
Class.forName("com.taosdata.jdbc.rs.RestfulDriver")
|
||||
val jdbcUrl = s"jdbc:TAOS-RS://$url/$db?user=$user&password=$password"
|
||||
|
||||
// 可以通过record.getxxx获取对应列的内容
|
||||
// 如table_name = record.getString(0)
|
||||
|
||||
val variable1 = record.getString(0)
|
||||
val variable2 = record.getInt(1)
|
||||
val variable3 = record.getFloat(2)
|
||||
|
||||
// 构建插入数据库的sql语句
|
||||
// 请注意,只有原生连接方式才支持参数绑定的插入方式,
|
||||
// 由于要在spark集群上跑,这里用的是Rest的连接方式,
|
||||
// 所以只能手动构建插入数据库的sql语句
|
||||
// 关于原生连接、Rest连接和参数绑定插入,请见官方文档
|
||||
|
||||
val sql = "insert to ..."
|
||||
|
||||
logger.info(sql)
|
||||
|
||||
conn = DriverManager.getConnection(jdbcUrl)
|
||||
stmt = conn.createStatement()
|
||||
stmt.execute(s"use $db")
|
||||
|
||||
try{
|
||||
stmt.execute(sql)
|
||||
conn.commit()
|
||||
}catch {
|
||||
case e: Exception => e.printStackTrace()
|
||||
}finally {
|
||||
conn.close()
|
||||
}
|
||||
}
|
||||
|
||||
// Tdengine2.6版本不支持事务,因此此处并无实际需要做的事情
|
||||
// 创建WriterCommitMessage类,绝对不能传null,网上有些代码是错误的
|
||||
object WriteSucceeded extends WriterCommitMessage
|
||||
override def commit(): WriterCommitMessage = WriteSucceeded
|
||||
|
||||
// Tdengine2.6版本不支持事务,因此此处并无实际需要做的事情
|
||||
override def abort(): Unit = Unit
|
||||
}
|
||||
```
|
||||
|
||||
5. 调用,在程序中指定自定义的datasource并传入参数
|
||||
```scala
|
||||
toTdSave
|
||||
.write
|
||||
.format("org.example.TDSourceV2")
|
||||
.mode(SaveMode.Append)
|
||||
// 地址
|
||||
.option("url", url)
|
||||
// 数据库名
|
||||
.option("db", db)
|
||||
// 用户名
|
||||
.option("user", user)
|
||||
// 超级表名
|
||||
.option("stable", stable)
|
||||
// 密码
|
||||
.option("password", password)
|
||||
.save()
|
||||
```
|
||||
|
||||
|
||||
参考文献:
|
||||
1. [暑期2021项目经验分享:实现Spark对接openGauss](https://www.modb.pro/db/132365)
|
||||
2. [Spark DataSource V1 & V2 API 一文理解](https://blog.csdn.net/penriver/article/details/115672072)
|
||||
3. [Spark SQL DataSource V2 学习入门 + 代码模板](http://www.jsledd.cn/2019/04/05/datasourcev2/)
|
||||
4. [Tdengine v2.6 官方文档](https://docs.taosdata.com/2.6/reference/connector/java/)
|
||||
@@ -0,0 +1,15 @@
|
||||
1. 错误:select list expression not prduced by aggregation ouput(missing from GROUP BY caluse?)
|
||||
|
||||
1. 原因:查询时查询结果的别名与group by字句中的列名冲突,如:
|
||||
|
||||
``` sql
|
||||
select
|
||||
sum(dd) as b
|
||||
from
|
||||
a
|
||||
group by
|
||||
b,
|
||||
c
|
||||
```
|
||||
|
||||
2. 解决方法:将查询结果中的列别名进行修改即可;
|
||||
@@ -0,0 +1,22 @@
|
||||
|
||||
|
||||
|
||||
|
||||
## 错误记录
|
||||
|
||||
### redis.exceptions.ResponseError: MISCONF Redis is configured to save RDB snapshots, but it is currently not able to persist on disk.
|
||||
|
||||
1. 现象:在执行命令时Redis报错,如下:
|
||||
```shell
|
||||
redis.exceptions.ResponseError: MISCONF Redis is configured to save RDB snapshots, but it is currently not able to persist on disk.
|
||||
```
|
||||
|
||||
2. 原因:Redis集群问题,根本原因需要检查Redis log并确认
|
||||
|
||||
3. 解决方案:**该方法为临时解决方案**
|
||||
|
||||
|
||||
对Redis进行如下设置:
|
||||
```python
|
||||
redis_cli.config_set("stop-writes-on-bgsave-error", "no")
|
||||
```
|
||||
@@ -0,0 +1,35 @@
|
||||
### 1. java.lang.ClassCastException: scala.runtime.BoxedUnit cannot be cast to java.lang.Integer
|
||||

|
||||
|
||||
1. 错误原因:在if-else语句中,对else情况没有给出结果,导致变量被赋予boxedunit类型的默认值,待满足判断条件后再想赋值正确的值就会导致类型错误
|
||||
- 错误代码:
|
||||
```scala
|
||||
some_var = if(someCondition){
|
||||
dosomething()
|
||||
}
|
||||
```
|
||||
- 正确代码
|
||||
```scala
|
||||
some_var = if(someCondition){
|
||||
dosomething()
|
||||
}else{
|
||||
default_value
|
||||
}
|
||||
```
|
||||
|
||||
### 2. java.lang.NoSuchMethodError: com.google.common.collect.Range.all()
|
||||
|
||||

|
||||
|
||||
1. 软件环境:
|
||||
1. TDengine:2.4.6;
|
||||
2. TDengine-JDBC驱动:2.0.42;
|
||||
3. Spark版本:2.4.0;
|
||||
2. 错误原因:TDengine的JDBC驱动引用了guava和failureaccess两个包与spark集群中的包版本不同,spark默认使用集群中的包,导致找不到对应的方法;
|
||||
3. 解决方法:下载正确的依赖,并上传hadoop,之后在spark提交任务时引用正确版本的依赖文件,指定依赖文件的方法:
|
||||
```shell
|
||||
--conf spark.driver.extraClassPath=guava-30.1.1-jre.jar:failureaccess-1.0.1.jar \
|
||||
--conf spark.executor.extraClassPath=guava-30.1.1-jre.jar:failureaccess-1.0.1.jar
|
||||
--jars path-to-jar/guava-30.1.1- jre.jar,path-to-jar/failureaccess-1.0.1.jar
|
||||
```
|
||||
|
||||
@@ -0,0 +1,371 @@
|
||||
Python版本:3.7.9
|
||||
Spark版本:2.4.7
|
||||
包版本:org.apache.spark:spark-sql-kafka-0-10_2.11:2.4.7
|
||||
|
||||
## 程序报没有写hdfs权限,但是程序本身没有写hdfs:
|
||||
|
||||

|
||||

|
||||
|
||||
1. 原因:没有设置checkpoint地址,程序尝试写入到默认地址,但在默认地址没有读写权限;
|
||||
|
||||
2. 解决方法:设置checkpoint地址:.option("checkpointLocation", "地址")
|
||||
|
||||
## 程序报错java.nio.ByteBuffer.allocate(ByteBuffer.java:334):
|
||||
|
||||
```shell
|
||||
java.lang.IllegalArgumentException at java.nio.ByteBuffer.allocate(ByteBuffer.java:334)
|
||||
|
||||
at org.apache.arrow.vector.ipc.message.MessageSerializer.readMessage(MessageSerializer.java:543)
|
||||
|
||||
at org.apache.arrow.vector.ipc.message.MessageChannelReader.readNext(MessageChannelReader.java:58)
|
||||
|
||||
at org.apache.arrow.vector.ipc.ArrowStreamReader.readSchema(ArrowStreamReader.java:132)
|
||||
|
||||
at org.apache.arrow.vector.ipc.ArrowReader.initialize(ArrowReader.java:181)
|
||||
|
||||
at org.apache.arrow.vector.ipc.ArrowReader.ensureInitialized(ArrowReader.java:172)
|
||||
|
||||
at org.apache.arrow.vector.ipc.ArrowReader.getVectorSchemaRoot(ArrowReader.java:65)
|
||||
|
||||
at org.apache.spark.sql.execution.python.ArrowPythonRunner$$anon$1.read(ArrowPythonRunner.scala:162)
|
||||
|
||||
at org.apache.spark.sql.execution.python.ArrowPythonRunner$$anon$1.read(ArrowPythonRunner.scala:122)
|
||||
|
||||
at org.apache.spark.api.python.BasePythonRunner$ReaderIterator.hasNext(PythonRunner.scala:406)
|
||||
|
||||
at org.apache.spark.InterruptibleIterator.hasNext(InterruptibleIterator.scala:37)
|
||||
|
||||
at org.apache.spark.sql.execution.python.ArrowEvalPythonExec$$anon$2.<init>(ArrowEvalPythonExec.scala:98)
|
||||
|
||||
at org.apache.spark.sql.execution.python.ArrowEvalPythonExec.evaluate(ArrowEvalPythonExec.scala:96)
|
||||
|
||||
at org.apache.spark.sql.execution.python.EvalPythonExec$$anonfun$doExecute$1.apply(EvalPythonExec.scala:127)...
|
||||
```
|
||||

|
||||
|
||||
|
||||
1. 错误原因:pyarrow版本不兼容导致;
|
||||
|
||||

|
||||
2. 解决方法:
|
||||
|
||||
1. 将pyarrow版本降到0.15.0以下,开发中使用的是0.14.0版本
|
||||
|
||||
2. 在spark安装目录的conf文件夹中的spark-env.sh文件中加入export RROW_PRE_0_15_IPC_FORMAT=1
|
||||
|
||||
## 程序报错 Did not pass numpy.dtype object:
|
||||
|
||||
```shell
|
||||
pyarrow.lib.ArrowTypeError: ('Did not pass numpy.dtype object', 'Conversion failed for column IN_MU_user_fee with type bool')
|
||||
```
|
||||
|
||||
|
||||
1. 错误原因:numpy版本不兼容导致;
|
||||
|
||||
2. 解决方法:将numpy版本降到1.20.0以下,开发中使用的是1.19.1
|
||||
|
||||
## 环境缺失组件:开发中遇到缺失rediscluster组件问题;
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
1. 解决方法(指定Python zip包):
|
||||
|
||||
1. 将所需的模块从安装目录打包为Zip包;
|
||||
|
||||
2. 上zip包上传至服务器;
|
||||
|
||||
3. 用hadoop fs -mkdir hdfs:/user/faw_vhm_admin/lizhenyang/建立文件夹;
|
||||
|
||||
4. 用hadoop fs -put python.zip hdfs:/user/faw_vhm_admin/lizhenyang/将zip包存入上面建立的文件夹内;
|
||||
|
||||
5. 在提交spark任务时,使用
|
||||
```shell
|
||||
--archives hdfs:/user/faw_vhm_admin/lizhenyang/spark_python_test.zip --conf spark.pyspark.python=./spark_python_test.zip/python374/bin/python3.7
|
||||
```
|
||||
|
||||
## pandas_udf函数不支持window聚合
|
||||
|
||||
1. 原因:在使用grouped_map模式的pandas_udf时,pandas_udf会将所有列都转为pandas的dataframe格式,这对window列是不可行的,因此引起报错
|
||||
|
||||

|
||||
|
||||
## 终端显示程序运行完毕,但是UI中显示有步骤Failed
|
||||
|
||||

|
||||
|
||||
|
||||
失败原因:Stage cancelled because SparkContext was shut down
|
||||
|
||||
1. 原因:集群的内存不足(设置使用的内存不足,非集群物理内存不足);
|
||||
|
||||
2. 解决方案:
|
||||
|
||||
在提交运算时,使用--driver-memory 16G --executor-memory 16G --executor-cores 12 --num-executors 12参数将可用内存、CPU核数及节点数增大;
|
||||
|
||||
## 在Structured steaming中实时向redis写入输入
|
||||
|
||||
1. 解决方法:自定义sink:
|
||||
|
||||
1. 定义save_to_redis函数
|
||||
|
||||
2. 在writestream中使用foreach(save_to_redis),foreach函数会将每一个行分别输入save_to_redis函数,在save_to_redis函数中对输入的每一行输入存入Redis
|
||||
|
||||
## 在json序列化的时候遇到datetime不可序列化
|
||||
|
||||
1. 报错内容:TypeError: Object of type ‘datetime‘ is not JSON serializable
|
||||
|
||||
2. 解决方案1:将需要序列化的列中的datetime转化为字符串
|
||||
```python
|
||||
str(df1[df1.collectTime != np.nan]["collectTime"].values[0])
|
||||
```
|
||||
|
||||
|
||||
3. 解决方案2:重写构造json类,遇到datetime类时特殊处理
|
||||
|
||||
```python
|
||||
from datetime import date, datetime
|
||||
|
||||
class ComplexEncoder(json.JSONEncoder):
|
||||
|
||||
def default(self, obj):
|
||||
|
||||
if isinstance(obj, datetime):
|
||||
|
||||
return obj.strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
elif isinstance(obj, date):
|
||||
|
||||
return obj.strftime('%Y-%m-%d')
|
||||
|
||||
else:
|
||||
|
||||
return json.JSONEncoder.default(self, obj)
|
||||
```
|
||||
并在json.dumps()时指定cls为ComplexEncoder
|
||||
```python
|
||||
import json
|
||||
|
||||
json.dumps(your_data, cls=ComplexEncoder)
|
||||
```
|
||||
|
||||
## 出现极大的统计项
|
||||
|
||||
1. 在部分指标中,出现上亿、上千万的值,如下图
|
||||
|
||||

|
||||
|
||||
2. 根本原因:在将新增数据的dataframe与原有数据的dataframe合并时,使用了union函数,而union函数是按列的位置进行合并,新增数据和原有数据dataframe中各个列的位置不同,从而导致将新增数据中的max_speed列错误合并到了rapid_acc_times列,导致错误
|
||||
|
||||

|
||||

|
||||
|
||||

|
||||
|
||||
3. 解决方法:使用unionbyname函数,该函数按列名进行合并,而不是列的位置;
|
||||
|
||||

|
||||
|
||||
## 在Spark SQL中将同一列的两个array量合并
|
||||
|
||||
1. 在统计dsm报警时间的时候,需要将同vin相同行程的dsm报警时间合并,因此需要在按vin码Group后将dsm_time_list列中的array合并;
|
||||
|
||||
2. 解决方法:先使用collect_list函数,再使用udf函数
|
||||
|
||||
[参考](https://stackoverflow.com/questions/48406304/groupby-and-concat-array-columns-pyspark/48407212](https://stackoverflow.com/questions/48406304/groupby-and-concat-array-columns-pyspark/48407212)
|
||||
|
||||
### python 2.x
|
||||
|
||||
```python
|
||||
spark.version
|
||||
|
||||
# u'2.2.0'
|
||||
|
||||
from pyspark.sql import functions as F
|
||||
|
||||
import pyspark.sql.types as T
|
||||
|
||||
|
||||
def fudf(val):
|
||||
|
||||
return reduce (lambda x, y:x+y, val)
|
||||
|
||||
flattenUdf = F.udf(fudf, T.ArrayType(T.IntegerType()))
|
||||
|
||||
df2 = df.groupBy("store").agg(F.collect_list("values"))
|
||||
|
||||
df2.show(truncate=False)
|
||||
|
||||
# +-----+----------------------------------------------+
|
||||
|
||||
# |store| collect_list(values) |
|
||||
|
||||
# +-----+----------------------------------------------+
|
||||
|
||||
# |1 |[WrappedArray(1, 2, 3), WrappedArray(4, 5, 6)]|
|
||||
|
||||
# |2 |[WrappedArray(2), WrappedArray(3)] |
|
||||
|
||||
# +-----+----------------------------------------------+
|
||||
|
||||
|
||||
df3 = df2.select("store", flattenUdf("collect_list(values)").alias("values"))
|
||||
|
||||
df3.show(truncate=False)
|
||||
|
||||
# +-----+------------------+
|
||||
|
||||
# |store| values |
|
||||
|
||||
# +-----+------------------+
|
||||
|
||||
# |1 |[1, 2, 3, 4, 5, 6]|
|
||||
|
||||
# |2 |[2, 3] |
|
||||
|
||||
# +-----+------------------+
|
||||
|
||||
```
|
||||
|
||||
### Python 3.x
|
||||
|
||||
```python
|
||||
spark.version
|
||||
|
||||
# u'2.2.0'
|
||||
|
||||
from pyspark.sql import functions as F
|
||||
|
||||
import pyspark.sql.types as T
|
||||
|
||||
import functools
|
||||
|
||||
def fudf(val):
|
||||
|
||||
return functools.reduce(lambda x, y:x+y, val)
|
||||
|
||||
flattenUdf = F.udf(fudf, T.ArrayType(T.IntegerType()))
|
||||
|
||||
df2 = df.groupBy("store").agg(F.collect_list("values"))
|
||||
|
||||
df2.show(truncate=False)
|
||||
|
||||
# +-----+----------------------------------------------+
|
||||
|
||||
# |store| collect_list(values) |
|
||||
|
||||
# +-----+----------------------------------------------+
|
||||
|
||||
# |1 |[WrappedArray(1, 2, 3), WrappedArray(4, 5, 6)]|
|
||||
|
||||
# |2 |[WrappedArray(2), WrappedArray(3)] |
|
||||
|
||||
# +-----+----------------------------------------------+
|
||||
|
||||
df3 = df2.select("store", flattenUdf("collect_list(values)").alias("values"))
|
||||
|
||||
df3.show(truncate=False)
|
||||
|
||||
# +-----+------------------+
|
||||
|
||||
# |store| values |
|
||||
|
||||
# +-----+------------------+
|
||||
|
||||
# |1 |[1, 2, 3, 4, 5, 6]|
|
||||
|
||||
# |2 |[2, 3] |
|
||||
|
||||
# +-----+------------------+
|
||||
```
|
||||
|
||||
## Spark SQL删除array中的指定值
|
||||
|
||||
1. 需求:删除dsm_time_list中代表未发生DSM报警的”0”;
|
||||
|
||||
2. 方法:使用array_remove函数;
|
||||
|
||||
```python
|
||||
df_join_tmp1 = df_join_tmp1.withColumn("dsm_time_list",
|
||||
|
||||
flattenUdf("dsm_time_list").alias("dsm_time_list"))\
|
||||
|
||||
.withColumn("dsm_time_list",
|
||||
|
||||
F.array_remove(F.col("dsm_time_list"), "0"))
|
||||
```
|
||||
|
||||
## 合并后的数据出现重复记录
|
||||
|
||||
1. 问题:在完成新增数据合并后,使用distinct函数将重复数据去除,但是不成功;
|
||||
|
||||
2. 原因:在取endTime和endMileage列的值时,错误使用了order命令,而max函数取的是到当前行为止之前所有数据的最大值,从而导致新增数据和原有数据有两个不同的endTime和endMileage值,因此无法使用distinct函数删除;
|
||||
|
||||
3. 解决方法:应按如下代码取endTime和endMileage
|
||||
|
||||
```python
|
||||
df_union_joinTmp1=df_union.withColumn("et",F.max("endTime").over(Window.partitionBy("vin")))\
|
||||
.withColumn("e_mil",F.max("endMileage").over(Window.partitionBy("vin")))
|
||||
```
|
||||
|
||||
## 设置Spark的log等级
|
||||
|
||||
1. Spark默认会打印Info等级的log,导致非常多的log产生;
|
||||
|
||||
2. 解决方法:使用setLogLevel设置log等级;
|
||||
|
||||
```python
|
||||
spark = SparkSession \
|
||||
|
||||
.builder \
|
||||
|
||||
.getOrCreate()
|
||||
|
||||
spark.sparkContext.setLogLevel("Warn")
|
||||
```
|
||||
|
||||
## torage.DiskBlockObjectWriter: Uncaught exception while reverting partial writes to file
|
||||
|
||||
1. 问题:exec在运行中报错:
|
||||
|
||||
|
||||
```shell
|
||||
21/11/09 17:35:59 ERROR storage.DiskBlockObjectWriter: Uncaught exception while reverting partial writes to file /data5/yarn/nm/usercache/faw_vhm_admin/appcache/application_1626872989805_21612/blockmgr-ef0c9858-08bc-48b9-b9a3-27fdcfe32ef0/17/temp_shuffle_1c325963-4015-4d79-b44f-5e971de31e10
|
||||
|
||||
java.nio.channels.ClosedByInterruptException
|
||||
|
||||
at java.nio.channels.spi.AbstractInterruptibleChannel.end(AbstractInterruptibleChannel.java:202)
|
||||
|
||||
at sun.nio.ch.FileChannelImpl.truncate(FileChannelImpl.java:372)
|
||||
|
||||
at org.apache.spark.storage.DiskBlockObjectWriter$$anonfun$revertPartialWritesAndClose$2.apply$mcV$sp(DiskBlockObjectWriter.scala:218)
|
||||
|
||||
at org.apache.spark.util.Utils$.tryWithSafeFinally(Utils.scala:1417)
|
||||
|
||||
at org.apache.spark.storage.DiskBlockObjectWriter.revertPartialWritesAndClose(DiskBlockObjectWriter.scala:214)
|
||||
|
||||
at org.apache.spark.shuffle.sort.BypassMergeSortShuffleWriter.stop(BypassMergeSortShuffleWriter.java:237)
|
||||
|
||||
at org.apache.spark.scheduler.ShuffleMapTask.runTask(ShuffleMapTask.scala:105)
|
||||
|
||||
at org.apache.spark.scheduler.ShuffleMapTask.runTask(ShuffleMapTask.scala:55)
|
||||
|
||||
at org.apache.spark.scheduler.Task.run(Task.scala:121)
|
||||
|
||||
at org.apache.spark.executor.Executor$TaskRunner$$anonfun$11.apply(Executor.scala:407)
|
||||
|
||||
at org.apache.spark.util.Utils$.tryWithSafeFinally(Utils.scala:1408)
|
||||
|
||||
at org.apache.spark.executor.Executor$TaskRunner.run(Executor.scala:413)
|
||||
|
||||
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
|
||||
|
||||
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.```shell
|
||||
```
|
||||
|
||||
2. 原因:Spark本身bug([https://issues.apache.org/jira/browse/SPARK-28340](https://issues.apache.org/jira/browse/SPARK-28340));
|
||||
|
||||
3. 解决:Spark 3.0已修复该bug;
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
|
||||
## 1. 利用Restful接口读取大量数据时报Timeout错误
|
||||
|
||||
```shell
|
||||
py4j.protocol.Py4JJavaError: An error occurred while calling o84.load.
|
||||
: java.sql.SQLException: ERROR (2318): Read timed out
|
||||
at com.taosdata.jdbc.TSDBError.createSQLException(TSDBError.java:72)
|
||||
at com.taosdata.jdbc.utils.HttpClientPoolUtil.execute(HttpClientPoolUtil.java:131)
|
||||
at com.taosdata.jdbc.rs.RestfulStatement.execute(RestfulStatement.java:66)
|
||||
at com.taosdata.jdbc.rs.RestfulStatement.executeQuery(RestfulStatement.java:37)
|
||||
at com.taosdata.jdbc.rs.RestfulPreparedStatement.executeQuery(RestfulPreparedStatement.java:45)
|
||||
at org.apache.spark.sql.execution.datasources.jdbc.JDBCRDD$.resolveTable(JDBCRDD.scala:61)
|
||||
at org.apache.spark.sql.execution.datasources.jdbc.JDBCRelation$.getSchema(JDBCRelation.scala:210)
|
||||
at org.apache.spark.sql.execution.datasources.jdbc.JdbcRelationProvider.createRelation(JdbcRelationProvider.scala:35)
|
||||
at org.apache.spark.sql.execution.datasources.DataSource.resolveRelation(DataSource.scala:317)
|
||||
at org.apache.spark.sql.DataFrameReader.loadV1Source(DataFrameReader.scala:223)
|
||||
at org.apache.spark.sql.DataFrameReader.load(DataFrameReader.scala:211)
|
||||
at org.apache.spark.sql.DataFrameReader.load(DataFrameReader.scala:167)
|
||||
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
|
||||
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
|
||||
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
|
||||
at java.lang.reflect.Method.invoke(Method.java:498)
|
||||
at py4j.reflection.MethodInvoker.invoke(MethodInvoker.java:244)
|
||||
at py4j.reflection.ReflectionEngine.invoke(ReflectionEngine.java:357)
|
||||
at py4j.Gateway.invoke(Gateway.java:282)
|
||||
at py4j.commands.AbstractCommand.invokeMethod(AbstractCommand.java:132)
|
||||
at py4j.commands.CallCommand.execute(CallCommand.java:79)
|
||||
at py4j.GatewayConnection.run(GatewayConnection.java:238)
|
||||
at java.lang.Thread.run(Thread.java:748)
|
||||
```
|
||||
|
||||
1. 错误原因:restful接口默认等待数据返回时间为5000ms,当数据量较大时等待时间超过5000ms导致超时;
|
||||
2. 解决方法:延长等待的时间;
|
||||
3. 代码:pyspark中可以用下面两种方法设置等待时间
|
||||
1. 方法一,在url中加入设置参数httpSocketTimeout=xx
|
||||
```python
|
||||
df = spark.read\
|
||||
.format("jdbc")\
|
||||
.option("driver", "com.taosdata.jdbc.rs.RestfulDriver")\
|
||||
.option("url", "jdbc:TAOS-RS://ip:port/db?user=user&password=password&httpSocketTimeout=xx")\
|
||||
.option("query", "select * from test.test")\
|
||||
.load()
|
||||
```
|
||||
2.方法二,在option中加入设置参数httpSocketTimeout
|
||||
```python
|
||||
df = spark.read\
|
||||
.format("jdbc")\
|
||||
.option("driver", "com.taosdata.jdbc.rs.RestfulDriver")\
|
||||
.option("url", "jdbc:TAOS-RS://ip:port/db?user=user&password=password")\
|
||||
.option("httpSocketTimeout", "xx") \
|
||||
.option("query", "select * from test.test")\
|
||||
.load()
|
||||
```
|
||||
Reference in New Issue
Block a user