网上关于java sound的正规资源讲解的非常好,本文不再给出示例,主要提供一些好的资源,并说说我的一些理解,用于形成对java sound的整体认识.

一.几个词汇

  • TTS:text-to-speech,文本到语音转换
  • OCR:optical-character-recignition光学字符识别
  • MIDI:Musical Instrument Digital Interface,乐器数字化接口

    MIDI是20世纪80年代初由Dave Smith提出的,目的是解决电声乐器之间的通信.现代音乐都是通过MIDI+音色库合成的.MIDI传输的不是声音信号而是一系列音符控制参数等指令,它告诉MIDI设备要做什么.MIDI传输的信号被统一成MIDIMessage,通过异步串行通信来传递.

  • Tritonus:java sound是一种标准,有两套实现.一套是Sun公司的,一套是Tritonus.在Java 1.3中,Sun公司的被纳入Java标准库.从那时起,Tritonus就很尴尬了.要想使用Tritonus就需要禁用掉Sun的,而禁用Sun的是一件多此一举的事情.Tritonus目前只支持Linux系统,但Tritonus的一些单独下载的插件也可以运行在其他系统上.
  • SPI:Service Provider Interface服务提供接口,这是API中常见的一种模式.把代码写成接口的形式,一些服务可以遵照这些接口来实现,从而实现可插拔式的编程.SPI还有另外一个意思:在电路中,SPI指串行外设接口,Serial Peripheral Interface,它是一种高速全双工,同步的通信总线.

  • Acousitic声学,Reverb回响,Gain增益,Pan声象.DAC(digital analog converter)数模转换器.

二.学习资源

jsresource

js指java sound,这个网站专门讲解java sound,包罗万象,堪称java sound的百科全书,有这一个网站就足够了,现在需要做的就是把这个网站从头看到尾.

oracle官网上的java sound介绍

官网一向都是最重要的文档提供者,在oracle官网上,有一个详尽强大的样例,它展示了java sound的各个方面.从这个页面上可以下载样例.这个样例特别好,竟然可以用来弹奏钢琴,充分展示了java sound的强大功能.

http://www.oracle.com/technetwork/java/javase/downloads/index.html

这个页面也是Oracle官网页面,是jdk下载页面.在这个页面中,有jdk的demo,doc的下载链接,这些都是学习java的上好资源,下载下来,仔细阅读javax.sound模块.

http://www.javazoom.net/index.shtml

java sound直接支持的音频格式非常少,只包括.wav(多见于windows)和.AIFF(多见于macintosh)和.au(多见于unix)三种格式的音频文件.但通过SPI,我们不须修改java代码,只需要提供相应格式的SPI就能够实现播放多种文件.javazoom网站提供了一套mp3解码库,名字叫做JLayer.
Java Zoom网站上有多个关于java音频的项目,这里主要介绍JLayer和MP3SPI和jlGUI.JLayer于1999年2月启动,目标是为Java提供实时的MP3解码器.它还包括JLayerME子项目,是JLayer在JavaME上的版本.MP3SPI是一个基于JLayer和Tritonus的Java插件,Tritonus是java sound标准的另一种实现,要想使用MP3SPI,需要三个jar包:mp3spi.jar和tritonus.jar和jlayer.jar,将这三个jar包放到类路径下,java sound便具备了播放MP3的能力.jlGUI是一个图形界面的音乐播放器,它纯粹用Java写成,依赖于MP3SPI,这个音乐播放器简洁简陋,用着还行.

http://www.sauronsoftware.it/projects/jave/manual.php

jave(java audio video encoder)是一个纯java版的音视频转码器.

三.概述

AudioSystem是javax.sound包的重要入口类,一切都是以它为中心展开的,AudioSystem的默认输入设备是麦克风,默认输出设备是扬声器
SourceDataLine和TargetDataLine都可以通过AudioSystem获得.SourceDataLine意思是"源数据流",是指AudioSystem的输入流,把音频文件写入到AudioSystem中,AudioSystem就会播放音频文件.TargetDataLine意思是"目标数据流",是指AudioSystem的输出流,是AudioSystem的target.所以,当播放文件时,把文件内容写入AudioSystem的SourceDataLine;当录音时,把AudioSystem的TargetDataLine中的内容读入内存.
Clip是"剪辑","片段",表示内存中的一段完整的音频数据,可以一遍一遍的播放,非常适合播放游戏的背景音乐.Clip和SourceDataLine都是AudioSystem的输入端口.
在java中处理声音的包括四个包:

  • javax.sound.sample处理数字音频
  • javax.sound.midi处理midi形式的音频
  • javax.sound.sample.spi相当于sample类型的服务提供接口
  • javax.sound.midi.spi相当于midi类型的服务提供接口

