0%

二级指针链表删除

最近看到linux之父的一些视频切片,十分的有感悟、他说的一句特别有意思的话:

”我自己不是一个梦想家,只是一个工程师,很高兴有这么多的人在仰望天空,但我只是看着地面,想在我自己摔倒之前填上面前的坑洼。“

结合视频里面看到的两段代码,尽管我对c语言并不熟练、但是在好奇心以及十分在意代码简洁度和美丽的洁癖心理作用下,我尝试去理解了代码神奇之处。

代码讲的是链表节点的删除操作,第一段代码是常规的链表节点删除,需要将目标节点的上一个节点指针指向目标节点的下一个指针,达到跳过该节点的目的,最后将内存释放即可。第二段则是让我有些眼花,二级指针一出来我直接蒙圈了、这指针的指针是用来干什么的。想了半天都没有答案。代码及其的简单,看上去和第一段代码没有任何关联,可他们做的是相同的事情,这一下子勾起了我的求知欲。

因为第一段代码看似逻辑简单,但是他需要区别三种情况

  • 当删除节点是头结点时
  • 当删除节点是尾节点时
  • 当删除节点是中间节点时
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
typedef struct node
{
struct node * next;
....
} node;

typedef bool (* remove_fn)(node const * v);

// Remove all nodes from the supplied list for which the
// supplied remove function returns true.
// Returns the new head of the list.
node * remove_if(node * head, remove_fn rm)
{
for (node * prev = NULL, * curr = head; curr != NULL; )
{
node * const next = curr->next;
if (rm(curr))
{
if (prev)
prev->next = next;
else
head = next;
free(curr);
}
else
prev = curr;
curr = next;
}
return head;
}

由于需要区分这几种情况,导致代码变得冗杂、所以第一段代码显得稍长,但只用到了一级指针、理解起来稍微没有那么费力。

但linus不同意、认为这样的代码比第二种更差,第二种代码明显是更加以思考的。

我大致复刻了一下代码的样子:

1
2
3
4
5
6
7
8
9
10
11
12
void remove_node0(node_t * pHead,bool (*should_remove)(node_t* pNode))
{
for (node_t ** curr = &pHead; *curr; /*curr will move*/) {
node_t * node = *curr;
if(should_remove(node))
{
*curr = node -> next;
free(node);
}
else curr = &node -> next;
}
}

写这段代码很有意思,利用二级指针指向头结点去遍历指针节点,这样删除的方式我认为是有区别第一种方式的

  • 删除指针并不是将前面节点的next指向当前节点的下一个节点,也就是说不是改变前面节点的next指向的地址、说白了与前面节点没有任何关系了
  • 他真正实现删除节点是通过将下一个节点指针的地址拖到了当前指针的地址处(后面节点的地址覆盖当前节点的地址)这样的好处是不需要考虑前节点是否真的存在,哪怕是最后一个节点大不了就覆盖了一个NULL过来,也是合理的。

用Linus的原话来讲___”纠正细节是令人自豪的事。也许这段代码并非庞大和重要,但我喜欢看那些注重代码细节的人写的代码,也就是清楚地了解如何才能编译出有效代码(而不是寄望于聪明的编译器来产生有效代码,即使是那些原始的汇编代码))。”___

构建你自己的 GPT4 聊天窗口

已经存在chatGPT,Gemini等等很好的AI聊天对话框、为啥还需要自己构建一个对话框 ?

  • 1、无法访问他们服务本身
  • 2、无法访问更高级的模型 : GPT4turbo

如果你刚好被以上问题困扰、那你很适合阅读该博文

准备条件

  • openAI 账号
  • 域名(可选: 反向代理方案)
  • github 账号
  • 信用卡(虚拟)

首先解释一下: 若需要使用 GPT4 服务 本身并不是免费的、所以需要花钱,需要用信用卡支付,如果你只是用GPT3.5的话、更没有必要构建自己的聊天窗口了,直接用官方的即可,花的钱也足够让你买机场好几个月

快速开始

1、进入openAI官网、 获取token(凭证) 保存下来,想要调用三方服务、少不了付费

2、购买域名、 利用域名做反向代理是能够在国内访问openai 服务的关键、如果不想每次访问前都要多一步操作、请不要省这点钱 便宜的域名 大概不到3$ (21RMB)/1 year,但不要买国内的,国内的使用起来会非常的麻烦(备案),我购买的是NameSilo上面的

3、将域名挂在到cloudflare上,操作比较简单、大致意思是将域名填到cloudflare上、然后将原本的购买服务商那里的删除 。。。

