Quantcast
Channel: CSDN博客推荐文章
Viewing all 35570 articles
Browse latest View live

Java8 I/O源码-RandomAccessFile

$
0
0

今天学习RandomAccessFile。

RandomAccessFile,用来随机访问文件的类。它支持对随机访问文件的读取和写入。这里的随机并不意味着不可控制,而是意味着可以访问文件内容的任意位置。

随机访问文件是如何实现的呢?

随机访问文件是通过“文件指针”来实现的。文件指针可看做指向文件中任意位置的光标或索引。该文件指针可以通过getFilePointer方法读取,通过seek方法设置。输入操作从文件指针开始读取字节,并随着对字节的读取而前移此文件指针。如果随机访问文件以读取/写入模式创建,则输出操作也可用;输出操作从文件指针开始写入字节,并随着对字节的写入而前移此文件指针。写入文件末尾之后的输出操作导致该数组扩展。

源码太长,放在最后,有兴趣的可以向后看。

demo

import java.io.File;
import java.io.RandomAccessFile;

import org.junit.Test;

import java.io.IOException;

/**
 * RandomAccessFile demo
 */
public class RandomAccessFileTest {

    private static final File file = new File("randomAccessFileTest.txt");

    private void prepare() {
        // 若文件存在,则删除该文件。目的是避免干扰。
        if (file.exists())
            file.delete();
    }

    // 测试seek、getFilePointer、length方法。打印结果为
    /**
     * 偏移量的设置超出文件末尾不会改变文件的长度。
     * getFilePointer():54
     * length():54
     * getFilePointer():111111
     * length():54
     * 只有在偏移量的设置超出文件末尾的情况下对文件进行写入才会更改其长度。
     */
    @Test
    public void testSeek() {
        prepare();
        try {
            RandomAccessFile raf = new RandomAccessFile(file, "rw");

            raf.writeChars("abcdefghijklmnopqrstuvwxyz\n");

            System.out.println("偏移量的设置超出文件末尾不会改变文件的长度。");
            System.out.println("getFilePointer():" + raf.getFilePointer());
            System.out.println("length():" + raf.length());
            raf.seek(111111);
            System.out.println("getFilePointer():" + raf.getFilePointer());
            System.out.println("length():" + raf.length());

            System.out.println("只有在偏移量的设置超出文件末尾的情况下对文件进行写入才会更改其长度。");

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //测试testSetLength方法。打印结果为
    /**
     * SetLength会改变文件大小,并没有改变下一个要写入的内容的位置。
     * getFilePointer():54
     * length():54
     * getFilePointer():54
     * length():111111
     */
    @Test
    public void testSetLength() {
        prepare();
        try {
            RandomAccessFile raf = new RandomAccessFile(file, "rw");

            raf.writeChars("abcdefghijklmnopqrstuvwxyz\n");

            System.out.println("SetLength会改变文件大小,并没有改变下一个要写入的内容的位置。");
            System.out.println("getFilePointer():" + raf.getFilePointer());
            System.out.println("length():" + raf.length());
            raf.setLength(111111l);
            System.out.println("getFilePointer():" + raf.getFilePointer());
            System.out.println("length():" + raf.length());

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 向文件中写入内容
     */
    @Test
    public void testWrite() {
        prepare();
        try {
            RandomAccessFile raf = new RandomAccessFile(file, "rw");

            raf.writeChars("abcdefg\n");
            raf.writeBoolean(true);
            raf.writeByte(0x51);
            raf.writeChar('h');
            raf.writeShort(0x3c3c);
            raf.writeInt(0x66);
            raf.writeLong(0x123456789L);
            raf.writeFloat(1.1f);
            raf.writeDouble(1.111);
            raf.writeUTF("潘威威的博客");
            raf.writeChar('\n');
            //追加内容
            raf.seek(raf.length());
            raf.write("追加的内容".getBytes());

            raf.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //读取文件并打印。打印结果为
    /**
     * readChar():a
     * read(byte[],int,len):
     */
    @Test
    public void testRead() {
        try {
            RandomAccessFile raf = new RandomAccessFile(file, "r");
            System.out.println("readChar():" + raf.readChar());

            raf.skipBytes(2);//跳过两个字节,即跳过了'b'字符

            byte[] buf = new byte[10];
            raf.read(buf, 0, buf.length);
            System.out.println("read(byte[],int,len):" + (new String(buf)));

            raf.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

源码

下面是观看源码的总结。

RandomAccessFile并没有继承InputStream或者OutputStream,而是实现了DataOutput和DataInput。这说明它可以操作Java基础数据类型,而且既可读,也可写。

打开文件的访问模式有四种:

  • “r”——以只读方式打开。调用结果对象的任何write方法都将导致抛出IOException。
  • “rw”——打开以便读取和写入。如果该文件尚不存在,则尝试创建该文件。
  • “rws”——打开以便读取和写入,对于 “rw”,还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备。
  • “rwd”——打开以便读取和写入,对于 “rw”,还要求对文件内容的每个更新都同步写入到底层存储设备。

一般使用前两个模式。

RandomAccessFile中读写Java基本数据类型的方法实现和DataInputStream与DataOutputStream极其相似,可以参考Java8 I/O源码-DataInputStream与DataOutputStream一文了解这些方法的实现。

/**
 * RandomAccessFile并没有继承InputStream或者OutputStream,而是实现了DataOutput和DataInput。
 * 这说明它可以操作Java基础数据类型,而且既可读,也可写。
 * 
 */
public class RandomAccessFile implements DataOutput, DataInput, Closeable {

    //文件描述符
    private FileDescriptor fd;
    //文件通道
    private FileChannel channel = null;
    //标识此文件是否既可以读又可以写
    private boolean rw;

    /**
     * 引用文件的路径
     */
    private final String path;

    //关闭锁
    private Object closeLock = new Object();
    //标识是否关闭
    private volatile boolean closed = false;

    //不同读写模式对应的值
    private static final int O_RDONLY = 1;//只读模式
    private static final int O_RDWR =   2;//读写模式
    private static final int O_SYNC =   4;
    private static final int O_DSYNC =  8;

    /**
     * 创建从中读取和向其中写入(可选)的随机访问文件流。
     * 将创建一个新的FileDescriptor对象来表示到文件的连接。
     * mode参数指定用以打开文件的访问模式。允许的值及其含意如RandomAccessFile(File,String)构造方法所指定的那样。
     * 
     * 如果存在安全管理器,则使用name作为其参数调用其checkRead方法,以查看是否允许对该文件进行读取访问。
     * 如果该模式允许写入,那么还使用name作为安全管理器的参数来调用其checkWrite方法,以查看是否允许对该文件进行写入访问。
     *
     * @param      name   文件名
     * @param      mode   访问模式,参考RandomAccessFile(File,String)构造方法
     */
    public RandomAccessFile(String name, String mode)
        throws FileNotFoundException
    {
        this(name != null ? new File(name) : null, mode);
    }

    /**
     * 创建从中读取和向其中写入(可选)的随机访问文件流,该文件由File参数指定。
     *
     * mode参数指定用以打开文件的访问模式。允许的值及其含意为:
     * "r"——以只读方式打开。调用结果对象的任何write方法都将导致抛出IOException。
     * "rw"——打开以便读取和写入。如果该文件尚不存在,则尝试创建该文件。
     * "rws"——打开以便读取和写入,对于 "rw",还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备。
     * "rwd"——打开以便读取和写入,对于 "rw",还要求对文件内容的每个更新都同步写入到底层存储设备。
     * 
     * 如果存在安全管理器,则使用name作为其参数调用其checkRead方法,以查看是否允许对该文件进行读取访问。
     * 如果该模式允许写入,那么还使用name作为安全管理器的参数来调用其checkWrite方法,以查看是否允许对该文件进行写入访问。
     */
    public RandomAccessFile(File file, String mode)
        throws FileNotFoundException
    {
        //获取文件名
        String name = (file != null ? file.getPath() : null);
        int imode = -1;
        //如果模式为只读模式
        if (mode.equals("r"))
            imode = O_RDONLY;
        else if (mode.startsWith("rw")) {
            imode = O_RDWR;
            rw = true;
            //还要根据具体的情况判断是rws还是rwd模式
            if (mode.length() > 2) {
                if (mode.equals("rws"))
                    imode |= O_SYNC;
                else if (mode.equals("rwd"))
                    imode |= O_DSYNC;
                else
                    imode = -1;
            }
        }
        //如果模式不合法,抛出异常
        if (imode < 0)
            throw new IllegalArgumentException("Illegal mode \"" + mode
                                               + "\" must be one of "
                                               + "\"r\", \"rw\", \"rws\","
                                               + " or \"rwd\"");
        //如果存在安全管理器,则使用name作为其参数调用其checkRead方法,以查看是否允许对该文件进行读取访问。
        //如果该模式允许写入,那么还使用name作为安全管理器的参数来调用其checkWrite方法,以查看是否允许对该文件进行写入访问。
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkRead(name);
            if (rw) {
                security.checkWrite(name);
            }
        }
        //如果文件名为null,抛出异常
        if (name == null) {
            throw new NullPointerException();
        }
        //如果文件路径不可用
        if (file.isInvalid()) {
            throw new FileNotFoundException("Invalid file path");
        }
        //创建一个新的FileDescriptor对象来表示到文件的连接。
        fd = new FileDescriptor();
        fd.attach(this);
        path = name;
        //以指定的模式打开指定文件
        open(name, imode);
    }

    /**
     * 返回与此流关联的不透明文件描述符对象。
     */
    public final FileDescriptor getFD() throws IOException {
        if (fd != null) {
            return fd;
        }
        throw new IOException();
    }

    /**
     * 返回与此文件关联的唯一FileChannel对象。
     */
    public final FileChannel getChannel() {
        synchronized (this) {
            if (channel == null) {
                channel = FileChannelImpl.open(fd, path, true, rw, this);
            }
            return channel;
        }
    }

    /**
     * 以指定模式打开指定文件
     * 
     * native关键字说明其修饰的方法是一个原生态方法,方法对应的实现不是在当前文件,而是在用其他语言(如C和C++)实现的文件中。
     * Java语言本身不能对操作系统底层进行访问和操作,但是可以通过JNI接口调用其他语言来实现对底层的访问。
     * JNI是Java本机接口(Java Native Interface),是一个本机编程接口,
     * 它是Java软件开发工具箱(Java Software Development Kit,SDK)的一部分。
     * JNI允许Java代码使用以其他语言编写的代码和代码库。
     * Invocation API(JNI的一部分)可以用来将Java虚拟机(JVM)嵌入到本机应用程序中,
     * 从而允许程序员从本机代码内部调用Java代码。
     * 所以想要了解open0方法的具体实现只能去查看JVM源码了。
     * 
     */
    private native void open0(String name, int mode)
        throws FileNotFoundException;

    // wrap native call to allow instrumentation
    /**
     * 参考open0(String name, int mode)
     */
    private void open(String name, int mode)
        throws FileNotFoundException {
        open0(name, mode);
    }

    // 'Read' primitives

    /**
     * 从此文件中读取一个数据字节。
     * 以整数形式返回此字节,范围在0到255(0x00-0x0ff)。如果尚无输入可用,将阻塞此方法。
     * 尽管RandomAccessFile不是InputStream的子类,但此方法的行为与 InputStream的InputStream.read()方法完全一样。
     *
     * @return   下一个数据字节,如果已到达文件的末尾,则返回-1。
     */
    public int read() throws IOException {
        return read0();
    }

    /**
     * 从此文件中读取一个数据字节。
     * 
     * 关于native的介绍请参考open0(String name, int mode)
     * /
    private native int read0() throws IOException;

    /**
     * Reads a sub array as a sequence of bytes.
     * @param b the buffer into which the data is read.
     * @param off the start offset of the data.
     * @param len the number of bytes to read.
     * @exception IOException If an I/O error has occurred.
     */
    private native int readBytes(byte b[], int off, int len) throws IOException;

    /**
     * 将最多len个数据字节从此文件读入byte数组。
     * 在至少一个输入字节可用前,此方法一直阻塞。
     * 尽管RandomAccessFile不是InputStream的子类,
     * 但此方法的行为与InputStream的InputStream.read(byte[], int, int)方法完全一样。
     */
    public int read(byte b[], int off, int len) throws IOException {
        return readBytes(b, off, len);
    }

    /**
     * 将最多b.length个数据字节从此文件读入byte数组。
     */
    public int read(byte b[]) throws IOException {
        return readBytes(b, 0, b.length);
    }

    /**
     * 将b.length个字节从此文件读入byte数组,并从当前文件指针开始。
     * 在读取到请求数量的字节之前,此方法将从该文件重复读取。
     * 在读取了请求数量的字节、检测到流的末尾或者抛出异常前,此方法一直阻塞。
     * 
     * 参考readFully(byte b[], int off, int len)
     */
    public final void readFully(byte b[]) throws IOException {
        readFully(b, 0, b.length);
    }

    /**
     * 将b.length个字节从此文件读入byte数组,并从当前文件指针开始。
     * 在读取到请求数量的字节之前,此方法将从该文件重复读取。
     * 在读取了请求数量的字节、检测到流的末尾或者抛出异常前,此方法一直阻塞。
     */
    public final void readFully(byte b[], int off, int len) throws IOException {
        int n = 0;
        //在读取到请求数量的字节之前,此方法将从该文件重复读取。
        do {
            int count = this.read(b, off + n, len - n);
            if (count < 0)
                throw new EOFException();
            n += count;
        } while (n < len);
    }

    /**
     * 尝试跳过输入的n个字节以丢弃跳过的字节。
     * 此方法可能跳过一些较少数量的字节(可能包括零)。
     * 这可能由任意数量的条件引起;在跳过n个字节之前已到达文件的末尾只是其中的一种可能。
     * 此方法从不抛出EOFException。返回跳过的实际字节数。
     * 如果n为负数,则不跳过任何字节。
     *
     * @param      n  要跳过的字节数。
     * @return     跳过的实际字节数。
     */
    public int skipBytes(int n) throws IOException {
        long pos;
        long len;
        long newpos;

        if (n <= 0) {
            return 0;
        }
        //获取文件指针当前位置
        pos = getFilePointer();
        len = length();
        newpos = pos + n;
        if (newpos > len) {
            newpos = len;
        }
        //将指针设置到新位置
        seek(newpos);

        /* 返回跳过的实际字节数 */
        return (int) (newpos - pos);
    }

    // 'Write' primitives

    /**
     * 向此文件写入指定的字节。
     * 从当前文件指针开始写入。
     */
    public void write(int b) throws IOException {
        write0(b);
    }

    /**
     * 向此文件写入指定的字节。
     * 从当前文件指针开始写入。
     * 
     * 关于native的介绍请参考open0(String name, int mode)
     */
    private native void write0(int b) throws IOException;

    /**
     * 将b.length个字节从指定byte数组写入到此文件,并从当前文件指针开始。
     * 
     * 关于native的介绍请参考open0(String name, int mode)
     */
    private native void writeBytes(byte b[], int off, int len) throws IOException;

    /**
     * 将b.length个字节从指定byte数组写入到此文件,并从当前文件指针开始。
     */
    public void write(byte b[]) throws IOException {
        writeBytes(b, 0, b.length);
    }

    /**
     * 将 len 个字节从指定 byte 数组写入到此文件,并从偏移量 off 处开始。
     */
    public void write(byte b[], int off, int len) throws IOException {
        writeBytes(b, off, len);
    }

    // 'Random access' stuff

    /**
     * 返回此文件中的当前偏移量。
     */
    public native long getFilePointer() throws IOException;

    /**
     * 设置到此文件开头测量到的文件指针偏移量,在该位置发生下一个读取或写入操作。
     * 偏移量的设置可能会超出文件末尾。
     * 偏移量的设置超出文件末尾不会改变文件的长度。只有在偏移量的设置超出文件末尾的情况下对文件进行写入才会更改其长度。
     */
    public void seek(long pos) throws IOException {
        if (pos < 0) {
            throw new IOException("Negative seek offset");
        } else {
            seek0(pos);
        }
    }

    private native void seek0(long pos) throws IOException;

    /**
     * 返回此文件的长度。
     */
    public native long length() throws IOException;

    /**
     * 设置此文件的长度。
     * 如果 length 方法返回的文件的现有长度大于 newLength 参数,则该文件将被截短。
     * 在此情况下,如果 getFilePointer 方法返回的文件偏移量大于 newLength,那么在返回此方法后,该偏移量将等于 newLength。
     * 如果 length 方法返回的文件的现有长度小于 newLength 参数,则该文件将被扩展。在此情况下,未定义文件扩展部分的内容。
     */
    public native void setLength(long newLength) throws IOException;

    /**
     * 关闭此随机访问文件流并释放与该流关联的所有系统资源。
     * 关闭的随机访问文件不能执行输入或输出操作,而且不能重新打开。
     * 如果此文件具有一个关联的通道,那么该通道也会被关闭。
     */
    public void close() throws IOException {
        synchronized (closeLock) {
            if (closed) {
                return;
            }
            closed = true;
        }
        if (channel != null) {
            channel.close();
        }

        fd.closeAll(new Closeable() {
            public void close() throws IOException {
               close0();
           }
        });
    }

    //
    //  下面都是读写Java基本数据类型的方法。这些方法是借鉴
    //  DataInputStream and DataOutputStream的.如果感兴趣可以参考 《Java8 I/O源码-DataInputStream与DataOutputStream》一文。
    // 链接为http://blog.csdn.net/panweiwei1994/article/details/78266266。
    //

    public final boolean readBoolean() throws IOException {
        int ch = this.read();
        if (ch < 0)
            throw new EOFException();
        return (ch != 0);
    }

    public final byte readByte() throws IOException {
        int ch = this.read();
        if (ch < 0)
            throw new EOFException();
        return (byte)(ch);
    }

    public final int readUnsignedByte() throws IOException {
        int ch = this.read();
        if (ch < 0)
            throw new EOFException();
        return ch;
    }

    public final short readShort() throws IOException {
        int ch1 = this.read();
        int ch2 = this.read();
        if ((ch1 | ch2) < 0)
            throw new EOFException();
        return (short)((ch1 << 8) + (ch2 << 0));
    }

    public final int readUnsignedShort() throws IOException {
        int ch1 = this.read();
        int ch2 = this.read();
        if ((ch1 | ch2) < 0)
            throw new EOFException();
        return (ch1 << 8) + (ch2 << 0);
    }

    public final char readChar() throws IOException {
        int ch1 = this.read();
        int ch2 = this.read();
        if ((ch1 | ch2) < 0)
            throw new EOFException();
        return (char)((ch1 << 8) + (ch2 << 0));
    }

    public final int readInt() throws IOException {
        int ch1 = this.read();
        int ch2 = this.read();
        int ch3 = this.read();
        int ch4 = this.read();
        if ((ch1 | ch2 | ch3 | ch4) < 0)
            throw new EOFException();
        return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0));
    }

    public final long readLong() throws IOException {
        return ((long)(readInt()) << 32) + (readInt() & 0xFFFFFFFFL);
    }

    public final float readFloat() throws IOException {
        return Float.intBitsToFloat(readInt());
    }

    public final double readDouble() throws IOException {
        return Double.longBitsToDouble(readLong());
    }

    public final String readLine() throws IOException {
        StringBuffer input = new StringBuffer();
        int c = -1;
        boolean eol = false;

        while (!eol) {
            switch (c = read()) {
            case -1:
            case '\n':
                eol = true;
                break;
            case '\r':
                eol = true;
                long cur = getFilePointer();
                if ((read()) != '\n') {
                    seek(cur);
                }
                break;
            default:
                input.append((char)c);
                break;
            }
        }

        if ((c == -1) && (input.length() == 0)) {
            return null;
        }
        return input.toString();
    }

    public final String readUTF() throws IOException {
        return DataInputStream.readUTF(this);
    }

    public final void writeBoolean(boolean v) throws IOException {
        write(v ? 1 : 0);
        //written++;
    }

    public final void writeByte(int v) throws IOException {
        write(v);
        //written++;
    }

    public final void writeShort(int v) throws IOException {
        write((v >>> 8) & 0xFF);
        write((v >>> 0) & 0xFF);
        //written += 2;
    }

    public final void writeChar(int v) throws IOException {
        write((v >>> 8) & 0xFF);
        write((v >>> 0) & 0xFF);
        //written += 2;
    }

    public final void writeInt(int v) throws IOException {
        write((v >>> 24) & 0xFF);
        write((v >>> 16) & 0xFF);
        write((v >>>  8) & 0xFF);
        write((v >>>  0) & 0xFF);
        //written += 4;
    }

    public final void writeLong(long v) throws IOException {
        write((int)(v >>> 56) & 0xFF);
        write((int)(v >>> 48) & 0xFF);
        write((int)(v >>> 40) & 0xFF);
        write((int)(v >>> 32) & 0xFF);
        write((int)(v >>> 24) & 0xFF);
        write((int)(v >>> 16) & 0xFF);
        write((int)(v >>>  8) & 0xFF);
        write((int)(v >>>  0) & 0xFF);
        //written += 8;
    }

    public final void writeFloat(float v) throws IOException {
        writeInt(Float.floatToIntBits(v));
    }

    public final void writeDouble(double v) throws IOException {
        writeLong(Double.doubleToLongBits(v));
    }

    @SuppressWarnings("deprecation")
    public final void writeBytes(String s) throws IOException {
        int len = s.length();
        byte[] b = new byte[len];
        s.getBytes(0, len, b, 0);
        writeBytes(b, 0, len);
    }

    public final void writeChars(String s) throws IOException {
        int clen = s.length();
        int blen = 2*clen;
        byte[] b = new byte[blen];
        char[] c = new char[clen];
        s.getChars(0, clen, c, 0);
        for (int i = 0, j = 0; i < clen; i++) {
            b[j++] = (byte)(c[i] >>> 8);
            b[j++] = (byte)(c[i] >>> 0);
        }
        writeBytes(b, 0, blen);
    }

    //使用 modified UTF-8 编码以与机器无关的方式将一个字符串写入该文件。
    //该功能是通过DataOutputStream.writeUTF方法来完成的
    public final void writeUTF(String str) throws IOException {
        DataOutputStream.writeUTF(str, this);
    }

    private static native void initIDs();

    private native void close0() throws IOException;

    static {
        initIDs();
    }
}

关于RandomAccessFile就讲到这里,想了解更多内容请参考

END.

作者:panweiwei1994 发表于2017/11/8 20:48:40 原文链接
阅读:1 评论:0 查看评论

hadoop 2.x简单介绍

$
0
0

hadoop

Hadoop是什么?Hadoop是一个开发和运行处理大规模数据的软件平台,是Appach的一个用java语言实现开源软件框架,实现在大量计算机组成的集群中对海量数据进行分布式计算.

大数据的四个特征:

海量的数据规模,多样的数据类型,快速的数据流转,数据价值的体现。

hadoop2x的核心模块

Hadoop Comon:

为其他Hadoop模块提供基础设施

Hadoop HDFS

一个高可靠,高吞吐量的分布式文件系统

Hadoop MapReduce:

一个分布式的离线并行计算框架

Hadoop YARN:

一个新的MapReduce框架,任务调度与资源管理

HDFS

Hadoop的设计思想受到Google公司的GFS设计思想的启示,基于一种开源的理念实现的分布式分布式文件系统。HDFS的设计基础与目标如下。

1)硬件错误(Hardware Failure)是常态,因而需要数据冗余技术。

2)流失数据访问(Streaming Data Access),即数据批量读取而非随机读写,Hadoop擅长做数据分析而不是事务处理。

3)大规模数据集(Large Data Sets)

4)简单一致性模型(Simple Coherency Model),即为了降低系统复杂度,对文件采用一次性写多次读的逻辑设计,也就是文件一经写入,关闭,就再不要修改。

5)“Moving Computation is Cheaper than Moving Data”,通俗理解,程序采用“数据就近”原则分配节点执行。

6)Portability Across Heterogeneous Hardware and Software Platforms,即有着很强的可扩展性。



