互联网人应该了解的一个数据结构Bloom Filter

先贴上已经写得非常好的一篇文章:http://blog.csdn.net/jiaomeng/article/details/1495500

Bloom Filter是一个用于标记和查找标记的一种数据结构,因为设计精妙,也有人认为它是一种算法。

它设计的大体思路是通过hash值来对一个二进制位的存储“条”上打上标记,同样判断是否已经存在时,只需要通过位运算进行匹配判断便可。

这里存在三种误判:1是因为存储空间的限制(根据使用的具体业务量计算或大致判断得出)导致可能存在多个hash值映射到了同一个(或一批:一批是指采用了拆分的方式将一个hash映射到了多个点)位置,导致误判;2是上一点中的拆分式的映射,导致位置标记覆盖速度加快,就可能存在某个hash映射到的几个点刚好是其他多个不同hash标记的点的组合;3是因为hash碰撞而导致两个字符串在Bloom Filter中存在相互误判。

这三种误判是不能完全避免的,只能是优化它,所以提取这三点中的关键部分-hash值,对其进行优化。于是,我们需要根据多套hash算法,计算出多个hash,将这些hash值都做标记,验证是否存在时,则需要所有的hash值都通过。通过这种方式,我们能很大幅度的减小误判。

因为Bloom Filter是存在误判的,所以我们只能使用于一些性能要求较高,但对准确率要求不高的场景。例如,我们可以做一个随机抽题的功能,就可以用它来标记那些已经抽过的题。

SpringBoot集成Spring Data JPA以及读写分离

JPA是什么

JPA(Java Persistence API)是Sun官方提出的Java持久化规范,它为Java开发人员提供了一种对象/关联映射工具 来管理Java应用中的关系数据.它包括以下几方面的内容:

  • 1.ORM映射 支持xml和注解方式建立实体与表之间的映射.
  • 2.Java持久化API 定义了一些常用的CRUD接口,我们只需直接调用,而不需要考虑底层JDBC和SQL的细节.
  • 3.JPQL查询语言 这是持久化操作中很重要的一个方面,通过面向对象而非面向数据库的查询语言查询数据,避免程序的SQL语句紧密耦合.

    在工作中,我们都会用到ORM技术,比如Hibernate,JOOQ等,根据需求的不同,我们会采用不同的ORM框架,当我们需要 更换ORM框架来满足我们的需求时,由于不同ORM框架的实现,使用方式的区别以及各自为营,我们往往需要对代码进行重构.JPA的 出现就是为了解决这个问题,JPA充分吸收了现有一些ORM框架的优点,具有易于使用,伸缩性强等优点,为ORM技术提供了一套标准的 接口用来整合不同的ORM框架.

Hibernate对JPA的实现

JPA本身并不做具体的实现,而只是定义了一些接口规范,让其它ORM来具体的实现这些接口,就目前来说,对JPA规范实现最好的就是 Hibernate了.这里提一下Mybatis,Mybatis并没有实现JPA规范,它本身也不能算做一个真正的ORM框架.

Spring Data JPA是什么

Spring Data JPA只是Spring Data框架的一个模块,可以极大的简化JPA的使用,Spring Data JPA强大的地方还在于能够简化我们 对持久层业务逻辑的开发,通过规范持久层方法的名称,通过名称来判断需要实现什么业务逻辑,我们机会可以在不写一句sql,不做任何dao层 逻辑的情况下完成我们绝大部分的开发,当然,对于一些复杂的,性能要求高的查询,Spring Data JPA一样支持我们使用原生的sql.

在这里我们不过多的去介绍JPA以及Spring Data JPA,主要还是与SpringBoot集成的一些细节以及示例.

引入依赖

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-jpa -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

我们引入这个依赖后,发现也引入了Hibernate的包,这是现在一种默认的做法,Hibernate已经被作为JPA规范的最好实现了.

配置我们的数据源以及JPA(Hibernate)

#配置模板
#https://docs.spring.io/spring-boot/docs/1.4.0.RELEASE/reference/html/common-application-properties.html

#数据源
spring.datasource.druid.write.url=jdbc:mysql://localhost:3306/jpa
spring.datasource.druid.write.username=root
spring.datasource.druid.write.password=1
spring.datasource.druid.write.driver-class-name=com.mysql.jdbc.Driver

spring.datasource.druid.read.url=jdbc:mysql://localhost:3306/jpa
spring.datasource.druid.read.username=root
spring.datasource.druid.read.password=1
spring.datasource.druid.read.driver-class-name=com.mysql.jdbc.Driver

#JPA (JpaBaseConfiguration, HibernateJpaAutoConfiguration)
spring.jpa.database-platform=org.hibernate.dialect.MySQL5Dialect
spring.jpa.database=mysql
spring.jpa.generate-ddl=true
#就是hibernate.hbm2ddl.auto,具体说明可以看README
spring.jpa.hibernate.ddl-auto=update
#通过方法名解析sql的策略
spring.jpa.hibernate.naming-strategy=org.hibernate.cfg.DefaultComponentSafeNamingStrategy
spring.jpa.show-sql=true
#spring.jpa.properties.*
#spring.jpa.properties.hibernate.hbm2ddl.auto=update
#spring.jpa.properties.hibernate.show_sql=true
#spring.jpa.properties.hibernate.use-new-id-generator-mappings=true

druid数据源注入

@Configuration
public class DruidDataSourceConfig {
    /**
     * DataSource 配置
     * @return
     */
    @ConfigurationProperties(prefix = "spring.datasource.druid.read")
    @Bean(name = "readDruidDataSource")
    public DataSource readDruidDataSource() {
        return new DruidDataSource();
    }


    /**
     * DataSource 配置
     * @return
     */
    @ConfigurationProperties(prefix = "spring.datasource.druid.write")
    @Bean(name = "writeDruidDataSource")
    @Primary
    public DataSource writeDruidDataSource() {
        return new DruidDataSource();
    }
}