4、github fork 项目、 该项目源码已经公开了、咱们要做的就是按照要求、一键部署

  • 1、进入源码界面
  • 2、在README中直接点击Deploy
  • 3、利用Vercel一键部署

他会让你先登录、用github关联、会让你fork该项目、然后会让你写三个参数:

OPENAI_API_KEY : 填入你openAI 的token即可

CODE : 设置访问你自己网站的访问码/密码

GOOLE_API_KEY : Google API token (可选)

接下来等待即可、傻瓜式部署就完成了、会生成一个随机命名的域名、就可以直接访问了

前面我们说到、自己购买的域名是可以自由访问的关键、因为给的域名随时有可能被墙的风险、所以自己设置一个域名是很有必要的、其次自己的域名可读性也更强,以后只需要记住域名就好了。

5、配置域名、与反向代理page

在cloudflare中新建一个page

…此处省略(不想写了)

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Run server

1
$ hexo server

More info: Server

Generate static files

1
$ hexo generate

More info: Generating

Deploy to remote sites

1
$ hexo deploy

More info: Deployment

lambda表达式

对于面向对象对象语言中 lambda表达式其实是不重要的

如果你有了解过JavaScript、你一定对函数式编程比较熟悉(Js太恶心啦!!! 用的时候看不了规则根本不知道怎么写)

多说无益、先看效果:首先我们先假设有这么一个场景要实现一个数组排序 给定一个数组、将数组按指定要求(正序/倒序)排序

Java中我们可以直接使用Arrays.sort()调用JDK中 的方法进行排序(但该方法只提供了正序排序的实现)

1
2
3
4
5
6
7
8
9
public class Sort {

public static void main(String[] args) {
int[] nums = new int[]{1,3,5,25,7,23,7,34,8,12,5,3,71,236,12};
Arrays.sort(nums);
System.out.println(Arrays.toString(nums));
// outputL: [1, 3, 3, 5, 5, 7, 7, 8, 12, 12, 23, 25, 34, 71, 236]
}
}

当然sort()方法也可以实现倒序排序、只不过有点不一样

1
2
3
4
5
6
7
8
9
10
public class Sort {


public static void main(String[] args) {
Integer[] nums = new Integer[]{1,3,5,25,7,23,7,34,8,12,5,3,71,236,12}; // 是包装类 Integer 不是int
Arrays.sort(nums, Collections.reverseOrder());
System.out.println(Arrays.toString(nums));
// output: [236, 71, 34, 25, 23, 12, 12, 8, 7, 7, 5, 5, 3, 3, 1]
}
}

ok , 通过这两个简单方法就可以实现我们的功能、但这并不是本问介绍的重点、重点是我们要通过这个案例来学习了解到lambda表达式、并且间接的感受Java函数式编程思想

接下来我们就来实现一个简易版本的Arrays.sort(T[]nums,Comparable)
首先确定想要的效果、T[]nums 确定一个任何对象类型的数组、传入一个比较器接口类、该接口仅仅做一件事情就是帮我比较两个数大小,叫做比较器,当我们的排序数据换成其他对象时候、原始的+-已经不能满足我们的需求了、比如比较String 大小 在String中存在CompareTo方法比较两个字符串大小,因此为满足其他复杂数据类型也能够排序、引入比较器来自定义比较两个数据大小尤为重要
先定义比较器、为了不予Jdk本身接口(Comparator)冲突我取名叫ShenComparator.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@FunctionalInterface // 做个约束 改接口只能定义一个属于自己的抽象方法
public interface ShenComparator<T> {

/**
* 比较两个数的大小
* @param o1 第一个数
* @param o2 第二个数
* @return > 0 ? 前面的数字大 : 后面的数字大
* NOTICE:: 这里不仅仅表示值 比如integer double 等数值 、
* 可以表示一切对象按照指定规则比较大小
*
*/

int compare(T o1,T o2);

// 该方法属于Object(出现在这里属于多余)
boolean equals(Object obj);

}

这里的比较器唯一的规则就是 比较大小结果是大于0还是小于0,举个例子 假设我们需要比较两个Integer类型数字、可以直接将前面数字减去后面的数字相减

1
2
3
4
5
6
// 实现类
// 如果 o1 > o2 结果自然大于零, o1 < o2 结果自然小于零, 如果将结果反序则得到的排序结果也是相反逻辑
int compare(Integer o1,Integer o2) {
return o1 - o2;
// return o2 - o1;
}