HDFS体系结构

HDFS体系结构如图1所示,它采用主从结构,Namenode属于主段,Datanode属于从端。

Namenode

1)管理文件系统的命名空间。

2)记录 每个文件数据快在各个Datanode上的位置和副本信息。

3)协调客户端对文件的访问。

4)记录命名空间内的改动或者空间本省属性的改动。

5)Namenode 使用事务日志记录HDFS元数据的变化。使用映像文件存储文件系统的命名空间,包括文件映射,文件属性等。

从社会学来看,Namenode是HDFS里面的管理者,发挥者管理、协调、操控的作用。

Datanode

1)负责所在物理节点的存储管理。

2)一次写入,多次读取(不修改)。

3)文件由数据库组成,一般情况下,数据块的大小为64MB。

4)数据尽量散步到各个节点。

从社会学的角度来看,Datanode是HDFS的工作者,发挥按着Namenode的命令干活,并且把干活的进展和问题反馈到Namenode的作用。

客户端如何访问HDFS中一个文件呢?具体流程如下。

1)首先从Namenode获得组成这个文件的数据块位置列表。

2)接下来根据位置列表知道存储数据块的Datanode。

3)最后访问Datanode获取数据。

注意:Namenode并不参与数据实际传输。

数据存储系统,数据存储的可靠性至关重要。HDFS是如何保证其可靠性呢?它主要采用如下机理。

1)冗余副本策略,即所有数据都有副本,副本的数目可以在hdfs-site.xml中设置相应的复制因子。

2)机架策略,即HDFS的“机架感知”,一般在本机架存放一个副本,在其它机架再存放别的副本,这样可以防止机架失效时丢失数据,也可以提供带宽利用率。

3)心跳机制,即Namenode周期性从Datanode接受心跳信号和快报告,没有按时发送心跳的Datanode会被标记为宕机,不会再给任何I/O请求,若是Datanode失效造成副本数量下降,并且低于预先设置的阈值,Namenode会检测出这些数据块,并在合适的时机进行重新复制。

4)安全模式,Namenode启动时会先经过一个“安全模式”阶段。

5)校验和,客户端获取数据通过检查校验和,发现数据块是否损坏,从而确定是否要读取副本。

6)回收站,删除文件,会先到回收站/trash,其里面文件可以快速回复。

7)元数据保护,映像文件和事务日志是Namenode的核心数据,可以配置为拥有多个副本。

8)快照,支持存储某个时间点的映像,需要时可以使数据重返这个时间点的状态。

Mapreduce

是一个计算框架,既然是做计算的框架,那么表现形式就是有个输入(input),mapreduce操作这个输入(input),通过本身定义好的计算模型,得到一个输出(output),这个输出就是我们所需要的结果。

MapReduce将计算过程分为两个阶段:Map和Reduce Map阶段并行处理输入数据,Reduce阶段对Map结果进行汇总。

Shuffle链接Map和Reduce两个阶段: Map Task将数据写到本地磁盘,Reduce Task从每个Map Task上读取一份数据

仅适合离线批处理:具有良好的容错性和扩展性,适合简单的批处理任务

缺点明显: 启动开销大,过多使用磁盘导致效率低下等

YARN

YARN是资源管理系统,理论上支持多种资源,目前支持CPU和内存两种资源

YARN产生背景
    直接源于MRv1在几个方面的缺陷
            扩展性受限
            单点故障
            难以支持MR之外的计算
    多计算框架各自为战,数据共享困难
            MR:离线计算框架
            Storm:实时计算框架
            Spark:内存计算框架
YARN设计目标
    通用的统一资源管理系统
        同时运行长应用程序和短应用程序
    长应用程序
        通常情况下,永不停止运行的程序
        Service、HTTP Server等
    短应用程序
        短时间(秒级、分钟级、小时级)内会运行结束的程序
        MR job、Spark Job等

YARN基本架构



ResourceManager
整个集群只有一个,负责集群资源的统一管理和调度
详细功能
        处理客户端请求
        启动/监控ApplicationMaster
        监控NodeManager
        资源分配与调度
NodeManager
整个集群有多个,负责单节点资源管理和使用
详细功能
        单个节点上的资源管理和任务管理
        处理来自ResourceManager的命令
        处理来自ApplicationMaster的命令
ApplicationMaster
每个应用有一个,负责应用程序的管理
详细功能
        数据切分
        为应用程序申请资源,并进一步分配给内部任务
        任务监控与容错
Container
对任务运行环境的抽象
描述一系列信息
        任务运行资源(节点、内存、CPU)
        任务启动命令
        任务运行环境
YARN运行过程


YARN容错性
ResourceManager
        存在单点故障;
        正在基于ZooKeeper实现HA。
NodeManager
        失败后,RM将失败任务告诉对应的AM;
        AM决定如何处理失败的任务。
ApplicationMaster
        失败后,由RM负责重启;
        AM需处理内部任务的容错问题;
        RMAppMaster会保存已经运行完成的Task,重启后无需重新运行。
YARN调度框架
    双层调度框架
        RM将资源分配给AM
        AM将资源进一步分配给各个Task
    基于资源预留的调度策略
        资源不够时,会为Task预留,直到资源充足
        与“all or nothing”策略不同(Apache Mesos)
YARN资源调度器
    多类型资源调度
        采用DRF算法(论文:“Dominant Resource Fairness: Fair Allocation of Multiple Resource Types”)
        目前支持CPU和内存两种资源
    提供多种资源调度器
        FIFO
        Fair Scheduler
        Capacity Scheduler
    多租户资源调度器
        支持资源按比例分配
        支持层级队列划分方式
        支持资源抢占

YARN资源隔离方案
    支持内存和CPU两种资源隔离
        内存是一种“决定生死”的资源
        CPU是一种“影响快慢”的资源
    内存隔离
        基于线程监控的方案
        基于Cgroups的方案
    CPU隔离
        默认不对CPU资源进行隔离
        基于Cgroups的方案
YARN支持的调度语义
    支持的语义
        请求某个特定节点/机架上的特定资源量
        将某些节点加入(或移除)黑名单,不再为自己分配这些节点上的资源
        请求归还某些资源
    不支持的语义
        请求任意节点/机架上的特定资源量
        请求一组或几组符合某种特质的资源
        超细粒度资源
        动态调整Container资源
作者:liaodehong 发表于2017/11/8 21:29:21 原文链接
阅读:21 评论:0 查看评论

Android 8.0 解决OkHttp问题:A connection to xxx was leaked. Did you forget to close a response body?

$
0
0

在Android中,我们访问网络时,最简单的方式类似与:

HttpURLConnection connection = null;
try {
    //xxxxx为具体的网络地址
    URL url = new URL("xxxxx");
    connection = (HttpURLConnection) url.openConnection();

    connection.connect();
    //进行一些操作
    ...............
} catch (IOException e) {
    e.printStackTrace();
} finally {
    if (connection != null) {
        connection.disconnect();
    }
}

最近在8.0的手机里跑类似上述代码时,突然发现会概率性地打印类似如下的log:

A connection to xxxxxx was leaked. Did you forget to close a response body?

仔细check了一下代码,发现connection用完后,已经disconnect了,
怎么还会打印这种让人觉得不太舒服的代码?

为了解决这个问题,在国内外的网站上找了很久,
但都没能找到真正可行的解决方案。

无奈之下,只好硬撸了一边源码,总算是找到了问题的原因和一个解决方案。
因此,在本片博客中记录一下比较重要的地方。


Android的源码中,我们知道URL的openConnection函数的底层实现依赖于OkHttp库,
对于这部分的流程,我之后专门写一篇文档记录一下。

现在我们需要知道的是:
OkHttp库中的创建的Http链接为RealConnection对象。
为了达到复用的效果,OkHttp专门创建了ConnectionPool对象来管理所有的RealConnection。
这有点像线程池会管理所有的线程一样。

当我们创建一个新的RealConnection时,会调用ConnectionPool的put函数:

void put(RealConnection connection) {
    assert (Thread.holdsLock(this));
    if (connections.isEmpty()) {
        //执行一个cleanupRunnable
        executor.execute(cleanupRunnable);
    }
    //将新的connection加入池子中
    connections.add(connection);
}

现在,我们来看看cleanupRunnable会干些啥:

private Runnable cleanupRunnable = new Runnable() {
    @Override public void run() {
        while (true) {
            //容易看出,其实就是周期性地执行cleanup函数
            long waitNanos = cleanup(System.nanoTime());
            if (waitNanos == -1) return;
            if (waitNanos > 0) {
                long waitMillis = waitNanos / 1000000L;
                waitNanos -= (waitMillis * 1000000L);
                synchronized (ConnectionPool.this) {
                    try {
                        ConnectionPool.this.wait(waitMillis, (int) waitNanos);
                    } catch (InterruptedException ignored) {
                    }
                }
            }
        }
    }
};

cleanup函数的真面目如下:

long cleanup(long now) {
    //记录在使用的connection
    int inUseConnectionCount = 0;

    //记录空闲的connection
    int idleConnectionCount = 0;

    //记录空闲时间最长的connection
    RealConnection longestIdleConnection = null;

    //记录最长的空闲时间
    long longestIdleDurationNs = Long.MIN_VALUE;

    synchronized (this) {
    for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
        RealConnection connection = i.next();

            // If the connection is in use, keep searching.
            // 轮询每一个RealConnection
            if (pruneAndGetAllocationCount(connection, now) > 0) {
                inUseConnectionCount++;
                continue;
            }

            idleConnectionCount++;

            //找到空闲时间最长的RealConnection
            long idleDurationNs = now - connection.idleAtNanos;
            if (idleDurationNs > longestIdleDurationNs) {
                longestIdleDurationNs = idleDurationNs;
                longestIdleConnection = connection;
            }
        }

        //空闲时间超过限制或空闲connection数量超过限制,则移除空闲时间最长的connection
        if (longestIdleDurationNs >= this.keepAliveDurationNs
                || idleConnectionCount > this.maxIdleConnections) {
            // We've found a connection to evict. Remove it from the list, then close it below (outside
            // of the synchronized block).
            connections.remove(longestIdleConnection);
        } else if (idleConnectionCount > 0) {
            // A connection will be ready to evict soon.
            //返回下一次执行cleanup需等待的时间
            return keepAliveDurationNs - longestIdleDurationNs;
        } else if (inUseConnectionCount > 0) {
            // All connections are in use. It'll be at least the keep alive duration 'til we run again.
            // 返回最大可等待时间
            return keepAliveDurationNs;
         } else {
            // No connections, idle or in use.
            return -1;
         }
    }

    //特意放到同步锁的外面释放,减少持锁时间
    Util.closeQuietly(longestIdleConnection.getSocket());
    return 0;
}

通过cleanup函数,不难看出该函数主要的目的就是:
逐步清理connectionPool中已经空闲的RealConnection。

现在唯一的疑点就是上文中的pruneAndGetAllocationCount函数了:

/**
 * Prunes any leaked allocations and then returns the number of remaining live allocations on
 * {@code connection}. Allocations are leaked if the connection is tracking them but the
 * application code has abandoned them. Leak detection is imprecise and relies on garbage
 * collection.
 */
private int pruneAndGetAllocationCount(RealConnection connection, long now) {
    //获取使用该RealConnection的对象的引用
    List<Reference<StreamAllocation>> references = connection.allocations;
    for (int i = 0; i < references.size(); ) {
        Reference<StreamAllocation> reference = references.get(i);

        //引用不为null,说明仍有java对象持有它
        if (reference.get() != null) {
            i++;
            continue;
        }

        //没有持有它的对象,说明上层持有RealConnection已经被回收了
        // We've discovered a leaked allocation. This is an application bug.
        Internal.logger.warning("A connection to " + connection.getRoute().getAddress().url()
                + " was leaked. Did you forget to close a response body?");

        //移除引用
        references.remove(i);
        connection.noNewStreams = true;

        // If this was the last allocation, the connection is eligible for immediate eviction.
        //没有任何引用时, 标记为idle,等待被cleanup
        if (references.isEmpty()) {
            connection.idleAtNanos = now - keepAliveDurationNs;
            return 0;
        }
    }

    return references.size();
}

从上面的代码可以看出,pruneAndGetAllocationCount发现没有被引用的RealConnection时,
就会打印上文提到的leaked log。

个人猜测,如果开头的代码执行完毕后,GC先回收HttpURLConnection(非直接持有)等持有RealConnection的对象,后回收RealConnection。
且在回收HttpURLConnection后,回收RealConnection前,刚好执行了pruneAndGetAllocationCount,就可能会打印这种log。
这也是注释中提到的,pruneAndGetAllocationCount依赖于GC。

不过从代码来看,这并没有什么问题,Android系统仍会回收这些资源。

在文章开头的代码中,最后调用的HttpURLConnection的disconnect函数。
该函数仅会调用StreamAllocation的cancel函数,且最终调用到RealConnection的cancel函数:

public void cancel() {
    // Close the raw socket so we don't end up doing synchronous I/O.
    Util.closeQuietly(rawSocket);
}

可以看出,该方法仅关闭了socket,并没有移除引用,不会解决我们遇到的问题。


经过不断地尝试和阅读源码,我发现利用下述方式可以解决这个问题:

