Nicksxs's Blog

What hurts more, the pain of hard work or the pain of regret?

题目介绍

You are given an n x n 2D matrix representing an image, rotate the image by 90 degrees (clockwise).

You have to rotate the image in-place, which means you have to modify the input 2D matrix directly. DO NOT allocate another 2D matrix and do the rotation.

如图,这道题以前做过,其实一看有点蒙,好像规则很容易描述,但是代码很难写,因为要类似于贪吃蛇那样,后来想着应该会有一些特殊的技巧,比如翻转等

代码

直接上码

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
public void rotate(int[][] matrix) {
// 这里真的傻了,长宽应该是一致的,所以取一次就够了
int lengthX = matrix[0].length;
int lengthY = matrix.length;
int temp;
System.out.println(lengthY - (lengthY % 2) / 2);
// 这里除错了,应该是减掉余数再除 2
// for (int i = 0; i < lengthY - (lengthY % 2) / 2; i++) {
/**
* 1 2 3 7 8 9
* 4 5 6 => 4 5 6 先沿着 4 5 6 上下交换
* 7 8 9 1 2 3
*/
for (int i = 0; i < (lengthY - (lengthY % 2)) / 2; i++) {
for (int j = 0; j < lengthX; j++) {
temp = matrix[i][j];
matrix[i][j] = matrix[lengthY-i-1][j];
matrix[lengthY-i-1][j] = temp;
}
}

/**
* 7 8 9 7 4 1
* 4 5 6 => 8 5 2 这里再沿着 7 5 3 这条对角线交换
* 1 2 3 9 6 3
*/
for (int i = 0; i < lengthX; i++) {
for (int j = 0; j <= i; j++) {
if (i == j) {
continue;
}
temp = matrix[i][j];
matrix[i][j] = matrix[j][i];
matrix[j][i] = temp;
}
}
}

还没到可以直接归纳题目类型的水平,主要是几年前做过,可能有那么点模糊的记忆,当然应该也有直接转的方法

最近在看 《rust 权威指南》,还是难度比较大的,它里面的一些概念跟之前的用过的都有比较大的差别
比起有 gc 的虚拟机语言,跟像 C 和 C++这种主动释放内存的,rust 有他的独特点,主要是有三条

  • Rust中的每一个值都有一个对应的变量作为它的所有者。
  • 在同一时间内,值有且只有一个所有者。
  • 当所有者离开自己的作用域时,它持有的值就会被释放掉。

    这里有两个重点:
  • s 在进入作用域后才变得有效
  • 它会保持自己的有效性直到自己离开作用域为止

然后看个案例

1
2
let x = 5;
let y = x;

这个其实有两种,一般可以认为比较多实现的会使用 copy on write 之类的,先让两个都指向同一个快 5 的存储,在发生变更后开始正式拷贝,但是涉及到内存处理的便利性,对于这类简单类型,可以直接拷贝
但是对于非基础类型

1
2
3
4
let s1 = String::from("hello");
let s2 = s1;

println!("{}, world!", s1);

有可能认为有两种内存分布可能
先看下 string 的内存结构

第一种可能是

第二种是

我们来尝试编译下

发现有这个错误,其实在 rust 中let y = x这个行为的实质是移动,在赋值给 y 之后 x 就无效了

这样子就不会造成脱离作用域时,对同一块内存区域的二次释放,如果需要复制,可以使用 clone 方法

1
2
3
4
let s1 = String::from("hello");
let s2 = s1.clone();

println!("s1 = {}, s2 = {}", s1, s2);

这里其实会有点疑惑,为什么前面的x, y 的行为跟 s1, s2 的不一样,其实主要是基本类型和 string 这类的不定大小的类型的内存分配方式不同,x, y这类整型可以直接确定大小,可以直接在栈上分配,而像 string 和其他的变体结构体,其大小都是不能在编译时确定,所以需要在堆上进行分配

这里需要说道函数和返回值了
可以看书上的这个例子

对于这种情况,当进入函数内部时,会把传入的变量的所有权转移进函数内部,如果最后还是要返回该变量,但是如果此时还要返回别的计算结果,就可能需要笨拙地使用元组

引用

此时我们就可以用引用来解决这个问题

1
2
3
4
5
6
7
8
9
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1);

println!("The length of '{}' is {}", s1, len);
}
fn calculate_length(s: &String) -> usize {
s.len()
}

这里的&符号就是引用的语义,它们允许你在不获得所有权的前提下使用值

由于引用不持有值的所有权,所以当引用离开当前作用域时,它指向的值也不会被丢弃

可变引用

而当我们尝试对引用的字符串进行修改时

1
2
3
4
5
6
7
fn main() {
let s1 = String::from("hello");
change(&s1);
}
fn change(s: &String) {
s.push_str(", world");
}

就会有以下报错,

其实也很容易发现,毕竟没有 mut 指出这是可变引用,同时需要将 s1 改成 mut 可变的

1
2
3
4
5
6
7
8
9
fn main() {
let mut s1 = String::from("hello");
change(&mut s1);
}


fn change(s: &mut String) {
s.push_str(", world");
}

再看一个例子