四.最简单的播放器

AudioInputStream cin = AudioSystem.getAudioInputStream(new File("haha.wav"));
AudioFormat format = cin.getFormat();
DataLine.Info info = new DataLine.Info(SourceDataLine.class, format);
SourceDataLine line = (SourceDataLine) AudioSystem.getLine(info);
line.open(format);//或者line.open();format参数可有可无
line.start();
int nBytesRead = 0;
byte[] buffer = new byte[512];
while (true) {
    nBytesRead = cin.read(buffer, 0, buffer.length);
    if (nBytesRead <= 0)
        break;
    line.write(buffer, 0, nBytesRead);
}
line.drain();
line.close();

这个程序只能播放wav,pcm文件,不能播放mp3文件.
第一步,从文件对象构建AudioInputStream cin,这个cin对象包含文件的格式数据和音频数据.
第二步,根据cin的AudioFormat创建DataLine.Info对象.
第三步,根据AudioFormat来获取SourceDataLine,扬声器有了SourceDataLine就有了数据流,扬声器就可以发声了.

SourceDataLine像一个管道一样,数据流从计算机内部的音频文件中流到AudioSystem音频系统中,line.open()打开管道入口端,line.start()打开管道的出口端.line.drain()将管道的出口端导入另一个地方将管道中剩余的数据流放空.line.close()关住了管道的出口端.

五.最简单的录音机

File outputFile = new File("recoder.wav");
AudioFormat audioFormat = new AudioFormat(
        AudioFormat.Encoding.PCM_SIGNED, 44100.0F, 16, 2, 4, 44100.0F,
        false);
DataLine.Info info = new DataLine.Info(TargetDataLine.class,
        audioFormat);
TargetDataLine targetDataLine = (TargetDataLine) AudioSystem
        .getLine(info);
targetDataLine.open(audioFormat);
targetDataLine.start();
new Thread() {
    public void run() {
        AudioInputStream cin = new AudioInputStream(targetDataLine);
        try {
            AudioSystem.write(cin, AudioFileFormat.Type.WAVE,
                    outputFile);
            System.out.println("over");
        } catch (IOException e) {
            e.printStackTrace();
        }
    };
}.start();
System.in.read();
targetDataLine.close();

跟SourceDataLine的获取一样,要想从AudioSystem中获取TargetDataLine,需要规定好AudioFormat.
AudioInputStream有两个构造函数:

  • AudioInputStream(TargetDataLine line)
  • AudioInputStream(InputStream stream, AudioFormat format, long length)

重点说说第二个函数,InputStream stream指的是音频的数据流,AudioFormat format指的是音频的形式,length表示stream中包含多少个frame,计算方法是stream.length/frameSize,其中frameSize表示一个frame占用的字节数.frameSize=channelCount*sampleSize,一个frame记录的是当前时刻各个声道的音频值.采样率指的是一秒钟在某个声道的取样数,也就是一秒钟内的frame数.sampleRate=frameRate.

AudioSystem.write()有两个重载函数:

  • write(AudioInputStream stream,AudioFileFormat.Type fileType, File out):写入到文件
  • write(AudioInputStream stream, AudioFileFormat.Type fileType, OutputStream out):写入到OutputStream

AudioSystem.write()函数是线程阻塞的,只要AudioInputStream没有结束,就会一直等待输入.所以必须另开一个线程来录音,否则就无法关闭录音了.

六.使用Clip循环播放的小段音频

    public static void main(String[] args) throws Exception {
        Clip clip = AudioSystem.getClip();
        clip.open(AudioSystem.getAudioInputStream(new File("haha.wav")));
        clip.start();
        clip.setLoopPoints(0, clip.getFrameLength() - 1);
        while (true) {

        }
    }