HttpURLConnection connection = null;
try {
    //xxxxx为具体的网络地址
    URL url = new URL("xxxxx");
    connection = (HttpURLConnection) url.openConnection();

    connection.connect();
    //进行一些操作
    ...............
} catch (IOException e) {
    e.printStackTrace();
} finally {
    if (connection != null) {
    try {
        //主动关闭inputStream
        //这里不需要进行判空操作
        connection.getInputStream().close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        connection.disconnect();
    }
}

当我们主动关闭HttpURLConnection的inputStream时,将会先后调用到StreamAllocation的noNewStreams和streamFinished函数:

public void noNewStreams() {
    deallocate(true, false, false);
}

public void streamFinished(HttpStream stream) {
    synchronized (connectionPool) {
        if (stream == null || stream != this.stream) {
            throw new IllegalStateException("expected " + this.stream + " but was " + stream);
        }
    }
    //调用deallocate
    deallocate(false, false, true);
}

//连续调用两次,第1、3个参数分别为true
private void deallocate(boolean noNewStreams, boolean released, boolean streamFinished) {
    RealConnection connectionToClose = null;
    synchronized (connectionPool) {
        if (streamFinished) {
            //第二次,stream置为null
            this.stream = null;
        }

        if (released) {
            this.released = true;
        }

        if (connection != null) {
            if (noNewStreams) {
                //第一次,noNewStreams置为true
                connection.noNewStreams = true;
            }

            //stream此时为null, 其它两个条件满足一个
            if (this.stream == null && (this.released || connection.noNewStreams)) {
                //就可以执行release函数
                release(connection);
                if (connection.streamCount > 0) {
                    routeSelector = null;
                }

                //idle的RealConnection可以在下文被关闭
                if (connection.allocations.isEmpty()) {
                    connection.idleAtNanos = System.nanoTime();
                    if (Internal.instance.connectionBecameIdle(connectionPool, connection)) {
                        connectionToClose = connection;
                    }
                }
                connection = null;
            }
        }
    }

    if (connectionToClose != null) {
        Util.closeQuietly(connectionToClose.getSocket());
    }
}

//最后看看release函数
private void release(RealConnection connection) {
    for (int i = 0, size = connection.allocations.size(); i < size; i++) {
        Reference<StreamAllocation> reference = connection.allocations.get(i);
        //移除该StreamAllocation对应的引用
        //解决我们遇到的问题
        if (reference.get() == this) {
            connection.allocations.remove(i);
            return;
        }
    }
    throw new IllegalStateException();
}

到此,我们终于知道出现该问题的原因及对应的解决方案了。

上述代码省略了HttpURLConnection及底层OkHttp的许多流程,
仅给出了重要的部分,后续我会专门写一篇博客来补充分析这部分代码。


这个问题说实话,个人感觉并不是很重要,
但想真正明白原理,还是需要细致阅读源码的。
一旦真正搞懂,确实有点GAI爷歌里的感觉:
一往无前虎山行,拨开云雾见光明。

作者:Gaugamela 发表于2017/11/8 21:31:57 原文链接
阅读:22 评论:0 查看评论

从零开始前端学习[45]:js中的所谓的事件类型,鼠标事件,表单事件,键盘事件以及系统事件

$
0
0

js中的所谓的事件类型

  1. 鼠标事件
  2. 表单事件
  3. 键盘事件
  4. 系统事件

提示:
博主:章飞_906285288
博客地址:http://blog.csdn.net/qq_29924041


鼠标事件

可以想象一下, 我们平时在使用鼠标的时候都是有哪些操作的,单击?双击?移动?亦或者按下,抬起,移入,移出等等一系列的事件,下面就主要针对鼠标的事件进行一点点小测试

事件类型 相关函数
点击事件 onclick
移入/移出 onmouseover/onmouseout
移入/移出 onmouseenter/onmouseleave
双击事件 ondbclick
鼠标按下事件 onmousedown
鼠标弹起事件 onmousedown
鼠标移动事件 onmousemove
鼠标抬起事件 onmouseup

上面主要有两个需要注意的地方,一个就是ondbclick,是鼠标双击事件,还有鼠标的移入移出是有两个对称函数的,一个就onmouseenter/onmouseleave,另外一个就是onmouseover和onmouseout,他们的使用都是对称的。

代码如下所示:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <meta charset="UTF-8"><!--申明当前网页的编码集UTF-8-->
  <meta name="Generator" content="EditPlus®">   <!--编辑器的名称-->
  <meta name="Author" content="作者是谁">       
  <meta name="Keywords" content="关键词">
  <meta name="Description" content="描述和简介">
  <style type="text/css">                                        
        body,dl,dd,dt,p,h1,h2,h3,h4,h5,h6{ margin: 0;}
        ul,ol{margin: 0; list-style: none; padding: 0;}
        a{ text-decoration: none; }
        *{ margin: 0; padding: 0; }
        .main{width: 700px;margin: 50px auto;box-shadow: 0 0 10px 0px blue;padding: 10px}
        p{width: 150px;height: 150px;box-shadow: 0 0  10px 0px deeppink;text-align: center;line-height: 150px;margin: 10px}
  </style>
</head>
<body>
  <div class="main">
    <p id="mouse_event_1">1</p>
    <p id="mouse_event_2">2</p>
    <p id="mouse_event_3">3</p>
  </div>

<script>
  var mouse_event_1 = document.getElementById("mouse_event_1");
  var mouse_event_2 = document.getElementById("mouse_event_2");
  var mouse_event_3 = document.getElementById("mouse_event_3");
  mouse_event_1.onclick = function () {
      mouse_event_1.innerHTML = "onclick Event";
  }
//  mouse_event_1.onmouseover = function () {
//      mouse_event_1.innerHTML = "onmouseover";
//  }
//  mouse_event_1.onmouseout = function () {
//      mouse_event_1.innerHTML = "onmouseout";
//  }
  mouse_event_1.onmouseleave = function () {
      mouse_event_1.innerHTML = "onmouseleave";
  }
  mouse_event_1.onmouseenter = function () {
      mouse_event_1.innerHTML = "onmouseenter";
  }
  mouse_event_2.onmousedown = function () {
      mouse_event_2.innerHTML = "onmousedown"
  }
  mouse_event_2.onmouseup = function () {
      mouse_event_2.innerHTML = "onmouseup"
  }
  mouse_event_3.ondblclick = function () {
      mouse_event_3.innerHTML = "ondbclick"
  }
</script>
</body>
</html>

这里写图片描述


表单事件

之前我们学了很多表单相关的东西,input等标签,那这个表单事件是什么??也就是对这些表单元素提交相关操作的时候,所需要的一些事件信息,那这些表单事件有哪些分类呢???
如下所示:

  • 元素失去焦点事件
  • 元素获得焦点事件
  • 当用户改变表单内容的事件
  • 重置按钮被点击之后的事件
  • 提交按钮被点击之后的事件

如下表所示:

事件类型 相关函数
元素失去焦点 onblur
元素获得焦点 onfocus
用户改变表单的内容 onchange
用户点击重置按钮 onreset
提交按钮被点击 onsubmit
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <meta charset="UTF-8"><!--申明当前网页的编码集UTF-8-->
  <meta name="Generator" content="EditPlus®">   <!--编辑器的名称-->
  <meta name="Author" content="作者是谁">       
  <meta name="Keywords" content="关键词">
  <meta name="Description" content="描述和简介">
  <style type="text/css">                                        
        body,dl,dd,dt,p,h1,h2,h3,h4,h5,h6{ margin: 0;}
        ul,ol{margin: 0; list-style: none; padding: 0;}
        a{ text-decoration: none; }
        *{ margin: 0; padding: 0; }
    .main{width: 800px;margin: 40px auto;box-shadow: 0 0 10px 0 deeppink}
    form{margin: auto}
    p{height: 50px;text-align: center;}
    .main #text{width: 300px;height: 40px}
    .main #btn_submit{width: 50px;height: 40px}
    .main #btn_reset{width: 50px;height: 40px}
  </style>
</head>
<script>
    function myFunction(){
        var x = document.getElementById("text");
        x.value=x.value.toUpperCase();
    }
</script>
<body>
  <div class="main">
    <form action="">
      <p id="output">output</p>
      <input id="text" type="text" onchange="myFunction()">
      <input id="btn_submit" type="submit">
      <input id="btn_reset" type="reset">
    </form>
  </div>
  <script>
    var text = document.getElementById("text");
    var btn_submit = document.getElementById("btn_submit");
    var btn_reset = document.getElementById("btn_reset");
    var output = document.getElementById("output");
    text.onblur = function () {
        output.innerHTML = "onblur"
    }
    text.onfocus = function () {
        output.innerHTML = "onfocus";
    }
    text.onchange = function () {
        output.innerHTML = "onchange";
    }
    btn_submit.onclick = function () {
        console.log("onsubmit");
    }
    btn_reset.onreset = function () {
        console.log("onreset");
    }

  </script>
</body>
</html>

注意以上,如果测试onchange的时候,需要将其他的都给注释掉,只留下onchange对应的表单相关的信息,因为会造成冲突
还有就是表单的onreset和onsubmit,因为表单提交或者重置后浏览器会清除掉相关信息,这个时候也不会有打印,一般这两个事件并不会经常使用到,因为他们都可以使用onclick来进行代替


键盘事件

键盘事件,顾名思义,也就是跟键盘相关的一些事件
主要有以下几个分类:

事件类型 相关函数
onkeydown 某个键盘的键被按下
onkeypress 某个键盘的键被按下或者按住
onkeyup 某个键盘的键被松开

键盘事件可能使用的不太多,暂时不赘述


系统事件

对于系统事件,主要是有以下几个分类

事件类型 相关函数
onload 某个页面或图像被完成加载,整个html页面,只允许有一次onload的过程
onresize 窗口或框架被调整尺寸的时候
onselect 文本被选中的时候的事件
onerror 当加载文档或图像时发生某个错误,这个就是文档加载路径出现了问题的时候

关于以上几个系统事件,其实使用都是比较简单的,这个可以结合css里面的resize等属性来进行联合使用监听。在这里也不赘述。

作者:qq_29924041 发表于2017/11/8 21:47:04 原文链接
阅读:9 评论:0 查看评论

BZOJ2208 [Jsoi2010]连通数

$
0
0

标签:tarjan,bitset,拓扑排序

Description


Input

输入数据第一行是图顶点的数量,一个正整数N。 接下来N行,每行N个字符。第i行第j列的1表示顶点i到j有边,0则表示无边。

Output

输出一行一个整数,表示该图的连通数。

Sample Input

3
010
001
100

Sample Output

9

HINT

对于100%的数据,N不超过2000。

 

分析:

Tarjan缩点重新构图

F[i][j]表示i和j点之间的联通情况,然后按照拓扑序从后往前整体取或,最后用每个联通块的大小来更新答案

Code

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<algorithm>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define dep(i,a,b) for(int i=a;i>=b;i--)
#define ll long long
#define mem(x,num) memset(x,num,sizeof x)
#ifdef WIN32
#define LL "%I64d"
#else
#define LL "%lld"
#endif
using namespace std;
inline ll read()
{
	ll f=1,x=0;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
const int maxn=2006;
int n,cnt=0,scc,top=0,tim,lim,ans=0;
int head[maxn],h[maxn],r[maxn],dfn[maxn],low[maxn],que[maxn],inque[maxn];
int belong[maxn],hav[maxn],mark[maxn][maxn];
char ch[maxn];
struct edge{int to,next;}e[4000006],ed[4000006];
#define reg(x) for(int i=head[x];i;i=e[i].next)
#define v e[i].to
#define newreg(x) for(int i=h[x];i;i=ed[i].next)
#define newv ed[i].to
void tarjan(int x)
{
	int now=0;
	dfn[x]=low[x]=++tim;que[++top]=x;inque[x]=1;
	reg(x)
	    if(!dfn[v]){tarjan(v);low[x]=min(low[x],low[v]);}
	    else if(inque[v])low[x]=min(low[x],dfn[v]);
	if(low[x]==dfn[x]){
	    scc++;
		while(now!=x){
		    now=que[top--];
			inque[now]=0;belong[now]=scc;hav[scc]++;
		}
	}
}

void rebuild()
{
	cnt=0;
	rep(k,1,n)
	    reg(k)
	        if(belong[k]!=belong[v])ed[++cnt]=(edge){belong[k],h[belong[v]]},h[belong[v]]=cnt,r[belong[k]]++;
}
int main()
{
	n=read();
	lim=n/30+1;
	rep(i,1,n){
		scanf("%s",ch);
		rep(j,1,n)
		    if(ch[j-1]-'0')e[++cnt]=(edge){j,head[i]},head[i]=cnt;
    }
    rep(i,1,n)
        if(!dfn[i])tarjan(i);
    rebuild();
    top=0;int now;
    rep(i,1,n)mark[belong[i]][i/30+1]|=(1<<(i%30));
    rep(i,1,scc)if(!r[i])que[++top]=i;
    while(top){
    	now=que[top--];
    	newreg(now){
    		rep(j,1,lim)mark[newv][j]|=mark[now][j];
    	    r[newv]--;
    	    if(!r[newv])que[++top]=newv;
    	}
    }
    rep(i,1,scc)
        rep(j,1,n)
            if((mark[i][j/30+1])&(1<<(j%30)))ans+=hav[i];
    cout<<ans<<endl;
    return 0;
}


作者:qwerty1125 发表于2017/11/8 21:51:48 原文链接
阅读:0 评论:0 查看评论

卷积神经网络入门详解

$
0
0

  本文主要内容为 CS231n 课程的学习笔记,主要参考 学习视频 和对应的 课程笔记翻译 ,感谢各位前辈对于深度学习的辛苦付出。在这里我主要记录下自己觉得重要的内容以及一些相关的想法,希望能与大家多多交流~

0. 回顾之前所写过的博客

  在之前的博客《十四、卷积神经网络(1):介绍卷积神经网络》《十五、卷积神经网络(2):卷积神经网络的结构》中只是介绍性的阐述了一些关于卷积神经网络的知识。这主要因为《Neural Networks and Deep Learning》这本书中的大部分章节在介绍神经网络,而仅在最后一个部分介绍了卷积神经网络,所以仅介绍了卷积神经网络中的相关概念,如“感受野”、“卷积层”、“池化层”、“权值共享”等等,并仅仅介绍了一种较为简单的卷积神经网络结构。值得注意的是,在这两节中的图像也是单通道的图像,并没有考虑多通道图像。

  在本文中将对卷积神经网络中的多个问题具体展开讲解。

1. 卷积神经网络结构概述

  如果用全连接神经网络处理大尺寸图像具有三个明显的缺点:首先将图像展开为向量会丢失空间信息;其次参数过多效率低下,同时大量的参数也很快会导致网络过拟合。所以采用卷积神经网络是一种很自然的想法。

  与常规神经网络不同,卷积神经网络的各层中的神经元是3维排列的:宽度、高度和深度(这里的深度指的是激活数据体的第三个维度,而不是整个网络的深度,整个网络的深度指的是网络的层数)。举个例子,CIFAR-10中的图像是作为卷积神经网络的输入,该数据体的维度是32x32x3(宽度,高度和深度)。我们将看到,层中的神经元将只与前一层中的一小块区域连接,而不是采取全连接方式。对于用来分类CIFAR-10中的图像的卷积网络,其最后的输出层的维度是1x1x10,因为在卷积神经网络结构的最后部分将会把全尺寸的图像压缩为包含分类评分的一个向量,向量是在深度方向排列的。下面是例子:


图 1. 全连接神经网络与卷积神经网络的对比

左边是一个3层的神经网络。右边是一个卷积神经网络,图例中网络将它的神经元都排列成3个维度(宽、高和深度)。卷积神经网络的每一层都将3D的输入数据变化为神经元3D的激活数据并输出。在这个例子中,红色的输入层装的是图像,所以它的宽度和高度就是图像的宽度和高度,它的深度是3(代表了红、绿、蓝3种颜色通道),与红色相邻的蓝色部分是经过卷积和池化之后的激活值(也可以看做是神经元) ,后面是接着的卷积池化层。

2. 构建卷积神经网络的各种层

  卷积神经网络主要由这几类层构成:输入层、卷积层,ReLU层、汇聚(Pooling)层和全连接层(全连接层和常规神经网络中的一样)。通过将这些层叠加起来,就可以构建一个完整的卷积神经网络。在实际应用中往往将卷积层与ReLU层共同称之为卷积层。具体说来,卷积层和全连接层(CONV/FC)对输入执行变换操作的时候,不仅会用到激活函数,还会用到很多参数(神经元的突触权值和偏差)。而ReLU层和汇聚层则是进行一个固定不变的函数操作。卷积层和全连接层中的参数会随着梯度下降被训练,这样卷积神经网络计算出的分类评分就能和训练集中的每个图像的标签吻合了。

2.1 卷积层

  卷积层是构建卷积神经网络的核心层,它产生了网络中大部分的计算量。注意是计算量而不是参数量。

2.1.1 卷积层作用

  1. 滤波器的作用或者说是卷积的作用。卷积层的参数是有一些可学习的滤波器集合构成的。每个滤波器在空间上(宽度和高度)都比较小,但是深度和输入数据一致。直观地来说,网络会让滤波器学习到当它看到某些类型的视觉特征时就激活,具体的视觉特征可能是某些方位上的边界,或者在第一层上某些颜色的斑点,甚至可以是网络更高层上的蜂巢状或者车轮状图案。

  2. 可以被看做是神经元的一个输出。神经元只观察输入数据中的一小部分,并且和空间上左右两边的所有神经元共享参数(因为这些数字都是使用同一个滤波器得到的结果)。

  3. 降低参数的数量。这个由于卷积具有“权值共享”这样的特性,可以降低参数数量,达到降级计算开销,防止由于参数过多而造成过拟合。

2.1.2 感受野

  在处理图像这样的高维度输入时,让每个神经元都与前一层中的所有神经元进行全连接是不现实的。相反,我们让每个神经元只与输入数据的一个局部区域连接。该连接的空间大小叫做神经元的感受野(receptive field),它的尺寸是一个超参数(其实就是滤波器的空间尺寸)。在深度方向上,这个连接的大小总是和输入量的深度相等。需要再次强调的是,我们对待空间维度(宽和高)与深度维度是不同的:连接在空间(宽高)上是局部的,但是在深度上总是和输入数据的深度一致,如图2 所示


图 2. 举例说明感受野的连接及尺寸说明

  在图 2 中展现的卷积神经网络的一部分,其中的红色为输入数据,假设输入数据体尺寸为[32x32x3](比如CIFAR-10的RGB图像),如果感受野(或滤波器尺寸)是5x5,那么卷积层中的每个神经元会有输入数据体中[5x5x3]区域的权重,共5x5x3=75个权重(还要加一个偏差参数)。注意这个连接在深度维度上的大小必须为3,和输入数据体的深度一致。其中还有一点需要注意,对应一个感受野有75个权重,这75个权重是通过学习进行更新的,所以很大程度上这些权值之间是不相等(也就是不是像想象的那样,前面的输入层的三个层对应的是通过以卷积模板)。在这里相当于前面的每一个层对应一个传统意义上的卷积模板,每一层与自己卷积模板做完卷积之后,再将各个层的结果加起来,再加上偏置,注意是一个偏置,无论输入输入数据是多少层,一个神经元就对应一个偏置。

2.1.3 神经元的空间排列

  感受野讲解了卷积层中每个神经元与输入数据体之间的连接方式,但是尚未讨论输出数据体中神经元的数量,以及它们的排列方式。3个超参数控制着输出数据体的尺寸:深度(depth),步长(stride)和零填充(zero-padding)。

  (1) 输出数据体的深度是一个超参数:它和使用的滤波器的数量一致,而每个滤波器在输入数据中寻找一些不同的东西,即图像的某些特征。如图2 所示,将沿着深度方向排列、感受野相同的神经元集合称为深度列(depth column),也有人使用纤维(fibre)来称呼它们。

  (2) 在滑动滤波器的时候,必须指定步长。当步长为1,滤波器每次移动1个像素。当步长为2(或者不常用的3,或者更多,这些在实际中很少使用),滤波器滑动时每次移动2个像素。这个操作会让输出数据体在空间上变小。

  (3) 有时候将输入数据体用0在边缘处进行填充是很方便的。这个零填充(zero-padding)的尺寸是一个超参数。零填充有一个良好性质,即可以控制输出数据体的空间尺寸(最常用的是用来保持输入数据体在空间上的尺寸,这样输入和输出的宽高都相等)。

  输出数据体在空间上的尺寸可以通过输入数据体尺寸(W1×H1×D1),卷积层中神经元的感受野尺寸(F),步长(S),滤波器数量(K)和零填充的数量(P)的函数来计算输出数据体的尺寸为W2×H2×D2

W2=(W1F+2P)/S+1H2=(H1F+2P)/S+1D2=K

  一般说来,当步长S=1时,零填充的值是P=(F-1)/2,这样就能保证输入和输出数据体有相同的空间尺寸。

  步长的限制:注意这些空间排列的超参数之间是相互限制的。举例说来,当输入尺寸W=10,不使用零填充则P=0,滤波器尺寸F=3,这样步长S=2就行不通,因为(W-F+2P)/S+1=(10-3+0)/2+1=4.5,结果不是整数,这就是说神经元不能整齐对称地滑过输入数据体。因此,这些超参数的设定就被认为是无效的,一个卷积神经网络库可能会报出一个错误,或者修改零填充值来让设置合理,或者修改输入数据体尺寸来让设置合理,或者其他什么措施。在后面的卷积神经网络结构小节中,读者可以看到合理地设置网络的尺寸让所有的维度都能正常工作,这件事可是相当让人头痛的。而使用零填充和遵守其他一些设计策略将会有效解决这个问题。

2.1.4 参数共享

  在卷积层中使用参数共享是用来控制参数的数量。假如在一个卷积核中,每一个感受野采用的都是不同的权重值(卷积核的值不同),那么这样的参数两是十分巨大的。

  作一个合理的假设:如果一个特征在计算某个空间位置(x,y)的时候有用,那么它在计算另一个不同位置(x2,y2)的时候也有用。基于这个假设,可以显著地减少参数数量。换言之,就是将深度维度上一个单独的2维切片看做深度切片(depth slice),比如一个数据体尺寸为[55x55x96]的就有96个深度切片,每个尺寸为[55x55]。在每个深度切片上的神经元都使用同样的权重和偏差。在这样的参数共享下,例子中的第一个卷积层就只有96个不同的权重集了,一个权重集对应一个深度切片,共有96x11x11x3=34,848个不同的权重,或34,944个参数(+96个偏差)。在每个深度切片中的55x55个权重使用的都是同样的参数。

  在反向传播的时候,都要计算每个神经元对它的权重的梯度,但是需要把同一个深度切片上的所有神经元对权重的梯度累加,这样就得到了对共享权重的梯度。这样,每个切片只更新一个权重集。这样做的原因可以通过下面这张图进行解释


图 3. 将卷积层用全连接层的形式表示

如上图所示,左侧的神经元是将感受野展开为一列并串联起来(就是展开排成一列,同一层神经元之间不连接)。右侧的 Deep1i 是深度为1的神经元的第 i 个, Deep2i 是深度为1的神经元的第 i 个,同一个深度的神经元的权值都是相同的,黄色的都是相同的(上面4个与下面4个的参数相同),蓝色都是相同的。所以现在回过头来看上面说的卷积神经网络的反向传播公式对梯度进行累加求和也是基于这点考虑(同一深度的不同神经元共用一组参数,所以累加);而每个切片只更新一个权重集的原因也是这样的,因为从图3 中可以看到,不同深度的神经元不会公用相同的权重,所以只能更新一个权重集。

  注意,如果在一个深度切片中的所有权重都使用同一个权重向量,那么卷积层的前向传播在每个深度切片中可以看做是在计算神经元权重和输入数据体的卷积(这就是“卷积层”名字由来)。这也是为什么总是将这些权重集合称为滤波器(filter)(或卷积核(kernel)),因为它们和输入进行了卷积。

  注意,有时候参数共享假设可能没有意义,特别是当卷积神经网络的输入图像是一些明确的中心结构时候。这时候我们就应该期望在图片的不同位置学习到完全不同的特征。一个具体的例子就是输入图像是人脸,人脸一般都处于图片中心。你可能期望不同的特征,比如眼睛特征或者头发特征可能(也应该)会在图片的不同位置被学习。在这个例子中,通常就放松参数共享的限制,将层称为局部连接层(Locally-Connected Layer)。

2.1.5 卷积层的超参数及选择

  由于参数共享,每个滤波器包含 FFD1 个权重,卷积层一共有 FFD1K个权重和 K 个偏置。在输出数据体中,第d个深度切片(空间尺寸是W2×H2),用第d个滤波器和输入数据进行有效卷积运算的结果(使用步长S),最后在加上第d个偏差。

  对这些超参数,常见的设置是 F=3S=1P=1。同时设置这些超参数也有一些约定俗成的惯例和经验,可以在下面的卷积神经网络结构章节中查看。

2.1.6 卷积层演示

  因为3D数据难以可视化,所以所有的数据(输入数据体是蓝色,权重数据体是红色,输出数据体是绿色)都采取将深度切片按照列的方式排列展现。输入数据体的尺寸是W1=5,H1=5,D1=3,卷积层参数K=2,F=3,S=2,P=1。就是说,有2个滤波器,滤波器的尺寸是33,它们的步长是2.因此,输出数据体的空间尺寸是(53+2)/2+1=3。注意输入数据体使用了零填充P=1,所以输入数据体外边缘一圈都是0。下面的例子在绿色的输出激活数据上循环演示,展示了其中每个元素都是先通过蓝色的输入数据和红色的滤波器逐元素相乘,然后求其总和,最后加上偏差得来。


图 4. 卷积层演示过程

2.1.7 用矩阵乘法实现卷积

  卷积运算本质上就是在滤波器和输入数据的局部区域间做点积。卷积层的常用实现方式就是利用这一点,将卷积层的前向传播变成一个巨大的矩阵乘法。

  (1) 输入图像的局部区域被 im2co l操作拉伸为列。比如,如果输入是[227x227x3],要与尺寸为11x11x3的滤波器以步长为4进行卷积,就取输入中的[11x11x3]数据块,然后将其拉伸为长度为11x11x3=363的列向量。重复进行这一过程,因为步长为4,所以输出的宽高为(227-11)/4+1=55,所以得到im2col操作的输出矩阵 Xcol 的尺寸是[363x3025],其中每列是拉伸的感受野,共有55x55=3,025个。注意因为感受野之间有重叠,所以输入数据体中的数字在不同的列中可能有重复。

  (2) 卷积层的权重也同样被拉伸成行。举例,如果有96个尺寸为[11x11x3]的滤波器,就生成一个矩阵Wrow,尺寸为[96x363]。

  (3) 现在卷积的结果和进行一个大矩阵乘 np.dot(Wrow,Xcol) 是等价的了,能得到每个滤波器和每个感受野间的点积。在我们的例子中,这个操作的输出是[96x3025],给出了每个滤波器在每个位置的点积输出。注意其中的 np.dot 计算的是矩阵乘法而不是点积。

  (4) 结果最后必须被重新变为合理的输出尺寸[55x55x96]。

  这个方法的缺点就是占用内存太多,因为在输入数据体中的某些值在Xcol中被复制了多次。但是,其优点是矩阵乘法有非常多的高效实现方式,我们都可以使用(比如常用的BLAS API)。还有,同样的im2col思路可以用在汇聚操作中。反向传播:卷积操作的反向传播(同时对于数据和权重)还是一个卷积(但是和空间上翻转的滤波器)。使用一个1维的例子比较容易演示。这两部分中,不是很懂如何用矩阵的形式进行汇聚操作和反向传播。

2.1.8 其他形式的卷积操作

  1x1卷积:一些论文中使用了1x1的卷积,这个方法最早是在论文Network in Network中出现。人们刚开始看见这个1x1卷积的时候比较困惑,尤其是那些具有信号处理专业背景的人。因为信号是2维的,所以1x1卷积就没有意义。但是,在卷积神经网络中不是这样,因为这里是对3个维度进行操作,滤波器和输入数据体的深度是一样的。比如,如果输入是[32x32x3],那么1x1卷积就是在高效地进行3维点积(因为输入深度是3个通道);另外的一种想法是将这种卷积的结果看作是全连接层的一种实现方式,详见本文2.4.2 部分。

  扩张卷积:最近一个研究(Fisher Yu和Vladlen Koltun的论文)给卷积层引入了一个新的叫扩张(dilation)的超参数。到目前为止,我们只讨论了卷积层滤波器是连续的情况。但是,让滤波器中元素之间有间隙也是可以的,这就叫做扩张。如图5 为进行1扩张。


图 5. 扩张卷积的例子及扩张前后的叠加效果

在某些设置中,扩张卷积与正常卷积结合起来非常有用,因为在很少的层数内更快地汇集输入图片的大尺度特征。比如,如果上下重叠2个3x3的卷积层,那么第二个卷积层的神经元的感受野是输入数据体中5x5的区域(可以成这些神经元的有效感受野是5x5,如图5 所示)。如果我们对卷积进行扩张,那么这个有效感受野就会迅速增长。

2.2 汇聚层 (池化层)

  通常,在连续的卷积层之间会周期性地插入一个汇聚层。它的作用是逐渐降低数据体的空间尺寸,这样的话就能减少网络中参数的数量,使得计算资源耗费变少,也能有效控制过拟合。汇聚层使用MAX操作,对输入数据体的每一个深度切片独立进行操作,改变它的空间尺寸。最常见的形式是汇聚层使用尺寸2x2的滤波器,以步长为2来对每个深度切片进行降采样,将其中75%的激活信息都丢掉。每个MAX操作是从4个数字中取最大值(也就是在深度切片中某个2x2的区域),深度保持不变。

  汇聚层的一些公式:输入数据体尺寸 W1H1D1,有两个超参数:空间大小F和步长S;输出数据体的尺寸W2H2D2,其中

W2=(W1F)/S+1H2=(H1F)/S+1D2=D1

这里面与之前的卷积的尺寸计算的区别主要在于两点,首先在汇聚的过程中基本不会进行另补充;其次汇聚前后深度不变。

  在实践中,最大汇聚层通常只有两种形式:一种是F=3,S=2,也叫重叠汇聚(overlapping pooling),另一个更常用的是F=2,S=2。对更大感受野进行汇聚需要的汇聚尺寸也更大,而且往往对网络有破坏性。
  普通汇聚(General Pooling):除了最大汇聚,汇聚单元还可以使用其他的函数,比如平均汇聚(average pooling)或L-2范式汇聚(L2-norm pooling)。平均汇聚历史上比较常用,但是现在已经很少使用了。因为实践证明,最大汇聚的效果比平均汇聚要好。

  反向传播:回顾一下反向传播的内容,其中max(x,y)函数的反向传播可以简单理解为将梯度只沿最大的数回传。因此,在向前传播经过汇聚层的时候,通常会把池中最大元素的索引记录下来(有时这个也叫作道岔(switches)),这样在反向传播的时候梯度的路由就很高效。(具体如何实现我也不是很懂)。

  不使用汇聚层:很多人不喜欢汇聚操作,认为可以不使用它。比如在Striving for Simplicity: The All Convolutional Net一文中,提出使用一种只有重复的卷积层组成的结构,抛弃汇聚层。通过在卷积层中使用更大的步长来降低数据体的尺寸。有发现认为,在训练一个良好的生成模型时,弃用汇聚层也是很重要的。比如变化自编码器(VAEs:variational autoencoders)和生成性对抗网络(GANs:generative adversarial networks)。现在看起来,未来的卷积网络结构中,可能会很少使用甚至不使用汇聚层

2.3 归一化层

  在卷积神经网络的结构中,提出了很多不同类型的归一化层,有时候是为了实现在生物大脑中观测到的抑制机制。但是这些层渐渐都不再流行,因为实践证明它们的效果即使存在,也是极其有限的

2.4 全连接层

  这个常规神经网络中一样,它们的激活可以先用矩阵乘法,再加上偏差。

2.4.1 将卷积层转化成全连接层

  对于任一个卷积层,都存在一个能实现和它一样的前向传播函数的全连接层。权重矩阵是一个巨大的矩阵,除了某些特定块(这是因为有局部连接),其余部分都是零。而在其中大部分块中,元素都是相等的(因为参数共享)。这点具体可以参考图3,如果把全连接层转化成卷积层,以输出层的 Deep11 为例,与它有关的输入神经元只有上面四个,所以在权重矩阵中与它相乘的元素,除了它所对应的4个,剩下的均为0,这也就解释了为什么权重矩阵中有为零的部分;另外要把“将全连接层转化成卷积层”和“用矩阵乘法实现卷积”区别开,这两者是不同的,后者本身还是在计算卷积,只不过将其展开为矩阵相乘的形式,并不是”用全连接层转化成卷积层”,所以除非权重中本身有的权重为零,否则不会出现零元素。

2.4.2 将全连接层转化成卷积层

  相反,任何全连接层都可以被转化为卷积层。比如,一个K=4096的全连接层,输入数据体的尺寸是 7×7×512,这个全连接层可以被等效地看做一个F=7,P=0,S=1,K=4096的卷积层。换句话说,就是将滤波器的尺寸设置为和输入数据体的尺寸一致了。因为只有一个单独的深度列覆盖并滑过输入数据体,所以输出将变成1×1×4096,这个结果就和使用初始的那个全连接层一样了。这个实际上也很好理解,因为,对于其中的一个卷积滤波器,这个滤波器的的深度为512,也就是说,虽然这个卷积滤波器的输出只有1个,但是它的权重有7×7×512,相当于卷积滤波器的输出为一个神经元,这个神经元与上一层的所有神经元相连接,而这样与前一层所有神经元相连接的神经元一共有4096个,这不就是一个全连接网络嘛~

  在上述的两种变换中,将全连接层转化为卷积层在实际运用中更加有用。。假设一个卷积神经网络的输入是224x224x3的图像,一系列的卷积层和汇聚层将图像数据变为尺寸为7x7x512的激活数据体(在AlexNet中就是这样,通过使用5个汇聚层来对输入数据进行空间上的降采样,每次尺寸下降一半,所以最终空间尺寸为224/2/2/2/2/2=7)。从这里可以看到,AlexNet使用了两个尺寸为4096的全连接层,最后一个有1000个神经元的全连接层用于计算分类评分。我们可以将这3个全连接转化为3个卷积层:

  (1) 针对第一个连接区域是[7x7x512]的全连接层,令其滤波器尺寸为F=7,这样输出数据体就为[1x1x4096]了。

  (2) 针对第二个全连接层,令其滤波器尺寸为F=1,这样输出数据体为[1x1x4096]。

  (3) 对最后一个全连接层也做类似的,令其F=1,最终输出为[1x1x1000]。

  这样做的目的是让卷积网络在一张更大的输入图片上滑动,得到多个输出,这样的转化可以让我们在单个向前传播的过程中完成上述的操作。

  举个例子,如果我们想让224x224尺寸的浮窗,以步长为32在384x384的图片上滑动,把每个经停的位置都带入卷积网络,最后得到6x6个位置的类别得分。上述的把全连接层转换成卷积层的做法会更简便。如果224x224的输入图片经过卷积层和汇聚层之后得到了[7x7x512]的数组,那么,384x384的大图片直接经过同样的卷积层和汇聚层之后会得到[12x12x512]的数组(因为途径5个汇聚层,尺寸变为384/2/2/2/2/2 = 12)。然后再经过上面由3个全连接层转化得到的3个卷积层,最终得到[6x6x1000]的输出(因为(12 - 7)/1 + 1 = 6)。这个结果正是浮窗在原图经停的6x6个位置的得分!

  面对384x384的图像,让(含全连接层)的初始卷积神经网络以32像素的步长独立对图像中的224x224块进行多次评价,其效果和使用把全连接层变换为卷积层后的卷积神经网络进行一次前向传播是一样的。自然,相较于使用被转化前的原始卷积神经网络对所有36个位置进行迭代计算,使用转化后的卷积神经网络进行一次前向传播计算要高效得多,因为36次计算都在共享计算资源。

  这里有几个问题,首先为什么是以32为步长,如果我以64为步长呢?再或者如果我们想用步长小于32(如16)的浮窗怎么办

  首先回答其中的第一个问题。这个是因为其中一个有五个汇聚层,因为25=32,也就是在原始图像上的宽或者高增加 32 个像素,经过这些卷积和汇聚后,将变为一个像素。现在进行举例说明,虽然例子并没有32那么大的尺寸,但是意义都是一样的。假设原始图像的大小为 4×4,卷积核 F=3,S=1,P=1,而较大的图像的尺寸为 8×8,假设对图像进行两层的卷积池化,在较大的图像上以步长为4进行滑动(22=4),如图5所示


图 5. 以步长为4在原始图像上滑动取出4×4窗口再计算卷积的结果

对原始图像(图5左图红框)进行卷积得到的结果是图5右图红色框内的结果,使用步长为4在较大的图像获得的结果为图5中右侧四种颜色加在一起的样子。所以以步长为4在8x8的图片上滑动,把每个经停的位置都带入卷积网络,最后得到2x2个位置的卷积结果,但是如果直接使用卷积核 F=3,S=1,P=1进行两次卷积池化的话,得到的结果的大小显然也是4×4的。

  所以从获得结果来看,这两者是相同的,但是不同点在哪呢?如图6所示,是在整个图像上进行卷积运算和以步长为4在8x8的图片上滑动所经停的第一个位置,这两种方法使用相同的卷积核进行计算的对比图。

这里写图片描述
图6. 使用整张图像和一部分图像计算某一点处的卷积

如图6所示,左图代表使用整张图像时计算a点处的卷积,右图代表使用滑动的方法第一次经停图像上a点的卷积,两张图中的a点是同一个a点。虽然同一个卷积模板进行计算,但是在计算卷积的过程是不同的!因为在右图中a的右侧及右下侧是0,而在左图中是原始的像素值,所以计算的卷积一定是不同的。但是要怎么理解这样的差别呢?这要从补零的意义讲起,补零是因为如果不补零的话,图像经过卷积之后的尺寸会小于原始的尺寸,补零可以保证图像的尺寸不变,所以归根结底补零实际上是一种图像填充的方法。左图中a的右边及右下角的像素是原始图像的像素,相当于在计算a点的时候,不是用0进行的补充,而是原始像素值进行补充,这样的不仅可以保持卷积前后图像的大小不变,而且可以这种图像填充方法得到的结果显然要比填0更接近与原始图像,保留的信息更多。

  小节

  (1) 用一整图像进行卷积和在较大的图像上通过滑动窗提取出一个个子图象进行卷积得到的效果是相同的。

  (2) 可以这样做的主要原因在于将最后的全连接层改写成了卷积层。

  (3) 在一整章图像做卷积的效率要远远高于在图像上滑动的效率,因为前者只需要依次前向传播,而后者需要多次

  (4) 用整张图像计算与滑动窗口的方法对比,所补充的零更少(如上所讲,不用零而是用在其旁边的像素代替),提取的信息损失的更少。

  即,用整张图像直接计算卷积不仅仅在效率上高于使用滑动窗口的方法,而且更多的保留了图像的细节,完胜!

  另外还可以得到另一个结论当在较大的图像上以步长为 2L进行滑动时,其效果与在有直接在大图像上进行卷积得到的结果上以步长为1移动是一样的。如图5中的大红色框对应小红色框,大黄色框对应小黄色框。所以当步长为64时,将相当于以步长为2在大图的卷积结果上移动。

  对于第二个问题,如果我非要以12为步长呢?是可以的,只是这个时候所获得结果不再是如图5的那种滑动的计算方式了。还是举例说明,不过为了方便说明改变了一下尺寸。将步长32改为4,将步长16改为2进行分析。假如说原始的输入图像为一个 4×4 的图像,现在将使用一个比原来大的图像,是一个8×8的图像,使用卷积核为 4×4 大小,步长为4,则在图像进行卷积运算的如图6左侧的4个部分(红黄绿蓝),而图6 右侧的是步长为2时与原始相比增加的部分。将图 6中两个部分相加就可以得到步长为2它时所有进行卷积运算的部分了。

这里写图片描述
图6 .步长为4时原始图像进行卷积的部分及将步长改为2时比原来多出的部分

  获得步长为2的时进行卷积的区域。首先像之前一样对原始图像做以4为步长的卷积,这时进行卷积的部分就是图6中左侧的部分;其次将原始图片沿宽度方向平移2个像素之后,依旧进行步长为4的卷积,这个时候进行卷积的部分为图6中的红色部分和绿色部分;然后沿高度方向平移2个像素之后,按步长为4进行卷积,这个时候进行卷积的部分为图6中的蓝色部分和黄色部分;最后沿高度方向和宽度方向同时移动2个像素,按步长为4进行卷积,这个时候进行卷积的部分为图6中的紫色部分。将这些部分加在一起就是进行卷积运算你得所有区域了。

  这个结果明显是无法通过像图5中的那样滑动得到了,这样的方法所需要进行卷积的区域要远远大于以4为步长时所需要就进行卷积运算的区域;后续的卷积都是在这一卷积的结果上进行的,所以后面的都会发生改变。

  综上,步长为32的正整数倍只是保证获得结果可以像图5那样滑动的获得的下限值。

3. 卷积神经网络的结构

  卷积神经网络通常是由三种层构成:卷积层,汇聚层(除非特别说明,一般就是最大值汇聚)和全连接层(简称FC)。ReLU激活函数也应该算是是一层,它逐元素地进行激活函数操作,常常将它与卷积层看作是同一层。

3.1 层的排列规律

  卷积神经网络最常见的形式就是将一些卷积层和ReLU层放在一起,其后紧跟汇聚层,然后重复如此直到图像在空间上被缩小到一个足够小的尺寸,在某个地方过渡成成全连接层也较为常见。最后的全连接层得到输出,比如分类评分等。换句话说,最常见的卷积神经网络结构如下:

INPUT[CONVRELU]N[POOL?]M[FCRELU]KFC

其中*指的是重复次数,POOL?指的是一个可选的汇聚层。其中N >=0,通常N<=3,M>=0,K>=0,通常K<3。例如,下面是一些常见的网络结构规律:

  • INPUT -> FC ,实现一个线性分类器,此处N = M = K = 0。

  • INPUT -> CONV -> RELU -> FC,单层的卷积神经网络

  • INPUT -> [CONV -> RELU -> POOL]*2 -> FC -> RELU -> FC,此处在每个汇聚层之间有一个卷积层,这种网络就是简单的多层的卷积神经网络。

  • INPUT -> [CONV -> RELU -> CONV -> RELU -> POOL]*3 -> [FC -> RELU]*2 -> FC ,此处每个汇聚层前有两个卷积层,这个思路适用于更大更深的网络(比如说这个思路就和VGG比较像),因为在执行具有破坏性的汇聚操作前,多重的卷积层可以从输入数据中学习到更多的复杂特征。

  最新进展:传统的将层按照线性进行排列的方法已经受到了挑战,挑战来自谷歌的Inception结构和微软亚洲研究院的残差网络(Residual Net)结构。这两个网络的特征更加复杂,连接结构也不同。

3.2 卷积层的大小选择

  几个小滤波器卷积层的组合比一个大滤波器卷积层好。假设你一层一层地重叠了3个3x3的卷积层(层与层之间有非线性激活函数)。在这个排列下,第一个卷积层中的每个神经元都对输入数据体有一个3x3的视野。第二个卷积层上的神经元对第一个卷积层有一个3x3的视野,也就是对输入数据体有5x5的视野。同样,在第三个卷积层上的神经元对第二个卷积层有3x3的视野,也就是对输入数据体有7x7的视野。假设不采用这3个3x3的卷积层,二是使用一个单独的有7x7的感受野的卷积层,那么所有神经元的感受野也是7x7,但是就有一些缺点。首先,多个卷积层与非线性的激活层交替的结构,比单一卷积层的结构更能提取出深层的更好的特征。其次,假设所有的数据有C个通道,那么单独的7x7卷积层将会包含C×(7×7×C)=49C2个参数,而3个3x3的卷积层的组合仅有3×(C×(3×3×C))=27C2个参数。直观说来,最好选择带有小滤波器的卷积层组合,而不是用一个带有大的滤波器的卷积层。前者可以表达出输入数据中更多个强力特征,使用的参数也更少。唯一的不足是,在进行反向传播时,中间的卷积层可能会导致占用更多的内存。

3.3 层的尺寸设置规律

  • 输入层 ,应该能被2整除很多次。常用数字包括32(比如CIFAR-10),64,96(比如STL-10)或224(比如ImageNet卷积神经网络),384和512。

  • 卷积层 ,应该使用小尺寸滤波器(比如3x3或最多5x5),使用步长S=1。还有一点非常重要,就是对输入数据进行零填充,这样卷积层就不会改变输入数据在空间维度上的尺寸。比如,当F=3,那就使用P=1来保持输入尺寸。当F=5,P=2,一般对于任意F,当P=(F-1)/2的时候能保持输入尺寸。如果必须使用更大的滤波器尺寸(比如7x7之类),通常只用在第一个面对原始图像的卷积层上。

  • 汇聚层 ,负责对输入数据的空间维度进行降采样。最常用的设置是用用2x2感受野(即F=2)的最大值汇聚,步长为2(S=2)。注意这一操作将会把输入数据中75%的激活数据丢弃(因为对宽度和高度都进行了2的降采样)。另一个不那么常用的设置是使用3x3的感受野,步长为2。最大值汇聚的感受野尺寸很少有超过3的,因为汇聚操作过于激烈,易造成数据信息丢失,这通常会导致算法性能变差。

  上文中展示的两种设置(卷积层F=3,P=1,汇聚层F=2,P=2)是很好的,因为所有的卷积层都能保持其输入数据的空间尺寸,汇聚层只负责对数据体从空间维度进行降采样。如果使用的步长大于1并且不对卷积层的输入数据使用零填充,那么就必须非常仔细地监督输入数据体通过整个卷积神经网络结构的过程,确认所有的步长和滤波器都尺寸互相吻合,卷积神经网络的结构美妙对称地联系在一起。

  为何使用零填充?使用零填充除了前面提到的可以让卷积层的输出数据保持和输入数据在空间维度的不变,还可以提高算法性能。如果卷积层值进行卷积而不进行零填充,那么数据体的尺寸就会略微减小,那么图像边缘的信息就会过快地损失掉。

  因为内存限制所做的妥协:在某些案例(尤其是早期的卷积神经网络结构)中,基于前面的各种规则,内存的使用量迅速飙升。例如,使用64个尺寸为3x3的滤波器对224x224x3的图像进行卷积,零填充为1,得到的激活数据体尺寸是[224x224x64]。这个数量就是一千万的激活数据,或者就是72MB的内存(每张图就是这么多,激活函数和梯度都是)。因为GPU通常因为内存导致性能瓶颈,所以做出一些妥协是必须的。在实践中,人们倾向于在网络的第一个卷积层做出妥协。例如,可以妥协可能是在第一个卷积层使用步长为2,尺寸为7x7的滤波器(比如在ZFnet中)。在AlexNet中,滤波器的尺寸的11x11,步长为4。

4. 案例学习

  下面是卷积神经网络领域中比较有名的几种结构:

  • LeNet ,第一个成功的卷积神经网络应用,是Yann LeCun在上世纪90年代实现的。当然,最著名还是被应用在识别数字和邮政编码等的LeNet结构。

  • AlexNet ,AlexNet卷积神经网络在计算机视觉领域中受到欢迎,它由Alex Krizhevsky,Ilya Sutskever和Geoff Hinton实现。AlexNet在2012年的ImageNet ILSVRC 竞赛中夺冠,性能远远超出第二名(16%的top5错误率,第二名是26%的top5错误率)。这个网络的结构和LeNet非常类似,但是更深更大,并且使用了层叠的卷积层来获取特征(之前通常是只用一个卷积层并且在其后马上跟着一个汇聚层)。

  • ZF Net ,Matthew Zeiler和Rob Fergus发明的网络在ILSVRC 2013比赛中夺冠,它被称为 ZFNet(Zeiler & Fergus Net的简称)。它通过修改结构中的超参数来实现对AlexNet的改良,具体说来就是增加了中间卷积层的尺寸让第一层的步长和滤波器尺寸更小

  • GoogLeNet ,ILSVRC 2014的胜利者是谷歌的Szeged等实现的卷积神经网络。它主要的贡献就是实现了一个奠基模块,它能够显著地减少网络中参数的数量(AlexNet中有60M,该网络中只有4M)。还有,这个论文中没有使用卷积神经网络顶部使用全连接层,而是使用了一个平均汇聚,把大量不是很重要的参数都去除掉了。GooLeNet还有几种改进的版本,最新的一个是Inception-v4

  • VGGNet ,ILSVRC 2014的第二名是Karen Simonyan和 Andrew Zisserman实现的卷积神经网络,现在称其为VGGNet。它主要的贡献是展示出网络的深度是算法优良性能的关键部分。他们最好的网络包含了16个卷积/全连接层。网络的结构非常一致,从头到尾全部使用的是3x3的卷积和2x2的汇聚。他们的预训练模型是可以在网络上获得并在Caffe中使用的。VGGNet**不好的一点是它耗费更多计算资源,并且使用了更多的参数,导致更多的内存占用(140M)。其中绝大多数的参数都是来自于第一个全连接层。后来发现这些全连接层即使被去除,对于性能也没有什么影响**,这样就显著降低了参数数量。

  • ResNet ,残差网络(Residual Network)是ILSVRC2015的胜利者,由何恺明等实现。它使用了特殊的跳跃链接,大量使用了批量归一化(batch normalization)。这个结构同样在最后没有使用全连接层。读者可以查看何恺明的的演讲(视频,PPT),以及一些使用Torch重现网络的实验。ResNet当前最好的卷积神经网络模型(2016年五月)。何开明等最近的工作是对原始结构做一些优化,可以看论文Identity Mappings in Deep Residual Networks,2016年3月发表。

4.1 VGGNet的细节

  我们进一步对VGGNet的细节进行分析学习。整个VGGNet中的卷积层都是以步长为1进行3x3的卷积,使用了1的零填充,汇聚层都是以步长为2进行了2x2的最大值汇聚。可以写出处理过程中每一步数据体尺寸的变化,然后对数据尺寸和整体权重的数量进行查看:

INPUT: [224x224x3]        memory:  224*224*3=150K   weights: 0
CONV3-64: [224x224x64]  memory:  224*224*64=3.2M   weights: (3*3*3)*64 = 1,728
CONV3-64: [224x224x64]  memory:  224*224*64=3.2M   weights: (3*3*64)*64 = 36,864
POOL2: [112x112x64]  memory:  112*112*64=800K   weights: 0
CONV3-128: [112x112x128]  memory:  112*112*128=1.6M   weights: (3*3*64)*128 = 73,728
CONV3-128: [112x112x128]  memory:  112*112*128=1.6M   weights: (3*3*128)*128 = 147,456
POOL2: [56x56x128]  memory:  56*56*128=400K   weights: 0
CONV3-256: [56x56x256]  memory:  56*56*256=800K   weights: (3*3*128)*256 = 294,912
CONV3-256: [56x56x256]  memory:  56*56*256=800K   weights: (3*3*256)*256 = 589,824
CONV3-256: [56x56x256]  memory:  56*56*256=800K   weights: (3*3*256)*256 = 589,824
POOL2: [28x28x256]  memory:  28*28*256=200K   weights: 0
CONV3-512: [28x28x512]  memory:  28*28*512=400K   weights: (3*3*256)*512 = 1,179,648
CONV3-512: [28x28x512]  memory:  28*28*512=400K   weights: (3*3*512)*512 = 2,359,296
CONV3-512: [28x28x512]  memory:  28*28*512=400K   weights: (3*3*512)*512 = 2,359,296
POOL2: [14x14x512]  memory:  14*14*512=100K   weights: 0
CONV3-512: [14x14x512]  memory:  14*14*512=100K   weights: (3*3*512)*512 = 2,359,296
CONV3-512: [14x14x512]  memory:  14*14*512=100K   weights: (3*3*512)*512 = 2,359,296
CONV3-512: [14x14x512]  memory:  14*14*512=100K   weights: (3*3*512)*512 = 2,359,296
POOL2: [7x7x512]  memory:  7*7*512=25K  weights: 0
FC: [1x1x4096]  memory:  4096  weights: 7*7*512*4096 = 102,760,448
FC: [1x1x4096]  memory:  4096  weights: 4096*4096 = 16,777,216
FC: [1x1x1000]  memory:  1000 weights: 4096*1000 = 4,096,000

TOTAL memory: 24M * 4 bytes ~= 93MB / image (only forward! ~*2 for bwd)
TOTAL params: 138M parameters

  注意,大部分的内存和计算时间都被前面的卷积层占用,大部分的参数都用在后面的全连接层,这在卷积神经网络中是比较常见的。在这个例子中,全部参数有140M,但第一个全连接层就包含了100M的参数

5. 计算上的考量

  在构建卷积神经网络结构时,最大的瓶颈是内存瓶颈,所以如何降低内存消耗量是一个值得思考的问题。三种内存占用来源:

  • 1 来自中间数据体尺寸:卷积神经网络中的每一层中都有激活数据体的原始数值,以及损失函数对它们的梯度(和激活数据体尺寸一致)。通常,大部分激活数据都是在网络中靠前的层中(比如第一个卷积层)。在训练时,这些数据需要放在内存中,因为反向传播的时候还会用到。但是在测试时可以聪明点:让网络在测试运行时候每层都只存储当前的激活数据,然后丢弃前面层的激活数据,这样就能减少巨大的激活数据量。这实际上是底层问题,在编写框架的过程中,设计者会进行这方面的考虑。

  • 2 来自参数尺寸:即整个网络的参数的数量,在反向传播时它们的梯度值,以及使用momentum、Adagrad或RMSProp等方法进行最优化时的每一步计算缓存。因此,存储参数向量的内存通常需要在参数向量的容量基础上乘以3或者更多。

  • 3 卷积神经网络实现还有各种零散的内存占用,比如成批的训练数据,扩充的数据等等。

  一旦对于所有这些数值的数量有了一个大略估计(包含激活数据,梯度和各种杂项),数量应该转化为以GB为计量单位。把这个值乘以4,得到原始的字节数(因为每个浮点数占用4个字节,如果是双精度浮点数那就是占用8个字节),然后多次除以1024分别得到占用内存的KB,MB,最后是GB计量。如果你的网络工作得不好,一个常用的方法是降低批尺寸(batch size),因为绝大多数的内存都是被激活数据消耗掉了。

作者:dugudaibo 发表于2017/11/8 15:52:14 原文链接
阅读:85 评论:0 查看评论

纪念碑谷2第一章(教学)

$
0
0

系统架构师-基础到企业应用架构-系统建模[上篇]

$
0
0

一、摘要

       本文主要从系统架构中的建模开始讲解,本文讲述的内容主要是我在工作和学习过程中的总结和经验,不足之处还请大家多多批评指出,有更好的建议也可以留言

说明。本意主旨是为不熟悉系统架构建模过程和不知道如何使用建模工具,或者不熟悉如何根据需求去建立模型的角度出发,简单的阐述了在系统架构的过程中我们应

该从什么样的角度出发去分析需求并且建立抽象模型。这应该说是架构师必备的技能。

       本文由浅入深,本篇将简单的介绍如何使用使用UML建模中的各个结构图与行为图,去完成抽象模型的建立。

二、本章内容

       1、摘要。

       2、本章内容。

       3、建模工具介绍及使用。

       4、建模中的抽象模型图。

       5、本质总结。

       6、系列进度。

       7、下篇预告。

三、建模工具介绍

      介绍建模工具之前,我们先来简单介绍下建模语言的定义。建模语言就是基于一系列规则、符号、图表、关键字的图形化或文本语言。建模语言的主要作用是对模

型的结构与行为进行描述。并且能够将知识和信息通过模型传递给熟悉该描述语言的人。

      当今的建模语言其实并不少,其中比较有规模的如下图:

      image

     不过最流行、最常用的当属UML建模语言(Unified Modeling Language) 统一建模语言。经过不断的发展,目前UML已成为业界公认的标准的建模语言。

     我们先来了解下UML建模语言的起源:

     回顾20世纪晚期--准确地说是1997年,OMG组织(Object Management Group对象管理组织)发布了统一建模语言(Unified Modeling Language,

UML)。UML的目标之一就是为开发团队提供标准通用的设计语言来开发和构建计算机应用。UML提出了一套IT专业人员期待多年的统一的标准建模符号。通过使用

UML,这些人员能够阅读和交流系统架构和设计规划--就像建筑工人多年来所使用的建筑设计图一样。

     到了21世纪--准确地说是2003年,UML已经获得了业界的认同。在我所见过的专业人员的简历中,75%都声称具备UML的知识。然而,在同绝大多数求职人员面

谈之后,可以明显地看出他们并不真正了解UML。通常地,他们将UML用作一个术语,或对UML一知半解。大家对UML缺乏理解的这种状况,促进我撰写这篇关于UML

建模。当阅读完本文时,您还不具备足够的知识可以在简历上声称自己掌握了UML,但是您已具有了进一步钻研该语言的良好起点。

四、建模中的抽象模型

     既然UML语言如此流行,本系列中也只用UML语言来进行建模,本系列中的后续章节也将基于UML建模图来完成相应的设计。

     学习过UML语言的开发人员都知道UML分为以下几类模型图:

     image

     通过上图我们知道UML的分类,分为结构型与行为型建模图形。下面的内容将详细的讲述每种建模图形的使用场景及如何使用。

    行为型:

     我们先从行为型的建模图形来开始讲起:

    1、用例图:

     我想用例图大家都应该基本上有所了解,只要使用过UML建模的除了基本的流程图基本上大家都会的使用外,用例图用过是最常见的一种建模图形。

     用例图中主要包含的元素:系统、参与者、用例、关系。

     用例图主要的应用场景:一般用例图用来描述需求中的系统应具有的功能,系统参与者(使用者,维护者、外部系统或者用户等)与系统如何交互进行一个模

型话的描述。

     用例图的目的:帮助开发团队以一种可视化的方式理解系统的功能需求。

     一般使用如下方式来进行操作:

     image 用来标识系统的参与者,任何与系统交互的对象,都可以叫参与者。

     image

     是用来描述系统中的某个模块与参与者的一次交互过程。

     系统参与者与用例之间的具体关系通过如下连线标示:

     image

     这几类不同的连线来标识不同的用例之间或者用例与参与者或者2个参与者直接直接的关系。

     UML定义了3类标准的关系:

     第一种:包含,通过一条直线链接2个用例,因此是用例之间的关系链接,表述了箭头的开始一端包含箭头指向的一端的用例。

     例如:image

     第二种:扩展,通过一个反向的直线来标识某个用例扩展了另外一个用例的行为,一般情况下箭头指向的用例即是被扩展的用例。

     例如: image

     第三种:泛化,用来标识具有同质关系的参与者与参与者或者用例与用例之间的关系,泛化类似继承关系。箭头指向的为父元素。

     例如:image

     除了以上的3中关系还有一种未列在规范关系的我们把它叫做关联关系。这种关系是用来描述用例与参与者直接的关系的。是通过一条直线来完成链接的,泛化关系

描述了链接的2个部分存在某种程度的交付。一般情况下,我们可以系统的功能情况分析出系统中的主动发和被动方。

     如何使用用例图:

     第一步:先把系统按照功能进行划分,比如一个简单的内容管理系统。先把他细化,细化成多个模块功能。每个模块的功能相对独立,但是可能又与另外一个有交

互。

     第二步:把功能需求抽象,达到高内聚,低耦合的标准,然后分析出该模块功能的参与者是什么,例如用户是谁?或者细分成角色,与该模块交互还可能是数据库?

等,把所有交互的对象分析出。

     第三步:把系统模块中的每个功能模块看是否能再按照子功能进行细分,细分后形成具体的用例。

     第四步:分析用例与参与者之间的关系,分析同质对象(参与者与参与者、用例与用例)之间的关系。

     第五步:根据以上四步完成建模。在建模的过程如果发现某块功能不清晰或者参与者不清晰,可重复前4步。

    2、类图:

     类图也是UML建模中最常用的一种结构图,类图用来标示系统的静态结构。静态结构是由类型及关系构成。

     类图表示不同的实体(人、事物和数据)如何彼此相关;换句话说,它显示了系统的静态结构。类图可用于表示逻辑类,逻辑类通常就是业务人员所谈及的事物种

类--摇滚乐队、CD、广播剧;或者贷款、住房抵押、汽车信贷以及利率。类图还可用于表示实现类,实现类就是程序员处理的实体。实现类图或许会与逻辑类图显示一

些相同的类。然而,实现类图不会使用相同的属性来描述,因为它很可能具有对诸如Vector和HashMap这种事物的引用。

     类图其实就是一个长方形,内部分成3个区域。每个区域的含义不同。

     image

      类图中也有命名空间的概念,是通过包来实现的如果想定义该类在某个命名空间中,则在定义类名时按照如下类似格式标示

      命名空间 :: 类名 [必须按照这样的形式才可以]。

      类图中的有3类修饰符,每种修饰符标示的含义不同。

      image

      具体用法如下:

      image

      理解成具体的类代码的格式如下:

      public class Product

      {

          Public string ProductName;

          public void GetProductLists(string sWhere)

          {

             //TODO….

          }

      }

      如果在类图中的属性定义与函数成员的定义是斜体表示的话,则表名该成员是虚成员。

      image 虚成员

      如果在类图中的属性定义与函数成员的定义是带下划线的话,则表名该成员是静态成员。

      image 静态成员

      当然这是最基本的类图,还有一种特殊的,类图支持参数化类型即是.NET中的特殊类型[泛型格式]标示。

      image 参数化类图

      具体的表示形式如:该符号在类的右上角有个长方形其中可输入类型如上图。

      类图中属性包含的元素:

      访问修饰符:Public、Protected、Private

      特性/属性名称:特性/属性名称

      类型:可以是自定义类型或者是系统类型。

      默认值:即特性/属性的默认值,如果有的话。

      重复性:可以用来定义多个对象的集合,特性值中包含的对象个数。

     类图中操作包含的元素:

      访问修饰符:Public、Protected、Private

      操作名称:函数名称

      操作列表:函数的参数列表。

      返回值:函数的返回值,如果有的话。

      函数参数列表中的参数方向:

      image

     类图之间的关联关系

      首先我们知道,我们在设计类的时候就是把独立的功能放在一个类中,不同的类之间进行交互,那么我们在类图中如何去表述这样的类之间的关系呢?

      类图直接的关系:

      1、关联关系:关联标识2个类直接存在关系。是通过一条线来表示,关联关系中包含了2种特殊的关系:聚合和组合

       聚合代表的2个类直接是has-a的关系,即部分与整体的关系,具体的图标通过一条虚线带有菱形箭头,箭头指向的方向即是整体的部分,代表该类包含另一部分。

      聚合例如:image 代表产品中具有ProductName这个成员。

      组合举例:组合关系的标示与聚合比较类似,唯一区别实心的菱形。

      组合例如:image

      组合与聚合的区别:

      在聚合关系中被包含对象可能不完全依赖容器对象,也就是说ProductName不完全依赖Product。如果Product对象销毁,但是可能ProductName对象没有被销

毁。可以这么想想产品的分类不会因为产品销毁而不存在。

       组合关系中则是比聚合的关联程度更高,Product完全包含ProductName。如果销毁Product时,那么ProductName也一定被销毁。产品从数据库被删除了,那

么与产品相关的的数据列属性也被删除了,这里只是举例子,可能不太合适。     

      类图之间的泛化关系

       泛化关系:存在2个类之间。一个类是另外一个类的子类,表示一个类是另外一个类的特例。

       表示方法:通过一个带有空的三角形箭头的线段标识,箭头指向父类型。

       image 表示火车和汽车是交通工具的子类型。

      类图之间的依赖关系

        依赖关系描述为:一个类型必须依靠另外一个类才能实现相应的功能。最简单的理解方式:依赖注入中的构造函数注入。

        具体的表示方法:一个带有箭头的虚线段。箭头方向标示被依赖的类型。

        例如:image

五、本章总结。

       本章主要是对UML有个简单的介绍及详细介绍了如何构建UML图形中的用例图与类图。这是我们在建模时常用的2类图形。也是必须掌握的建模图形。

同时通过本质我们应该大脑中对UML有个新的认识,UML建模可以让我多个角度的去分析问题,然后不断的改进设计,同时能很清晰的表达功能需求功能的分离和组合

关系。本文只是简单的抛砖引玉,不足之处,在所难免,请大家批评指出。

六、系列进度。

       1、系统架构师-基础到企业应用架构系列之--开卷有益

       2、系统架构师-基础到企业应用架构-系统建模[上篇]

       3、系统架构师-基础到企业应用架构-系统建模[中篇](上)

       4、系统架构师-基础到企业应用架构-系统建模[中篇](下)

       5、系统架构师-基础到企业应用架构-系统建模[下篇]

       不断更新中(请持续关注…)

七、下篇预告。

      下一篇中将介绍UML建模过程中其他的比较常用的UML建模图形:顺序图、组件图、状态图等。

作者:hegezhou 发表于2011/9/10 12:23:01 原文链接
阅读:2360 评论:2 查看评论

系统架构师-基础到企业应用架构-系统建模[中篇](上)

$
0
0

一、上章回顾

       上篇文章主要简单的介绍了建模中使用的标准建模语言UML的相关内容,包括用例图与类图的使用方法及如何建模。相信大家对UML建模语言已经有了初步的认

识,还请大家谨记UML不同的建模图形的用处。比如,用例图主要用来描述系统的功能需求。类图主要用来描述实体间的关系。谨记这些就可以帮助我们在系统架构的

过程中深入的分析。

       首先向大家道歉,上篇中有部分描述错误的地方,可能对大家造成一定的错误引导。

       image 这是上篇给出的图,我描述的是组合关系。

       特别更正为:

       image 这是正确的结果。箭头指向聚合类。描述的信息并无任何错误。希望能对大家指正。

二、摘要

       本文主要从系统架构中的建模开始讲解,本文讲述的内容主要是我在工作和学习过程中的总结和经验,不足之处还请大家多多批评指出,有更好的建议也可以留言

说明。本意主旨是为不熟悉系统架构建模过程和不知道如何使用建模工具,或者不熟悉如何根据需求去建立模型的角度出发,简单的阐述了在系统架构的过程中我们应

该从什么样的角度出发去分析需求并且建立抽象模型。这应该说是架构师必备的技能。

       本文由浅入深,本篇将简单的介绍如何使用使用UML建模中的各个结构图与行为图,去完成抽象模型的建立。

       本文主要讲解以下几个建模图形:顺序图、组件图、状态图、活动图、部署图。当然本文也只是讲述了基本理论介绍及如何设计使用,系统架构师-基础到企业应

用架构-系统建模[下篇] 将会详细的讲解通过具体实例讲解如何使用这些已经介绍的抽象模型图形去描述。

三、本章内容

       1、上章回顾。

       2、摘要。

       3、本章内容。

       4、建模中的抽象模型图。

       5、本章总结。

       6、系列进度。

       7、参考文献。

       8、下篇预告。

四、建模中的抽象模型图。

1、顺序图。

介绍

       顺序图也称序列图,主要用来系统中的某个流程的详细步骤。顺序图能够给出流程中一系列对象的交互顺序。通过顺序图可以让我们更好的了解如何实现某个用例

的方法。我们知道用例图用来描述系统的功能需求。而顺序图清晰的描述了某个用例也就是系统功能的的实现方法。

详解

       在顺序图中包含的元素:

       image

       对象:用来标识流程中的详细步骤中的对象。

       活动条:用来标识当前对象是活动的,如果想表示某个对象是活动的,那么必须使用一个虚线+活动图的形式来构建。

       例如我们现在要标示一个简单的做公交车的刷卡流程:

       image IC卡刷卡操作。

       相关解释说明:

       公交卡,首先放在刷卡终端上,终端读取卡中的余额信息,然后刷卡终端与终端中的扣款程序对象交互,扣款程序根据读取的余额信息,与刷卡终端中的固定刷卡

金额对比,如果当前IC卡的余额大雨刷卡终端的固定金额则,扣除金额,并且返回一个消息,提示刷卡成功的操作。

       途中的实线表示调用被调用对象的方法,虚线表示当被调用对象执行成功后,返回的虚线上表示返回值的逻辑名称,这样可以提高了可读性。

       在公交卡与活动条之间,应有一个虚线链接。

       在上图中我们使用了活动条,活动条作为生命线的一部分。我们并没有定义对象的创建和销毁,因此我们来看UML建模语言提供的描述对象的创建与销毁实例。

image

       上图中的X符号的图标代表的时候对象的销毁。创建对象通过new来创建,上图中,我用中文描述“创建对象”来完成对象的创建,那么在生命线下的的X符号代

表销毁对象,从内存中移除对象。当然这个对象的销毁对不同的开发语言有这不同的处理方式。C++中的销毁对象,必须调用析构函数来销毁对象。C#与JAVA语言中

则只是说明当前需要销毁的对象没有被其他的对象引用,那么这类语言编译器提供垃圾回收器来完成回收。

       注意:当某个对象引用了另外一个对象,该对象有责任销毁被引用对象并且必须显示销毁该被引用对象时,那么必须要显示的发送被引用对象销毁的通知消息。白

话文来说就是显示的调用被引用对象的销毁方法。

       顺序途中的同步与异步。

       顺序图中的同步与异步与我们平时书写代码中的同步与异步的解释意思差不多。这里不过多解释,通过图例说明:

       image 客户去餐厅吃饭,首先要点餐,必须等待点餐完了才能上菜。意思就是可以这样简单描述。A简单调用B方

法,必须等待,等到B方法执行完毕后,继续执行。

      image 函数A调用函数B,如果B需要的时间特别长,那么此时A可以去继续执行做其他的事情比如做和函

数C交互,等B函数执行完了,只需要回调通知A,B函数执行完了即可。在函数调用中的术语就是回调。

      UML建模语言中同步与异步消息的标识格式:

      image

      UML提供了一些顺序图的高级功能:例如可以通过顺序图实现流程的控制。具体的实现工具是通过UML提出的交互框来实现流程条件的控制。

      交互框其实就是定义了流程控制图中的控制逻辑,基于交互框定义流程执行的条件。如果满足这个条件,那么则执行交互框中已定义好的顺序步骤。否则不做任何

操作。交互框中除了定义流程控制的条件外,还有一些自己特殊的操作符,具体的操作符及其作用,如下列表:

      image

    每个关键字代表的含义都有相应的描述。大家应该都可以看明白,上述的所有含义都是针对交互框来说的。

   总结

    如果在系统功能中有特殊需求,那么顺序图中的交互框是可以支持嵌套的。嵌套交互框的话,会提高顺序图的复杂度,降低可读性。因此我们设计时的原则尽量把复

杂的流程拆分成几个简单的,分别绘制顺序图来完成相应步骤。

  2、组件图。

简介

    众所周知,组件图是用来描述系统中的各组件之间的关系。首先我们必须知道组件的定义是什么,然后组件之间有哪些关系。理清楚这些,我们在以后的设计中才能

派上用场。UML语言对组件的定义已发生了巨大变化。在之前的版本里面,UML如下定义组件的:

   UML1.1语言中对组件的描述:把某个文件或者可以运行的程序称之为组件。但是我们知道,UML出现组件图以前,组件一般用来描述COM组件或者其他的组件,因此造成冲突,所以随着后续UML语言的发布,修改了原有的含义。

   UML2.x语言中对组件的的描述:组件是独立的,是运行在一个系统中的封装单位,提供了一系列的服务。

   通过上述UML语言中的变迁,目前的理解是:一个系统,可以随意更换系统中的某个组建。而不会影响系统的运行。这可以理解为类似,大家熟悉IOC容器的都应该

知道,运行在IOC容器中的对象,可以看作组件,那么替换其中的提供某一服务的组件,只要满足该组件服务的相关契约就能自动完成替换。而不会影响系统的运行。

每个组件都封装了一些特殊的行为,实现了特定的服务。

   组件之间的关系有哪些呢?我们通过下图来看看,组件直接可能存在的关系:

   image  组件直接的关系基本上来说就这2种。下面会举例区别2中关系。

   组件图提供的服务:组件图为系统架构师提供了解决方案的自然形式。组件图允许架构师验证系统的必需功能是由组件来完成的。组件是可以复用的。

详解

   组件图中包含的元素:

   image

   下面我们分别讲解:

   (1)、组件:我们知道组件是组件图中最基本的组成元素,组件上面已经讲述了组件的定义。这里就不在多介绍,组件图组成的基本单位即组件。

   (2)、容器:可以为多个组件提供服务的管理容器,容器中的组件相互交互。

   (3)、包:可以看作一个子系统,其实也可以看作是特殊的组件。

   (4)、约束:用于定义接口规范。

   (5)、给组件图中的相应元素添加相应注释信息。

    image 组件上可以定义自己的接口。例如上图,人这个组件提供了2个接口。Thinking与Sleep接口。

   组件关系的建模:

   我们来看看组件之间的关系的表示,根据上面讲解的组件的关系有依赖和泛化,参考类图中的依赖和泛化。

   image 依赖关系,标识一个组件依赖另外一个组件,如果被依赖组件无法正常运行,那么该组件也无法运行。

  image 泛化关系。标识一个组件与其他多个组件的关系为继承关系。

总结:

   通过上面的学习我们知道:组件图主要是为系统架构师对整个系统的解决方案的自然形成,可以通过组件图的形式把系统的大体功能进行区分和设计。通过组件图把

系统功能进行抽象和分离。然后通过顺序图把功能流程细分成多个步骤,然后通过类图去构建每个流程步骤中的每个类应具有的个方法。最后形成一个完整的设计文

档。

  3、状态图。

简介

  状态图其实是针对一个对象(实体、组件其他元素等)来说的。主要是描述了,对象的行为如何改变状态的反映。我们研究UML状态图的目的就是为了搞清楚,对

象状态的变化与行为的关系。建模对象的实时行为。创建状态图的条件:当对象行为的改变与状态有关的时候才创建状态图。状态图反映了对象如何改变状态相应行为

的变化和展示对象的生命周期的创建和删除的全过程。

详细

  状态图可建模的对象:

  image

  用例:可以描述用例图中的某个用例状态的变化。

  类:可以描述某个类的状态的变化。

  子系统:可以描述某个子系统中状态的变化。

  整个系统:类似(WF)工作流中的流程,每个节点其实就相当于一个状态。

image

  上面简单的绘制了一个去餐厅吃饭的状态变化,每个状态变化的行为都有描述,当然我这里只是简单的举例说明状态图的变化,并没有详细分析的所有可能状态都画出来。

具体的状态还请大家自己练习画出来,此处只是简单的举例说明。

  状态图中的元素:

  状态标记:

  image

  状态图中可以标识一个或多个初始状态,也可以包含一个或多个结束状态。

  状态图中不同状态之间的关系:

  image 转移关系,用来描述对象的状态从一个状态转移到另外一个状态的处理流,箭头指向转移后的状态。

   状态图中提供了类似流程图中的判定的功能元素:决策点。

   通过元素决策点来完成:

   image 决策点,用来决策跳向不同的状态。

   具体用例如下:

   image 就是起到了一个决策的作用。这里不在复述。

   状态图中的同步:

   状态图中的同步主要是为了说明并发工作流的分岔和联合。下图描述了状态图中的同步条:

image

    初始状态进入到同步条中分岔同步执行操作A与B,分别进入A状态、B状态,然后分别执行A1,B1联合进入到结束状态。

    一个对象可以通过同步操作同事拥有多个状态。有时候一个对象还可以拥有不同层次的多个状态。当单个状态拥有独立的附加子状态时就可以在状态图中使用层次结

构的状态。

   组合状态就是这样的比较复杂的状态结构图,有时候我们需要把一个复杂的状态细化成多个子状态的合成,那么这个复杂的状态就可以叫组合状态。

   下面举例说明:

image

  组合状态B,也即复合状态B,内部可能有比较复杂的状态(C-D状态)。这种只是组合状态B中存在单个状态变化流程的情况,还可能组合状态B中包含更多的状态流。

  那么我们就要用如下的状态图完成:

image

   上图中1代表的是下单的流程,2代表付款流程。

总结

   通过上面的学习我想大家对状态图有了一定的了解,那么我们来总结下,如何建模状态图。

   第一步:我们知道建模状态图,首先需要抽象出来要建模的对象。

   第二步:我们基于这个对象分析出该对象具有的所有状态及发生状态改变的行为。

   第三步:标识每个对象状态的起始状态与结束状态。

   第四步:开始创建对象的状态图,分析是否有必要创建复杂的组合状态。

   系统架构设计的过程中,我们首先要分析出哪些对象需要使用状态图来描述。如果某个对象具有复杂的行为,那么可以使用活动图来建模比使用状态图更适合。每个

状态图必须至少有一个起始状态和结束状态。并且详细的分析对象发生状态改变的行为。从某个状态转移到另外一个状态的行为是什么。在某些情况下,如果对象的某

个状态无法清晰的表达时,可以通过创建组合状态来进一步细化该状态,这样能更清晰的表达对象的状态变化。

五、本章总结。

  本章主要讲述了UML建模图形中的顺序图、状态图、组件图。并且分析了什么情况下使用各种UML建模图进行建模。并且通过简单实例说明如何使用。等UML所有的

建模图形介绍完毕后,我将针对如何我目前遇到一些问题进行分析讲解,如何遇到功能需求进行功能的分离及建模。希望大家多多提出宝贵意见。

六、系列进度。

       1、系统架构师-基础到企业应用架构系列之--开卷有益

       2、系统架构师-基础到企业应用架构-系统建模[上篇]

       3、系统架构师-基础到企业应用架构-系统建模[中篇](上)

       4、系统架构师-基础到企业应用架构-系统建模[中篇](下)

       5、系统架构师-基础到企业应用架构-系统建模[下篇]

       不断更新中(请持续关注…)

七、参考文献。

八、下篇预告。

     下一篇将把本章没有讲述完毕的活动图与部署图讲解完毕,其他的不常用的建模图形可能只是简单的讲解,不会像这几篇文章那样具有说明的讲解。由于本人才疏

学浅,可能对UML建模的认识不够深入,还请各位多多支出宝贵意见,我将在后续的文章中不断的改进和学习,将自己掌握的内容写出来,一方面是帮助不熟悉UML的

朋友尽快的上手,另外也可以让自己加深印象。

作者:hegezhou 发表于2011/9/10 12:24:19 原文链接
阅读:2042 评论:0 查看评论

系统架构师-基础到企业应用架构-系统建模[中篇](下)

$
0
0

一、上章回顾

        首先、我们先来回顾下,上篇讲解的内容,加深下印象。上篇我们主要讲解了3个建模图形分别是:顺序图(序列图)、组件图、状态图。

        具体功能描述如下图:这里不详细解释,如果不清楚请看:系统架构师-基础到企业应用架构-系统建模[中篇](上)

        image

         由于全部放在一篇中篇幅太长了,所以分开讲解。

二、摘要

       本文主要讲解:UML建模图中的活动图、部署图等

       image

       上图中就是本章要讲解的内容,本质将仔细的剖析,部署图与组件图的关系与区别,活动图与状态图的关系与区别。

三、本章内容

       1、上章回顾。

       2、摘要。

       3、本章内容。

       4、建模中的抽象模型图之部署图、活动图。

       5、本章总结。

       6、系列进度。

       7、下篇预告。

四、抽象模型图之部署图、活动图

部署图

      首先,我们先来讲解部署图。部署图主要是用来描述一系列组件部署到节点运行的结构。部署图显示了系统运行时的结构。一般情况下部署图帮助我们来理解分布

式应用系统。同时部署图还传达了构建应用系统的软件与硬件元素的配置及部署方式。

      部署图中的基本元素:

      1、节点:这里就是指组件运行的环境。可以是软件(操作系统、其他等)或硬件资源(计算机,其他硬件)。

      UML建模语言中的通用图形化表示为:

      image

       2、节点实例:节点实例与节点的区别就是有下划线和冒号,节点实例必须紧跟冒号,当然这个节点实例名称可以为空,节点必须要有。

      image 

      3、组件容器:一个节点可以包含其他节点,可以是组件,也可以是节点。

      image

      4、节点之间的关系

        (1)、单向依赖:

          image

         上图表示 查询统计组件,通过.net提供的ADO.NET访问SQLServer2005数据库。

         (2)、双向依赖:

          image

          上图表示:产品管理模块会把数据写入到数据库中,同时产品管理中的信息会从数据库中读取,双向依赖。

         (3)、通信:

          image

          上图表示:应用软件系统与数据库通过.NET提供的方式相互通信,个人理解任务就是双向通信(双向依赖)[错误之处,还请高人指出]。

        5、实例讲解:

        下面我们已一个简单的系统B2C来进行讲解:

        我们先来看看B2C系统中的相应节点:

        image

        客户端通过浏览器访问B2C站点,首先进入会员管理,如果注册,则进入到注册系统。会员管理中完成对采购的管理、支付、发布等。

        节点描述:

        浏览器:通过键入网站地址访问B2C站点。这是与B2C系统交互的唯一入口。

        注册系统:完成用户的注册与数据库通信。图上并未画出,所有的节点除了浏览器不需要直接与数据库交互外,其他的模块都需要与数据库通信。

        会员管理:完成会员中心的管理。会员的个人信息,开店的店铺信息,收货地址等等信息的管理,我的采购,我发布的产品等等。

        采购系统:系统中的子功能,用于完成买家的产品采购。

        发布系统:主要为卖家提供服务,发布产品信息等。与数据库通信

        支付系统:完成支付交易的操作。与个人账户进行通信。

        当然这里只是举个简单的例子,其他的内容,比如前台的展示等等,这些目前都没有考虑其中,也没有仔细分析,这里只是达到介绍的目的。

        6、总结

       通过上面的讲解相信大家对部署图已经有了基本的认识,部署图主要是用来完成将组件部署到节点上运行的结构。从整体上描述了,系统运行时的结构。部署图是

必须要掌握的建模图。

活动图

        活动图主要是用来描述系统的动态行为,从一个活动到另一活动的控制流。活动图的本质是流程图,但是与流程图又有所不同。在本小节中将会详细的讲解活动

图与流程图的本质的区别及活动图与状态图的区别。

        按照惯例,我们先来看看活动图的元素:

        1、动作状态:

        image

        通过用圆形边的长方形来表示一个动作状态。动作状态有几个特点:原子性(要么执行,要么不执行)、不可中断的操作,并且此次动作完成后一定转向到另外一种

状态。 动作状态是构造活动图的最小单位。

        状态图区别:

        a、活动图中动作状态可以有入转换与出转换,意思就是说可以从当前状态转向到另外一个状态,也可以从另外一个状态转换到当前状态。图形化的表示如下:

        image B动作状态,可以有入转换A,出转换C。

         动作状态必须至少有一个出转换,转换都是以内部的完成为起点,与外部事件无关。

         实心圆:代表起始状态。

         环形内的实心圆:代表结束状态。

         b、动作状态与状态图不同的是,动作状态不能有入口动作与出口动作。更不能有内部转移。

        2、活动状态:

        image

        通过二个半圆与一个长方形组合起来来标识活动状态。

        活动状态首先可以被分解成多个子活动或者多个子动作状态。活动状态他不像动作状态是原子性的。活动状态是非原子性。活动图内部的活动,可以用另外一个

活动图来表示。活动状态可以看作多个动作状态和多个子活动的组合。

        活动状态与动作状态不同,动作状态是活动状态的一个特例,当某个活动状态只有一个动作状态时,这个活动状态就是一个动作状态。活动状态可以有入口动作

和出口动作。还可以有内部转移。因为活动图是多个子活动和多个动作状态的组合,所以本来动作状态直接的转向就可以看作是内部转移了,所以就很好理解了。

        image

        上图已经基本表示出来了活动状态中的动态状态的转移等。我相信大家都能理解。

        3、动作节点之间的关系

        a、控制流:image 与状态图中的转向相同,活动图也使用一个带箭头的线段,箭头指向要转入的状态。

        b、分支:image   活动状态从A分支出来活动状态B、C,

        c、合并:image 活动状态B从活动状态A与C合并后得到。

        d、泳道:泳道将活动图中的多个活动划分成多个组。并且把每一组活动都由对象来负责组织业务,泳道区分了负责活动的对象。并且泳道明确的表

示了哪些活动是由哪些对象进行的。泳道通过垂直线来区分。而2个垂直线分割的区域即是一个泳道。上面的解释可能有点绕,说白了泳道即是上面说的对象,对象就是

泳道。把不同的泳道就叫一个对象。每个活动状态在有泳道的活动图中,只能属于一个泳道。

        下面来看有泳道的图例:

        image 上面有2个泳道,分别是我是泳道1,我是泳道,并且我是泳道1中的D与我

是泳道中的活动状态A有转向关系。

       e、对象流。

        对象流是对象与动作状态或者活动状态直间的依赖关系。表示动作使用对象或者动作对对象的影响。一般我们在使用中,我们可以把对象通过依赖关系与动作状态或者活动状态进行链接。

        对象流的几个特点:

        (1)、一般一个对象可以由多个活动状态或动作状态操作。

        (2)、一个活动状态或动作状态的输出对象可以作为另一个活动状态或动作状态的输入。

        (3)、一个对象可以在一个活动图中多次出现,但是有点需要注意,这个对象多次出现时表名该对象处于生命周期的不同时期。

        包含对象流的活动图:

        image

        泳道M1中出现了对象。并且该对象与活动状态B有依赖关系。

   总结

        本节中讲解了,活动图的基本知识,下面我们以我们平时比较熟悉的B2C业务,电子商城为例说明下,会员的产品管理流程。通过状态图的形式来表达。以巩固

下我们学习的成果。

        例如B2C中的产品管理。首先必须是会员才能登入系统中,然后必须是我是卖家,然后才能进行发布产品的操作。

        image 会员先要开启店铺,设置权限后才能进行产品管理

        image

五、本章总结

        本章主要讲述了部署图与活动图。现在我们回顾下本章要点。

        部署图:主要用来描述一系列组件部署在节点上运行的结构,是系统运行是的结构的描述。主要用于软件系统的

        活动图:主要用来描述系统的动态行为,从一个活动转换到另外一个活动状态。通过一系列的操作将业务流程通过工作流的形式来描述。一系列操作就是一系列

的活动状态。

六、系列进度。

前篇

       1、系统架构师-基础到企业应用架构系列之--开卷有益

       2、系统架构师-基础到企业应用架构-系统建模[上篇]

       3、系统架构师-基础到企业应用架构-系统建模[中篇](上)

       4、系统架构师-基础到企业应用架构-系统建模[中篇](下)

       5、系统架构师-基础到企业应用架构-系统建模[下篇]

      6、系统架构师-基础到企业应用架构-系统设计规范与原则[上篇]

      7、系统架构师-基础到企业应用架构-系统设计规范与原则[下篇]

      8、系统架构师-基础到企业应用架构-设计模式[上篇]

      9、系统架构师-基础到企业应用架构-设计模式[中篇]

      10、系统架构师-基础到企业应用架构-设计模式[下篇]

中篇

      11、系统架构师-基础到企业应用架构-企业应用架构

      12、系统架构师-基础到企业应用架构-分层[上篇]

      13、系统架构师-基础到企业应用架构-分层[中篇]

      14、系统架构师-基础到企业应用架构-分层[下篇]

      15、系统架构师-基础到企业应用架构-表现层

      16、系统架构师-基础到企业应用架构-服务层

      17、系统架构师-基础到企业应用架构-业务逻辑层

      18、系统架构师-基础到企业应用架构-数据访问层

      19、系统架构师-基础到企业应用架构-组件服务

      20、系统架构师-基础到企业应用架构-安全机制

后篇

      21、单机应用、客户端/服务器、多服务、企业数据总线全解析

      22、系统架构师-基础到企业应用架构-单机应用(实例及demo)

      23、系统架构师-基础到企业应用架构-客户端/服务器(实例及demo)

      24、系统架构师-基础到企业应用架构-多服务(实例及demo)

      25、系统架构师-基础到企业应用架构-企业数据总线(实例及demo)

      26、系统架构师-基础到企业应用架构-性能优化(架构瓶颈)

      27、系统架构师-基础到企业应用架构-完整的架构方案实例[上篇]

      28、系统架构师-基础到企业应用架构-完整的架构方案实例[中篇]

      29、系统架构师-基础到企业应用架构-完整的架构方案实例[下篇]

      30、系统架构师-基础到企业应用架构-总结及后续

七、下篇预告。

         下一篇中我们将会讲述:简单讲述其他的UML建模图,并且结合B2C实例,详细分析B2C系统应该具有的功能模块。及每个模块通过不同的建模图形的表示方

法,及如何在功能分析时使用恰当的建模图。

后语

      希望看完本章的朋友可以从本篇中学到相应的UML建模知识,懂的人可以巩固下UML知识,本篇希望能够抛砖引玉,希望大家能够多提出宝贵意见。由于是本人

平时工作中的理解与总结,不足之处再所难免,还请大家批评指出!如果您有什么意见或建议,请多多提出!大家的支持就是我的最大动力!

作者:hegezhou 发表于2011/9/10 12:25:27 原文链接
阅读:2046 评论:0 查看评论

系统架构师-基础到企业应用架构-系统建模[下篇]

$
0
0

一、上章回顾

       上一篇:系统架构师-基础到企业应用架构-系统建模[中篇](下) 中我们主要讲解了部署图、活动图,我们在这里也是参考上篇的形式,这里不再详细介绍。上篇主

要讲解了下面2类建模图:

        image

二、摘要

       本文将讲解其他的几个类型的建模图当然只是简单的讲解,并且将结合B2C电子商城系统进行分析通过使用我们已经讲解的建模图为例。分析系统可划分的子功能

模块,每个功能模块内部的运行步骤等等。

       image image

       上面的2个不同类型的进行划分的建模图,本章将对上述6个建模图进行分别举例讲解。

三、本章内容

       1、上章回顾。

       2、摘要。

       3、本章内容。

       4、结构图。

       5、行为图。

       6、本章总结。

       7、系列进度。

       8、下篇预告。

四、结构图

      1、对象图

        首先、我们闲来讲解对象图。对象图用来描述系统的各个对象在某一时刻的状态。对象和类图一样他们是静态结构图。他们是从实际的或者原型化的场景去表达

的。对象图显示了某一时刻对象与对象的关系。一个对象图可以看作类图的特殊用例,类图中的关系同样适用在对象图中。可以这样理解,对象图就是类图的实例。对

象图是有生命周期的,因此对象图只在某个时间段内存在。

        对象图中的元素在类图中都可以找到,只是把类图中的类元素换成对象即可。而类图中类元素之间的关系,在对象图中同样适用。这里不在复述。如果对类图不

是特别的熟悉,请看这篇文章中的讲解:系统架构师-基础到企业应用架构-系统建模[上篇]

        下面讲解对象图的举例:

        image 这里的对象是指某个类的实例。

        image 这样的格式表示了某个类的实例的格式,冒号“:”后面跟着类名,也就是这里的“父类”。另外还必须加上下划

线。

        对象首先是一个确定,所以一般情况下,对象属性一般把值直接列出来。如下形式:

        image

        对象图中的所有的对象名可以为空,但是为了更好的标识对象图中的对象,不建议这么做,并且如果未指定对象名那么必须指定该对象所属的类格式如下:

        image 没有对象名的对象实例。

        下面以B2C中的订单系统中的新订单的状态为例,讲述下各对象的状态。

        image 这里的关系表示的是组合关系

        上图中的订单信息的状态:订单(新订单)-物流信息(未发货)-支付信息(未支付)-产品状态(产品信息)。

      2、包图

        包图就是由包与包之间的关系组成的。

        包图也是一种静态结构,包可以拥有的元素:

        image

        我想上面的元素大家都是有所了解的,我这里就不一样举例说明了,下面通过一个例子来显示如何使用包图。

        包的访问限制:与我们平时了解的3个访问权限设置关键字用法相同。

        image

       包与包之间的关系:

       a、引入与访问依赖:首先这个关系与平时我们说的类的继承关系是不同的.包括包的访问域不能继承。

       image 用于在一个包中引入另一个包输出的元素,因此A依赖B,包A引入包B中的B方法。B这里的访问权限是公共的。A中的方法是保护的。

       b、泛化关系:       

       image 泛化关系描述了一种继承关系。即基类与特殊类之间的关

系,途中描述的意思是只要是包A出现的位置都可以用包B替换。

      3、组合结构图

        组合结构图:以结构化的方式给出类型的内部结构。组合结构图是一种静态结构,它显示了一个类型内部的成员及成员之间的关系。组合结构图可以这样理解,

就是描述类的内部结构及成员之间的调用关系的建模图。组合结构图用于扑捉类的内部细节,描述了对象如何在某个类中协同工作。

       组合图中其实就是描述类的内部的结果,基本上的元素有:类、对象,其他等,具体的关系请参考类图中的关系。

       组合图实例:

       image 上图显示了产品与产品品牌与产品分类的组合关系。产品品牌与产品分类是关联关系(关联关系可

以是1:N),通过一条直线来链接。如果有不清楚的地方请看类图的相关介绍:系统架构师-基础到企业应用架构-系统建模[上篇]

五、行为图

      1、通信图

        在UML建模中除了顺序图(序列图)可以表示对象之间的交互外,通信图也可以完成通用的描述。一般情况下,二类图可以进行互转。

        首先、我们先来分析下2类图的不同。

       image

        我们还是先来看下通信图中的元素:

       image

        上图中的主要组成元素是对象。对象之间的关系,通过链接来完成。然后通过一个带有实体三角形的线段指向要发送消息的对象。

        下面来解释下对象之间的关系。

        image

        image 还有就是消息给自己发送的特殊消息。

        返回消息的类型:

        image

        下面我们来简单举例如何说明:

        image 上图中简单描述了发送邮件的过程。我们可以看出通信图相比顺序图更注重对象之间的

链接关系。我们通过通信图能够知道一个系统中的某个流程中各对象直接的链接关系及传递的完整信息传递过程。

        首先、我们闲来讲解对象图。对象图用来描述系统的各个对象在某一时刻的状态。

      2、时间图

        时间图:主要用来描述一段时间内对象的行为(状态的变化,执行的动作等等)。主要用来模拟即时系统模型。与对象图类似,不同的是时间图描述的一个时间段,而

对象图描述的一个时间点。对象图关心对象的状态,而时间图关心的是对象的行为。

        image 这里设置了定时器之后,对象的状态将会从A,在定时器时间到达后自动的跳转

到状态B。这里表示的是定时器来控制状态的迁移。

       时间图中支持异步信息,时间图采用垂直图的方式来描述系统的执行顺序。

       时序图等于是在序列图(顺序图上)加上时间的限制就构成了时间图。

       image 从垂直的角度来看,就是说A调用B先于B返回值给A,从时间图上就可以清晰的看出来对象的行

为。 一般情况下我们可以通过序列图来完成相应的描述。除非需要定义一个时间段的对象时才会用到时间图。包括某个对象到某个对象的调用都可以通过时间限制来进

行行为的控制等。

六、本章总结。

        本章中主要简单的讲述了几类UML2.0语言相比UML1.0新增的几类建模图,虽然我们平时可能用的比较少,但是某些特定的领域范围,通过这些图可能更能清晰的

表达抽象模型。

         1、对象图:描述系统在某一时刻的状态。

         2、包图:描述系统中的某个模块的内部组成结构,是由元素及元素之间的关系构成的。

         3、组合结构图:以结构化的方式描述了系统中的某个类型的内部结构。

         4、通信图:描述了对象之间的交互与顺序图不同的是,交互图更关注对象之间的链接及通信。

         5、时间图:描述了在一个时间段内的对象的行为。

        当然具体的建模图如何使用及在实际的项目中如何去应用分析,这才是最主要的目的,当然这就需要理论结合实际来达到学以致用的效果。下一篇,我讲专门的讲

解针对B2C电子商城为例,详细的剖析UML建模的具体应用。

七、系列进度。

     前篇

      1、系统架构师-基础到企业应用架构系列之--开卷有益

      2、系统架构师-基础到企业应用架构-系统建模[上篇]

      3、系统架构师-基础到企业应用架构-系统建模[中篇](上)

      4、系统架构师-基础到企业应用架构-系统建模[中篇](下)

      5、系统架构师-基础到企业应用架构-系统建模[下篇]

      6、系统架构师-基础到企业应用架构-系统设计规范与原则[上篇]

      7、系统架构师-基础到企业应用架构-系统设计规范与原则[下篇]

      8、系统架构师-基础到企业应用架构-设计模式[上篇]

      9、系统架构师-基础到企业应用架构-设计模式[中篇]

      10、系统架构师-基础到企业应用架构-设计模式[下篇]

     中篇

      11、系统架构师-基础到企业应用架构-企业应用架构

      12、系统架构师-基础到企业应用架构-分层[上篇]

      13、系统架构师-基础到企业应用架构-分层[中篇]

      14、系统架构师-基础到企业应用架构-分层[下篇]

      15、系统架构师-基础到企业应用架构-表现层

      16、系统架构师-基础到企业应用架构-服务层

      17、系统架构师-基础到企业应用架构-业务逻辑层

      18、系统架构师-基础到企业应用架构-数据访问层

      19、系统架构师-基础到企业应用架构-组件服务

      20、系统架构师-基础到企业应用架构-安全机制

     后篇

      21、单机应用、客户端/服务器、多服务、企业数据总线全解析

      22、系统架构师-基础到企业应用架构-单机应用(实例及demo)

      23、系统架构师-基础到企业应用架构-客户端/服务器(实例及demo)

      24、系统架构师-基础到企业应用架构-多服务(实例及demo)

      25、系统架构师-基础到企业应用架构-企业数据总线(实例及demo)

      26、系统架构师-基础到企业应用架构-性能优化(架构瓶颈)

      27、系统架构师-基础到企业应用架构-完整的架构方案实例[上篇]

      28、系统架构师-基础到企业应用架构-完整的架构方案实例[中篇]

      29、系统架构师-基础到企业应用架构-完整的架构方案实例[下篇]

      30、系统架构师-基础到企业应用架构-总结及后续

八、下篇预告。

       下一篇将会已我比较熟悉的B2C电子商城来深入的剖析,如何使用UML建模工具去分析一个系统及如何分析一个模块的流程,包括部署等等。通过我们已经讲过的

UML建模图形来结合实例来讲解。如果大家有好的意见和建议可以及时反馈,谢谢您的宝贵意见。

后语

       希望看完本章的朋友可以从本篇中学到相应的UML建模知识,懂的人可以巩固下UML知识,本篇希望能够抛砖引玉,希望大家能够多提出宝贵意见。由于是本人

平时工作中的理解与总结,不足之处再所难免,还请大家批评指出!如果您有什么意见或建议,请多多提出!大家的支持就是我的最大动力!

作者:hegezhou 发表于2011/9/10 12:26:08 原文链接
阅读:2462 评论:0 查看评论

老子的软件之道 - 道篇 24 设计师守则

$
0
0
摘要:老子哲学 道德经 软件哲学、软件之道               参阅:  消灭人狼  软件的十大命题 编程规则  

      圣人曰:企者不立;跨者不行。自见者不明;自是者不彰。自伐者无功;自矜者不长。其在道也曰∶馀食赘形。物或恶之,故有道者不处。

      “踮起脚跟,想高人一头,但你很难站稳、很难持久。将两腿使劲跨开想超有别人,但反而无法行走。”圣人用这样简单的例子告诉我们一个深刻的道理:实事求是,按自然规律办事!一切形式的主观的、激进的行为都是背道而驰的。只有遵循客观规律,脚踏实地,循序渐进,具有诚心和恒心,才能达到目的。

      还记得3.4大师风范中圣人关于“自见、自是、自伐、自矜”的论述吗?本节作为守则,要求设计师摒弃如下不良行为:

      固执己见,容易导致不能接受正确观点,因此对事物的认识会出现偏门,不利于综合多方观点搞清事物本质;

      自以为是,认为自己的观点和做法总是正确,从不听取他人的意见和建议;过分主观、不虚心。这必然会导致一叶障目,事物的本源就难以彰显,更得不到多数人的配合和支持。

      自我夸耀,是名利之心的膨胀和表现,这样会受到大家的鄙视,因此无法获得成功;

      自高自大,是骄傲自满的表现,这样的人就无法进步了,也就不可能取得新的成就。

      以上行为都是多余的、无用的,更是有害的和令人厌恶的,遵循软件之道的设计师是绝不会有类似行为的。

 

      作为软件设计师你必须遵守上面的总则,此外你还要遵守如下具体规则,这样你才能逐步成长为卓越的设计师。

       1)追本求源;深入研究涉及的业务领域,把握它稳定、本质的逻辑和流程,这是良好设计的基础;不要被表面现象蒙蔽,面向功能的设计必然是平庸的设计!不能完成具体功能要求的设计,则是失败的设计!就如国画大师白石老人所云:“太似则媚俗,不似则欺世”,大道相通啊!

       2)抽象;必须对该领域内的概念、数据、功能等进行反复认真的综合分析,并抽象出应用领域稳定的业务模型,对业务领域的本质抽象是卓越设计的关键。

       3)追求完美、精益求精;少于三个方案的设计就不是设计!低于五次修改的设计一定不是一个优秀的设计

       4)不断学习实践,勇于批判和质疑;学习、实践、体会、思考、批判、总结、再实践,“批评性实践”是设计师的可贵精神。

       5)热爱设计,追求卓越;建立高远的信念;设计给你带来快乐,设计使你的生命更丰富,设计可以改变世界。