ok、有了比较器也还先别着急、 先用最简易版本的数字排序来熟悉一下排序的过程、这里为了简单我直接用快速排序算法来实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

public class Utils {

/**
* 排序
* @param nums 待排序的数组
* 而是一个具体的排序规则 ::
* 字符串有字符串的规则
* 数字类型有数字类型比较规则
*/
public static void sort(int[] nums) {
quickSort(nums,0,nums.length - 1);
}


private static void quickSort(int[] nums,int left,int right) {
if (left >= right)
return;
int pivot = partition(nums, left, right);
quickSort(nums, left, pivot - 1);
quickSort(nums, pivot + 1, right);
}

private static void swap(int[] nums,int x, int y) {
int mid = nums[x];
nums[x] = nums[y];
nums[y] = mid;
}

/* 哨兵划分 */
private static int partition(int[] nums, int left, int right) {
// 以 nums[left] 作为基准数
int i = left, j = right;
while (i < j) {
while (i < j && nums[j] >= nums[left])
j--;
while (i < j && nums[i] <= nums[left])
i++;
swap(nums, i, j);
}
swap(nums, i, left);
return i;
}
}

现在已经实现了基本的排序功能、但是只针对于int[]数组、而且不支持随意切换正序还是倒序,现在测试一下

1
2
3
4
5
6
7
8
9
public class Main {

private static final int[] nums = {43,1,56,7,23,12,46,23,8,9,4,23,56,30,23,5};
public static void main(String[] args) {
Utils.sort(nums);
System.out.println(Arrays.toString(nums));
// output: [1, 4, 5, 7, 8, 9, 12, 23, 23, 23, 23, 30, 43, 46, 56, 56]
}
}

可见基本上排序功能是已经实现了、但是还缺少本文想要描述的重点、假设现在不是一堆数字要排序、而是一堆’学生’对象想通过成绩、年龄或通过名称排序,那该怎么办,接下来就需要引入我们自定义的比较器接口 以及给我们的int[] 都替换成 T[] 来实现, 另外我们算法中对排序数比较大小的-+号都可以用comparator.compare()方法来代替

加上比较器和泛型后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class Utils {

/**
* 排序
* @param nums 待排序的数组
* @param comparator 排序规则比较器 传入不是具体的某一个数据
* 而是一个具体的排序规则 ::
* 字符串有字符串的规则
* 数字类型有数字类型比较规则
*/
public static <T> void sort(T[] nums, ShenComparator<T> comparator) {
quickSort(nums,0,nums.length - 1,comparator);
}


private static <T> void quickSort(T[] nums,int left,int right,ShenComparator<T> comparator) {
if (left >= right)
return;
int pivot = partition(nums, left, right,comparator);
quickSort(nums, left, pivot - 1,comparator);
quickSort(nums, pivot + 1, right,comparator);
}

private static <T> void swap(T[] nums,int x, int y) {
T mid = nums[x];
nums[x] = nums[y];
nums[y] = mid;
}

/* 哨兵划分 */
private static <T> int partition(T[] nums, int left, int right,ShenComparator<T> comparator) {
// 以 nums[left] 作为基准数
int i = left, j = right;
while (i < j) {
// 替换 nums[j] >= nums[left]
while (i < j && comparator.compare(nums[j],nums[left]) >= 0)
j--;
// 替换nums[i] <= nums[left]
while (i < j && comparator.compare(nums[i],nums[left]) <= 0)
i++;
swap(nums, i, j);
}
swap(nums, i, left);
return i;
}
}

直接测试!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Main {


public static void main(String[] args) {

Student[] students = new Student[]{
new Student(18,"lip",99F),
new Student(19,"dabbe",45.5F),
new Student(20,"miky",50.5F)};

Utils.sort(students, new ShenComparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
// 按照成绩从大到小排序
return (int)(o2.getScore() - o1.getScore());
}
});
System.out.println(Arrays.toString(students));
//output:[Student(age=18, name=lip, score=99.0), Student(age=20, name=miky, score=50.5), Student(age=19, name=dabbe, score=45.5)]
}
}

ok 这里可以看到、我们调用Utils.sort方法时new 了一个匿名类部类 并重写里面的 compare方法来实现传递比较器,这就是自定义排序规则的根本原因。

但是到现在我们还根本没有跟lambda表达式沾边、是的、java8 以后匿名类部类可以改写简化成lambda表达式、但是有前提:

  • 该接口中(ShenComparator)只能有一个抽象方法
  • 也许还有。。。不记得了