AudioSystem就像一个芯片,它有三种引脚:SourceDataLine,TargetDataLine,Clip.这三个东西都是继承自DataLine接口的接口.SourceDataLine用于播放音频,TargetDataLine用于录音,Clip用于循环播放一段音频,可以设置循环次数等.
使用Clip播放音频时,会开启一个线程去播放音频,所以Clip是不阻塞的,这一点跟SourceDataLine不同.所以这个程序末尾写了一个死循环.
Clip的获取方式除了直接从AudioSystem.getClip(),AudioSystem.getClip(Info)直接获取,还可以像SourceDataLine,TargetDataLine那样获取.

AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(clipFile);
AudioFormat    format = audioInputStream.getFormat();
DataLine.Info    info = new DataLine.Info(Clip.class, format);
Clip clip = (Clip) AudioSystem.getLine(info);

七.播放MP3音频

播放非pcm格式的音频时,必须有对应的解码器将相应格式转化为pcm格式才能够播放.pcm格式有三种:PCM_FLOAT,PCM_SIGNED,PCM_UNSIGNED.
JLayer是一款MP3解码器,MP3SPI是基于JLayer和Tritonus的一款MP3 service provider interface.将jlayer.jar和mp3spi.jar和tritonus.jar三个jar包放到classpath中,解码时会自动查找相应的解码器进行解码.本示例程序依赖上述三个jar包.

        AudioInputStream stream = AudioSystem
                .getAudioInputStream(new File("haha.mp3"));
        AudioFormat format = stream.getFormat();
        if (format.getEncoding() != AudioFormat.Encoding.PCM_SIGNED) {
            format = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,
                    format.getSampleRate(), 16, format.getChannels(),
                    format.getChannels() * 2, format.getSampleRate(), false);
            stream = AudioSystem.getAudioInputStream(format, stream);
        }
        DataLine.Info info = new DataLine.Info(SourceDataLine.class,
                stream.getFormat());
        SourceDataLine sourceDataLine = (SourceDataLine) AudioSystem
                .getLine(info);
        sourceDataLine.open(stream.getFormat(), sourceDataLine.getBufferSize());
        sourceDataLine.start();
        int numRead = 0;
        byte[] buf = new byte[sourceDataLine.getBufferSize()];
        while ((numRead = stream.read(buf, 0, buf.length)) >= 0) {
            int offset = 0;
            while (offset < numRead) {
                offset += sourceDataLine.write(buf, offset, numRead - offset);
            }
            System.out.println(sourceDataLine.getFramePosition() + " "
                    + sourceDataLine.getMicrosecondPosition());
        }
        sourceDataLine.drain();
        sourceDataLine.stop();
        sourceDataLine.close();
        stream.close();

先获取原文件的AudioFormat,然后根据这个旧的AudioFormat创建一个新的AudioFormat,注意新的AudioFormat除了Encoding发生改变,sampleSizeInBits也要改成16,相应的frameSize也要发生变化.frameSize表示一个frame占用的字节数,frameSize=channelCount*sampleSizeInBytes,也就是声道数channelCount*2.
构建完了新的AudioFormat,通过AudioSystem.getAudioInputStream(format,stream)这个函数就能完成解码工作,这个函数会调用解码器进行解码.一旦解码完成,就会获得一个新的AudioInputStream,就可以像播放普通的wav文件一样进行播放了.
在上面使用byte[]buf数组进行读取的过程中使用了两重循环,为什么呢?第一重是必需的,里面的第二重是怕sourceData里面写不完buf,如果只写一次有可能造成数据丢失.
在上面播放过程中,不停地输出播放的frame数和播放的毫秒数,这是为了展示SourceDataLine的这两个函数.
转码的另一种方式是

AudioInputStream stream = AudioSystem.getAudioInputStream(new File("haha.mp3"));
stream = AudioSystem.getAudioInputStream(AudioFormat.Encoding.PCM_SIGNED, stream);

这种方式通过两句话就能把mp3转码成pcm,但是却报错缺少这样的转码器.所以还是按照上面说的那种方法来吧.
AudioSystem.getAudioInputStream()函数有5种形式:从文件中读取,从InputStream中读取,从URL中读取,从AudioInputStream中读取并将结果转换成某种编码,从AudioInputStream中读取并将结果转换成某种AudioFormat.

