Roc`s 随想录

诚信,认真,专注,踏实


  • 首页

  • 分类

  • 标签

  • 归档

  • 关于

JavaScript 中的回调函数

发表于 2015-04-07   |   分类于 知识记录   |     |   阅读次数

在Node.js中使用异步的方式读取一个文件:

/**
 * Created by chen on 1月14日.
 */
var fs = require('fs');
fs.readFile('readfile.js','utf-8',function(err,data){
    if(err){
        console.error(err);
    }else{
        console.log(data);
    }
    console.log('21212123.')
});
console.log('end.');

运行结果如下:

end.
/**
 * Created by chen on 1月14日.
 */
....

基于多线程的模型也有相应的解决方案,如轻量级线程(lightweight thread)等。事件驱动的单线程异步模型与多线程同步模型到底谁更好是一件非常有争议的事情,因为尽管消耗资源,后者的吞吐率并不比前者低。

Node.js也提供了同步读取文件的API:

/**
 * Created by chen on 1月14日.
 */

/***另一种的读取方式***/
//readfilesync.js

var fs = require('fs');
var data =  fs.readFileSync('index.js','utf-8');
console.log(data);
console.log('end.');

运行结果与前面不同,如下所示:

$ node readfilesync.js
Contents of the file.
end.

同步读取文件的方式比较容易理解,将文件名作为参数

同步式读取文件的方式比较容易理解,将文件名作为参数传入 fs.readFileSync 函数,阻塞等待读取完成后,将文件的内容作为函数的返回值赋给 data 变量,接下来控制台
输出 data 的值,最后输出 end. 。
异步式读取文件就稍微有些违反直觉了, end. 先被输出。要想理解结果,我们必须先知道在 Node.js 中,异步式 I/O 是通过回调函数来实现的。 fs.readFile 接收了三个参数,第一个是文件名,第二个是编码方式,第三个是一个函数,我们称这个函数为回调函数。
JavaScript 支持匿名的函数定义方式,譬如我们例子中回调函数的定义就是嵌套在 fs.readFile 的参数表中的。这种定义方式在 JavaScript 程序中极为普遍。

fs.readFile 调用时所做的工作只是将异步式 I/O 请求发送给了操作系统,然后立即返回并执行后面的语句,执行完以后进入事件循环监听事件。当 fs 接收到 I/O 请求完成的
事件时,事件循环会主动调用回调函数以完成后续工作。因此我们会先看到 end.,再看到
file.txt 文件的内容。

最初的想法

发表于 2015-04-07   |   分类于 毕业设计   |     |   阅读次数

###一开始的时候想法很是丰富,

  • 网络抓取网站更新数量分析用户习惯,目标有开源中国和腾讯QQ(行为分析)
  • 继续完善YoutubeAtChina,加入登陆系统(自娱自乐)
  • 基层社区政策讨论匿名举报(为了更好的中国)
  • 知识框架填充网站(优点是有全局性和指导意义)
  • 视频推荐系统

最后,决定做视频栏目汇总推荐系统。


###提问:

  1. 为什么要做这个网站呢?(意义和特点)

    我们大部分人有时候会很无聊,除了学习工作之外可能还有一些其他娱乐,比如看优酷爱奇艺等,看视频是一种比较好的娱乐放松方式。比如我就很喜欢飞碟说,报告老板,万万没想到,暴走漫画等网络节目,这种节目有一个特点,那就是定时更新,而且呢,还在不同的地方。

    那我就在想,可不可以做一个汇合呢?也就是我在一个页面就可以看到时间流排列的我喜欢的这些视频,显示顺序根据视频的更新时间排序。

    嗯,方便自己爱看这种视频的需求,而且不用去很多网站去找。

    这种视频的特点还有就是:每集连贯性不大,随时可以看。

  2. 还有什么值得优化和改进的地方?

    很自然而然的,自己用爽了,肯定愿意让更多的人一起爽。那就不得不实行多人的策略。因为每个人的口味不一样,所以视频流也会不一样。根据什么呢,让用户按照自己喜好添加节目好了。这是一种策略。

    另外,我们可以用另一种思路考虑,加入推荐系统,引导用户更容易的找到自己喜欢的东西。

  3. 那这个推荐系统的模型是什么?

    根据知乎的一个问题,我找到了一个模型,认为很适合。网易云音乐的歌单推荐算法是怎样的?

    里边主要提到了两个推荐模型,现取其中一个。其核心是数学中的“多维空间中两个向量夹角的余弦公式”。

    回答记录摘抄如下:

    第一类,以人为本,先找到与你相似的人,然后看看他们买了什么你没有买的东西。这类算法最经典的实现就是“多维空间中两个向量夹角的余弦公式”;
    第二类, 以物为本直接建立各商品之间的相似度关系矩阵。这类算法中最经典是’斜率=1’ (Slope One)。amazon发明了暴力简化的第二类算法,‘买了这个商品的人,也买了xxx’。

    我们先来看看第一类,最大的问题如何判断并量化两人的相似性,思路是这样 –
    例子:
    有3首歌放在那里,《最炫民族风》,《晴天》,《Hero》。
    A君,收藏了《最炫民族风》,而遇到《晴天》,《Hero》则总是跳过;
    B君,经常单曲循环《最炫民族风》,《晴天》会播放完,《Hero》则拉黑了
    C君,拉黑了《最炫民族风》,而《晴天》《Hero》都收藏了。

    我们都看出来了,A,B二位品味接近,C和他们很不一样。
    那么问题来了,说A,B相似,到底有多相似,如何量化?

    我们把三首歌想象成三维空间的三个维度,《最炫民族风》是x轴,《晴天》是y轴,《Hero》是z轴,对每首歌的喜欢程度即该维度上的坐标,
    并且对喜欢程度做量化(比如: 单曲循环=5, 分享=4, 收藏=3, 主动播放=2 , 听完=1, 跳过=-1 , 拉黑=-5 )。
    那么每个人的总体口味就是一个向量,A君是 (3,-1,-1),B君是(5,1,-5),C君是(-5,3,3)。 (抱歉我不会画立体图)
    我们可以用向量夹角的余弦值来表示两个向量的相似程度, 0度角(表示两人完全一致)的余弦是1, 180%角(表示两人截然相反)的余弦是-1。

    根据余弦公式, 夹角余弦 = 向量点积/ (向量长度的叉积) = ( x1x2 + y1y2 + z1z2) / ( 跟号(x1平方+y1平方+z1平方 ) x 跟号(x2平方+y2平方+z2平方 ) )
    可见 A君B君夹角的余弦是0.81 , A君C君夹角的余弦是 -0.97 ,公式诚不欺我也。
    以上是三维(三首歌)的情况,如法炮制N维N首歌的情况都是一样的。
    假设我们选取一百首种子歌曲,算出了各君之间的相似值,那么当我们发现A君还喜欢听的《小苹果》B君居然没听过,相信大家都知道该怎么和B君推荐了吧。

    好,回到视频节目这边。同样,也是以人为本,首先选择1000个热门用户,然后随机选取100个为seed用户,模版用户(之所以不是全部用户比较是为了降低运算量,同时热门用户也代表了目标用户趋势),然后在用户第一次登录进系统的时候首先会推荐热门单,然后带用户选择之后就可以根据该用户与100个seed用户之间的相似度来进行推荐了。

    用户热度:更多的是参与度。登录,关注节目数量,赞,踩,评论,看的视频数量等。

  1. 详细模型

    • 100个seed用户会更新
    • 100个seed用户的关注不会完全一致,相同者取活跃度大的
    • 用户可以添加视频列表,可以提出公共类,一点点实现增加支持的网站
    • 用户随时可以关注视频列表
    • 有最热视频榜单(被参与度最高)
    • …

目前在更视频列表:

  • 飞碟说
  • 飞碟一分钟
  • 报告老板
  • 万万没想到
  • 暴走大事件
  • 神街坊
  • 扑通扑通的良心
  • 大学搜有聊
  • 工大版《屌丝男士》
  • 天天向上
  • 我是歌手第3季
  • …

完结版处理

  • 推荐系统随机推荐
  • 其他板块

SpringBoot中读取YAML

发表于 2015-04-07   |   分类于 知识记录   |     |   阅读次数
  1. 在使用框架的时候,能用框架解决的使用框架解决。实在不能用框架解决的再考虑使用其他办法。
  1. 使用Spring注解填充信息的对象不可以自己实例化,应该使用注解(@Resource或这 @Autowired )实例化。

读取YAML步骤:

  1. 在application.yml文件中,加入需要注入的信息:

    my:
        servers:
            - dev.bar.com
            - foo.bar.com
    
  2. 在需要注解信息的Java文件中进行以下注解:

    @Component("config")
    @ConfigurationProperties(prefix = "my")
    public class Config {
        //@Value("my.servers") 
            //这一句完全不需要,无效。需要把该填充的数据放到`application.yml`即可!
        private List<String> servers = new ArrayList<String>();
        public List<String> getServers() {
            return this.servers;
        }
    }
    
  3. 在需要引用的地方使用Spring注入(@Resource或这 @Autowired)实例化。

Java5新特征之foreach语句使用总结

发表于 2015-04-07   |   分类于 Java基础   |     |   阅读次数

Java5新特征之foreach语句使用总结

java.util.List继承了java.lang.Iterable接口.

jdk api文档中是这样描述Iterable接口的:实现这个接口允许对象成为 “foreach“ 语句的目标。不过咋一看Iterable接口并没啥特别之处,只是定义了一个迭代器而已。

public interface Iterable<T> {

/**
* Returns an iterator over a set of elements of type T.
*
* @return an Iterator.
*/
Iterator&lt;T> iterator();

}

究竟是如何实现foreach的呢,想想可能是编译器做了优化,就看了下最终编译成的字节码

public class Iterable_eros {

List<String> strings;

public void display(){

for(String s : strings){

System.out.println(s);

}

}

}

相应的字节码为:

public void display (){
line0 : aload_0
getfield java.util.List my.lang.Iterable_eros.strings
invokeinterface java.util.Iterator java.util.List.iterator() 1
astore_2
goto line30
line13 : aload_2
invokeinterface java.lang.Object java.util.Iterator.next() 1
checkcast java.lang.String
astore_1
line23 : getstatic java.io.PrintStream java.lang.System.out
aload_1
line27 : invokevirtual void java.io.PrintStream.println(java.lang.String)
line30 : aload_2
invokeinterface boolean java.util.Iterator.hasNext() 1
ifne line13
line39 : return

JDK1.8 中 接口和抽象类的异同

发表于 2015-04-07   |   分类于 Java基础   |     |   阅读次数

在最新版本Java1.8中的变化里, Stephen Colebourne 告诉Hartmut Schlosser,“我认为最重要的语言上的改变不是Lambdas表达式,而是在接口上的静态和默认方法”。ColeBoune补充道,“默认方法功能的增加减少了许多使用抽象类的原因”。当我读到这里时,我意识到Colebourne是正确的,而且我通常用抽象类的许多场景可以通过JDK1.8的默认方法(default method)替换为JDK1.8接口。

在网上论坛和博客中讨论Java接口和抽象类异同的例子不胜枚举。这些讨论包括但不限于, JavaWorld ‘s Abstract classes vs. interfaces , StackOverflow ‘s When do I have to use interfaces instead of abstract classes? , Difference Between Interface and Abstract Class , 10 Abstract Class and Interface Interview Questions Answers in Java , 作为曾经有用的信息,许多现在已经过时了,可能会成为让Java体验更加混乱的部分。

在我思考Java1.8中接口和抽象类尚存在的不同,我决定去看看Java Tutorial关于这部分说过的信息。这个引导(Tutorial)已经更新到JDK 8 而且关于抽象方法和类的讨论有一个章节,称作 抽象类 vs 接口。这个章节指出在JDK 8 中接口和抽象类的异同点。着重强调的不同点是数据成员和方法的作用域(访问权限):

  • 抽象类允许非静态和非final的域,允许方法成为public,static和final
  • 所有的接口方法本质为public的。

在Java Tutorial接着列出了许多应该考虑抽象类的场景,和许多应该考虑接口的场景。不出意料,这些源于前面提到的不同、分歧,主要处理你是否需要域和方法成为private,protected,non-static,或者 not-final(偏向抽象类),或者你需要把重点放在类型而不是考虑实现的能力(偏向接口)。

因为Java语序一个类实现多个接口但只能继承一个类,接口可能被视为有利于多继承的实现。多亏JDK 8 的默认方法,这些接口甚至能提供默认的实现。

那么,一个很自然的问题是:当一个类实现了两个接口,而这两个接口描述了一个具有相同特点的默认方法,Java怎么处理。答案是,这会报一个编译错误。这是一个屏幕截图,NetBeans8上运行报的这个错误。

编译报错

结论

JDK 8 争议性的把抽象类的优点带给接口。造成的影响是今天有大量的抽象类通过默认方法可能被替换为接口。

翻译文章来源: Abstract Class Versus Interface in the JDK 8 Era

how-the-time-is-used

发表于 2015-04-07   |   分类于 生活与心情   |     |   阅读次数

流水账

昨天晚上,捣鼓微博和看codepiano的微博,remove掉了自己原先的微博记录,写了一个小脚本。

早晨9:23第一次醒,没注意,接着睡。

然后,过了一个钟头吧,又一次醒了,玩了会手机一直到12:00.期间下载了豆瓣,发了一条微博,刷了很多知乎。

思考

这期间我收获了什么?

貌似不多,大量的时间被浪费,截止到现在,估计浪费了2小时。2小时可以完成老师的任务了吧。

任务列表

斜体表示已经完成。

  • Python入门,重点是UDP和TCP的实现
  • 吴雄华老师的推荐系统
  • 理学院数学文化节
  • 我知科技的脚本执行shell实现,更改ip:重点有两个地方,inip.pp 和 ifcfg-eth0 修改

加油!

Java读取文本文件

发表于 2015-04-07   |   分类于 Java基础   |     |   阅读次数

Java IO系统里读写文件使用Reader和Writer两个抽象类,Reader中read()和close()方法都是抽象方法。Writer中 write(),flush()和close()方法为抽象方法。子类应该分别实现他们。
Java IO已经为我们提供了三个方便的Reader的实现类,FileReader,InputStreamReader和BufferedReader。其中最重要的类是InputStreamReader, 它是字节转换为字符的桥梁。你可以在构造器重指定编码的方式,如果不指定的话将采用底层操作系统的默认编码方式,例如GBK等。

FileReader读txt文件例子

@Test
public void readByFileReader() throws IOException {
    try (FileReader reader = new FileReader(file)) {
        int ch = 0;
        while ((ch = reader.read()) != -1) {
            System.out.print((char) ch);
        }
    }
}

其中read()方法返回的是读取得下个字符。
读取 3608 行数据耗时 2.075s

InputStreamReader读txt文件例子

@Test
public void readByBufferedReader() throws IOException {
    try (BufferedReader bufferedReader = new BufferedReader(new FileReader(
            file))) {
        String lineTxt = null;
        while ((lineTxt = bufferedReader.readLine()) != null) {
            System.out.println(lineTxt);
        }
    }
}

读取 3608 行数据耗时 0.174s

IntelliJ Idea 常用快捷键列表

发表于 2015-04-07   |   分类于 知识记录   |     |   阅读次数

Alt+Enter 导入包,自动修正
Ctrl+N 查找类
Ctrl+Shift+N 查找文件
Ctrl+Alt+L 格式化代码
Ctrl+Alt+O 优化导入的类和包
Alt+Insert 生成代码(如get,set方法,构造函数等)
Ctrl+E或者Alt+Shift+C 最近更改的代码
Ctrl+R 替换文本
Ctrl+F 查找文本
Ctrl+Shift+Space 自动补全代码
Ctrl+空格 代码提示
Ctrl+Alt+Space 类名或接口名提示
Ctrl+P 方法参数提示
Ctrl+Shift+Alt+N 查找类中的方法或变量
Alt+Shift+C 对比最近修改的代码

Shift+F6 重构-重命名
Ctrl+Shift+先上键
Ctrl+X 删除行
Ctrl+D 复制行
Ctrl+/ 或 Ctrl+Shift+/ 注释(// 或者/…/ )
Ctrl+J 自动代码
Ctrl+E 最近打开的文件
Ctrl+H 显示类结构图
Ctrl+Q 显示注释文档
Alt+F1 查找代码所在位置
Alt+1 快速打开或隐藏工程面板
Ctrl+Alt+ left/right 返回至上次浏览的位置
Alt+ left/right 切换代码视图
Alt+ Up/Down 在方法间快速移动定位
Ctrl+Shift+Up/Down 代码向上/下移动。
F2 或Shift+F2 高亮错误或警告快速定位

StringStringBuilderStringBuffer对比

发表于 2015-04-07   |   分类于 Java基础   |     |   阅读次数

作者:每次上网冲杯Java时,都能看到关于String无休无止的争论。还是觉得有必要让这个讨厌又很可爱的String美眉,赤裸裸的站在我们这些Java色狼面前了。嘿嘿….

众所周知,String是由字符组成的串,在程序中使用频率很高。Java中的String是一个类,而并非基本数据类型。 不过她却不是普通的类哦!!!

1. String对象的创建

关于类对象的创建,很普通的一种方式就是利用构造器,String类也不例外:String s=new String(“Hello world”); 问题是参数”Hello world”是什么东西,也是字符串对象吗?莫非用字符串对象创建一个字符串对象?

当然,String类对象还有一种大家都很喜欢的创建方式:String s=”Hello world”; 但是有点怪呀,怎么与基本数据类型的赋值操作(int i=1)很像呀?

在开始解释这些问题之前,我们先引入一些必要的知识:

1.1 Java class文件结构 和常量池

我们都知道,Java程序要运行,首先需要编译器将源代码文件编译成字节码文件(也就是.class文件)。然后在由JVM解释执行。

class文件是8位字节的二进制流 。这些二进制流的涵义由一些紧凑的有意义的项 组成。比如class字节流中最开始的4个字节组成的项叫做魔数 (magic),其意义在于分辨class文件(值为0xCAFEBABE)与非class文件。class字节流大致结构如下图左侧。

其中,在class文件中有一个非常重要的项——常量池 。这个常量池专门放置源代码中的符号信息(并且不同的符号信息放置在不同标志的常量表中)。如上图右侧是HelloWorld代码中的常量表(HelloWorld代码如下),其中有四个不同类型的常量表(四个不同的常量池入口)。关于常量池的具体细节,请参照我的博客《Class文件内容及常量池 》

public class HelloWorld{  
    void hello(){  
        System.out.println("Hello world");  
    }  
} 

通过上图可见,代码中的”Hello world”字符串字面值被编译之后,可以清楚的看到存放在了class常量池中的字符串常量表中(上图右侧红框区域)。

1.2 JVM运行class文件

源代码编译成class文件之后,JVM就要运行这个class文件。它首先会用类装载器加载进class文件。然后需要创建许多内存数据结构来存放class文件中的字节数据。比如class文件对应的类信息数据、常量池结构、方法中的二进制指令序列、类方法与字段的描述信息等等。当然,在运行的时候,还需要为方法创建栈帧等。这么多的内存结构当然需要管理,JVM会把这些东西都组织到几个“运行时数据区 ”中。这里面就有我们经常说的“方法区 ”、“堆 ”、“Java栈 ”等。详细请参见我的博客《Java 虚拟机体系结构》 。

上面我们提到了,在Java源代码中的每一个字面值字符串,都会在编译成class文件阶段,形成标志号 为8(CONSTANT_String_info)的常量表 。 当JVM加载 class文件的时候,会为对应的常量池建立一个内存数据结构,并存放在方法区中。同时JVM会自动为CONSTANT_String_info常量表中 的字符串常量字面值 在堆中 创建 新的String对象(intern字符串 对象,又叫拘留字符串对象)。然后把CONSTANT_String_info常量表的入口地址转变成这个堆中String对象的直接地址(常量池解 析)。

这里很关键的就是这个拘留字符串对象 。源代码中所有相同字面值的字符串常量只可能建立唯一一个拘留字符串对象。 实际上JVM是通过一个记录了拘留字符串引用的内部数据结构来维持这一特性的。在Java程序中,可以调用String的intern()方法来使得一个常规字符串对象成为拘留字符串对象。我们会在后面介绍这个方法的。

1.3操作码助忆符指令

有了上面阐述的两个知识前提,下面我们将根据二进制指令来区别两种字符串对象的创建方式:
(1) String s=new String("Hello world");编译成class文件后的指令(在myeclipse中查看):
Class字节码指令集代码

0  new java.lang.String [15]  //在堆中分配一个String类对象的空间,并将该对象的地址堆入操作数栈。  
3  dup //复制操作数栈顶数据,并压入操作数栈。该指令使得操作数栈中有两个String对象的引用值。  
4  ldc <String "Hello world"> [17] //将常量池中的字符串常量"Hello world"指向的堆中拘留String对象的地址压入操作数栈  
6  invokespecial java.lang.String(java.lang.String) [19] //调用String的初始化方法,弹出操作数栈栈顶的两个对象地址,用拘留String对象的值初始化new指令创建的String对象,然后将这个对象的引用压入操作数栈  
9  astore_1 [s] // 弹出操作数栈顶数据存放在局部变量区的第一个位置上。此时存放的是new指令创建出的,已经被初始化的String对象的地址 (此时的栈顶值弹出存入局部变量中去)。  

注意:

【这里有个dup指令。其作用就是复制之前分配的Java.lang.String空间的引用并压入栈顶。那么这里为什么需要这样么做呢?因为invokespecial指令通过[15]这个常量池入口寻找到了java.lang.String()构造方法,构造方法虽然找到了。但是必须还得知道是谁的构造方法,所以要将之前分配的空间的应用压入栈顶让invokespecial命令应用才知道原来这个构造方法是刚才创建的那个引用的,调用完成之后将栈顶的值弹出。之后调用astore_1将此时的栈顶值弹出存入局部变量中去。】

事实上,在运行这段指令之前,JVM就已经为”Hello world”在堆中创建了一个拘留字符串( 值得注意的是:如果源程序中还有一个”Hello world”字符串常量,那么他们都对应了同一个堆中的拘留字符串)。然后用这个拘留字符串的值来初始化堆中用new指令创建出来的新的String对象,局部变量s实际上存储的是new出来的堆对象地址。 大家注意了,此时在JVM管理的堆中,有两个相同字符串值的String对象:一个是拘留字符串对象,一个是new新建的字符串对象。如果还有一条创建语句String s1=new String(“Hello world”);堆中有几个值为”Hello world”的字符串呢? 答案是3个,大家好好想想为什么吧!

(2)将String s="Hello world";编译成class文件后的指令:
Class字节码指令集代码

0  ldc <String "Hello world"> [15]//将常量池中的字符串常量"Hello world"指向的堆中拘留String对象的地址压入操作数栈  
2  astore_1 [str] // 弹出操作数栈顶数据存放在局部变量区的第一个位置上。此时存放的是拘留字符串对象在堆中的地址  

和上面的创建指令有很大的不同,局部变量s存储的是早已创建好的拘留字符串的堆地址(没有new 的对象了)。 大家好好想想,如果还有一条穿件语句String s1="Hello word";此时堆中有几个值为”Hello world”的字符串呢?答案是1个。那么局部变量s与s1存储的地址是否相同呢? 呵呵, 这个你应该知道了吧。

镜头总结:

String类型脱光了其实也很普通。真正让她神秘的原因就在于CONSTANT_String_info常量表 和拘留字符串对象 的存在。现在我们可以解决江湖上的许多纷争了。

【 纷争1】关于字符串相等关系的争论

//代码1  
String sa=new String("Hello world");            
String sb=new String("Hello world");      
System.out.println(sa==sb);  // false  


//代码2    
String sc="Hello world";    
String sd="Hello world";  
System.out.println(sc==sd);  // true   

代码1中局部变量sa,sb中存储的是JVM在堆中new出来的两个String对象的内存地址。虽然这两个String对象的值(char[]存放的字符序列)都是”Hello world”。 因此”==”比较的是两个不同的堆地址。代码2中局部变量sc,sd中存储的也是地址,但却都是常量池中”Hello world”指向的堆的唯一的那个拘留字符串对象的地址 。自然相等了。
【纷争2】 字符串“+”操作的内幕

//代码1  
String sa = "ab";                                          
String sb = "cd";                                       
String sab=sa+sb;                                      
String s="abcd";  
System.out.println(sab==s); // false  
//代码2  
String sc="ab"+"cd";  
String sd="abcd";  
System.out.println(sc==sd); //true  

代码1中局部变量sa,sb存储的是堆中两个拘留字符串对象的地址。而当执行sa+sb时,JVM首先会在堆中创建一个StringBuilder类,同时用sa指向的拘留字符串对象完成初始化,然后调用append方法完成对sb所指向的拘留字符串的合并操作,接着调用StringBuilder的toString()方法在堆中创建一个String对象,最后将刚生成的String对象的堆地址存放在局部变量sab中。而局部变量s存储的是常量池中”abcd”所对应的拘留字符串对象的地址。 sab与s地址当然不一样了。这里要注意了,代码1的堆中实际上有五个字符串对象:三个拘留字符串对象、一个String对象和一个StringBuilder对象。

代码2中”ab”+”cd”会直接在编译期就合并成常量”abcd”, 因此相同字面值常量”abcd”所对应的是同一个拘留字符串对象,自然地址也就相同。

【镜头二】 String三姐妹(String,StringBuffer,StringBuilder)
String扒的差不多了。但他还有两个妹妹StringBuffer,StringBuilder长的也不错哦!我们也要下手了:

String(大姐,出生于JDK1.0时代)          不可变字符序列
StringBuffer(二姐,出生于JDK1.0时代)    线程安全的可变字符序列
StringBuilder(小妹,出生于JDK1.5时代)   非线程安全的可变字符序列 

★StringBuffer与String的可变性问题。
我们先看看这两个类的部分源代码:

//String   
public final class String  
{  
        private final char value[];  

         public String(String original) {  
              // 把原字符串original切分成字符数组并赋给value[];  
         }  
}  

//StringBuffer   
public final class StringBuffer extends AbstractStringBuilder  
{  
         char value[]; //继承了父类AbstractStringBuilder中的value[]  
         public StringBuffer(String str) {  
                 super(str.length() + 16); //继承父类的构造器,并创建一个大小为str.length()+16的value[]数组  
                 append(str); //将str切分成字符序列并加入到value[]中  
        }  
}  

很显然,String和StringBuffer中的value[]都用于存储字符序列。但是,

(1) String中的是常量(final)数组,只能被赋值一次。
比如:new String(“abc”)使得value[]={‘a’,’b’,’c’}(查看jdk String 就是这么实现的),之后这个String对象中的value[]再也不能改变了。这也正是大家常说的,String是不可变的原因 。
注意:这个对初学者来说有个误区,有人说String str1=new String(“abc”); str1=new String(“cba”);不是改变了字符串str1吗?那么你有必要先搞懂对象引用和对象本身的区别。这里我简单的说明一下,对象本身指的是存放在堆空间中的该对象的实例数据(非静态非常量字段)。而对象引用指的是堆中对象本身所存放的地址,一般方法区和Java栈中存储的都是对象引用,而非对象本身的数据。

(2) StringBuffer中的value[]就是一个很普通的数组,而且可以通过append()方法将新字符串加入value[]末尾。这样也就改变了value[]的内容和大小了。
比如:new StringBuffer(“abc”)使得value[]={‘a’,’b’,’c’,’’,’’…}(注意构造的长度是str.length()+16)。如果再将这个对象append(“abc”),那么这个对象中的value[]={‘a’,’b’,’c’,’a’,’b’,’c’,’’….}。这也就是为什么大家说 StringBuffer是可变字符串 的涵义了。从这一点也可以看出,StringBuffer中的value[]完全可以作为字符串的缓冲区功能。其累加性能是很不错的,在后面我们会进行比较。

总结,讨论String和StringBuffer可不可变。本质上是指对象中的value[]字符数组可不可变,而不是对象引用可不可变。

★StringBuffer与StringBuilder的线程安全性问题
StringBuffer和StringBuilder可以算是双胞胎了,这两者的方法没有很大区别。但在线程安全性方面,StringBuffer允许多线程进行字符操作。这是因为在源代码中StringBuffer的很多方法都被关键字synchronized 修饰了,而StringBuilder没有。
有多线程编程经验的程序员应该知道synchronized。这个关键字是为线程同步机制 设定的。我简要阐述一下synchronized的含义:
每一个类对象都对应一把锁,当某个线程A调用类对象O中的synchronized方法M时,必须获得对象O的锁才能够执行M方法,否则线程A阻塞。一旦线程A开始执行M方法,将独占对象O的锁。使得其它需要调用O对象的M方法的线程阻塞。只有线程A执行完毕,释放锁后。那些阻塞线程才有机会重新调用M方法。这就是解决线程同步问题的锁机制。
了解了synchronized的含义以后,大家可能都会有这个感觉。多线程编程中StringBuffer比StringBuilder要安全多了 ,事实确实如此。如果有多个线程需要对同一个字符串缓冲区进行操作的时候,StringBuffer应该是不二选择。
注意:是不是String也不安全呢?事实上不存在这个问题,String是不可变的。线程对于堆中指定的一个String对象只能读取,无法修改。试问:还有什么不安全的呢?

★String和StringBuffer的效率问题(这可是个热门话题呀!)
首先说明一点:StringBuffer和StringBuilder可谓双胞胎,StringBuilder是1.5新引入的,其前身就是StringBuffer。StringBuilder的效率比StringBuffer稍高,如果不考虑线程安全,StringBuilder应该是首选。另外,JVM运行程序主要的时间耗费是在创建对象和回收对象上。

我们用下面的代码运行1W次字符串的连接操作,测试String,StringBuffer所运行的时间。

//测试代码  
public class RunTime{  
    public static void main(String[] args){  
           ● 测试代码位置1  
          long beginTime=System.currentTimeMillis();  
          for(int i=0;i<10000;i++){  
                 ● 测试代码位置2  
          }  
          long endTime=System.currentTimeMillis();  
          System.out.println(endTime-beginTime);     
    }  
}  

★ 镜头总结:

(1) 在编译阶段就能够确定的字符串常量,完全没有必要创建String或StringBuffer对象。直接使用字符串常量的”+”连接操作效率最高。
(2) StringBuffer对象的append效率要高于String对象的”+”连接操作。
(3) 不停的创建对象是程序低效的一个重要原因。那么相同的字符串值能否在堆中只创建一个String对象那。显然拘留字符串能够做到这一点,除了程序中的字符串常量会被JVM自动创建拘留字符串之外,调用String的intern()方法也能做到这一点。当调用intern()时,如果常量池中已经有了当前String的值,那么返回这个常量指向拘留对象的地址。如果没有,则将String值加入常量池中,并创建一个新的拘留字符串对象。

String相关类对比2

发表于 2015-04-07   |   分类于 Java基础   |     |   阅读次数

String的值是不可变的,这就导致每次对String的操作都会生成新的String对象,不仅效率低下,而且大量浪费有限的内存空间,StringBuffer是可变类,和线程安全的字符串操作类,任何对它指向的字符串的操作都不会产生新的对象,StringBuffer和StringBuilder类功能基本相似.

1. String 类

String的值是不可变的,这就导致每次对String的操作都会生成新的String对象,不仅效率低下,而且大量浪费有限的内存空间。

String a = "a"; //假设a指向地址0x0001 
a = "b";//重新赋值后a指向地址0x0002,但0x0001地址中保存的"a"依旧存在,但已经不再是a所指向的,a 已经指向了其它地址。 

因此String的操作都是改变赋值地址而不是改变值操作。

2. StringBuffer 类

StringBuffer是可变类,和线程安全的字符串操作类,任何对它指向的字符串的操作都不会产生新的对象。 每个StringBuffer对象都有一定的缓冲区容量,当字符串大小没有超过容量时,不会分配新的容量,当字符串大小超过容量时,会自动增加容量。

StringBuffer buf=new StringBuffer(); //分配长16字节的字符缓冲区 
StringBuffer buf=new StringBuffer(512); //分配长512字节的字符缓冲区 
StringBuffer buf=new StringBuffer("this is a test")//在缓冲区中存放了字符串,并在后面预留了16字节的空缓冲区。 

3.StringBuffer

StringBuffer和StringBuilder类功能基本相似,主要区别在于StringBuffer类的方法是多线程、安全的,而StringBuilder不是线程安全的,相比而言,StringBuilder类会略微快一点。对于经常要改变值的字符串应该使用StringBuffer和StringBuilder类。

4.线程安全

  • StringBuffer 线程安全
  • StringBuilder 线程不安全

5.速度

一般情况下,速度从快到慢:StringBuilder>StringBuffer>String,这种比较是相对的,不是绝对的。

6.总结

(1).如果要操作少量的数据用 = String
(2).单线程操作字符串缓冲区 下操作大量数据 = StringBuilder
(3).多线程操作字符串缓冲区 下操作大量数据 = StringBuffer

1…3456
Coder_Roc

Coder_Roc

关注技术趋势,服务端技术细节,Java语言,技术热点等,关注思维和最佳实践。

58 日志
10 分类
80 标签
RSS
GitHub Weibo Zhihu
© 2014 - 2017 Coder_Roc
由 Hexo 强力驱动
主题 - NexT.Pisces