作者:xabcdjon 发表于2011/9/10 19:07:13 原文链接
阅读:23211 评论:4 查看评论

老子的软件之道 - 道篇 25 道为何物

$
0
0
 摘要:老子哲学 道德经 软件哲学、软件之道               参阅:  消灭人狼  软件的十大命题 编程规则 

      圣人曰:有物混成先天地生。寂兮寥兮独立不改,周行而不殆,可以为天下母。吾不知其名,强字之曰道。强为之名曰大。大曰逝,逝曰远,远曰反。故道大、天大、地大、王亦大。域中有四大,而王居其一焉。人法地,地法天,天法道,道法自然。 

       这是“道德经”的第25篇,到此为止“道”到底为何物?我们仍然不清楚,这也是“道”难于表述的原因,老子在全书中有三篇具体描述“道”的文章,但也很难对它进行全面的表述,“道可道,非常道”啊。需要反复通读全文才能有些体会。

       本篇阐述的关键有两点,首先老子认为“道”在天地万物生成之前就存在了,我们应感谢东方的先哲,他们没有创造神和宗教,而是建立的以无神论为基础的博大文化体系,使我们能长久地屹立与世界民族之林,而且按当代几位大思想家的说法,拯救目前世界上各种危机的唯一办法就是东方智慧,这句话同样适用于软件领域啊

       其次提出“道法自然”,注意这里自然的意思并非是我们目前自然科学的自然,那时还没大自然的概念;自然就是简单的字面意思,即自己的样子,这句话翻译过来就是:“道就是遵循它自己的样子”,你可能觉得气愤,但我认为这句话非常深刻,事实上一切真理、一切规律都客观地存在着,它就在那里等待我们去认识、感知和掌握;能够认识到这一点是多么重要啊!

       道德经这段文字到底是什么意思?

       软件之道到底是什么呢?

       我会阐述我的观点,也希望听到你的想法…… 

       首先我们来看看老子描述的高度抽象的“道”吧:有一个东西在天地万物之前就已经存在了,它寂静无声,空旷无际;它独立存在,本性如一;它周而复始,循环运转。它是形成世间万物的母体。这是老子的宇宙观,他不知道这东西是什么,勉强称之为“道”,它数量巨大,无穷无尽,逐渐向周围散去,扩撒到无穷远处,远到极限后开始返回。这是老子对道在"有"(物质)这个层面上的描述。实际上目前的物理学研究成果,已接近证明老子宇宙观的正确性—宇宙之初没有任何物质,只有无穷的能量集中在一点,它扩散开去,两秒后氢元素诞生了,然后是氦元素……,经过几十亿万年的演变,宇宙就呈现出目前我们看到的样子,整个宇宙目前仍在向远方扩撒,当扩散到极点,“熵”达到零,此后有两种可能,一是保持永恒的寂寞;二是开始返回。伟大的老子预测是第二种情况,宇宙会“周行而不殆”。参阅:什么是哲学

       世间万物道与天地相比,道大,天与地比,天大,地与人类社会比,地大,人间王法与人的个体比,王法大。因此人要遵循、效法地的规律,地要遵循、效法天的规律,天要遵循、效法道的规律,道则遵循效法它自己。这是从“无”(精神、信息、规律层面)的层面上描述“道”,道代表事物的本质的跟本规律,每种事物都有它的道,我们研究软件之道主要是在这个层面进行研究啊。

 

 