八.使用SourceDataLine进行循环播放

前面说过Clip非常适合进行循环播放,实际上SourceDataLine也是可以很方便进行循环播放的.实现的关键在于AudioInputStream的mark(int readLimit)和reset()两个函数.mark+reset是一种机制,在java流体系中很常见,它是这样一种机制:比如当前位置为第3个字节处,一旦调用mark(100),就表示在3处做了一个标记,然后继续往前走,比如,假设走到66处,执行了reset(),于是一下子就回到了第3个字节处;如果在66处没有执行reset()而是继续往前走,走到110处(已经超过了100步限制),那么刚才在3处的mark标记就失效了.不是所有的InputStream子类都支持mark机制,可以通过调用InputStream#markSurported()函数来检测当前流是否支持mark机制.BufferedInputStream和ByteArrayInputStream是支持mark机制的.对于readLimit,并不是所有的流在走完readLimit步之后都会将标记置成无效,而是在走完readLimit步之后执行reset仍旧回到上一个mark处,简言之,就是实际的readLimit为max(显式readLimit,该流在内存中的最大空间).

byte[]    abData = new byte[EXTERNAL_BUFFER_SIZE];
int    nBytesRead = 0;
int nPlayCount = 0;
if (audioInputStream.getFrameLength() == AudioSystem.NOT_SPECIFIED ||
    audioFormat.getFrameSize() == AudioSystem.NOT_SPECIFIED)
{
    out("cannot calculate length of AudioInputStream!");
    System.exit(1);
}
long lStreamLengthInBytes = audioInputStream.getFrameLength()
    * audioFormat.getFrameSize();
if (lStreamLengthInBytes > Integer.MAX_VALUE)
{
    out("length of AudioInputStream exceeds 2^31, cannot properly reset stream!");
    System.exit(1);
}
int nStreamLengthInBytes = (int) lStreamLengthInBytes;

line.start();