1
2
3
4
5
fn main() {
let mut s1 = String::from("hello");
let r1 = &mut s1;
let r2 = &mut s1;
}

这个例子在书里是会报错的,因为同时存在一个以上的可变引用,但是在我运行的版本里前面这段没有报错,只有当我真的要去更改的时候

1
2
3
4
5
6
7
8
9
10
11
12
fn main() {
let mut s1 = String::from("hello");
let mut r1 = &mut s1;
let mut r2 = &mut s1;
change(&mut r1);
change(&mut r2);
}


fn change(s: &mut String) {
s.push_str(", world");
}


这里可能就是具体版本在实现上的一个差异,我用的 rustc 是 1.44.0 版本
其实上面的主要是由 rust 想要避免这类多重可变更导致的异常问题,总结下就是三个点

  • 两个或两个以上的指针同时同时访问同一空间
  • 其中至少有一个指针会想空间中写入数据
  • 没有同步数据访问的机制
    并且我们不能在拥有不可变引用的情况下创建可变引用

悬垂引用

还有一点需要注意的就是悬垂引用

1
2
3
4
5
6
7
8
fn main() {
let reference_to_nothing = dangle();
}

fn dangle() -> &String {
let s = String::from("hello");
&s
}

这里可以看到其实在 dangle函数返回后,这里的 s 理论上就离开了作用域,但是由于返回了 s 的引用,在 main 函数中就会拿着这个引用,就会出现如下错误

总结

最后总结下

  • 在任何一个段给定的时间里,你要么只能拥有一个可变引用,要么只能拥有任意数量的不可变引用。
  • 引用总是有效的。

这几天去了趟厦门,原来几年前就想去了,本来都请好假了,后面因为一些事情没去成,这次刚好公司组织,就跟 LD 一起去了厦门,也不洋洋洒洒地写游记了,后面可能会有,今天先来总结下好的地方和比较坑的地方。
这次主要去了中山路、鼓浪屿、曾厝(cuo)垵、植物园、灵玲马戏团,因为住的离环岛路比较近,还有幸现场看了下厦门马拉松,其中

中山路

这里看上去是有点民国时期的建筑风格,部分像那种电视里的租界啥的,不过这次去的时候都在翻修,路一大半拦起来了,听导游说这里往里面走有个局口街,然后上次听前同事说厦门比较有名的就是沙茶面和海蛎煎,不出意料的不太爱吃,沙茶面比较普通,可能是没吃到正宗的,海蛎煎吃不惯,倒是有个大叔的沙茶里脊还不错,在局口街那,还有小哥在那拍,应该也算是个网红打卡点了,然后吃了个油条麻糍也还不错,总体如果是看建筑的话可能最近不是个好时间,个人也没这方面爱好,吃的话最好多打听打听沙茶面跟海蛎煎哪里正宗。如果不知道哪家好吃,也不爱看这类建筑的可以排个坑。

鼓浪屿

鼓浪屿也是完全没啥概念,需要乘船过去,但是只要二十分钟,岛上没有机动车,基本都靠走,有几个比较有名的地方,菽庄花园,里面有钢琴博物馆,对这个感兴趣的可以去看看,旁边是沙滩还可以逛逛,然后有各种博物馆,风琴啥的,岛上最大的特色是巷子多,道听途说有三百多条小巷,还有几个网红打卡点,周杰伦晴天墙,还有个最美转角,都是挤满了人排队打卡拍照,不过如果不着急,慢慢悠悠逛逛还是不错的,比较推荐,推荐值☆☆

曾厝垵

一直读不对这个字,都是叫:那个曾什么垵,愧对语文老师,这里到算是意外之喜,鼓浪屿回来已经挺累了,不过由于比较饿(什么原因后面说),并且离住的地方不远,就过去逛了逛,东西还蛮好吃的,芒果挺便宜,一大杯才十块,无骨鸡爪很贵,不是特别爱,臭豆腐不错的,也不算很贵,这里想起来,那边八婆婆的豆乳烧仙草还不错的,去中山路那会喝了,来曾厝垵也买了,奶茶爱好者可以试试,含糖量应该很高,不爱甜食或者减肥的同学慎重考虑好了再尝试,晚上那边从牌坊出来,沿着环岛路挺多夜宵店什么的,非常推荐,推荐值☆☆☆☆

植物园

植物园还是挺名副其实的,有热带植物,沙漠多肉,因为赶时间逛得不多,热带雨林植物那太多人了,都是在那拍照,而且我指的拍照都是拍人照,本身就很小的路,各种十八线网红,或者普通游客在那摆 pose 拍照,挺无语的;沙漠多肉比较惊喜,好多比人高的仙人掌,一大片的仙人球,很可恶的是好多大仙人掌上都有人刻字,越来越体会到,我们社会人多了,什么样的都有,而且不少;还看了下百花厅,但没什么特别的,可能赶时间比较着急,没仔细看,比较推荐,推荐值☆☆☆

灵玲马戏团