作者:xabcdjon 发表于2011/9/10 20:56:51 原文链接
阅读:26085 评论:4 查看评论

赤手空拳如何成就百万富翁?——网络营销之五(第二招:SEO)

$
0
0

17、什么是次导航?次导航有什么作用?

拉拉:有一个术语叫次导航?感觉看的稀里糊涂的?能不能给我说个明白?

 蒋老师:顾名思义,次导航是相对主导航而言,主导航我们都知道,但有时我们并不能把我们想要关键词出现在主导航,我们常常在首页底部加上次导航。显然,对于主导航位置没有办法设置关键字时,对于我们来说是非常郁闷的事,这个时候次导航给我们起到了很好的替代作用。

拉拉:是不是优化网站时,我们尽可能考虑次导航呢?

蒋老师:也不尽然,如果我们的关键词密度、主导航已经起到了良好的设置,次导航不要也可以,因为次导航让用户体验有时并不友好,有时又想让别人搜到,只能平衡下孰轻孰重。

18、如何确定目标关键词?

拉拉:目标关键词这个不是很简单吗?譬如我的目标关键词就是“婚纱摄影”、“婚纱摄影工作室”,最不济加上“郑州”。

蒋老师:听着是这个理,听听我说之后,你再考虑你确定的是不是最佳的,目标关键词选择是否正确,可是在相当大的程度上决定着你网站的流量,不可掉以轻心。