用lambda表达式改写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Main {


public static void main(String[] args) {

Student[] students = new Student[]{
new Student(18,"lip",99F),
new Student(19,"dabbe",45.5F),
new Student(20,"miky",50.5F)};

Utils.sort(students, (o1, o2) -> (int)(o2.getScore() - o1.getScore())); // 只需要一行代码及可完成
System.out.println(Arrays.toString(students));
}
}
1
Utils.sort(students,(a,b) -> b.getName().compareTo(a.getName())); // 根据名字倒序排序
1
Utils.sort(students,(a,b) -> a.getAge() - b.getAge()); // 根据年龄正序排序

备注

为了更容易理解、贴出类项目结构(额、好像没必要,算了贴都贴了反正就两个重要类ShenComparator 与 Utils)

建议查看java函数式编程的源码内容本文只是仿照他写的一个简易实现

我是怎么使用Lambda表达式的?

首先Lambda表达式主要用于集合的操作中

其中java Stream流配合Lambda表达式对集合操作就非常便捷

比如:假设有一个包含员工信息的列表,我们希望按照部门对员工进行分组,并计算每个部门的平均工资。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class Main {


@Data // 为了节省空间就不写setter getter构造方法了 直接用lombok 自动生成了
@AllArgsConstructor
static class Employee {
private String name;
private String department;
private double salary;
}

public static void main(String[] args) {

List<Employee> list = Arrays.asList(new Employee("Alice", "HR", 50000),
new Employee("Bob", "Engineering", 60000),
new Employee("Charlie", "HR", 55000),
new Employee("David", "Engineering", 70000),
new Employee("Eva", "HR", 48000),
new Employee("Frank", "Sales", 75000));


Map<String, Double> averageSalaryByDepartment = list.stream()
.collect(Collectors.groupingBy(Employee::getDepartment, // 按部门分组
Collectors.averagingDouble(Employee::getSalary))); // 求平均值

averageSalaryByDepartment.forEach((department, salaryAVG) -> System.out.println("Department: " + department + ", Average Salary: " + salaryAVG));
//output:Department: Engineering, Average Salary: 65000.0
//Department: Sales, Average Salary: 75000.0
//Department: HR, Average Salary: 51000.0
}

}

另外使用lambda表达式除了要会查文档外更建议用Idea查看具体需要类型,如 我这里使用filter方法实现过滤功能、但看不明白需要传递参数是什么、可以通过idea查看、需要一个Predicate (断言)接口 、里面一个test方法 返回值为boolean类型 需要一个参数、知道这些信息就可以很轻松写出lambda表达式,不需要new 个匿名类部类再改写。

代理模式

What is that

代理模式是一种结构型设计模式,它允许通过创建一个代理对象来控制原始对象的访问。代理对象充当一个中介,通过在客户端和目标对象之间建立通信桥梁,帮助管理对目标对象的访问。代理类在接收到客户端的请求时,可以在执行具体操作之前或之后,对原始对象进行额外的处理,和如果我需要执行某段程序前想要输出日志,我不需要每次都将日志输出代码都写入代码块里,造成代码冗余。

How to use it

1
2
3
4
5
6
7
8
9
10
11
package shen.dao;

import shen.entity.User;