EntityManagerFactory实例注入

EntityManagerFactory类似于Hibernate的SessionFactory,mybatis的SqlSessionFactory 总之,在执行操作之前,我们总要获取一个EntityManager,这就类似于Hibernate的Session, mybatis的sqlSession. 注入EntityManagerFactory有两种方式,一种是直接注入EntityManagerFactory,另一种是通过 LocalContainerEntityManagerFactoryBean来间接注入.虽说这两种方法都是基于 LocalContainerEntityManagerFactoryBean的,但是在配置上还是有一些区别.

  • 1.直接注入EntityManagerFactory

配置:通过spring.jpa.properties.*来配置Hibernate的属性

spring.jpa.properties.hibernate.hbm2ddl.auto=update
spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.use-new-id-generator-mappings=true
@Configuration
@EnableJpaRepositories(value = "com.lc.springBoot.jpa.repository",
                        entityManagerFactoryRef = "writeEntityManagerFactory",
                        transactionManagerRef="writeTransactionManager")
public class WriteDataSourceConfig {

    @Autowired
    JpaProperties jpaProperties;

    @Autowired
    @Qualifier("writeDruidDataSource")
    private DataSource writeDruidDataSource;

    /**
     * EntityManagerFactory类似于Hibernate的SessionFactory,mybatis的SqlSessionFactory
     * 总之,在执行操作之前,我们总要获取一个EntityManager,这就类似于Hibernate的Session,
     * mybatis的sqlSession.
     * @return
     */
    @Bean(name = "writeEntityManagerFactory")
    @Primary
    public EntityManagerFactory writeEntityManagerFactory() {
        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
        factory.setJpaVendorAdapter(vendorAdapter);
        factory.setPackagesToScan("com.lc.springBoot.jpa.entity");
        factory.setDataSource(writeDruidDataSource);//数据源

        factory.setJpaPropertyMap(jpaProperties.getProperties());
        factory.afterPropertiesSet();//在完成了其它所有相关的配置加载以及属性设置后,才初始化
        return factory.getObject();
    }

    /**
     * 配置事物管理器
     * @return
     */
    @Bean(name = "writeTransactionManager")
    @Primary
    public PlatformTransactionManager writeTransactionManager() {
        JpaTransactionManager jpaTransactionManager = new JpaTransactionManager();
        jpaTransactionManager.setEntityManagerFactory(this.writeEntityManagerFactory());
        return jpaTransactionManager;
    }
}
  • 2.先注入LocalContainerEntityManagerFactoryBean,再获取EntityManagerFactory

配置:

spring.jpa.database-platform=org.hibernate.dialect.MySQL5Dialect
spring.jpa.database=mysql
spring.jpa.generate-ddl=true
#就是hibernate.hbm2ddl.auto,具体说明可以看README
spring.jpa.hibernate.ddl-auto=update
#通过方法名解析sql的策略,具体说明可以看README,这里就不配置了
spring.jpa.hibernate.naming-strategy=org.hibernate.cfg.DefaultComponentSafeNamingStrategy
spring.jpa.show-sql=true
@Configuration
@EnableJpaRepositories(value = "com.lc.springBoot.jpa.repository",
        entityManagerFactoryRef = "writeEntityManagerFactory",
        transactionManagerRef = "writeTransactionManager")
public class WriteDataSourceConfig1 {

    @Autowired
    JpaProperties jpaProperties;

    @Autowired
    @Qualifier("writeDruidDataSource")
    private DataSource writeDruidDataSource;

    /**
     * 我们通过LocalContainerEntityManagerFactoryBean来获取EntityManagerFactory实例
     * @return
     */
    @Bean(name = "writeEntityManagerFactoryBean")
    @Primary
    public LocalContainerEntityManagerFactoryBean writeEntityManagerFactoryBean(EntityManagerFactoryBuilder builder) {
        return builder
                .dataSource(writeDruidDataSource)
                .properties(jpaProperties.getProperties())
                .packages("com.lc.springBoot.jpa.entity") //设置实体类所在位置
                .persistenceUnit("writePersistenceUnit")
                .build();
        //.getObject();//不要在这里直接获取EntityManagerFactory
    }

    /**
     * EntityManagerFactory类似于Hibernate的SessionFactory,mybatis的SqlSessionFactory
     * 总之,在执行操作之前,我们总要获取一个EntityManager,这就类似于Hibernate的Session,
     * mybatis的sqlSession.
     * @param builder
     * @return
     */
    @Bean(name = "writeEntityManagerFactory")
    @Primary
    public EntityManagerFactory writeEntityManagerFactory(EntityManagerFactoryBuilder builder) {
        return this.writeEntityManagerFactoryBean(builder).getObject();
    }

    /**
     * 配置事物管理器
     * @return
     */
    @Bean(name = "writeTransactionManager")
    @Primary
    public PlatformTransactionManager writeTransactionManager(EntityManagerFactoryBuilder builder) {
        return new JpaTransactionManager(writeEntityManagerFactory(builder));
    }
}

对于这个配置

   @Bean(name = "writeEntityManagerFactoryBean")
    @Primary
    public LocalContainerEntityManagerFactoryBean writeEntityManagerFactoryBean(EntityManagerFactoryBuilder builder) {
        return builder
                .dataSource(writeDruidDataSource)
                .properties(jpaProperties.getProperties())
                .packages("com.lc.springBoot.jpa.entity") //设置实体类所在位置
                .persistenceUnit("writePersistenceUnit")
                .build();
        //.getObject();//不要在这里直接获取EntityManagerFactory
    }

getObject()方法可以获取到EntityManagerFactory的实例,看似跟第一种没有什么区别,但是我们不能直接用 getObject(),不然会获取不到,报空指针异常.

