左耳听风 第四十二周

左耳听风 第四十二周

每周完成一个ARTS: 每周至少做一个 leetcode 的算法题、阅读并点评至少一篇英文技术文章、学习至少一个技术技巧、分享一篇有观点和思考的技术文章。(也就是 Algorithm、Review、Tip、Share 简称ARTS)

Algorithm

继续按顺序来完成「LeetCode」前 200 题,以下为个人题解:

LeetCode49. 字母异位词分组

LeetCode50. Pow(x, n)

review

Garbage Collection: How it’s done 「垃圾回收:它是怎么做的呢?」

在早期的编程语言:C 、C++ 中,例如数组这样的引用类型我们需要手动分配内存,并在使用完手动回收内存,产生了大量的重复操作:分配内存 —— 释放内存。

于是在高级语言中,引入了「垃圾回收机制」,帮助我们解决了内存分配与释放问题。例如:C#、JAVA 。

早期的「垃圾回收」采用的是「引用计数法」,对象创建时,引用计数 1,每多一个对象的引用 计数+1,同样,每减少一个引用,计数 -1。当引用计数为 0 时,该对象所分配的内存空间就可以被回收。

Object obj = new Object(); // 创建对象,计数 1
Object obj2 = obj; // 多一个对象的引用,计数+1 =2
obj2 = null; // 移除一个对象的引用,计数 -1 = 1
obj = null; // 再移除一个对象的引用,计数 -1 = 0,此时该对象内存可被回收

后来,在实际引用中,出现循环引用,计数无法被清除置 0,容易出现内存泄露情况,例如以下代码:A、B 都指向了 null,但是因为其内部成员变量互相应用,所以二者标记计数还是 1,但是已没有可以访问二者的方式,导致了内存泄露,无法正确回收。

class A{
    private B b;

    public void setB(B b){
        this.b = b;
    }
}
class B{
    private A a;

    public void setA(A a){
        this.a = a;
    }
}
public class Client {
    public static void main(String[] args) {
        A a = new A();
        B b = new B();
        a.setB(b);
        b.setA(a);
        a = null;
        b = null;
    }

}

于是「标记清除法」成为了更适合的选择,垃圾回收分为了「标记」、「清除」两个时期。

标记时期,垃圾回收器将所有还可使用的对象(引用可达 —— 存在强引用)标记。

清除时间,将所有未标记的对象内存进行回收。

但是「标记清除法」存在一个问题:被标记的内存散列分布在堆中,当创建的对象过多,会产生许多小块的内存,而缺乏足够大的连续内存存储新的内存空间。

于是垃圾回收中,增加了一个「压缩」事情,将原有散列分布的内存(依旧存活的对象)压缩到一块,将原本散列的小块内存整合成一大块完整内存。—— 「标记清除压缩法

但是这样做似乎又有点麻烦,我们不知道什么时候压缩比较合适,于是「标记复制法」出现了,将内存分为两块,每次内存都分配在同一块区域,当该区域内存分配结束(内存用完),触发垃圾回收,将所有标记可用的对象内存复制到另一款未使用过的内存区,然后清除之前存储对象的内存区,下一次内存分配完时重复操作。

在 Java 中采用的是「分代垃圾回收」,将堆内存分为「新生代」、「老年代」两个区域,新创建的对象放入新生代,当新生代内存分完时触发垃圾回收,将新生代被标记对象放入老年代,清楚「新生代」未被标记的对象。

Tip

Java 1.7 版本之前,try catch finlly 中包含了多个资源需要循环嵌套,即影响代码可读性,也存在诸多问题,很难正确释放资源,最重要的是出现异常时,内嵌的异常很容易被吞,导致程序出错难以调试。

传统写法:

public static void main(String[] args) throws IOException {
        InputStream in = new FileInputStream("C:\\");;
        try {
            OutputStream os = new FileOutputStream("C:\\");
            try {
                byte[] buf = new byte[1024];
                int n;
                while ((n = in.read(buf) )>= 0) {
                    os.write(buf, 0, n); // 写入
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                os.close(); // 关闭资源
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            in.close();
        }
    }

在 1.7 版本以后,官方推出了 try with resource 为推荐的释放资源的异常处理。

public static void main(String[] args) throws IOException {
        try (InputStream in = new FileInputStream("C:\\");
             OutputStream os = new FileOutputStream("C:\\");) {
            byte[] buf = new byte[1024];
            int n;
            while ((n = in.read(buf)) >= 0) {
                os.write(buf, 0, n); // 写入
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

相比传统写法,try with resource,写法更为简洁易懂,也更容易诊断,将 Java 开发人员从繁琐的资源正确释放中释放,而且提高了代码的可靠性,希望大家在日常使用中,注意传统写法的替换。

share

分享一下最近整理的设计模式七原则:

1、Single Responsibility Principle。单一责任原则。即每一个类所负责的应当是某一个功能的实现,如果一个类中出现了超过 1 个的责任时,就应考虑分解这个类的责任。这也符合软件工程所提倡的「低耦合,高聚合」(耦合指模块内部应该职责同一,非共同职责的功能不应放在同一模块。聚合指的是模块之间,彼此相互联系)。

2、 Open-Closed Principle。开闭原则。即对扩展开放,对修改闭合。对于类、模块、方法等我们应该更多的提高其可拓展性,而非不断的因新数据的出现而修改。(对应着面向对象的「继承」)

3、Liskov Substitution Principle。里式替换原则。子类和父类之间应当对应 「is a」关系,即子类一定是父类中的成员,对于子类中都需要实现却彼此不同的功能,可以抽象到父类中形成抽象方法。

4、Interface Segregation Principle。接口分离原则。不应强制一个类实现接口中不使用的方法。即多个专用接口胜过一个聚合功能的端口。

5、Dependency Inversion Principle。依赖倒置原则。高层模块不应依赖底层模块,二者都应依赖于抽象。抽象不依赖于细节,细节依赖抽象。

6、Demeter principle。迪米特法则 —— 最少知道原则。只与最直接的朋友通信,被依赖类将逻辑封在内部,只对外提供 public 方法。

7、Composite Reuse Principle。合成复用原则。尽量使用 合成/聚合 的方式,而不是继承。
设计模式七原则

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值