public interface IUserDao {
/*
* 查找单个用户
* */

User findUserById(Integer id);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package shen.dao.impl;

import shen.dao.IUserDao;
import shen.entity.User;

public class UserDaoImpl implements IUserDao {

@Override
public User findUserById(Integer id) {

User user = new User(0,"you catch me","Hi");
return user;
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package shen.entity;

public class User {
private Integer id;
private String username;
private String password;

public User() {}
public User(Integer id, String username, String password) {
this.id = id;
this.username = username;
this.password = password;
}

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package shen.handler;
import shen.dao.IUserDao;
import shen.dao.impl.UserDaoImpl;
import shen.entity.User;

public class ProxyHandler implements IUserDao {


public void before() {
System.out.println("turn on transaction");
}
@Override
public User findUserById(Integer id) {
this.before();
UserDaoImpl userDao = new UserDaoImpl();
User user = userDao.findUserById(id);
System.out.println(user);
this.after();
return user;
}
public void after() {
System.out.println("turn off transaction");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package shen.test;

import shen.entity.User;
import shen.handler.ProxyHandler;

public class test {
public static void main(String[] args) {
IUserDao proxyHandler = new ProxyHandler();
proxyHandler.findUserById(0);
}
}
```output
turn on transaction
User{id=0, username='You catch me', password='Hi'}
turn off transaction

通过这样静态代理实现接口的方式用proxyHandler实现获取User的同时还完成了after(),与before()两个方法

Why

代理模式存在的原因是为了达到一些额外的目的,例如:提供一些额外的安全措施、控制对资源的访问、实现懒加载等。通过代理模式,我们可以灵活地管理和控制对目标对象的访问。

JDK动态代理

What is that

如果每个类都自己实现编写静态代理,不仅麻烦而且会导致程序非常复杂,这时可以用到JDK的动态代理API,JDK动态代理是一种特殊类型的代理模式,它允许在运行时动态生成代理类和代理对象。JDK动态代理需要使用Java的反射机制,利用代理类和InvocationHandler接口来实现代理功能。

How to use it

在以上代码基础上对handler 包和test包进行更改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package shen.handler;

import shen.dao.IUserDao;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class DaoTransactionHandler implements InvocationHandler {
// if other class want use proxy you can choose Object class
private Object obj;

public DaoTransactionHandler(Object obj) {
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object res = null;
// if you want to enhance just only one method
if ("findUserById".equals(method.getName())) {
this.before();
res = method.invoke(obj,args);
this.after();
}else {
res = method.invoke(args);
}
return res;
}

private void before() {
System.out.println("turn on enhance transaction");
}
private void after(){
System.out.println("turn off enhance transaction");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package shen.test;

import shen.dao.IUserDao;
import shen.dao.impl.UserDaoImpl;
import shen.handler.DaoTransactionHandler;

import java.lang.reflect.Proxy;

public class test {
public static void main(String[] args) {
// target Class
UserDaoImpl userDao = new UserDaoImpl();
// enhance Class
// invocation class
DaoTransactionHandler daoTransactionHandler = new DaoTransactionHandler(userDao);
// act proxy object
IUserDao userDaoProxy =(IUserDao) Proxy.newProxyInstance(UserDaoImpl.class.getClassLoader(),
UserDaoImpl.class.getInterfaces(), daoTransactionHandler);
userDaoProxy.findUserById(0);

}
}
```output
turn on enhance transaction
User{id=0, username='you catch me', password='Hi'}
turn off enhance transaction

Why

JDK动态代理的好处在于,它不需要事先编写代理类的源代码,而是在运行时根据需要动态生成代理类和代理对象。这样可以大大简化开发过程,特别是当需要为多个类生成代理时,能够提高代码的灵活性和可维护性。

JDK动态代理的原理是利用了Java的反射机制,通过在运行时创建一个新的类,实现目标类所实现的接口,并在方法调用时使用InvocationHandler的实现类来处理额外逻辑。当代理对象的方法被调用时,JDK动态代理会通过反射将调用转发给InvocationHandler实例中的invoke()方法,从而实现代理功能。

可以集合JDK自动生成的$Proxy的源码分析:

修改test中代码可以生成$Proxy文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package shen.test;

import shen.dao.IUserDao;
import shen.dao.impl.UserDaoImpl;
import shen.handler.DaoTransactionHandler;
import sun.misc.ProxyGenerator;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Proxy;

public class test {
public static void main(String[] args) {
// target Class
UserDaoImpl userDao = new UserDaoImpl();
// enhance Class
// invocation class
DaoTransactionHandler daoTransactionHandler = new DaoTransactionHandler(userDao);
// act proxy object
IUserDao userDaoProxy =(IUserDao) Proxy.newProxyInstance(UserDaoImpl.class.getClassLoader(),
UserDaoImpl.class.getInterfaces(), daoTransactionHandler);
userDaoProxy.findUserById(0);
// path such as "C:\\IdeaProject\\proxyDemo\\src"
saveProxyClass("__your project path __\\src");

}
//writing $proxy to contain in localhost
private static void saveProxyClass(String path) {
byte[] $Proxy1s = ProxyGenerator.generateProxyClass("$Proxy1",
UserDaoImpl.class.getInterfaces());
FileOutputStream out = null;
try {
out = new FileOutputStream(new File(path + "$Proxy1.class"));
out.write($Proxy1s);
}catch (Exception e) {
e.printStackTrace();
}finally {
if (out != null) {
try {
out.flush();
out.close();
} catch (IOException e) {
throw new RuntimeException(e);
}

}
}
}
}

分析源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
public final class $Proxy1 extends Proxy implements IUserDao {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;

public $Proxy1(InvocationHandler var1) throws {
super(var1);
}

public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}

public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}

public final User findUserById(Integer var1) throws {
try {
return (User)super.h.invoke(this, m3, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}

public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}

static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("shen.dao.IUserDao").getMethod("findUserById", Class.forName("java.lang.Integer"));
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}

JDK动态代理的原理是利用了Java的反射机制,通过在运行时创建一个新的类,实现目标类所实现的接口,并在方法调用时使用InvocationHandler的实现类来处理额外逻辑。当代理对象的方法被调用时,JDK动态代理会通过反射将调用转发给InvocationHandler实例中的invoke()方法,从而实现代理功能。

uml图

以上就是我的理解!

香橙派打造软路由

硬件介绍

Orange Pi R1 Plus LTS(软路由)此产品定位即为了打造路由器

官方参数如下:

官方自带有openwrt、ubuntu、debian固件等,but相信大家伙眼界没有受限于此

博主也建议选择社区版本openwrt固件这样优点很明显,能够拥有更多你想要的功能,废话不多说

固件引用

官方固件

推荐固件(如果你与博主用的硬件一样请选择最后一个镜像下载)

安装固件

需要条件:

1、microSD卡(读卡器)

2、烧录工具

3、固件镜像文件

操作步骤:

1、下载镜像文件、烧录工具

2、插入读卡器(microSD卡)

3、烧录系统(烧录好后千万不要格式化)

4、插上Pi 直接可以启动

启动路由器

操作步骤

1、连接电源,插上SD卡,连接lan网口至PC

2、PC检查网络连接找到IPv4 DNS服务器ip地址

3、浏览器访问该网址:默认为192.168.1.1

出现网站信息即成功!!!

4、登录openwrt、账号:root、原版没有密码、lean大佬版本密码:password

祝你好运!!!

附录

编译固件

想要自己diy插件功能等,可以自行编译固件,有详细教程,不需要编程知识,要求具备一定互联网搜索解决问题能力。

源码及编译教程地址

鸣谢

感谢固件提供地址:

1
https://openwrt.mpdn.fun:8443/?dir=lede/rockchip

感谢openwrt可编译源码

1
https://github.com/coolsnowwolf/lede

模拟电商日志mapRuce计算

准备环境

  • ubuntu22.04虚拟机/物理机
  • jdk安装以及环境配置
  • maven安装以及环境配置
  • hadoop安装以及环境配置

日志信息模拟生成

利用javaIO模拟生成100w条购买记录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
&20320230401170400&Larry&虚拟产品&68.0&3.0&204.0&北京
&66420230401170400&吴九&电脑&56.0&13.0&728.0&北京
&90620230401170400&郑十&牙刷&32.0&9.0&288.0&北京
&60020230401170400&郑十&毛巾&78.0&1.0&78.0&长沙
&23020230401170400&吴九&虚拟产品&61.0&17.0&1037.0&长沙
&70520230401170400&张三&鞋子&70.0&15.0&1050.0&上海
&44620230401170400&王五&牙刷&11.0&7.0&77.0&上海
&18320230401170400&赵六&篮球&48.0&3.0&144.0&上海
&61620230401170400&周八&牙刷&30.0&19.0&570.0&上海
......
&80320230401170400&李四&水杯&19.0&9.0&171.0&北京
&48620230401170400&周八&牙刷&97.0&9.0&873.0&张家界
&43120230401170400&张三&篮球&36.0&6.0&216.0&佛山
&93720230401170400&李四&毛巾&56.0&19.0&1064.0&广州

构建maven项目

项目结构:将hadoop/etc/hadoop目录下的core-site.xml,hdfs-site.xml,log4g.properties文件分别复制到resources目录下

修改pom.xml 配置文件(博主用的Hadoop2.10.1,jdk1.8 maven3.9)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>org.example</groupId>
<artifactId>mapReduceDemo</artifactId>
<version>1.0-SNAPSHOT</version>

<repositories>
<repository>
<id>apache</id>
<url>http://maven.apache.org</url>
</repository>
</repositories>


<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<version>2.10.1</version>
</dependency>

<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-hdfs</artifactId>
<version>2.10.1</version>
</dependency>

<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-mapreduce-client-core</artifactId>
<version>2.10.1</version>
</dependency>

<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-mapreduce-client-jobclient</artifactId>
<version>2.10.1</version>
</dependency>

<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>

</project>

计算每个城市对应的购买总金额,accountByCity.java代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
//import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

import java.io.IOException;
import java.util.StringTokenizer;

/**
* <城市,小计金额>
*
* <城市,{小计金额,小计金额...}>
*
* <城市,总计金额>
*/
public class accountByCity {
public static class myMapper extends Mapper<Object,Text, Text, LongWritable> {
private final static LongWritable account = new LongWritable();
private Text city = new Text();
private long startTime;
@Override
protected void setup(Context context)throws IOException,InterruptedException{
super.setup(context);
startTime = System.currentTimeMillis();

}
@Override
public void map(Object key,Text value, Context context)
throws IOException,InterruptedException{
// 此处的value是文档中的第一行文本数据
String msg = value.toString();
String[] list = msg.split(";&";);
city.set(list[7]);
double myDouble = Double.parseDouble(list[6]);
long myLong = (long)myDouble;
account.set(myLong);
context.write(city,account);
}

protected void cleanup(Context context) throws IOException,InterruptedException {
super.cleanup(context);
long endTime = System.currentTimeMillis();
long duration = endTime - startTime;
System.out.println(";MapperTime : ";+ duration+ ";ms";);//获取map计算总时间
}


}

public static class myReduce extends Reducer<Text,LongWritable,Text,LongWritable> {
private LongWritable result = new LongWritable();
private long startTime;
@Override
protected void setup(Context context) throws IOException,InterruptedException{
super.setup(context);
startTime = System.currentTimeMillis();
}
// 从这可以看出reduce处理的输入数据是<key,value-list>类型的键值对
public void reduce(Text key, Iterable<LongWritable> values, Context context)
throws IOException,InterruptedException{
int sum = 0;
// reduce 函数就是对列表value中的数值进行相加
for(LongWritable val : values) {
sum += val.get();
}
result.set(sum);
// 将结果写入context
context.write(key,result);
}
@Override
protected void cleanup(Context context) throws IOException,InterruptedException{
super.cleanup(context);
long endTime = System.currentTimeMillis();
long duration = endTime - startTime;
System.out.println(";ReducerTime :";+duration+"; ms";);//获取reduce计算总时间
}
}
/**
* 1、accountByCity的main函数
* 2、main函数主要创建一个job对象,然后对accountBycity任务所需要的map函数,reduce函数,输入文件路径,输出文件路径的信息
* 进行配置
*/
public static void main(String[] args) throws Exception{
Configuration conf = new Configuration();
Job job = Job.getInstance(conf,";;accountByCity";);//获取一个任务实例
job.setJarByClass(accountByCity.class);//设置工作类
job.setMapperClass(myMapper.class);//设置Mapper类
job.setReducerClass(myReduce.class);//设置Reduce类
job.setOutputKeyClass(Text.class);//设置输出键值对中的key的类型
job.setOutputValueClass(LongWritable.class);//设置输出键值对中value的类型
FileInputFormat.addInputPath(job,new Path(args[0]));//设置输入文件的路径
FileOutputFormat.setOutputPath(job,new Path(args[1]));
FileSystem fs = FileSystem.get(conf);//获取HDFS文件系统
fs.delete(new Path(args[1]),true);//删除输出路径下可能已经存在的文件

运行项目

首先启动Hadoop服务

start-all.sh

上传日志文件到Hadoop的hdfs文件关系系统

关于Hadoop shell指令上传日志文件

假设这是我的日志文件路径/hadoop/test

hadoop fs -mkdir /data创建一个data目录

hadoop fs -mkdir /output创建一个output目录

hadoop fs -put /hadoop/test /data上传文件到data目录下

idea构建项目

执行项目

—success

map 100% reduce 100% 说明运行成功

1
2
3
4
5
6
7
8
......
23በ቞ 22:57:00 INFO mapred.LocalJobRunner: Finishing task: attempt_local524911839_0001_r_000000_0
23በ቞ 22:57:00 INFO mapred.LocalJobRunner: reduce task executor complete.
23በ቞ 22:57:01 INFO mapreduce.Job: map 100% reduce 100%
23በ቞ 22:57:01 INFO mapreduce.Job: Job job_local524911839_0001 completed successfully
23በ቞ 22:57:01 INFO mapreduce.Job: Counters: 35
......

查看生成数据

音频转文字

书接上回,AI图片生成,问答生成,接下来博主继续介绍 openAI的音频转换API,实现音频提取并翻译(->English)文字

1、 准备环境

    1、python基本环境
    2、pip install openai(下载第三方库,改库来自openAI官方)
    3、拥有openAI账号(chatGPT账号,没有可以注册一个)
    4、具有代理上网能力(全局),或者挂在境外服务器上运行

2、实践

首先进入openAI_API的官网获取API_Key—>openAI

进入View API keys

点击创建Create new secret key

并复制所创建的API_Key备用

博主依旧是润色官网代码实现功能:

    1、提取文字(不翻译)
1
2
3
4
5
6
7
8
# Note: you need to be using OpenAI Python v0.27.0 for the code below to work
import openai

openai.api_key = "your API_Key"
audio_file= open("./demo2.mp3", "rb") #路径修改为你的音频文件路径
# 音频转化为文字
response = openai.Audio.transcribe("whisper-1", audio_file)
print(response['text'])
    2、提取文字并翻译为英文
1
2
3
4
5
6
7
8
# Note: you need to be using OpenAI Python v0.27.0 for the code below to work
import openai

openai.api_key = "your API_Key"
audio_file= open("./demo2.mp3", "rb") #路径修改为你的音频文件路径
# 音频转化为文字并翻译为英文
response = openai.Audio.translate("whisper-1",audio_file)
print(response['text'])

3、运行截图

4、总结

相比于图形生成其效果还可以,也许适用于做短视频的字幕生成。

大家对AI感兴趣的可以阅读官方文档—>openAI_Doc

值得注意的是使用API并不是免费的,每分钟的音频计费$0.006,但每个账户都有$18的额度

调用openAI-Image models的API

调用openAI-Image models的API

博主这里承接上文调用chatGPT的API实现AI对话,接着继续用API实现AI画图

依旧用Python代码实现

1、 准备环境

    1、python基本环境
    2、pip install openai(下载第三方库,改库来自openAI官方)、pip install requests
    3、拥有openAI账号(chatGPT账号,没有可以注册一个)
    4、具有代理上网能力(全局),或者挂在境外服务器上运行

2、实践

首先进入openAI_API的官网获取API_Key—>openAI

进入View API keys

点击创建Create new secret key

并复制所创建的API_Key备用

博主这里从官网直接复制代码,并添加了存储本地功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import openai
import os
import requests
def image(prompt):
openai.api_key = '你自己的API_Key'
response = openai.Image.create(
prompt=prompt,
n=1,
size="1024x1024"
)
image_url = response['data'][0]['url']
# 存储图片
fileDown(image_url)


def fileDown(url):
if not os.path.exists('./image'):
os.mkdir('./image')
headers={
"User-Agent":"Mozilla/5.0(Windows NT 10.0;Win64;x64) AppleWebKit / 537.36(KHTML, likeGecko) Chrome / 88.0.4324.150 Safari / 537.36"
}
res = requests.get(url=url,headers=headers).content
src_path = './image/' +url.split('/')[-1]+ '.jpg'
with open(src_path,'wb') as fp:
fp.write(res)

if __name__ == "__main__":
prompt = input('请输入描述信息:')
print('请稍后...')
image(prompt=prompt)
print('完成,请移步当前目录下image文件...')

3、运行结果截图

效果如大家所见,就是难以形容,大家可以自己尝试一下!!!

大家对AI感兴趣的可以阅读官方文档—>openAI_Doc

值得注意的是使用API并不是免费的,每张1024*1024的图片花费 $0.020,但每个账户都有$18的额度

调用chatGPT的API(GPT-3.5-turbo)

博主在这里用python编写代码的方式实现调用GPT-3.5-turbo的API实现终端访问chatGPT

1、 准备环境

    1、python基本环境
    2、pip install openai(下载第三方库,改库来自openAI官方)
    3、拥有openAI账号(chatGPT账号,没有可以注册一个)
    4、具有代理上网能力(全局),或者挂在境外服务器上运行

2、实践

首先进入openAI_API的官网获取API_Key—>openAI

进入View API keys

点击创建Create new secret key

并复制所创建的API_Key备用

博主这里从官网直接复制并添加润色了以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Note: you need to be using OpenAI Python v0.27.0 for the code below to work
import openai
openai.api_key = '你的API'
response = openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages=[
# {"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Who are you?"},#这里content表示输入问题
# {"role": "assistant", "content": "The Los Angeles Dodgers won the World Series in 2020."},
# {"role": "user", "content": "Where was it played?"}
]
)
print(response['choices'][0]['message']['content'])

3、结果显示

打印类似如下内容说明成功!

1
As an AI language model developed by OpenAI, I am not a person or a human being. I am a computer program designed to understand and generate natural language responses to interact with humans.

大家对AI感兴趣的可以阅读官方文档—>openAI_Doc

值得注意的是使用API并不是免费的,但每个账户都有US$ 18的额度

参考视频—> TechDIYLife