读写分离配置

自定义注入AbstractRoutingDataSource

@Configuration
public class DataSourceConfig {

    private final static String WRITE_DATASOURCE_KEY = "writeDruidDataSource";
    private final static String READ_DATASOURCE_KEY = "readDruidDataSource";

    /**
     * 注入AbstractRoutingDataSource
     * @param readDruidDataSource
     * @param writeDruidDataSource
     * @return
     * @throws Exception
     */
    @Bean
    public AbstractRoutingDataSource routingDataSource(
            @Qualifier(READ_DATASOURCE_KEY) DataSource readDruidDataSource,
            @Qualifier(WRITE_DATASOURCE_KEY) DataSource writeDruidDataSource
    ) throws Exception {
        DynamicDataSource dataSource = new DynamicDataSource();

        Map<Object, Object> targetDataSources = new HashMap();
        targetDataSources.put(WRITE_DATASOURCE_KEY, writeDruidDataSource);
        targetDataSources.put(READ_DATASOURCE_KEY, readDruidDataSource);
        dataSource.setTargetDataSources(targetDataSources);
        dataSource.setDefaultTargetDataSource(writeDruidDataSource);
        return dataSource;
    }
}

自定义注解

    @Target({ElementType.METHOD, ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface TargetDataSource {
        String dataSource() default "";//数据源
    }

使用ThreadLocal使数据源与线程绑定

    public class DynamicDataSourceHolder {
        //使用ThreadLocal把数据源与当前线程绑定
        private static final ThreadLocal<String> dataSources = new ThreadLocal<String>();

        public static void setDataSource(String dataSourceName) {
            dataSources.set(dataSourceName);
        }

        public static String getDataSource() {
            return (String) dataSources.get();
        }

        public static void clearDataSource() {
            dataSources.remove();
        }
    }
    public class DynamicDataSource extends AbstractRoutingDataSource {
        @Override
        protected Object determineCurrentLookupKey() {

            //可以做一个简单的负载均衡策略
            String lookupKey = DynamicDataSourceHolder.getDataSource();
            System.out.println("------------lookupKey---------"+lookupKey);

            return lookupKey;
        }
    }

定义切面

    @Aspect
    @Component
    public class DynamicDataSourceAspect {
        @Around("execution(public * com.lc.springBoot.jpa.service..*.*(..))")
        public Object around(ProceedingJoinPoint pjp) throws Throwable {
            MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
            Method targetMethod = methodSignature.getMethod();
            if (targetMethod.isAnnotationPresent(TargetDataSource.class)) {
                String targetDataSource = targetMethod.getAnnotation(TargetDataSource.class).dataSource();
                System.out.println("----------数据源是:" + targetDataSource + "------");
                DynamicDataSourceHolder.setDataSource(targetDataSource);
            }
            Object result = pjp.proceed();//执行方法
            DynamicDataSourceHolder.clearDataSource();

            return result;
        }
    }

微信扫描公众号二维码,用户的网站账号自动登陆网站

 

微信公众号是在2012年8月份正式上线的。很荣幸,我在2013年底开始接触到了它并自己学习摸索开发了一些简单的功能,比如自动回复、自定义菜单。自动回复的时候如果是英文单词我还会帮用户翻译,类似于一个英语词典。最开始就是照着简洁的官方文档来做,慢慢摸索。不过后来接触多了慢慢发现,公众号的架构与开发者逻辑是很严谨也很安全的。举个简单的例子,比如要开发自定义菜单功能,需要用秘钥A和秘钥B去拿秘钥C,在后台管理里配置安全域名后,才可以用秘钥C用特定的方式去访问自定义菜单的接口。微信还有一个开放平台,我也使用过,那个就更复杂一点。

 

切入正题,后来换了一家公司,他们正好也会做一些公众号的业务,我就帮他们的网站做了一个个性的登录功能—-公众号扫码登录。这个登录不是“授权登录”,而是先把公众号与网站上自己的账号做绑定,然后网站的登录处选择公众号扫码登录,用户如果未关注则需要先关注公众号,然后绑定账号,如果已经关注且绑定了,扫码后公众号里则回复登录提示,点击后网站直接登录已经绑定的账号。关键点就在这个绑定的网站账号上,如果是授权登录,公众号就可以直接获取你的用户信息并登录,而扫码登录需要知道你在哪台电脑登录、让谁登录、何时登录。

下面是简易的技术流程图:

1

*角色:网站(web)、公众号项目(mobile)、用户(user)
登陆步骤:

1.网站在约定的场景id随机数里生成带参数二维码,供用户扫码(生成二维码以后网站不断轮询)
2.用户未关注时,让他关注公众号,并绑定自己的网站账号;已关注时则判断是否绑定账号,如果未绑定,跳转到绑定页面

3.已关注并绑定时直接回复消息“点击登陆网站”,用户点击登陆按钮,修改状态state=1(默认是0)
4.当网站查询到用户的登陆状态变化后(state=1),立刻返回给网站前端账号信息(同时恢复状态state=0),未查询到则返回-1(账号信息RC4加密)
5.当网站收到接口返回的账号信息后立刻将该账号登陆

Electron – 创建跨平台的桌面客户的应用程序

Electron – 创建跨平台的桌面客户的应用程序
Electron 框架的前身是 Atom Shell,可以让你写使用 JavaScript,HTML 和 CSS 构建跨平台的桌面应用程序。它是基于io.js 和 Chromium 开源项目,并用于在 Atom 编辑器中。Electron 是开源的,由 GitHub 维护,有一个活跃的社区。最重要的是,Electron 应用服务构建和运行在 Mac,Windows 和 Linux。
Electron 结合了 Chromium、Node.js 和用于调用操作系统本地功能的 API(如打开文件窗口、通知、图标等)。
![title](https://leanote.com/api/file/getImage?fileId=58f4ce1aab64415134004063)

开发体验如何?
基于 Electron 的开发,就好像开发一个网页一样,而且能够无缝地 使用 Node。或者说:就好像构建一个 Node app,并通过 HTML 和 CSS 构建界面。另外,你只需为一个浏览器(最新的 Chrome)进行设计。

如何运用Electron开发桌面应用?

Electron是一个通过node服务搭建web的app。
Electron中的app模块主宰着这个应用的生命周期,创建可视window窗口需要等app模块确认准备就绪。
BrowserWindow模块 控制着可视窗口的创建,所有被创建出的窗口都是BrowserWindow模块的 new BrowserWindow(WINDOW_BOUNDS)方法来实例化的,通过实例的方法loadURL(HTML_URL)方法来加载要渲染的页面。这样一个简单的桌面应用便生成了。
简例如下:

app.on(‘ready’, () => {
let mainWindow = new BrowserWindow(win_para);
mainWindow.loadURL(config.mainUrl);
mainWindow.on(‘closed’, () => {
mainWindow = null;
app.quit()
});
mainWindow.on(‘ready-to-show’, () => {
mainWindow.show()
mainWindow.focus()
});
return mainWindow;
});

Electron 的进程:
Electron 有两种进程:主进程和渲染进程。有些模块会工作在其中一个进程上,而有些会在两个进程之上。主进程多地充当幕后角色,而渲染进程则是应用的每个窗口(window)。因为爱学习授课客户端的需求,所以目前的桌面应用暂时应用了一个主进程和一个渲染进程(后台进程,主要支持下载)
主进程:
主进程,通常通常是类似命名为main.js主文件所创建,该文件是每个 Electron 应用的入口。它控制了应用的生命周期(从打开到关闭)。它能调用原生元素和创建新的(多个)渲染进程,而且整个 Node API 是内置其中的。主进程中的IpcMain模块是与渲染进程通讯的主要手段
![title](https://leanote.com/api/file/getImage?fileId=58f4dac3ab64415134004173)
渲染进程:
渲染进程,可以是一个或多个创建出来的window模块,这些进程不被允许直接操控GUI,通常需要IpcRenderer模块与IpcMain模块进行通讯,也可以通过remote模块间接操控主进程,但是确实不提倡,也是不安全的。
![title](https://leanote.com/api/file/getImage?fileId=58f4daceab64414e1d004216)
简例:

/**
* 主进程 监听 自定义事件: 下载续传
*/
ipcMain.on(‘download-resume-hanlder’, (ipcResumeEvent, options) => {
if (_QUEUE.queueList[options].canResume()) {
_QUEUE.queueList[options].resume();
// 通讯事件通过sender.send(para)方法与渲染进程异步通讯
ipcResumeEvent.sender.send(‘download-has-resumed’, _QUEUE.queueList, _QUEUE.queueList.length);
} else {
IpcResumeEvent.sender.send(‘download-has-destroyed’);
}
});
/**
* 渲染进程 监听 自定义事件: 下载续传
*/
ipcRenderer.on(‘download-has-resumed’, (ipcResumeEvent, fileQueue, times) => {
//code to operate js
});
/**
* 渲染进程 发射 自定义事件: ‘download-has-paused’
*/
ipcRenderer.send(‘download-has-paused’, msg);

![title](https://leanote.com/api/file/getImage?fileId=58f4e25cab644151340041f4)

模块:
Electron 的 API 是根据它们的功能进行分组。例如:dialog 模块拥有所有原生 dialog 的 API,如打开文件、保存文件和弹窗。
授课客户端暂时用到的模块主要如下:
app, browserWinodw, ipcMain, ipcRender, remote, dialog, session, webContents, shell, fs, path。
app 模块是为了控制整个应用的生命周期设计的。
BrowserWindow 模块让你有创建一个浏览器窗口的权力。
ipcMain 模块是类 EventEmitter 的实例.当在主进程中使用它的时候,它控制着由渲染进程(web page)发送过来的异步或同步消息.从渲染进程发送过来的消息将触发事件。
ipcRenderer 模块是一个 EventEmitter 类的实例. 它提供了有限的方法,你可以从渲染进程向主进程发送同步或异步消息。 也可以收到主进程的响应。
remote 模块提供了一种在渲染进程(网页)和主进程之间进行进程间通讯(IPC)的简便途径。
dialog 模块提供了api来展示原生的系统对话框,例如打开文件框,alert框,所以web应用可以给用户带来跟系统应用相同的体验。
session 模块可以用来创建一个新的 Session 对象。主要应用于下载。
webContents 是一个 事件发出者。它负责渲染并控制网页,也是 BrowserWindow 对象的属性。
shell 模块提供了集成其他桌面客户端的关联功能。
fs 模块主要是文件读写,本地文件系统操作。
path 模块主要是文件在系统中的路径。

Electron使用一个 package.json 文件。该文件能定义哪个文件作为主进程,并因此让 Electron 知道从何启动你的应用。然后主进程能创建渲染进程,并能使用 IPC 让两者间进行消息传递。
![title](https://leanote.com/api/file/getImage?fileId=58f4e289ab644151340041f6)

以上是关于Elertron这个APP的简短介绍。下面是客户端的Two-Package Structure,及APP Package等方面的简介。

Two-Package Structure:

Two-Package Structure 是 pack 工具 electron-builder 给的约定,也是目前业界用的较多的方案。

![Two-Package Structure](https://leanote.com/api/file/getImage?fileId=58f4e9e7ab64415134004267)

为啥用 Two-Package Structure?

最大的好处是可以很好地分离开发依赖和生成环境依赖。开发依赖存 package.json ,生产依赖存 app/package.json ,这样在 pack 后交付给用户时就不会包含 webpack, mocha 等等的开发依赖了。

Electron 应用的打包

Electron 应用的打包 是应用了脚手架 electron-packager。首先将项目文件打包,然后应用electron-packager来打包electron应用.
可以用命令打包一个供windows使用的app: electron-packager ./app aixuexi –platform=win32 –arch=x64 –out=./dist –asar true –overwrite “,
也可以自行添加文件配置electron的打包参数 如:

building: {
‘app-version’: appPackage.version,
arch: [‘x64’, ‘ia32’], ia32, x64, armv7l, all
asar: false,
dir: path.join(__dirname, ‘/app’),
icon: path.join(__dirname, ‘app/icons/icon’),
ignore: /src|main.ejs|icons/,
out: path.join(__dirname, ‘/app/dist’),
overwrite: true,
platform: process.env.PLATFORM_TARGET || ‘all’,
name: appPackage.product
}

待续…

synchonized加锁的虚拟机实现

synchonized实现有两种方式,一种是直接加在方法上,对整个方法进行加锁。另一种是对方法内的某一个代码块进行加锁,那么这两种加锁方式在虚拟机内的实现是怎样的呢。

对方法直接进行加锁:方法级的同步时隐式的,即无须通过字节码指令来控制,它实现在方法调用和返回操作之间。虚拟机可以从方法常量池的方法表结构中的ACC_SYNCHRONIZED访问标志得知一个方法是否声明为同步方法。当方法调用时,调用指令将会检查方法的ACC_SYNCHRONIZED访问标志是否被设置,如果设置了,执行线程就要先成功持有管程,然后才能执行方法,最后当方法完成时释放管程(无论是否正常完成都会释放)。在方法执行期间,执行线程持有了管程,其他任何线程都无法再获取到同一个管程。如果一个同步方法执行期间抛出了异常,并且在方法内部无法处理此异常,那么这个方法所持有的管程将在异常抛到同步方法之外时自动释放。

对方法内部的某一个代码块进行加锁:java虚拟机的指令集中有monitorenter和monitorexit两条指令来支持synchronized关键字的语义,正确实现synchronized关键字需要javac 编译器和java虚拟机两者共同协作支持。在代码块执行开始的时候,将会调用monitorenter指令,当代码块结束时将调用monitorexit指令,编译器必须确保无论方法通过何种方式完成,方法中调用过的每条monitorenter指令都必须执行其对应的monitorexit指令,而无论这个方法是正常结束还是异常结束。为了保证在方法异常完成时monitorenter和monitorexit指令依然可以正确配对执行,编译器会自动产生一个异常处理器,这个异常处理器声明可处理所有异常,它的目的就是用来执行monitorexit指令。

html2canvas的那些事:如何生成高清canvas图片

前几天产品提出一个问题,教师端app绘制出的用于微信分享的排行榜图片像素比较差,部分老师不愿意分享。针对这个问题,我去尝试做了一些优化。

方案一:
原理如下:先画个2倍的所谓的渣像素的图片,然后缩小2倍显示。
代码如下:(最终生成base64)

        getBase64 (ref) {
             returnnewPromise ((resolve) => {
                   setTimeout(() =>{
                           var _canvas = document.createElement(“canvas”),
                                  w = ref.offsetWidth,
                                  h = ref.offsetHeight;
                           _canvas.width = w * 2;
                           _canvas.height = h * 2;
                           _canvas.style.width = w + “px”;
                           _canvas.style.height = h + “px”;
                           var context = _canvas.getContext(“2d”);
                           //然后将画布缩放,将图像放大两倍画到画布上
                           context.scale(2,2);
                           html2Canvas(ref, {
                                canvas: _canvas,
                                onrendered: (canvas) => {
                                       resolve(canvas.toDataURL(“image/png”));
                                }
                           });
                    });
               });
           }
但是遇到一个问题:部分安卓手机,比如华为P9,会崩掉,猜测是Hbuiler打开webview的内存限制,而oppo R9就可以绘制出高清的图片。由于不能适配所有手机,故没有采用这种方案。
方案二:

如果保存为JPEG图片,还可以通过第二个参数指定图片质量:

var imgsrc = myCanvas.toDataURL('image/jpeg', quality);

quality的取值范围为0.0-1.0,0.0代表图片质量最差,1.0代表图片质量最好。

但是,此方案需要客户端修改截取base64数据的位数,即客户端需要更新,暂时放弃此法,可作为以后参考用。

目前,优化搁浅,尚未找到合适的方案。后面我会尝试确认方案一中手机崩掉的具体原因,并尝试解决。网上很多人都是通过该方法解决canvas绘制图片的清晰度的问题的,证明此法应该是行得通的,可能是自己哪里配置/写法有问题。

如有好的意见或者方法,欢迎提出来。我也会持续跟进更新这个问题。。。

java 实现彩色图片转成黑白图片

因业务需求,前段时间做了彩色图片转为黑白图片。以下代码作为参考:

public static void changeImge(File img) {
try {
Image image = ImageIO.read(img);
int srcH = image.getHeight(null);
int srcW = image.getWidth(null);
BufferedImage bufferedImage = new BufferedImage(srcW, srcH,BufferedImage.TYPE_3BYTE_BGR);
bufferedImage.getGraphics().drawImage(image, 0,0, srcW, srcH, null);
bufferedImage=new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_GRAY),null).filter (bufferedImage,null);
FileOutputStream fos = new FileOutputStream(img);
JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(fos);
encoder.encode(bufferedImage);
fos.close();
} catch (IOException e) {
e.printStackTrace();
throw new IllegalStateException("图片转换出错!", e);
}
}

 

敏捷测试思路之初识

最初知道敏捷测试是源于公司采用敏捷开发模式,对于这个模式有了最初的认识,简单的说敏捷测试应该是一套解决方案、一类测试操作与管理的框架、一组实践或由一定顺序的测试活动构成的测试流程。
敏捷测试的特点:
1.更关注被测系统的本身而不是测试文档
2.测试的参与人员不仅仅只是测试人员,而是包含整个团队
3.敏捷测试强调面对面的沟通、协作,强调团队的责任
4.自动化测试在敏捷测试中占有绝对的主导地位

敏捷测试中的关键过程:
在一个sprint中,测试人员的工作内容主要分为五个部分:user story分析、测试用例设计开发、测试执行和分析、测试持续集成、回归测试。这五个部分的工作均要持续到sprint结束,只是启动时刻有早有晚。
1.user story分析工作:敏捷测试是不断确认客户的需求得以圆满实现,因此对用户需求的分析、理解需要一直持续下去,发现有偏差及时纠正,及时设置合理的验收点、测试项。
2.Testcase Develop工作:设计测试用例,完成测试代码的开发、测试数据的准备,并及时与开发人员沟通软件接口,确保测试代码能够成功驱动业务代码
3.Testing & Analysing工作:执行测试,统计测试覆盖率,分析测试结果,若发现bug,及时沟通,并协助定位bug。
4.Continuous Integration工作:将测试代码进行集成,以保证当前功能若被后续集成代码污染是能够及时得到报警,不断地完善软件产品的功能基线
5.RegressionTesting工作:在完成全部user story后,对所有代码进行完整的回归测试,对所有bug修复情况进行确认。

工程师成长之路:工程师如何突破瓶颈期?

工程师职业发展的四个阶段

在我看来,一个工程师的养成可以分为四个阶段:

新人期

  • 硬技能:理论转化实践
  • 软素质:养成基本的职业观

成熟期

  • 硬技能:积累业务知识,积累技术知识
  • 软素质:固化职业观

发展期

  • 硬技能:明确细化的职业发展方向,深入研究「可能技术, 可能管理, 可能两者兼具」
  • 软素质:逐步总结并形成自己的方法论

事业期

  • 硬技能:释放自己,创造价值
  • 软素质:输出方法论,影响其他人

我自己目前徘徊在发展期,能看到的问题有限。另一方面,俗话说“三岁看大,七岁看老”,很大程度上,职业发展的前面两个阶段,已经决定了未来的职业发展路径。

所以这篇文章,主要针对新人期/成熟期的一些问题,进行阐述分析。

现象:成长越来越慢

在我接触过的工程师中,这是一个非常普遍的现象。并且这个现象多发于工作 3 年以上的同学身上。

这张图,是我理解的职业发展模型。

  • 首先,职业发展过程是阶梯式的,一个台阶一个台阶往上走,而不是线性提升。
  • 其次,职业发展前期,上台阶所需时间更短,即:成长速度更快。

是什么造就了这种现象?

我认为造成这种现象,主要有两方面原因:

  • 随着职业等级提升,所需基本素质越来越抽象:没有及时从“积累具体知识”调整到“提升抽象思维”;
  • 随着职业等级提升,所需的驱动力越来越转向内在:没有及时从“被动成长”调整到“主动成长”;

首先,没有及时从“积累具体知识”调整到“提升抽象思维”

回到职业发展的四个阶段,看看各阶段职级都在做什么样的事情:

  • 新人期:完成一项具体的任务。
  • 成熟期:完成一个项目的整体把控。
  • 发展期:引领一个专业方向的发展。
  • 事业期:引领一个或多个专业方向的团队,将技能转化为实际生产力。

很容易看出,随着职业的发展,需要具备的基本素质越来越抽象。

结合上面的职业发展模型图,一个人的成长,分为两个方向:

  • 一方面是横向的同级别的知识/经验积累
  • 另一方面,是更高层级的抽象思考

我认为这就是造成这个现象的第一个关键点。

很多人都只关注了同级别的知识积累,而一个人能 Hold 的同级别知识总量是有限的。

所以,工作两三年后,相关的东西就积累的差不多了,你很难找到一些自己“不会”的东西。

那么问题就来了:

  • 感觉成长越来越慢了
  • 不知道该学什么了
  • 是不是该考虑做做管理了
  • ……

建议:提前用下一个职业等级的思路(或者说超越当下的视角)去思考问题。

其次,没有及时从“被动成长”调整到“主动成长”

还是从职业发展的四个阶段来看,每个阶段是什么在驱动你做事情?

  • 新人期:按照领导的安排,1,2,3 个步骤,完成一件事情。
  • 成熟期:按照领导的安排,把一件大的事情拆解到具体的事情并独立完成;或者和其他人合作完成。
  • 发展期:在完成领导安排的既定事务的同时,还要从专业角度主动发现并解决问题。
  • 事业期:需要自主的从公司或产品战略出发,全方位找到要解决的问题。

可以看出,随着职业发展,驱动力越来越收敛到自身。

  • 前两个阶段,更多的是公司的事情驱动着你前进,通过公司的驱动力,被动的获取成长。
  • 后两个阶段,更多则需要自己主动驱动事情往前走,通过这种方式,主动获取成长。

所以从这个角度讲,一个人要想获得更好的职业发展,一定要很好的去培养自己的主动性,一方面主动获取成长,另一方面,还要在公司中主动的承担更多的问题,这样才能够获取到更多思考的机会。

如何避免走向平庸?

上面所提的现象,如果不注意去克服,就很容易走向平庸。

在上面的分析中,有一些建议,这里要分享的是 3 点具体的总结。

学习方法

我对学习方法有一个抽象的总结,是在阅读了『如何阅读一本书』这本书之后,在一次和团队成员的 One-On-One 中总结出来的。

我认为,知识的传递和网络通信模型有异曲同工之妙。

从知识的分享者角度来看,比如,一本结构良好的书,应该有几个明确的、抽象的基本观点。 其他所有的内容,都是围绕着这些基本观点,一层一层将问题具象化,帮助读者逐层的理解最终那几个抽象的观点。

(Ps. 并不适用所有书籍,比如小说类书籍,可能抽象程度更高,更加隐晦)

从读者角度来看,一定是先理解了那些具体的实例,然后才能逐步 Get 到原作者想要表达的抽象观点。 通过这个过程,吸取原作者分享的知识。

所以,在技术领域的学习方面,我觉得有两点特别关键:

  • 从实践出发,因为有效的信息传递大部分都是通过这层完成。 所以大量的实践才能让你 Get 到原作者直接传递的更多信息。
  • 以抽象的思想为目标,所有实践的目的,都是为了尽可能多的 Get 到原作者表达的抽象思想,只有理解了抽象的思想,才能用它指导自己解决大量同类问题,甚至对原作者提出的概念进行延伸,形成自己的方法论。

工作方法

从工作的角度去看一个问题,我认为分 3 个阶段:

  1. 接收需求
  2. 执行需求
  3. 完成需求

我见过相当一部分工程师,其实只有“执行”这一个阶段。

拿到一个需求,草草看几眼 MRD,然后就开始编码,这是对自己不负责,对公司不负责,对其他同事不负责。

对一个系统的设计和实现之间的权衡,我有这样一个观点:

思考 1 – 3 年的变数,设计 6 – 12 月的架构,只实现当下需要的。

所以,我认为,作为一名优秀的工程师,接到一份需求的时候,首先应该做的就是认真阅读需求,从产品角度思考这个需求背后的逻辑。只有真正理解一个需求之后,才能够考虑到未来 1 – 3 年可能存在的变数,才可能设计出符合 6-12 月业务发展所需的架构。

再来看经常被忽视的“完成需求”阶段。 通常,工程师在整个项目的研发过程中,只是其中一环,要使得整个流程能够顺畅的运转起来,每一环就必须都及时的通知到下一个环节。

上面这是站在公司角度考虑的。

另一方面,站在个人角度而言。对自己做过的事情,复盘总结往往是最佳的成长机会。因为当你完成一件事情之后,你就对事情的全盘有了了解。这个时候,回过头去看这件事情,就是现成的“站在更高视角看问题”的机会。

技能是什么?

技能 = 技术 + 能力。

技术,是由你的知识体系外加你的经验构成的。它能够通过量的积累直接获得提升。不过它只能用来解决已知的问题。

能力,是由你的抽象思维能力,你已有的方法论构成的。它不能直接通过量的积累获得。一方面,它会由天生的智商情商等决定一部分;另一方面,要在量化积累的基础上,经过深度的思考,找寻问题的本质,引发质变才能获得。能力可以被用来解决未知问题。

技术和能力是相辅相成的。分享这一点,是因为在我看来,如果能够认清两者的区别,对两方面的应用和提升就会更加得心应手。

新人期常见问题分析

下面列举三个在我平时和团队成员 One-On-One 过程中,经常碰到的软素质方面的问题,以及我认为比较好的解决方案,供参考。

如何处理并行任务?

有没有碰到过你的 Leader 在你上一件事情没有完成的时候,给你就分配了下一件事情的情况?同时又来了几个来自其他同事的需求。

在新人期的工程师,主动性方面通常并不会差,他们希望快点把所有问题解决,但又无法同时都解决掉。

所以,“好烦啊”,焦虑感就产生了。

一般而言,很多新人都会使用下面两种方式之一进行处理:

  • FIFO:谁先找我,就先把谁的问题处理完,然后再去看后面的事情。
  • LIFO:谁现在来找我,我就处理谁的问题。

哪种方式好呢? 都不好 !

首选方案,是建议大家查阅时间管理相关资料,找到适合自己的解决方案。

下面是我建议的一种解决方案(GTD 工作法):

  • 全身心投入当下正在处理的事情
  • 如果有新的需求过来,2 分钟内,判断出这件事情的重要性。
    • 重要且十万火急的事情,立即处理
    • 否则,扔进自己的 TODO List,设定一个时间提醒自己再进行关注。
  • 完成手头的事情后,从你的 TODO List 整理,找出下一件最重要的事情。
  • 如果有多项重要紧急的事情,无法独自完成,将问题暴露给 Leader。
  • 每天早晨看看自己的 TODO List,对当天一定要处理的事情有明确的认知
  • 每周 review 回顾自己做过的事情。

看看上面方法的核心是什么? 让所有的事情,掌握在你的 TODO List 中,对它们形成一种明确的管理。 当一切都了然于胸的时候,你自然知道该怎么为这些事情分配自己的时间片。

无法找到当下最重要的事情?

上一个问题的建议方案中提到,我们需要找到最重要的事情。那如何找到最重要的事情呢?

从我以往的经验来看,在新人期和成熟期碰到的事情中,只需要问一个问题,就能够判断大部分事情是否重要。

“如果这件事现在不做,会有什么后果?”

比如:

  • 会导致这个版本不能如期发布
  • 会导致在线服务故障

不过,通常我们得到的答案都是“没什么影响”。

新人很容易陷入无法判断优先级的困境,这是人类天性的弱点。 我们会对未知产生恐惧,进而产生焦虑,进入一个恶性的循环状态。

解除这种状态的思路,就是让未知变成已知。

如何准确评估排期?

经理安排下来一个项目,询问排期,这个时候,新人很容易慌乱,不知道该如何评估。

这个问题的产生,和上面两个问题,是一样的。因为对新人而言,并不了解一个项目到底需要多少时间才能完成。

那就束手无策了吗?

试想,“读完『钢铁是怎样炼成的』需要多少时间?”,面对这个问题,你怎么解答?

思路是不是这样?

  • 看这本书有多少页?
  • 经验告诉自己,读一页需要 3 分钟。
  • 然后计算出需要的总时间。

软件开发的排期预估,是同样的思想。

大事化小。

大事是不明确的,无法直接给出工作量预估,那么把它拆解到你能预估的小的事情就可以了。

同时,在你评估过工作量的事情,最终完成之后,去复盘,看自己的评估是否准确,如果有偏差,思考问题出在哪里,长此以往,一些大事你也就可以直接评估出工作量了。

总结

通过上面三个问题,可以看出一些共性,三个问题,都是通过把抽象的、不明确的事务,拆分成具体的、明确的事务,使我们心里更加有谱。

如果你有这三个问题之外的其他问题,不妨也试试这种思路。

成熟期常见问题分析

成熟期,是工程师成长中的一个关键时期,迈过去这个坎儿,更多需要的是精神层面的东西。所以这里提到的成熟期的三个常见问题,都和“心”有关。

如何保持高速成长?

首先,请再思考下“现象:成长越来越慢”中,提到的造成这个现象的原因:

  • 没有及时从“积累具体知识”调整到“提升抽象思维”;
  • 没有及时从“被动成长”调整到“主动成长”

仔细研究这两个原因,都可以看做是在没有挑战性的状态,形成了舒适区造成的。

对大部分工程师而言,参加工作是第一次正式的和社会接触,基本正式脱离了“学习压力”所带来的约束。而新人期又可以“轻松的”(主要指心理压力方面)通过公司获取被动性的成长。

温水煮青蛙。大部分人都会被煮熟的。

请思考一个问题:

是不是你所学的 20% 就足以解决工作中 80% 的问题?

请再思考一个问题:

是不是工作中剩下 20% 的问题,Google 和咨询别人可以全部解决?

进入成熟期后,很快工作就很少,甚至不能给你的成长产生驱动力了。

剩下的就得靠自己。告诉自己两件事:“主动提升”,“主动思考下一职级的问题”。

我想通过这篇文章传递的核心价值就是这一点,希望能帮助一些工程师(尤其工作 3 年内的)意识到舒适区,并跳出舒适区。

如何使自己更自信?

这个问题源自一周前和一位团队成员的沟通。

我仔细的思考了自信的来源,然后我认为:自信是成就感驱动的。

成就感来自哪里?我认为可以从内部/外部两个角度去看:

内部:来自自己的认可

  • 自己的技术或能力得到提升,让自己觉得自己牛逼
  • 将技术或能力,应用到实际项目中,让自己相信自己真牛逼

外部:来自别人的认可

  • 将自己的实践,讲出来让别人认可,让别人觉得自己牛逼
  • 将自己的实践,抽象总结出来,帮助别人变得牛逼

每个人因为性格的不同,成就感获取的主要渠道可能不尽相同,但结合自己的情况,找到适合自己的成就感获取渠道,通过成就感建立自信,是一种有效的方法。

不过,自信和自大一线之隔,谨慎。

XX 同学已经月薪 30K 了,赶紧跳槽求加薪吧

现实的诱惑,也是容易让人迷茫的一个点,不过如果对一些基本面有正确的认识,可以帮助我们做出更加合理的决定。

首先,一个人的薪资,是由专业能力、软素质、行业经验、公司内经验、稀缺度、人脉、运气等等方方面面的因素共同决定的。

千万不要因为薪资的攀比而鲁莽的跳槽。

跳槽的确会大概率增加薪资,因为总有一些公司是到非常着急用人的时候才去招人的,他们被迫给出更高的工资;同时你上一家公司的薪水,会给你提供一定的背书。所以如果沟通表达能力好,理论上短期是可以通过跳槽,拿到超出当前能力的薪资水平。

但是,频繁跳槽会阻碍工程师的长线发展,一般而言,公司是不会把核心职位交给刚入职半年一年的人,如果你频繁跳槽,就注定长期处于一线。现在的市场行情下,天花板也就是 20 多 K 不超过 30K。

且不说钱的天花板,关键在于长期处于一线,你的斗志就会被消磨殆尽,从而缩短你的职业生涯。

因而,从长线发展来看,通过跳槽加薪,并不是一种可取的选择。

如果你想要高薪,首先让自己变得优秀,那么就应该静下心来,在一家公司,给自己定几个目标,达成之后再去考虑离开的事情,这样对大家都好。

总结

成熟期是整个职业生涯中最关键的时期,快的可能一两年就能走过去;慢的,可能整个职业生涯都定格在成熟期。

从上面分析中可以看到,这些问题最终都和“心”相关。

要想高速成长,就得克服掉温水中的舒适。

要想提高自信,还得思考成就感获取渠道这么虚头巴脑的问题。

干这么多,还不能跳槽加薪迎娶白富美。

所以要想迈过去这个坎儿,必须有一颗非常强大的内心。

 

最后总结

技能 = 技术 + 能力。技术和能力是相辅相成的

技术,是由你的知识体系外加你的经验构成的。它能够通过量的积累直接获得提升。不过它只能用来解决已知的问题。

能力,是由你的抽象思维能力,你已有的方法论构成的。它不能直接通过量的积累获得。一方面,它会由天生的智商情商等决定一部分;另一方面,要在量化积累的基础上,经过深度的思考,找寻问题的本质,引发质变才能获得。能力可以被用来解决未知问题。

养成良好的工作习惯,

每天早晨看看自己的 TODO List,对当天一定要处理的事情有明确的认知

每周 review 回顾自己做过的事情。

让所有的事情,掌握在你的 TODO List 中,对它们形成一种明确的管理。 当一切都了然于胸的时候,你自然知道该怎么为这些事情分配自己的时间片。通过把抽象的、不明确的事务,拆分成具体的、明确的事务,使我们心里更加有谱。