对这个其实比较排斥,主要是比较晚了,跑的有点远(我太懒了),一开始真的挺拉低体验感受的,上来个什么书法家,现场画马,卖画;不过后面的还算值回票价,主题是花木兰,空中动作应该很考验基本功,然后那些老外的飞轮还跳绳(不知道学名叫啥),动物那块不太忍心看,应该是吃了不少苦头,不过人都这样就往后点再心疼动物吧。

总结

厦门是个非常适合干饭人的地方,吃饭的地方大部分是差不多一桌菜十个左右就完了,而且上来就一大碗饭,一瓶雪碧一瓶可乐,对于经常是家里跟亲戚吃饭都得十几二十个菜的乡下人来说,不太吃得惯这样的🤦‍♂️,当然很有可能是我们预算不足,点的差。但是有一点是我回杭州深有感触的,感觉杭州司机的素质真的是跟厦门的司机差了比较多,杭州除非公交车停了,否则人行道很难看到主动让人的,当然这里拿厦门这个旅游城市来对比也不是很公平,不过这也是体现城市现代化文明水平的一个维度吧。

之前没注意到这一块,只是比较模糊的印象 dubbo 自己基于 ThreadPoolExecutor 定义了几个线程池,但是没具体看过,主要是觉得就是为了避免使用 jdk 自带的那几个(java.util.concurrent.Executors),防止出现那些问题
看下代码目录主要是这几个

  • FixedThreadPool:创建一个复用固定个数线程的线程池。
    简单看下代码
    1
    2
    3
    4
    5
    6
    public Executor getExecutor(URL url) {
    String name = url.getParameter("threadname", "Dubbo");
    int threads = url.getParameter("threads", 200);
    int queues = url.getParameter("queues", 0);
    return new ThreadPoolExecutor(threads, threads, 0L, TimeUnit.MILLISECONDS, (BlockingQueue)(queues == 0 ? new SynchronousQueue() : (queues < 0 ? new LinkedBlockingQueue() : new LinkedBlockingQueue(queues))), new NamedThreadFactory(name, true), new AbortPolicyWithReport(name, url));
    }
    可以看到核心线程数跟最大线程数一致,也就是说就不会在核心线程数和最大线程数之间动态变化了
  • LimitedThreadPool:创建一个线程池,这个线程池中线程个数随着需要量动态增加,但是数量不超过配置的阈值的个数,另外空闲线程不会被回收,会一直存在。
    1
    2
    3
    4
    5
    6
    7
    public Executor getExecutor(URL url) {
    String name = url.getParameter("threadname", "Dubbo");
    int cores = url.getParameter("corethreads", 0);
    int threads = url.getParameter("threads", 200);
    int queues = url.getParameter("queues", 0);
    return new ThreadPoolExecutor(cores, threads, 9223372036854775807L, TimeUnit.MILLISECONDS, (BlockingQueue)(queues == 0 ? new SynchronousQueue() : (queues < 0 ? new LinkedBlockingQueue() : new LinkedBlockingQueue(queues))), new NamedThreadFactory(name, true), new AbortPolicyWithReport(name, url));
    }
    这个特点主要是创建了保活时间特别长,即可以认为不会被回收了
  • EagerThreadPool :创建一个线程池,这个线程池当所有核心线程都处于忙碌状态时候,创建新的线程来执行新任务,而不是把任务放入线程池阻塞队列。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public Executor getExecutor(URL url) {
    String name = url.getParameter("threadname", "Dubbo");
    int cores = url.getParameter("corethreads", 0);
    int threads = url.getParameter("threads", 2147483647);
    int queues = url.getParameter("queues", 0);
    int alive = url.getParameter("alive", 60000);
    TaskQueue<Runnable> taskQueue = new TaskQueue(queues <= 0 ? 1 : queues);
    EagerThreadPoolExecutor executor = new EagerThreadPoolExecutor(cores, threads, (long)alive, TimeUnit.MILLISECONDS, taskQueue, new NamedThreadFactory(name, true), new AbortPolicyWithReport(name, url));
    taskQueue.setExecutor(executor);
    return executor;
    }
    这个是改动最多的一个了,因为需要实现这个机制,有兴趣的可以详细看下
  • CachedThreadPool: 创建一个自适应线程池,当线程处于空闲1分钟时候,线程会被回收,当有新请求到来时候会创建新线程
    1
    2
    3
    4
    5
    6
    7
    8
    public Executor getExecutor(URL url) {
    String name = url.getParameter("threadname", "Dubbo");
    int cores = url.getParameter("corethreads", 0);
    int threads = url.getParameter("threads", 2147483647);
    int queues = url.getParameter("queues", 0);
    int alive = url.getParameter("alive", 60000);
    return new ThreadPoolExecutor(cores, threads, (long)alive, TimeUnit.MILLISECONDS, (BlockingQueue)(queues == 0 ? new SynchronousQueue() : (queues < 0 ? new LinkedBlockingQueue() : new LinkedBlockingQueue(queues))), new NamedThreadFactory(name, true), new AbortPolicyWithReport(name, url));
    }
    这里可以看到线程池的配置,核心是 0,最大线程数是 2147483647,保活时间是一分钟
    只是非常简略的介绍下,有兴趣可以自行阅读代码。
0%