注意关键词的确定一定要明确你的客户群体,再考虑网络上的用户的需求,然后再来根据搜索引擎搜索的频度来衡量的。绝不能凭想当然坐在那儿拍脑袋确定关键词。以下几点你参考一下:

(1)确定关键词的秘诀是:把你当成网络的搜索者或消费者,而不是网站的管理者。

(2)如果竞争对手做推广比较成功,那么竞争对手选择的关键词也是你可以参考的。

(3)确定的关键词一定是和你的站点高度相关的。太热门的关键词小站或许做不来,但如果是没有流量的关键词,则排第一也是没有意义。

(4)初步确定关键词之后,查看一下这些关键词的频度。最后敲定最终的“目标关键词”。

拉拉:上面说的关键词的频度是不是指这个关键词的搜索量?初选的关键词如何查看频度?

蒋老师:是的,频度就是指的搜索量,也叫百度指数。我们可以通过网址:http://index.baidu.com来查看某一个关键词的频度。

19、如何选择长尾关键词?

拉拉:长尾关键词我已经完全明白了?但是如何选择合适的长尾关键词,还比较迷茫,有没有工具?

蒋老师:拉拉呀,你是干什么都想走捷径啊!

拉拉:那是,牛顿还是站在巨人的肩膀上呢?

蒋老师:这个可没有工具?我就把我的经验告诉你吧,你还要不断地拓展才对。