while (nPlayCount < PLAY_COUNT)
{
    nPlayCount++;
    audioInputStream.mark(nStreamLengthInBytes);
    nBytesRead = 0;
    while (nBytesRead != -1)
    {
        try
        {
            nBytesRead = audioInputStream.read(abData, 0, abData.length);
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
        if (nBytesRead >= 0)
        {
            int    nBytesWritten = line.write(abData, 0, nBytesRead);
        }
    }
    audioInputStream.reset();
}
line.drain();
line.close();

九.关于混音器mixer

SourceDataLine和Clip是AudioSystem的输入端,TargetDataLine是AudioSystem的输出端.AudioSystem就像一个黑盒子,那么盒子里面装的是啥?实际上就是混音器.当同时播放两首乐曲时,需要混音器来将多个SourceDataLine和多个Clip的音频数据进行混合.
查看系统中的全部混音器

    public static void listMixers() {
        Mixer.Info[] a = AudioSystem.getMixerInfo();
        for (int i = 0; i < a.length; i++) {
            System.out.println(a[i].getName());
        }
    }

并不是所有的mixer都支持三种类型的DataLine,实际上,输入混音器和输出混音器是分开的.有的混音器支持SourceDataLine和Clip,有的混音器支持TargetDataLine,有的混音器什么也不支持.通过AudioSystem这个类来获取三种DataLine就避免了程序员手动查看哪些混音器可用于输入,哪些混音器可用于输出.

        Mixer.Info[] a = AudioSystem.getMixerInfo();
        for (int i = 0; i < a.length; i++) {
            Mixer mixer = AudioSystem.getMixer(a[i]);
            Line.Info[] b = {new Line.Info(SourceDataLine.class),
                    new Line.Info(TargetDataLine.class),
                    new Line.Info(Clip.class)};
            int ans = 0;
            for (int j = 0; j < b.length; j++) {
                if (mixer.isLineSupported(b[j])) {
                    ans |= (1 << j);
                }
            }
            System.out.println(a[i].getName() + " " + ans);
        }

要想获得mixer,就要使用AudioSystem#getMixer(Mixer.Info)函数.要想获得DataLine,就要通过Mixer#getLine(Line.Info)函数.这个过程需要检测mixer支持DataLine的情况,这是挺费事的.幸好通过AudioSystem#getLine(Line.Info)函数可以直接获得想要的DataLine,这个函数把底层封装了一下.

查看支持的文件类型

    public static void listSupportedTypes() {
        AudioFileFormat.Type[] aTypes = AudioSystem.getAudioFileTypes();
        for (int i = 0; i < aTypes.length; i++) {
            System.out.println(aTypes[i].getExtension());
        }
    }

十.查看文件元数据

查看文件元数据主要通过三个类:AudioFormat,AudioFileFormat,AudioInputStream.

File file = new File("haha.wav");
AudioFileFormat aff = AudioSystem.getAudioFileFormat(file);
AudioInputStream ais = AudioSystem.getAudioInputStream(file);
//AudioFormat既可以通过AudioInputStream获取,也可以通过AudioFileFormat获取.
AudioFormat af = ais.getFormat();// aff.getFormat()
out("---------AudioFileFormat---------");
out("Type " + aff.getType());
out("byteLength " + aff.getByteLength());
out("frame length " + aff.getFrameLength());
out("format " + aff.getFormat());
out("properties " + aff.properties());
out("--------AudioFormat----------");
out("encoding " + af.getEncoding());
out("channels " + af.getChannels());
out("sampleRate " + af.getSampleRate());
out("frameRate " + af.getFrameRate());
out("properties " + af.properties());
out("sampleSizeInBits " + af.getSampleSizeInBits());
out("frameSize " + af.getFrameSize());
out("--------------AudioInputStream-------");
out("frameLength " + ais.getFrameLength());
播放时长=frameLength/frameRate
frameRate=sampleRate
frameSize=channels*sampleSizeInBytes=channels*sampleSizeInBits/8
AudioFileFormat.getByteLength=文件头长度+数据长度=文件头长度+frameSize*frameLength

以上结论完全适用于wav文件,但不适用于mp3文件,因为mp3文件是经过压缩的,并不是原始音频数据.

十一.音频文件类型转换

AudioSystem.write(AudioInputStream,AudioFileFormat.Type,File)函数可以实现wav,aiff,au之间的转换.

十二.播放音频的其他方法

(1)使用Applet.getAudioClip(URL)来获取AudioClip

import java.applet.Applet;
import java.applet.AudioClip;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.net.MalformedURLException;
import java.net.URL;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder;

public class TestAudioClip extends JPanel {

    AudioClip audioClip;

    TestAudioClip(String source) throws MalformedURLException {
        super(new GridLayout(1, 0, 10, 10));
        setBorder(new EmptyBorder(20, 20, 20, 20));
        JButton play = new JButton("Play");
        play.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent ae) {
                audioClip.play();
                audioClip.loop();
            }
        });
        add(play);

        JButton stop = new JButton("Stop");
        stop.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent ae) {
                audioClip.stop();
            }
        });
        add(stop);

        URL url = new URL(source);
        audioClip = Applet.newAudioClip(url);
        audioClip.play();
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Thread() {
            public void run() {
                JFrame frame = new JFrame();
                TestAudioClip pc = null;
                try {
                    pc = new TestAudioClip(
                            "file:///C:/Users/weidiao/Documents/eclipseProject/实验室java/haha.mp3");
                } catch (MalformedURLException e) {
                    e.printStackTrace();
                }
                frame.getContentPane().add(pc);
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setVisible(true);
            }
        });
    }
}