(1)长尾关键词应该满足的要求:首先,长尾关键词应该与我们的网站内容相关;其次,长尾关键词必须是用户可能的词;第三,用户通过查询来到你的网站,你应该能满足用户的需求,千万不要只做标题党。

(2)选择长尾关键词的方法:首先,可以通过构思与网站内容或者服务相关的关键词;其次,通过分析竞争对手的长尾关键词;第三,进一步,把你想到的关键词,通过百度搜索,在底部都会显示相关搜索,这里往往有很多你可以利用的长尾关键词,而且有一定的搜索量才会形成相关搜索。

 20、如何在网站添加流量统计?

拉拉:师傅领进门,修行在个人,SEO骨子里面的东西是不是就这么多了?

蒋老师:我暂时能够想到的就这么多,总感言犹未尽,等我想起来再补充吧。

拉拉:优化做完了,我怎么才能知道我网站的流量或者关键词的效果呢?

蒋老师:流量统计工具很多,主要功能大同小异,我给你介绍一个CNZZ的“免费网站流量统计”工具吧,网址为:http://www.cnzz.com/

第一步、注册CNZZ;

第二步、登录CNZZ;

第三步、添加站点。

第四步、复制代码

第五步、查看流量

 

21、如何作一名优秀的Seoer?

拉拉:SEO知识点感觉也不是太难?如何才能做一名优秀的Seoer?

蒋老师:呵呵,Seoer这个单词不知道老外认识不认识?不过很符合中国人英语的逻辑。成为优秀的Seoer,感觉还真的不简单!

拉拉:好了好了,蒋老师,知道你很谦虚,但是太谦虚了,会让弟子感觉你是保守呢。

蒋老师:最重要的你把自己当成Boss,无论你是打工仔还是经营决策者,这是至关重要的。以下内容仅供参考!

首先站在网络营运的角度看SEO。在以前我们做SEO,特别侧重于链接,关键词排名,但是近两年来做法完全不一样了,我们面临挑战越来越大,外界各种因素越来越复杂。

现在要把一个网站做好的话,以下三点需进行关注。业务目标,市场需求和竞争态势。业务目标是我们必须关注的,一般来说做SEO会有阶段性的指标,除了考虑基本态势以外,我们还需要了解市场的需求,因为不同的需求有不同的操作方法,也会给我们带来不同的机会,也可以给我们产生一些差异化竞争的方法。从三个维度考虑,我们对SEO的态势形势心里会比较清楚,这个基础之上,我们再进行操作就会比较方便。

搜索引擎营销的三角格局,这个比较简单,网站,访问者,搜索引擎,它们三个之间的关系,网站,访问者和搜索引擎,当我们了解了搜索引擎的三角格局以后,就要了解搜索引擎它有什么样的规则和方法。在了解了它的规则以后,才避免犯错误,特别是对于一个大型的网站来说,首先我们要预防的是风险,而不是目标,因为搜索引擎这个行业,特别是SEO这个行业,我们不可以从头再来,我们首先预防风险。搜索引擎的收录,排序和惩罚,熟悉游戏规则,合理规避风险。

在了解搜索引擎的规则以后,我们接下来需要了解一下访问者他在搜索,检索结果的时候有一些什么样的行为,当访问者通过搜索引擎检索出结果的时候,他的注意力33%在看标题,40%的注意力在看摘要,21%的精力在路径,大家可以思考一下其他5%他们在看什么。在研究了搜索引擎的规则,也研究了访问者的心理行为的变化,我们根据这两者推测出一种合理的模型,为我们的**导航网站,为我们的企业创造收入。

访问者,搜索引擎,对于搜索SEO而言,我们从关键词,外链,内容,代码去考虑,这四个维度。我们了解了SEO的知识体系以后还不够,我们还需要有正确的理念来支撑,我们才能有一种比较合理的操作方式,所以正确理念是第一条,理念是心态。价值观的直接因为我们的操作。

再来谈谈SEO执行力,大家可以看一下,我们了解规则以后,了解我们的支撑体系,也了解访问者的心理,有了这些,我们网站的效益能不能出来,还不一定,包括我们有最优秀的SEO的策略,也不见得,必须要有比较强的执行力。影响力,判断力,驱动力、无授权领导力。影响力是施加自己的影响,说服他人和自己合作,我们讨论合作,如果你不能影响他人,别人不能跟你合作,你再好的策略都是没有用的。我们有了判断力,影响力以后,接下来我们需要达成目标,这就是驱动力。驱动力的特征就是我们需要有结果,导向,不满足于现状。无授权领导力,在很多大型网站里面,有很多事情是没有授权的,没有人家授权的时候,怎么办,我们需要作为一个成功的SEO人,即使没有明确的授权,我们也应该通过影响力,判断力,驱动力,以及沟通能力,达成我们的目标。

接下来回到我们的日常工作,每一天我们应该怎么办,做为一个SEO人,每天思考从哪些出发点,从哪个角度。首先是思考,我们要制订合理的计划,网站空间购买有效的计划,计划制订出来以后,我们需要分配任务,当然在分配任务的时候也包括资源的分配,包括一些对风险的控制。把任务分布下去以后,接下来需要辅导我们的同事和团队,一起来达成目标。在辅导的过程中,我们有些阶段性的目标和文件需要签署,目标达成了没有,然后确定,签署文件。做一个SEO人应该从这五个维度考虑,首先最重要的还是思考。

我们制订计划和任务也有了,这个项目能不能做好,我们还需要去寻求最佳平衡,因为有些风险是不可预知的,我们需要在发起一个项目的时候,我们的SEO工作,是一个一个项目发起的,不像比较小的网站那么容易操作,所以我们发起取得一个项目的时候,首先要建立它的范围,多长时间完成,需要多少成本,花多少人力,人员配置,资金支持,包括资源购买等等。还有达成什么样的质量,这些都有的时候我们还要识别风险,风险的识别的控制,转化。

做为一名优秀SEO人,要想成功,以上几点我们需要理解并运用到实际中,首先通过改变我们的内在因素来影响外在,首先是见识,包括我们的行业知识,我们的专业知识,这个行业整个的变化等等,这是我们的见识水平需要不停的提高。第二,我们的策略,我们需要好的策略,必须基于现有的资源基础去考虑。第三个就是性格,在一个团队里面有不同的人,性格都不一样,我们要考虑互补。当我们见识、策略、性格都比较完善的时候,我们要不停的增加积累我们的资源,我们有了资源,机会来了我们就能够抓住。  

※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※

拉拉:SEO是不是到此结束了。

蒋老师:没有,对于你来说,不是结束反而是刚刚起航!最重要的是,你眼里什么时候没有了SEO你才成为真正的SEO高手!

作者:jianghuiquan 发表于2011/9/11 20:26:14 原文链接
阅读:2803 评论:0 查看评论

赤手空拳如何成就百万富翁?——网络营销之六(第三招:博客营销)

$
0
0

三个月后,拉拉打电话告诉我网站开单了……

三个月又半月,拉拉再次打电话告诉我她这半个月网站开了10单……

六个月后,拉拉再次约我到了迪欧咖啡馆,告诉了我一个所谓她的“天大的好消息:网上的业务订单全面超过了线下的业务!”。

拉拉:都不知道怎么谢你了,蒋老师,能不能正式聘请你作为我们的商业顾问!

蒋老师:那就是顾上就问问了。

拉拉:不,这是我和孙总达成的协议,你仍然负责我们网络营销的战略指导工作,我们具体实施,您时间自由,和以前一样!对于以前呢?送您这台苹果笔记本以示心意,以后呢?按我们纯利润的10%作为您的酬劳。

蒋老师:容我想想,我经常当免费顾问,这样无拘无束!

拉拉:就别推辞了。

 

1、有没有博客营销成功的例子?

拉拉:到目前为止,我对博客的感觉除了SEO用于和企业网站做外链之外,实在不知道如何与网络营销紧密联系在一起。有没有成功的例子?

蒋老师:博客营销成功的例子太多了,你可百度一下“健康24小时加油站”,请输入网址或者从搜索结果中单击“24小时健康加油站_新浪博客”。

浏览网址:http://blog.sina.com.cn/jiankangjiayouzhan

 

 

以上我们看到搜索结果是一个系列,我们看看这个博客的博主合作伙伴,凭借你商业敏锐的眼光,你一定能够猜出来博客的商业价值。博主整个博客就是围绕“健康”这个大主题,做的非常成功。

拉拉:想不到博客也可以做的如此成功!

蒋老师:只有想不到的,没有做不到的!

拉拉:哎,我这自诩营销人怎么如此不开窍呢?!

 

2、博客有新浪、网易、百度空间、QQ空间等等,是每个都建还是只建1个?

拉拉:蒋老师,不说国外,即使国内也有众多博客提供商供我们选择,譬如新浪、网易、百度空间和QQ空间等等,他们之间有什么区别?是我申请一堆呢还是只做一个呢?

蒋老师:实际上博客只是一个工具而已,用这几个哪个都可以!如果非要有所选择的话:新浪偏重于投资与IT、QQ空间则具有较大的大学生基础、百度空间则由于是百度的产品,可能搜索具有天生的优越性。

我认为最好先扎根于一个平台进行精耕细作,尤其是前期不建议在多个平台做,这是因为真正的去熟悉一些群体,形成稳定关系,

是需要时间去进行有效的交流和互动的。当然如果从SEO角度打算通过博客优化主站,则可能多建几个也有好处,但有维护成本。

 

3、博客对企业的营销价值有哪些?

拉拉:那么,博客对企业的营销价值如何呢?

蒋老师:我们知道美国打仗,都是海陆空三军,博客你可以认为三军中的一支劲旅,配合主场很容易打赢一场阻击战。主要有以下几个方面:

(1)可以直接带来潜在用户:无论我们是使用新浪还是网易,这些网站往往拥有大量的用户群体,有价值的博客内容会吸引大量潜在用户浏览,从而达到向潜在用户传递营销信息的目的。

(2)通过软文巧妙导入企业产品信息:在博客中介绍产品相关知识,不经意中可以导入企业网站的产品或者在线活动,达到网站推广的目的。

(3)博客比企业网站更容易获得搜索引擎青睐:一般来说,访问量较大的博客网站比企业网站的搜索引擎友好性要好,用户通过搜索引擎发现博客内容比企业网站内容更容易。

(4)博客文章增加企业网站的外链:提高企业的外链数量,使企业网站获得更好的排名打下基础。

 

4、建立博客应该持什么样的心态?应该注意哪些事项?

拉拉:蒋老师,建立博客容易,坚持维护难,总会为自己找理由懈怠管理,如何克服?

蒋老师:说得很好,我认为要沉下心来,耐得住寂寞,需精心耕耘,细心打理。十年磨一剑,冰冻三尺非一日之寒。与我们爬山一样,拾阶而上,想象一下成功的喜悦和一览众山小会督促你不断地前进!

拉拉:有什么要注意的吗?

蒋老师:博客营销作为企业营销航母的一个重要组成部分,不可像土匪一样没有章法,随性情而为。注意以下两点:

(1)需要有战略规划:需要有一个明确的核心主题和战略规划,并围绕这个核心主题去写作和这个主题有关的真实故事,努力改进自己的博客技巧,让自己变得更有影响力。

(2)避免流于形式,没有实效内容:如果你的博客没有太多内容,或东拼西凑,这样的博客不会有太大的价值,不太会吸引访问者,甚至会严重影响企业品牌和形象,有不如无!正确的做法应该是进行精心的编辑,将优秀的内容、行业新闻信息、行业发展动态、行业最新研究动向、企业研究课题成果等同行关心的内容进行分类和组合,使企业博客成为一个优秀的信息平台。这样就会不断吸引同行或想了解相关信息的人来访问,并不断扩大影响力,从而达到传播的效果。

(3)避免商业味太浓:尽管在自己的博客上发布广告,不会被别人删除,但是客户如果惹不起你,还是躲得起的,故而不要生硬的发布广告,要结合博文,让你的粉丝发现不经意间你为他们提供了一份无私的帮助。

(4)全员参与禁忌:博客的成本低廉,全民动员,全民战斗,虽然能够快速形成一定的规模效应,企业就会迅速提高知名度。但是如果没有系统指导,对产品理解不够深刻或者偏差较大时,反而会形成相反的宣传效果,所以全员参与的前提是大家对产品的认识已经统一,否则博客就是双刃剑!

(5)坚持是最重要的:需要有长期坚持的毅力,这是最重要的,因为博客不是短期内就可以马上为你带来效益的,而且也是一项很艰辛的工作,需要做整体的博客规划,要坚持更新文章,还要学会传播、互动,让自己的品牌和影响力传播的更远。最终目的,需要的是一群忠诚的读者(也是未来潜在的客户),一群规模庞大,有影响力,并且愿意听你讲故事的群体。

作者:jianghuiquan 发表于2011/9/11 20:28:54 原文链接
阅读:3054 评论:0 查看评论

赤手空拳如何成就百万富翁?——网络营销之七(第四招:百度文库+QQ群)

$
0
0

  百度文库(wenku.baidu.com)是供网友在线分享文档的开放平台,于2009年11月12日上线。在这里,用户可以在线阅读和下载涉及课件,习题,考试题库,论文报告,专业资料,各类公文模板。

 

  豆丁网(www.docin.com)是一个专业的社会化阅读分享平台及文档内容营销平台。自2007年创立以来,经过四年的运营,豆丁网已经成功跻身互联网全球500强,成为文档分享领域的知名平台之一。目前,豆丁网拥有世界最大的中文文档库,现有文档超过1亿份,覆盖商业、办公、教育、财经、实用信息等各领域。

 

  QQ群是腾讯公司推出的多人聊天交流服务,群主在创建群以后,可以邀请朋友或者有共同兴趣爱好的人到一个群里面聊天。在群内除了聊天,腾讯还提供了群空间服务,在群空间中,用户可以使用群BBS、相册、共享文件等多种方式进行交流。QQ群的理念是群聚精彩,共享盛世。

 

1、百度文库(豆丁)的文档会不会影响主站的长尾流量?

  拉拉:百度文库和豆丁我都用过,有时查资料不经意间都会发现来自文库和豆丁,用这个做网络营销,是不是可以把长尾关键词形成一篇软文,让浏览者从知识中“润物细 无声”的做了一个软广告。

  蒋老师:是的,文库和豆丁收录非常快,排名也非常好。这是百度文库和豆丁的优势,缺点是百度文库文档如果和主站长尾关键词相同,会影响主站长尾的流量,但我们目的是为了产生销售,如果文库和豆丁能带来销售,损失一些流量还是很值得的。对于新站,即使做了100%的原创内容,长尾关键词排名也不是很好,这样的站点做百度文库更加有优势,但对于权重较高的老站而言,可能需要根据自身的实际情况,斟酌利弊,考虑下应当怎么操作。

 

2、为什么采用QQ群的方式

  很多网站都有在线的客服系统,本来是方便潜在客户方便交流,但是相当多的网站都想强行让你去“被沟通”,自然用户体验非常糟糕,让人生厌;反过来,对于经营者,哪个都希望让自己有机会发言,至少把自认为的“好产品”介绍给客户,如果连介绍都没有机会,如何可能产生购买呢?这有点像销售新手。

  人与人之间什么时候愿意交流?一是有共同的兴趣与话题;二是求助解决问题,有人乐意回答。

  而潜在的用户在文库(或豆丁)可能会很认同你文档的内容,但不会浏览你的网站,这时候你的在线客服系统,就不可能起到作用;即使浏览了你的网站,会不会让他们像躲避“保险公司业务员”那样躲避你的在线客服系统也是问题。

  所以在文档中加入QQ群,这样就提供了一个交流的平台,可能会更方便用户进行相关的问题沟通,尤其是技术问题,很多人都喜欢到群里和大家一起讨论,而不是选择客服系统,因为客服系统可能比较有针对性,如果别人到你的网站,可能不方便咨询其他品牌的问题,但qq群拥有更好的开放性。

 

【案例三】跟我学编程网站的无意推广

  有一段时间,我一天接到几十个加群的请求,我感到甚是奇怪,因为我没有在什么地方刻意做过软文和广告,后来问的时候他们是看了一篇“如何学编程”文章,原来如斯。这只是一篇在普通不过的文章,也仅仅是放在了我的QQ空间而已。

  后来我查询了一下百度“如何学编程”,发现了原因,是被大量转载,而在那一时段产生了峰涌。这是我的无意之作,如果你是有意而为之,是不是很可观。

  以下为部分搜索结果截图,这一篇文章搜索有几页之多。

 

3、百度文库(豆丁)有没有时限性?

  拉拉:是不是一篇文章在百库文库(豆丁)一放就一劳永逸了呢?

  蒋老师:显然不是,譬如上面这篇“如何学编程”有一段时间一直排在第一位,后来搜索很靠后,加入群的人数明显减少了;后来又多了,我查了一下,文库又有了,但是发布者已经不是原来的那位了。我发现这两位贡献者都要“分”,即使如此,还是有不少的下载量。

  这就意味着,如果你是网站的经营者:第一、那么最好不要“分”,这样影响文章的有效传播;第二、可以定期在文库重新提交。(这一条,真的不好,会形成文库垃圾,但对于推广则有利)。

 

4、如何准备?

  拉拉:如何准备呢?

  蒋老师:准备工作主要包括,获取长尾关键词、建立word水印模板以及宣传图片、建立QQ群、以及百度文库文档简介的制作。

  (1)获取长尾关键词:这个不用说了,SEO部分已经讲的很清楚了;

  (2)如何去百度文库中插入一些外链和广告

  ① 整理出一批跟本行业相关的文档,如果此文档在百度文库中已经存在,你可以考虑换个标题。

  ② 把标题弄出来以后,到百度指数里面查询一下指数,最好是关键词有指数。这样还会有一些其他的展示机会。

  ③ 调整文档的相关格式,条理清楚。

  ④ 给文档加上相关的外链,可以是图片广告或者是页眉的了解等都可以。

  ⑤ 多拥有几个账号,有一两个高级点的账号即可,其他账号都维持在3级左右。

 

5、有没有注意事项?

  ①文档简介:要特别注重百度文档的简介填写,其实这是你的文章被搜索的时候出现在百度结果中的重要内容。这就跟我们网站的摘要是一样的。

  ② 文档标题: 文档的标题最好能多用陈诉语句或者反问句,比如如何,怎么等。标题要客观,可读性强一点,这样浏览者才会从众多的文档中选中你的文档。

  ③ 文档字数:文档尽量字数多一些,以5-10页最好,太短的文档百度会和核定为文档质量不高或者可读性差,从而不给通过。

  ④ 文档字号:如果你的文档里面放了一些动态或者静态的图片广告,在文库不下载的时候不能浏览。这样你就需要把你的文档的字体变小一点。比如9号字,同时不要给文档设置财富值,这样读者大都会选择把文档下载下来,你的展示目的也就达到了。

  ⑤ 文档格式:通常通过的概率从大到小依次为:PDF格式—>WORD格式—>PPT格式—>TXT格式。

  ⑥ 文档分类:如果是一篇非推广的文档,我觉得你不放对分类也有可能通过,但是如果是推广类的文档,为了提高通过率,还是需要按相关分类放好。否则通过率低,直接影响你的账号。

 

6、能做外链吗?

  拉拉:博客我们可以做外链,文库(豆丁)可以吗?有什么技巧?

  蒋老师:也可以的,技巧如下:

  ① 页眉或者页脚处写上链接,能得到直接展示的机会;

  ② 在文章里面做内部链接,就是选中锚文本,给锚文本加链接;

  ③ 文章内容中直接带出链接,比如:详情可以访问www.genwoxue.com获得。

 

7、能插入广告吗?

  拉拉:我可不可以直接插入广告?

  蒋老师:最好不好,不过艺术一点的广告也未尝不可。

 

作者:jianghuiquan 发表于2011/9/11 20:36:11 原文链接
阅读:2986 评论:0 查看评论

RMAN简明教程之四——RMAN备份详解

$
0
0

       RMAN可以用来备份主备用数据库,如表空间、数据文件、归档日志、控制文件、服务器文件与备份集,下面我们分情况进行试验。

一、文件拷贝
      原始文件的拷贝,有点类似于OS热备份,可以拷贝整个数据文件到另外一个地点,但是结果仅仅只能写入到硬盘,而且单独的文件是分开的。
数据文件拷贝实例:
run {
allocate channel d1 type disk;
allocate channel d2 type disk;
allocate channel d3 type disk;
copy # first
datafile 1 to '$HOME/prd1.dbf',
datafile 2 to '$HOME/prd2.dbf';
copy # second
datafile 3 to '$HOME/prd3.dbf';
sql 'alter system archive log current';
}
二、备份与备份集 
       RMAN 的常规备份是产生只有 RMAN 才能识别的备份集,所以,除了 copy 命令之外的其他备份,都是RMAN 产生的备份集以及对应的备份片。
如下是备份数据库的实例,它开启两个通道,将数据库备份到磁带:
run {
allocate channel t1 type 'SBT_TAPE';
allocate channel t2 type 'SBT_TAPE';
backup
filesperset 2
format 'df_%t_%s_%p'
database;
}
      RMAN 也可以实现多个镜相的备份:
Run{
allocate channel d1 type disk;
allocate channel d2 type disk;
allocate channel d3 type disk;
SET BACKUP COPIES 3;
BACKUP DATAFILE 7 FORMAT '/tmp/%U','?/oradata/%U','?/%U';
};
      以下是常见的备份归档的例子:

RMAN>sql ‘alter system archive log current’;
RMAN>backup archivelog all delete input;
RMAN> backup archivelog from time '01-jan-00' until time '30-jun-00';
RMAN> backup archivelog like 'oracle/arc/dest/log%';   10
RMAN> backup archivelog all;
RMAN> backup archivelog from logseq 20 until logseq 50 thread 1;
RMAN> backup archivelog from scn 1 until scn 9999;
       在RAC环境中,因为数据库是共享的,所以可以连接到一个实例就可以备份整个数据库,但是,因为归档日志可以备份在本地,所以 RAC归档日志的备份就变的复杂一些,我们可以通过连接到两个实例的通道来备份两个实例的归档日志。
run{
ALLOCATE CHANNEL node_c1 DEVICE TYPE DISK CONNECT
'sys/pass@dbin1';
ALLOCATE CHANNEL node_c2 DEVICE TYPE DISK CONNECT
'sys/pass@dbin2';
sql 'ALTER SYSTEM ARCHIVE LOG CURRENT';
backup archivelog all delete input format '/u01/dbbak/%U_%s.bak' filesperset = 5;
}
三、常见备份参数
1、Keep参数可以长期的保持特殊的备份或者拷贝,让它们不受默认备份保持策略的影响,如
RMAN> BACKUP DATABASE KEEP UNTIL TIME
2> "to_date('31-MAR-2002','DD_MM_YYYY)" nologs;
RMAN> BACKUP TABLESPACE SAMPLE KEEP FOREVER NOLOGS;
其中 NOLOGS 表示可以不保留该备份以来的归档日志,默认是 LOGS,表示保留该备份以来的参数,如果想让该备份永久有效,可以使用 FOREVER参数。
2、Tag参数指明了备份集的标志,可以达到 30 个字符长度,如
RMAN> BACKUP DEVICE TYPE DISK DATAFILE 1 TAG
2> "wkly_bkup";
在 Oracle  92 版本以后,RMAN 自动提供一个 TAG,格式为 TAGYYYYMMDDTHHMMSS如TAG20020208T133437,通过备份标志 TAG,也可以很方便的从备份集进行恢复,如Restore database from tag=’tag name’
四、增量备份
       在说明增量备份之前, 首先要理解差异增量与累计增量备份,以及增量备份的备份与恢复原理。差异增量,是默认的增量备份方式。 
差异增量是备份上级或同级备份以来变化的块,累计增量是备份上级备份以来变化的块。累计增量增加了备份的时间,但是因为恢复的时候,需要从更少的备份集中恢复数据,所以,为了减少恢复的时候,累计增量备份将比差异增量备份更有效。 
       不管怎么样增量备份,在 Oracle 版本 9 中,还是需要比较数据库中全部的数据块,这个过程其实也是一个漫长的过程,而且由于增量备份形成多个不同的备份集,使得恢复变的更加不可靠而且速度慢,所以增量备份在版本 9 中仍然是鸡肋,除非是很大型的数据仓库系统,没有必要选择增量备份。
       Oracle 版本10在增量备份上做了很大的改进,可以使增量备份变成真正意义的增量,因为通过特有的增量日志,使得 RMAN 没有必要去比较数据库的每一个数据块,当然,代价就是日志的IO 与磁盘空间付出,完全还是不适合OLTP系统。另外,版本10 通过备份的合并,使增量备份的结果可以合并在一起,而完全的减少了恢复时间。
增量备份都需要一个基础,比如0 级备份就是所有增量的基础备份,0级备份与全备份的不同就是0级备份可以作为其它增量备份的基础备份而全备份是不可以的, 是否选择增量备份作为你的备份策略,最终,需要你自己有一个清醒的认识。
以下是零级备份的例子
backup incremental level 0 database;
一级差异增量例子
backup incremental level 1 database;
一级累计增量例子
backup incremental level 1 cumulative database;   12
五、备份检查
      我们可以通过Validate命令来检查是否能备份,如数据文件是否存在,是否存在坏块不能被备份,如:
BACKUP VALIDATE DATABASE;
BACKUP VALIDATE DATABASE ARCHIVELOG ALL;
六、重新启动备份
       对于异常结束了的备份,很多人可能不想再重新开始备份了吧,特别是备份到 90%以上,因为异常原因终止了该备份,那怎么办呢?RMAN 提供一个重新开始备份的方法,通过简单的命令,你就可以只备份那不到10%的数据了。
RMAN> BACKUP NOT BACKED UP SINCE TIME 'SYSDATE-14'
2> DATABASE PLUS ARCHIVELOG;
七、RMAN 动态性能视图
以下是与 RMAN 备份有关系的一些动态性能视图,信息是从控制文件中获取的。
V$ARCHIVED_LOG
V$BACKUP_CORRUPTION
V$COPY_CORRUPTION
V$BACKUP_DATAFILE
V$BACKUP_REDOLOG
V$BACKUP_SET
V$BACKUP_PIECE
V$BACKUP_DEVICE
V$CONTROLFILE_RECORD_SECTION
这里还有一个视图,可以大致的监控到 RMAN备份进行的程度。如通过如下的 SQL脚本,
将获得备份的进度。
SQL> SELECT SID, SERIAL#, CONTEXT, SOFAR, TOTALWORK,
2 ROUND(SOFAR/TOTALWORK*100,2) "%_COMPLETE"
3 FROM V$SESSION_LONGOPS
4 WHERE OPNAME LIKE 'RMAN%'
5 AND OPNAME NOT LIKE '%aggregate%'
6 AND TOTALWORK != 0
7 AND SOFAR <> TOTALWORK;
SID SERIAL# CONTEXT SOFAR    TOTAL   WORK %_COMPLETE
---    -------      -------        -------     ---------    ----------
13   75          1           9470      15360    61.65
12   81          1           15871    28160    56.36 
 Oracle社区PDM中文网:http://www.pdmcn.com/bbs

 Oracle 专家QQ群:60632593、60618621

 Oracle技术资料:《Oracle 9i RMAN参考使用手册》、《ORACLE10G备份与恢复》、《Oracle Database 10gRMAN备份与恢复

 

作者:newhappy2008 发表于2011/9/12 7:48:00 原文链接
阅读:5098 评论:0 查看评论

RMAN简明教程之五——RMAN还原和恢复

$
0
0

 一、常规还原与恢复
      RMAN 的整个恢复过程可以分为还原(restore)与恢复(recover) ,他们在含义上是有很大差别的,一个是指物理意义的文件的还原与拷贝,一个是指数据库一致性的恢复,所以,正确的理解这两个概念,有助于正确的恢复数据库。
对于RMAN 的备份,还原操作只能是在用 RMAN 或 RMAN 包来做了,对于恢复操作则是很灵活的了,除了 RMAN,也可以在 SQLPLUS 中完成。还原与恢复一个数据库,可以用如下两个简单的命令完成
RMAN>restore database;
RMAN>recover database;
恢复一个表空间,或者恢复一个数据文件,相对比较恢复数据库可能花费更少的时间。
RMAN> SQL "ALTER TABLESPACE tools OFFLINE IMMEDIATE";
RMAN> RESTORE TABLESPACE tools;
RMAN> RECOVER TABLESPACE tools;
RMAN> SQL "ALTER TABLESPACE tools ONLINE";
对于数据库与数据文件,可以从指定的tag恢复
RMAN>RESTORE DATAFILE 1 FROM TAG=’tag name’
对于时间点恢复等不完全恢复,可能只有完全的还原数据库了。
RMAN> RUN {
2> ALLOCATE CHANNEL c1 TYPE DISK;
3> ALLOCATE CHANNEL c2 TYPE DISK;
4> SET UNTIL TIME = '2002-12-09:11:44:00';
5> RESTORE DATABASE;
6> RECOVER DATABASE;
7> ALTER DATABASE OPEN RESETLOGS; }
不完全恢复在 RMAN 中还可以用基于日志的恢复
RMAN> RUN {
2> SET UNTIL SEQUENCE 120 THREAD 1;
3> ALTER DATABASE MOUNT;
4> RESTORE DATABASE;
5> RECOVER DATABASE; # recovers through log 119
6> ALTER DATABASE OPEN RESESTLOGS;
7> }
如果有可能,也可以恢复数据文件到一个新的位置
SET NEWNAME FOR datafile
'/u01/oradata/tools01.dbf' TO '/tmp/tools01.dbf';
RESTORE datafile '/u01/oradata/tools01.dbf';
SWITCH DATAFILE ALL;
除了恢复数据库与数据文件,我们也可以恢复控制文件,需要启动到 nomount 下,用如下
的命令即可   14
Restore controlfile from ‘file name’
Restore controlfile from autobackup
Restore controlfile from tag=‘……’
在正常情况下,不用恢复归档日志,恢复进程会自动寻找所需要的归档日志,当然我们也可以指定恢复到哪里。
SET ARCHIVELOG DESTINATION TO '/u02/tmp_restore';
RESTORE ARCHIVELOG ALL;
如果使用的服务器参数文件(spfile) ,RMAN 可以备份该参数文件,如果发生文件损坏,可以用 RMAN 恢复 spfile 参数文件,在没有参数文件的情况下,用 Rman 的临时参数文件启动数据库到Nomount下,执行如下命令即可
Restore controlfile from autobackup 
Restore controlfile from ‘file name’
二、特殊情况下的恢复
在假定丢失了恢复目录与控制文件,只剩下备份集与备份片,这个时候,可能只能从文件中恢复了。以下是调用dbms_backup_restore包,从文件中恢复的例子。
declare
devtype varchar2(100);
done boolean;
recid number;
stamp number;
fullname varchar2(80);
begin
devtype :=
dbms_backup_restore.deviceallocate('sbt_tape',params=>'ENV=
(NSR_SERVER=backup_server)');
dbms_backup_restore.restoresetdata file;
dbms_backup_restore.restorecontrolfileto(
'first_control_file');
dbms_backup_restore.restorebackuppiece('backup_piece', done);
dbms_backup_restore.copycontrolfile ('first_control_file',
'second_control_file', recid, stamp,fullname);
-- repeat the above copycontrolfile for each control file
end; /
三、还原检查与恢复测试
与备份检查一样,还原操作也可以检查是否能正常restore或者是否该备份集是否有效。如: 
RMAN> RESTORE DATABASE VALIDATE;
RMAN> VALIDATE BACKUPSET 218;
Recover 还可以进行测试,检测恢复的错误,错误信息记载在alert文件中,通过测试,我们可以知道该恢复操作是否能正常完成。   15
SQL>RECOVER TABLESPACE sales TEST;
SQL>RECOVER DATABASE UNTIL CANCEL TEST
四、块级别的恢复
块恢复Block Media Recovery (BMR), 块是恢复的最小单元, 通过块可以减少恢复时间,而且数据文件可以在线。恢复块的时候,必须指定具体的块号,如:
BLOCKRECOVER datafile 6 BLOCK 3;
要恢复的坏块信息可以从报警与跟踪文件,表与索引的分析,DBV 工具或第三方媒体管理工具以及具体的查询语句中获得。产生块损坏的原因一般是间断或随机的 IO 错误或者是内存的块错误。
块的错误信息保存在 V$DATABASE_BLOCK_CORRUPTION,用如下命令恢复该视图中列出的坏块
RMAN> BLOCKRECOVER CORRUPTION LIST
2> RESTORE UNTIL TIME 'sysdate – 10';
备份的坏块信息保存在
V$BACKUP_CORRUPTION
V$COPY_CORRUPTION
可以用如下的命令来恢复坏块。
BLOCKRECOVER datafile 2 BLOCK 12, 13 datafile 7 BLOCK 5, 98, 99 datafile 9 BLOCK 19;
BLOCKRECOVER  TABLESPACE  SYSTEM  DBA  4194404,  4194405  FROM  TAG
"weekly_backup";
BLOCKRECOVER TABLESPACE SYSTEM DBA 4194404, 4194405 RESTORE UNTIL TIME
'SYSDATE-2';
五、数据库复制
可以用RMAN 来进行数据库的复制与克隆,RMAN提供一个专门的命令来完成这个操作。如
CONNECT TARGET
CONNECT AUXILIARY
SYS/aux_pwd@newdb
DUPLICATE TARGET DATABASE TO ndbnewh 
   LOGFILE 
     '?/dbs/log_1.f' SIZE 100M,
     '?/dbs/log_2.f' SIZE 100M 
   SKIP READONLY 
   NOFILENAMECHECK;
在以上的命令执行之前,注意如下几点
1、备份主库上的所有数据文件,控制文件以及备份时与备份后产生的归档日志,并把该备份拷贝到需要复制的机器同样的目录下(如果不是同样的目录,在linux/unix 环境下可以考虑建立一个链接来完成) 。
2、拷贝主数据库的初始化参数文件到复制的机器上,并做相应的修改,如修改数据库名称与实例名称

3、在要复制的机器上创建新的密码文件,并启动复制的数据库到 nomount下。
4、配置主数据库到复制数据库的网络连接或者复制数据库到主数据库的连接。
5、在主数据库或者复制的数据库上运行RMAN,分别连接主数据库与复制数据库实例。
6、运行复制命令,命令将还原所有数据文件,重新创建控制文件,并利用新的参数文件启动恢复数据库到一致状态,最后用resetlog方式打开数据库,创建指定的redolog。
复制命令也可以从磁带上的备份进行复制,并改变数据库名称,也可以改变数据库文件的新的路径以及恢复到以前的时间点, 跳过不需要复制的表空间等, 如一个比较复杂的复制命令: 
RUN
{  
   ALLOCATE AUXILIARY CHANNEL newdb1 DEVICE TYPE sbt; 
   DUPLICATE TARGET DATABASE TO newdb
   DB_FILE_NAME_CONVERT=('/h1/oracle/dbs/trgt/','/h2/oracle/oradata/newdb/')
   UNTIL TIME 'SYSDATE-1'   # specifies incomplete recovery
   SKIP TABLESPACE cmwlite, drsys, example    # skip desired tablespaces
   PFILE = ?/dbs/initNEWDB.ora
   lOGFILE
       GROUP 1 ('?/oradata/newdb/redo01_1.f', 
                  '?/oradata/newdb/redo01_2.f') SIZE 200K, 
       GROUP 2 ('?/oradata/newdb/redo02_1.f', 
                  '?/oradata/newdb/redo02_2.f') SIZE 200K 
       GROUP 3 ('?/oradata/newdb/redo03_1.f',
                  '?/oradata/newdb/redo03_2.f') SIZE 200K REUSE;
}
六、利用 RMAN创建备用数据库
利用 RMAN 创建备用数据库可以用两种办法,一种是常规的 Restore 命令,利用从主数据库拷贝过去的备用控制文件,把备用数据库启动到备用 mount 下,这个时候的备用数据库是没有数据文件的。 然后在备用端, 启动 RMAN命令, 连接该数据库 (与主数据库 DBID一样) ,把从主数据库拷贝过来的RMAN 备份还原出来。最后就与其它方法一样了,进入备用的管理恢复模式。
另外一个办法就是复制命令了,如
DUPLICATE TARGET DATABASE FOR STANDBY NOFILENAMECHECK;
以下详细的介绍了这一个过程。
1、创建备用参数文件与密码文件,启动备用数据库到 nomount下
2、备份主数据库与备用控制文件以及所有归档
RMAN> Backup Database;
RMAN> Backup current controlfile for standby;
RMAN> sql "Alter System Archive Log Current"; 
RMAN> Backup filesperset 10 ArchiveLog all delete input;
3、拷贝所有的备份到备用数据库相同路径下
4、配置主数据库到备用数据库的连接
5、启动RMAN
rman target / auxiliary
sys/change_on_install@STANDBY    17
6,开始创建备用数据库
RMAN> duplicate target database for standby dorecover nofilenamecheck;
整个过程包括了备用控制文件的创建,启动到 Mount 下,参数文件中指定的路径转换与数据文件的还原,归档日志的还原等。
7、最后恢复日志并启动到管理恢复模式下。
SQL> recover standby database; 
SQL> alter database recover managed standby database disconnect;

 Oracle社区PDM中文网:http://www.pdmcn.com/bbs

 Oracle 专家QQ群:60632593、60618621

 Oracle技术资料:《Oracle 9i RMAN参考使用手册》、《ORACLE10G备份与恢复》、《Oracle Database 10gRMAN备份与恢复

 

作者:newhappy2008 发表于2011/9/12 15:52:27 原文链接
阅读:5175 评论:0 查看评论

RMAN简明教程之六——RMAN的管理

$
0
0

 一、Report命令
Report 命令可以检测那些文件需要备份,那些备份能被删除以及那些文件能不能获得的信息,如
报告数据库的所有能备份数据文件对象
Report schema
或者
RMAN> REPORT SCHEMA AT TIME 'SYSDATE-14';
RMAN> REPORT SCHEMA AT SCN 1000;
RMAN> REPORT SCHEMA AT SEQUENCE 100 THREAD 1;
报告需要备份的数据文件
Report need backup [ redundancy | days | incremental n];
报告过期了的数据文件或者不可用的备份与拷贝
Report obsolete [orphan]
报告不能获得或者不能到达的数据文件信息
Report unrecoverable [database]
二、List命令
List命令一般用来查看备份与拷贝信息,如
查看备份信息
List backup
查看备份汇总信息
List backup summary
查看文件拷贝的信息
List copy
查看具体的备份信息
List backup of datafile ‘file name’
list incarnation of database;   18
三、Crosscheck命令
检查磁盘或磁带上的备份或拷贝是否正确,并更新备份或者拷贝的状态。如果不正确,将标记为expired(过期)
Crosscheck backup;
Crosscheck archivelog all;
Delete [noprompt] expired backup命令删除过期备份
也可以用 List来查看相应的报告
LIST EXPIRED BACKUP;
LIST EXPIRED BACKUP SUMMARY; 
四、 Delete 命令
Delete命令可以用来删除指定的备份或者用来删除废弃或者是过期的备份集如删除指定的备份集与备份片
RMAN> DELETE BACKUPPIECE 101;
RMAN> DELETE CONTROLFILECOPY '/tmp/control01.ctl';
RMAN> DELETE BACKUP OF TABLESPACE users DEVICE TYPE sbt;
删除过期或者废弃了的备份
RMAN> DELETE EXPIRED BACKUP;
RMAN> DELETE NOPROMPT OBSOLETE;
RMAN> DELETE OBSOLETE REDUNDANCY = 3;
RMAN> DELETE OBSOLETE RECOVERY WINDOW OF 7 DAYS;
删除指定的备份归档
RMAN> DELETE NOPROMPT ARCHIVELOG UNTIL SEQUENCE = 300;

PDM中文网Oracle社区http://www.pdmcn.com/bbs

 Oracle 专家QQ群:60632593、60618621

 Oracle技术资料:《Oracle 9i RMAN参考使用手册》、《ORACLE10G备份与恢复》、《Oracle Database 10gRMAN备份与恢复

 

作者:newhappy2008 发表于2011/9/12 15:55:55 原文链接
阅读:4442 评论:1 查看评论

RMAN简明教程之七——恢复目录与恢复目录的使用

$
0
0

       Oracle版本9因为控制文件的自动备份,可以很大程度成不需要使用恢复目录,但是使用恢复目录的也有如下好处
·有些命令只被恢复目录支持(对于9i来说,也就是专门操作恢复目录的语句而已)
·能保留更多的历史备份信息
·一个恢复目录能管理与备份多个目标数据库
·如果在9i以前,丢失控制文件而没有恢复目录将是难以恢复的
·如果没有恢复目录,而且发生了结构上的改变,时间点的恢复需要小心操作
·能存储备份与恢复的脚本
      可以看到,主要是可以保留更多的备份信息与方便的管理多个目标数据库,这个在众多目标数据库的情况下,是可以考虑的。  

一、创建恢复目录
      注意,恢复目录不要与目标数据库在同一台机器上,而且大小要求比较小。
SQL> create user RMAN identified by RMAN
2 temporary tablespace TEMP
3 default tablespace RCVCAT
4 quota unlimited on RCVCAT;
SQL> grant recovery_catalog_owner to RMAN;
RMAN> create catalog
RMAN> register database;
恢复目录可以采用如下命令升级与删除
RMAN> UPGRADE CATALOG;
RMAN> DROP CATALOG;
二、恢复目录管理
恢复目录支持如下的命令
{CREATE|UPGRADE|DROP} CATALOG
{CREATE|DELETE|REPLACE|PRINT} SCRIPT
LIST INCARNATION
REGISTER DATABASE
REPORT SCHEMA AT TIME
RESET DATABASE
RESYNC CATALOG
1、Resync命令
Resync可以同步数据库与恢复目录之间的信息,在实际情况下,rman一般可以自动同步。
在如下情况下需要同步
·数据库物理结构的改变
·数据文件增加或者是改变大小
·表空间删除
·回滚段的创建与删除
·每产生10个归档日志
2、Reset  命令
目标数据库resetlogs之后,需要重新设置恢复目录。Reset命令就用来重新设置恢复目录。
三、恢复目录视图
恢复目录本身有一组视图,用于存放目标数据库与备份信息,如恢复目录的相关视图
RC_DATABASE
RC_DATAFILE
RC_STORED_SCRIPT   20
RC_STORED_SCRIPT_LINE
RC_TABLESPACE
可以通过如下命令来查看相关信息
select * from rc_database;
四、存储脚本
存储脚本

RMAN> creata script level0backp{
       backup
       incremental level 0
       format '/u01/db01/backup/%U'
       filesperset 5
       database plus archivelog delete input;
       sql 'alter database archive log current';
       }
执行脚本
RMAN> run {execute script Level0backup;}
更新脚本
RMAN> replace script level0backup{
      ……
       }
删除脚本
RMAN> delete script Level0backup;
查看脚本
RMAN> print script level0backup; 
 
一个实用脚本,包括备份RAC数据库与归档日志的 shell脚本
[oracle@db worksh]$ more rmanback.sh 
#!/bin/sh
#set env
export ORACLE_HOME=/opt/oracle/product/9.2
export ORACLE_SID=db2in1
export NLS_LANG="AMERICAN_AMERICA.zhs16gbk"
export PATH=$PATH:$ORACLE_HOME/bin:/sbin:/usr/sbin
 
echo "-----------------------------start-----------------------------";date
#backup start
$ORACLE_HOME/bin/rman <<EOF
connect target
delete noprompt obsolete;
backup database include current controlfile format '/rmanback/db2/%U_%s.bak' filesperset = 2;
 
run{
ALLOCATE CHANNEL node_c1 DEVICE TYPE DISK CONNECT
'sys/pass@db1in1';
ALLOCATE CHANNEL node_c2 DEVICE TYPE DISK CONNECT
'sys/pass@db2in2';
sql 'ALTER SYSTEM ARCHIVE LOG CURRENT';
backup archivelog all delete input format '/ rmanback/db2/%U_%s.bak' filesperset = 5;
}
 
list backup;
exit;
EOF
echo "------------------------------end------------------------------";date

 

 

Oracle社区PDM中文网http://www.pdmcn.com/bbs

 Oracle 专家QQ群:60632593、60618621

 Oracle技术资料:《Oracle 9i RMAN参考使用手册》、《ORACLE10G备份与恢复》、《Oracle Database 10gRMAN备份与恢复

作者:newhappy2008 发表于2011/9/12 16:03:23 原文链接
阅读:3141 评论:0 查看评论
Viewing all 35570 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>