(2)使用sun的AudioPlayer.player
sun这个包不是java标准包,在eclipse中编写如下代码会报错:
Access restriction: The type 'AudioPlayer' is not API (restriction on requir
解决方法是:右键项目>属性>buildpath,把jdk这个库的access rule(权限规则)放宽些.

import sun.audio.AudioPlayer;

public class TestAudioPlayer {
    public static void main(String[] args) throws FileNotFoundException {
        AudioPlayer.player.start(new FileInputStream("haha.mp3"));
    }
}

十三.播放MIDI文件

Sequencer sequencer = MidiSystem.getSequencer();
sequencer.open();
sequencer.setSequence(new FileInputStream("haha.mid"));
sequencer.start();

如果在classpath中添加了MP3SPI,播放很有可能没有声音.因为mp3spi.jar依赖的tritonus.jar会改变程序运行时行为.解决方法就是把mp3spi.jar和jlayer.jar和tritonus.jar移除掉.这个问题曾经令我困惑不已,为啥没有声音,我还以为我的扬声器坏了.结果把java文件在控制台下运行就能够发声了.
运行完了之后会发现这个程序无法终止,始终可以在任务管理器中看到.这是因为Sequencer没有close.给Sequencer添加一个事件监听器,当播放结束时,关闭Sequencer.这样程序就可以正常终止了.同样的问题在sample那一家子里面也同样存在,可以通过添加事件监听器来监听播放结束事件.

Sequencer sequencer = MidiSystem.getSequencer();
sequencer.open();
sequencer.setSequence(new FileInputStream("haha.mid"));
sequencer.start();
sequencer.addMetaEventListener(new MetaEventListener() {
    @Override
    public void meta(MetaMessage meta) {
    if (meta.getType() == 47) {
        sequencer.close();
    }
    }
});

十四.Midi系统概述

java sound明显分为两大门派:sample和midi.sample直接描述音频数据波形,相当于直接告诉你应该做成什么样.midi描述的是音频指令,告诉终端应该发的音符,响度,持续时间等.MIDI把命令直接发送给终端,终端有很大的自主权决定怎样发声.
sample系统和midi系统在Java API中设计的十分对称.熟悉了sample的那套API之后再看midi就有一种熟悉感.midi系统地入口类为MidiSystem,跟sample中的AudioSystem地位相当.通过MidiSystem可以管理MidiDivice,通过AudioSystem可以管理AudioDivice.通过MidiSystem可以获取Sequencer(序列器)和Synthesizer(合成器),序列器Sequencer用于播放一段声音,Synthesizer用于合成声音.
Sequencer和Synthesizer都是接口,继承自MidiDivice接口,而MidiDivice继承自java.io.Closable.
Transmitter和Receiver也都是接口,继承自java.io.Closable接口.Transmitter只有一个子接口MidiDiviceTransmitter,Receiver只有一个子接口MidiDiviceReceiver. Transmitter是MIDI输入端口,用于播放MIDI音频,Receiver是MIDI输出端口,用于录音.

MidiEvent=MidiMessage(这是一个实体类)+持续时间tick.
MidiMessage有三个子类:ShortMessage,MetaMessage,SysexMessage.
MidiMessage有两个成员变量:int length和byte[] data. length表示数据长度,data表示数据.其中,data的第一个字节表示status.

在java中,会看见很多byte使用int表示的,比如InputStream#read()读取一个字节,返回值为int.当读到末尾时,返回值为-1.之所以使用int来表示byte,是因为这里的byte是无符号byte,而java中有一个原则:一切数字皆有符号.于是有符号的byte无法表示128~255了,所以用int表示无符号byte.

instruments乐器,programs,pathes,timbres意义相近,都是指某种音色,某种乐器.midi说的soundbank跟java中的soundbank不太一样,midi中的一个soundbank可以包含128种乐器,而java中的soundbank包含16383*128种乐器.也就是一个java soundbank包含16383个midi bank. 为了定位一个乐器,java sound使用了Patch来定位Instrument,Patch只有两个成员方法:getBank(),getProgram().

十五.后记

Java sound越学越感觉像一个无底洞,任何一门技术都是一个无底洞.世界上有那么多的无底洞,千万不能掉到一个无底洞里出不来,不能因为探索一个无底洞而忽略了其它无底洞.
本文远写于2016.9.12,用markdown重写于2016.11.20

java sound初探的更多相关文章

  1. java并发初探ConcurrentSkipListMap

    java并发初探ConcurrentSkipListMap ConcurrentSkipListMap以调表这种数据结构以空间换时间获得效率,通过volatile和CAS操作保证线程安全,而且它保证了 ...

  2. java并发初探ConcurrentHashMap

    java并发初探ConcurrentHashMap Doug Lea在java并发上创造了不可磨灭的功劳,ConcurrentHashMap体现这位大师的非凡能力. 1.8中ConcurrentHas ...

  3. java并发初探ThreadPoolExecutor拒绝策略

    java并发初探ThreadPoolExecutor拒绝策略 ThreadPoolExecuter构造器 corePoolSize是核心线程池,就是常驻线程池数量: maximumPoolSize是最 ...

  4. java并发初探CyclicBarrier

    java并发初探CyclicBarrier CyclicBarrier的作用 CyclicBarrier,"循环屏障"的作用就是一系列的线程等待直至达到屏障的"瓶颈点&q ...

  5. java并发初探CountDownLatch

    java并发初探CountDownLatch CountDownLatch是同步工具类能够允许一个或者多个线程等待直到其他线程完成操作. 当前前程A调用CountDownLatch的await方法进入 ...

  6. java并发初探ReentrantWriteReadLock

    java并发初探ReentrantWriteReadLock ReenWriteReadLock类的优秀博客 ReentrantReadWriteLock读写锁详解 Java多线程系列--" ...

  7. Java内部类初探

    Java内部类初探 之前对内部类的概念不太清晰,在此对内部类与外部类之间的关系以及它们之间的调用方式进行一个总结. Java内部类一般可以分为以下三种: 成员内部类 静态内部类 匿名内部类 一.成员内 ...

  8. Java 正则初探

    正则表达 初探* 走进沼泽 问题引出 问题:判断一个String字符串是否为数字字符串 将字符串转换为字符数组 判断每一个字符是否在"0~9"范围之间 public class T ...

  9. Java Sound : generate play sine wave - source code

    转载自:http://ganeshtiwaridotcomdotnp.blogspot.com/2011/12/java-sound-generate-play-sine-wave.html Work ...

随机推荐

  1. Rafy 框架 - 执行SQL或存储过程

    有时候,开发者不想通过实体来操作数据库,而是希望通过 SQL 语句或存储过程来直接访问数据库.Rafy 也提供了一组 API 来方便实现这类需求. IDbAccesser 接口 为了尽量屏蔽各数据库中 ...

  2. Apache Hadoop2.x 边安装边入门

    完整PDF版本:<Apache Hadoop2.x边安装边入门> 目录 第一部分:Linux环境安装 第一步.配置Vmware NAT网络 一. Vmware网络模式介绍 二. NAT模式 ...

  3. Scala快速概览

    IDEA工具安装及scala基本操作 目录 一. 1. 2. 3. 4. 二. 1. 2. 3. 三. 1. 2. 3. 4. 5. 6. 7. 四. 1. (1) (2) (3) (4) (5) ( ...

  4. atitit.http原理与概论attilax总结

    atitit.http原理与概论attilax总结 1. 图解HTTP 作者:[日]上野宣 著1 2. HTTP权威指南(国内首本HTTP及其相关核心Web技术权威著作)1 3. TCP/IP详解(中 ...

  5. ArcGIS Engine开发之地图基本操作(4)

    ArcGIS Engine开发中数据库的加载 1.加载个人地理数据库数据 个人地理数据库(Personal Geodatabase)使用Miscrosoft Access文件(*.mdb)进行空间数据 ...

  6. 调用sharepoint 2010 REST报版本过低

    问题描述: 写了一个webservice调用sharepoint REST,本机测试成功,部署到服务器上后报错 (System.Data.Services.Client.DataServiceQuer ...

  7. iOS版打地鼠游戏源码

    打地鼠游戏源码,游戏是一款多关卡基于cocos2d的iPad打地鼠游戏源码,这也是一款高质量的打地鼠游戏源码,可以拥有逐步上升的关卡的设置,大家可以在关卡时设置一些商业化的模式来盈利的,非常完美的一款 ...

  8. 了解npm的文件结构(npm-folders)和配置文件(npm-mrc)

    一.npm的文件结构 npm的安装: 本地安装 1. 将安装包放在 ./node_modules 下(运行 npm 命令时所在的目录),如果没有 node_modules 目录,会在当前执行 npm ...

  9. Node.js学习——基本模块之fs

    基本模块之fs 异步读文件 异步读取一个文本文件的代码如下: 'use strict'; var fs = require('fs'); fs.readFile('sample.txt', 'utf- ...

  10. 【译】Spring 4 基于TaskScheduler实现定时任务(注解)

    前言 译文链接:http://websystique.com/spring/spring-job-scheduling-with-scheduled-enablescheduling-annotati ...