From 2201be708e24478b3a546d027d623fe5185bc6e8 Mon Sep 17 00:00:00 2001 From: CyC2018 <1029579233@qq.com> Date: Thu, 22 Feb 2018 22:06:47 +0800 Subject: [PATCH] add some source code --- notes/src/String.java | 2984 ++++++++++ notes/src/StringBuffer.java | 527 ++ notes/src/StringBuilder.java | 369 ++ notes/笔记/2016 校招真题题解.md.txt | 716 --- notes/笔记/2017 校招真题题解.md.txt | 1 - notes/笔记/C++.md.txt | 339 -- notes/笔记/Git.md.txt | 164 - notes/笔记/Github.md.txt | 143 - notes/笔记/HTTP+.md.txt | 29 - notes/笔记/HTTP.md.txt | 360 -- notes/笔记/JVM.md.txt | 611 --- notes/笔记/Java IO.md.txt | 376 -- notes/笔记/Java 基础语法.md.txt | 128 - notes/笔记/Java 容器.md.txt | 341 -- notes/笔记/Java 并发.md.txt | 536 -- notes/笔记/Java 源码解析.md.txt | 1 - notes/笔记/Leetcode 题解.md.txt | 4844 ----------------- notes/笔记/Linux+.md.txt | 17 - notes/笔记/Linux.md.txt | 987 ---- notes/笔记/MapReduce.md.txt | 176 - notes/笔记/MySQL+.md.txt | 28 - notes/笔记/MySQL.md.txt | 382 -- notes/笔记/PHP.md.txt | 1 - notes/笔记/Python 基本语法.md.txt | 548 -- notes/笔记/Python 知识点扩展.md.txt | 471 -- notes/笔记/Redis.md.txt | 284 - notes/笔记/SQL 实战.md.txt | 225 - notes/笔记/SQL 语法.md.txt | 702 --- notes/笔记/一致性原理.md.txt | 219 - notes/笔记/分布式基础.md.txt | 116 - notes/笔记/剑指 offer 题解.md.txt | 1728 ------ notes/笔记/扩展性与空间限制.md.txt | 163 - notes/笔记/数据库系统原理.md.txt | 301 - .../笔记/文档-Google Java 编程风格指南.md.txt | 115 - notes/笔记/智力题.md.txt | 193 - notes/笔记/正则表达式.md.txt | 391 -- notes/笔记/程序员的职业素养.md.txt | 120 - notes/笔记/算法.md.txt | 1539 ------ notes/笔记/编写可读代码的艺术.md.txt | 328 -- notes/笔记/计算机操作系统+.md.txt | 10 - notes/笔记/计算机操作系统.md.txt | 657 --- notes/笔记/计算机网络+.md.txt | 21 - notes/笔记/计算机网络.md.txt | 760 --- notes/笔记/设计模式.md.txt | 1703 ------ notes/笔记/重构.md.txt | 877 --- notes/笔记/面向对象思想.md.txt | 292 - notes/笔记/面向对象程序设计.md.txt | 403 -- notes/笔记/黑客与画家.md.txt | 128 - ...台开发面试常见问题汇总_讨论区_牛客网 -.txt | 1259 ----- .../MySQL 经典面试题 - 王守昌 - 博客园.txt | 218 - notes/面经/csdn-腾讯后台开发面试经验.md.txt | 13 - notes/面经/java后台研发面经.md.txt | 1 - notes/面经/亚辉的面试经验-cvte.md.txt | 92 - notes/面经/亚辉的面试经验-美团.md.txt | 36 - notes/面经/亚辉的面试经验-腾讯.md.txt | 261 - notes/面经/亚辉的面试经验-阿里.md.txt | 147 - notes/面经/知识点差缺补漏.txt | 6 - notes/面经/腾讯后台研发面试.md.txt | 1 - notes/面经/面经文档.txt | 1 - notes/面经/面经汇总.md.txt | 119 - notes/面经/面试.md.txt | 7 - notes/面经/面试题-HTTP.txt | 6 - notes/面经/面试题-Linux.md.txt | 144 - notes/面经/面试题-java.md.txt | 1 - notes/面经/面试题-mysql.md.txt | 1 - notes/面经/面试题-操作系统.md.txt | 2 - notes/面经/面试题-计算机网络.md.txt | 1 - 67 files changed, 3880 insertions(+), 24790 deletions(-) create mode 100644 notes/src/String.java create mode 100644 notes/src/StringBuffer.java create mode 100644 notes/src/StringBuilder.java delete mode 100644 notes/笔记/2016 校招真题题解.md.txt delete mode 100644 notes/笔记/2017 校招真题题解.md.txt delete mode 100644 notes/笔记/C++.md.txt delete mode 100644 notes/笔记/Git.md.txt delete mode 100644 notes/笔记/Github.md.txt delete mode 100644 notes/笔记/HTTP+.md.txt delete mode 100644 notes/笔记/HTTP.md.txt delete mode 100644 notes/笔记/JVM.md.txt delete mode 100644 notes/笔记/Java IO.md.txt delete mode 100644 notes/笔记/Java 基础语法.md.txt delete mode 100644 notes/笔记/Java 容器.md.txt delete mode 100644 notes/笔记/Java 并发.md.txt delete mode 100644 notes/笔记/Java 源码解析.md.txt delete mode 100644 notes/笔记/Leetcode 题解.md.txt delete mode 100644 notes/笔记/Linux+.md.txt delete mode 100644 notes/笔记/Linux.md.txt delete mode 100644 notes/笔记/MapReduce.md.txt delete mode 100644 notes/笔记/MySQL+.md.txt delete mode 100644 notes/笔记/MySQL.md.txt delete mode 100644 notes/笔记/PHP.md.txt delete mode 100644 notes/笔记/Python 基本语法.md.txt delete mode 100644 notes/笔记/Python 知识点扩展.md.txt delete mode 100644 notes/笔记/Redis.md.txt delete mode 100644 notes/笔记/SQL 实战.md.txt delete mode 100644 notes/笔记/SQL 语法.md.txt delete mode 100644 notes/笔记/一致性原理.md.txt delete mode 100644 notes/笔记/分布式基础.md.txt delete mode 100644 notes/笔记/剑指 offer 题解.md.txt delete mode 100644 notes/笔记/扩展性与空间限制.md.txt delete mode 100644 notes/笔记/数据库系统原理.md.txt delete mode 100644 notes/笔记/文档-Google Java 编程风格指南.md.txt delete mode 100644 notes/笔记/智力题.md.txt delete mode 100644 notes/笔记/正则表达式.md.txt delete mode 100644 notes/笔记/程序员的职业素养.md.txt delete mode 100644 notes/笔记/算法.md.txt delete mode 100644 notes/笔记/编写可读代码的艺术.md.txt delete mode 100644 notes/笔记/计算机操作系统+.md.txt delete mode 100644 notes/笔记/计算机操作系统.md.txt delete mode 100644 notes/笔记/计算机网络+.md.txt delete mode 100644 notes/笔记/计算机网络.md.txt delete mode 100644 notes/笔记/设计模式.md.txt delete mode 100644 notes/笔记/重构.md.txt delete mode 100644 notes/笔记/面向对象思想.md.txt delete mode 100644 notes/笔记/面向对象程序设计.md.txt delete mode 100644 notes/笔记/黑客与画家.md.txt delete mode 100644 notes/面经/C++后台开发面试常见问题汇总_讨论区_牛客网 -.txt delete mode 100644 notes/面经/MySQL 经典面试题 - 王守昌 - 博客园.txt delete mode 100644 notes/面经/csdn-腾讯后台开发面试经验.md.txt delete mode 100644 notes/面经/java后台研发面经.md.txt delete mode 100644 notes/面经/亚辉的面试经验-cvte.md.txt delete mode 100644 notes/面经/亚辉的面试经验-美团.md.txt delete mode 100644 notes/面经/亚辉的面试经验-腾讯.md.txt delete mode 100644 notes/面经/亚辉的面试经验-阿里.md.txt delete mode 100644 notes/面经/知识点差缺补漏.txt delete mode 100644 notes/面经/腾讯后台研发面试.md.txt delete mode 100644 notes/面经/面经文档.txt delete mode 100644 notes/面经/面经汇总.md.txt delete mode 100644 notes/面经/面试.md.txt delete mode 100644 notes/面经/面试题-HTTP.txt delete mode 100644 notes/面经/面试题-Linux.md.txt delete mode 100644 notes/面经/面试题-java.md.txt delete mode 100644 notes/面经/面试题-mysql.md.txt delete mode 100644 notes/面经/面试题-操作系统.md.txt delete mode 100644 notes/面经/面试题-计算机网络.md.txt diff --git a/notes/src/String.java b/notes/src/String.java new file mode 100644 index 00000000..b68f7ff2 --- /dev/null +++ b/notes/src/String.java @@ -0,0 +1,2984 @@ + +package java.lang; + +import java.io.ObjectStreamClass; +import java.io.ObjectStreamField; +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Formatter; +import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +public final class String + implements java.io.Serializable, Comparable, CharSequence +{ + /** The value is used for character storage. */ + private final char value[]; + + /** The offset is the first index of the storage that is used. */ + private final int offset; + + /** The count is the number of characters in the String. */ + private final int count; + + /** Cache the hash code for the string */ + private int hash; // Default to 0 + + /** use serialVersionUID from JDK 1.0.2 for interoperability */ + private static final long serialVersionUID = -6849794470754667710L; + + /** + * Class String is special cased within the Serialization Stream Protocol. + * + * A String instance is written initially into an ObjectOutputStream in the + * following format: + *
+     *      TC_STRING (utf String)
+     * 
+ * The String is written by method DataOutput.writeUTF. + * A new handle is generated to refer to all future references to the + * string instance within the stream. + */ + private static final ObjectStreamField[] serialPersistentFields = + new ObjectStreamField[0]; + + /** + * Initializes a newly created {@code String} object so that it represents + * an empty character sequence. Note that use of this constructor is + * unnecessary since Strings are immutable. + */ + public String() { + this.offset = 0; + this.count = 0; + this.value = new char[0]; + } + + /** + * Initializes a newly created {@code String} object so that it represents + * the same sequence of characters as the argument; in other words, the + * newly created string is a copy of the argument string. Unless an + * explicit copy of {@code original} is needed, use of this constructor is + * unnecessary since Strings are immutable. + * + * @param original + * A {@code String} + */ + public String(String original) { + int size = original.count; + char[] originalValue = original.value; + char[] v; + if (originalValue.length > size) { + // The array representing the String is bigger than the new + // String itself. Perhaps this constructor is being called + // in order to trim the baggage, so make a copy of the array. + int off = original.offset; + v = Arrays.copyOfRange(originalValue, off, off+size); + } else { + // The array representing the String is the same + // size as the String, so no point in making a copy. + v = originalValue; + } + this.offset = 0; + this.count = size; + this.value = v; + } + + /** + * Allocates a new {@code String} so that it represents the sequence of + * characters currently contained in the character array argument. The + * contents of the character array are copied; subsequent modification of + * the character array does not affect the newly created string. + * + * @param value + * The initial value of the string + */ + public String(char value[]) { + int size = value.length; + this.offset = 0; + this.count = size; + this.value = Arrays.copyOf(value, size); + } + + /** + * Allocates a new {@code String} that contains characters from a subarray + * of the character array argument. The {@code offset} argument is the + * index of the first character of the subarray and the {@code count} + * argument specifies the length of the subarray. The contents of the + * subarray are copied; subsequent modification of the character array does + * not affect the newly created string. + * + * @param value + * Array that is the source of characters + * + * @param offset + * The initial offset + * + * @param count + * The length + * + * @throws IndexOutOfBoundsException + * If the {@code offset} and {@code count} arguments index + * characters outside the bounds of the {@code value} array + */ + public String(char value[], int offset, int count) { + if (offset < 0) { + throw new StringIndexOutOfBoundsException(offset); + } + if (count < 0) { + throw new StringIndexOutOfBoundsException(count); + } + // Note: offset or count might be near -1>>>1. + if (offset > value.length - count) { + throw new StringIndexOutOfBoundsException(offset + count); + } + this.offset = 0; + this.count = count; + this.value = Arrays.copyOfRange(value, offset, offset+count); + } + + /** + * Allocates a new {@code String} that contains characters from a subarray + * of the Unicode code point array + * argument. The {@code offset} argument is the index of the first code + * point of the subarray and the {@code count} argument specifies the + * length of the subarray. The contents of the subarray are converted to + * {@code char}s; subsequent modification of the {@code int} array does not + * affect the newly created string. + * + * @param codePoints + * Array that is the source of Unicode code points + * + * @param offset + * The initial offset + * + * @param count + * The length + * + * @throws IllegalArgumentException + * If any invalid Unicode code point is found in {@code + * codePoints} + * + * @throws IndexOutOfBoundsException + * If the {@code offset} and {@code count} arguments index + * characters outside the bounds of the {@code codePoints} array + * + * @since 1.5 + */ + public String(int[] codePoints, int offset, int count) { + if (offset < 0) { + throw new StringIndexOutOfBoundsException(offset); + } + if (count < 0) { + throw new StringIndexOutOfBoundsException(count); + } + // Note: offset or count might be near -1>>>1. + if (offset > codePoints.length - count) { + throw new StringIndexOutOfBoundsException(offset + count); + } + + final int end = offset + count; + + // Pass 1: Compute precise size of char[] + int n = count; + for (int i = offset; i < end; i++) { + int c = codePoints[i]; + if (Character.isBmpCodePoint(c)) + continue; + else if (Character.isValidCodePoint(c)) + n++; + else throw new IllegalArgumentException(Integer.toString(c)); + } + + // Pass 2: Allocate and fill in char[] + final char[] v = new char[n]; + + for (int i = offset, j = 0; i < end; i++, j++) { + int c = codePoints[i]; + if (Character.isBmpCodePoint(c)) + v[j] = (char) c; + else + Character.toSurrogates(c, v, j++); + } + + this.value = v; + this.count = n; + this.offset = 0; + } + + /** + * Allocates a new {@code String} constructed from a subarray of an array + * of 8-bit integer values. + * + *

The {@code offset} argument is the index of the first byte of the + * subarray, and the {@code count} argument specifies the length of the + * subarray. + * + *

Each {@code byte} in the subarray is converted to a {@code char} as + * specified in the method above. + * + * @deprecated This method does not properly convert bytes into characters. + * As of JDK 1.1, the preferred way to do this is via the + * {@code String} constructors that take a {@link + * java.nio.charset.Charset}, charset name, or that use the platform's + * default charset. + * + * @param ascii + * The bytes to be converted to characters + * + * @param hibyte + * The top 8 bits of each 16-bit Unicode code unit + * + * @param offset + * The initial offset + * @param count + * The length + * + * @throws IndexOutOfBoundsException + * If the {@code offset} or {@code count} argument is invalid + * + * @see #String(byte[], int) + * @see #String(byte[], int, int, java.lang.String) + * @see #String(byte[], int, int, java.nio.charset.Charset) + * @see #String(byte[], int, int) + * @see #String(byte[], java.lang.String) + * @see #String(byte[], java.nio.charset.Charset) + * @see #String(byte[]) + */ + @Deprecated + public String(byte ascii[], int hibyte, int offset, int count) { + checkBounds(ascii, offset, count); + char value[] = new char[count]; + + if (hibyte == 0) { + for (int i = count ; i-- > 0 ;) { + value[i] = (char) (ascii[i + offset] & 0xff); + } + } else { + hibyte <<= 8; + for (int i = count ; i-- > 0 ;) { + value[i] = (char) (hibyte | (ascii[i + offset] & 0xff)); + } + } + this.offset = 0; + this.count = count; + this.value = value; + } + + /** + * Allocates a new {@code String} containing characters constructed from + * an array of 8-bit integer values. Each character cin the + * resulting string is constructed from the corresponding component + * b in the byte array such that: + * + *

+     *     c == (char)(((hibyte & 0xff) << 8)
+     *                         | (b & 0xff))
+     * 
+ * + * @deprecated This method does not properly convert bytes into + * characters. As of JDK 1.1, the preferred way to do this is via the + * {@code String} constructors that take a {@link + * java.nio.charset.Charset}, charset name, or that use the platform's + * default charset. + * + * @param ascii + * The bytes to be converted to characters + * + * @param hibyte + * The top 8 bits of each 16-bit Unicode code unit + * + * @see #String(byte[], int, int, java.lang.String) + * @see #String(byte[], int, int, java.nio.charset.Charset) + * @see #String(byte[], int, int) + * @see #String(byte[], java.lang.String) + * @see #String(byte[], java.nio.charset.Charset) + * @see #String(byte[]) + */ + @Deprecated + public String(byte ascii[], int hibyte) { + this(ascii, hibyte, 0, ascii.length); + } + + /* Common private utility method used to bounds check the byte array + * and requested offset & length values used by the String(byte[],..) + * constructors. + */ + private static void checkBounds(byte[] bytes, int offset, int length) { + if (length < 0) + throw new StringIndexOutOfBoundsException(length); + if (offset < 0) + throw new StringIndexOutOfBoundsException(offset); + if (offset > bytes.length - length) + throw new StringIndexOutOfBoundsException(offset + length); + } + + /** + * Constructs a new {@code String} by decoding the specified subarray of + * bytes using the specified charset. The length of the new {@code String} + * is a function of the charset, and hence may not be equal to the length + * of the subarray. + * + *

The behavior of this constructor when the given bytes are not valid + * in the given charset is unspecified. The {@link + * java.nio.charset.CharsetDecoder} class should be used when more control + * over the decoding process is required. + * + * @param bytes + * The bytes to be decoded into characters + * + * @param offset + * The index of the first byte to decode + * + * @param length + * The number of bytes to decode + + * @param charsetName + * The name of a supported {@linkplain java.nio.charset.Charset + * charset} + * + * @throws UnsupportedEncodingException + * If the named charset is not supported + * + * @throws IndexOutOfBoundsException + * If the {@code offset} and {@code length} arguments index + * characters outside the bounds of the {@code bytes} array + * + * @since JDK1.1 + */ + public String(byte bytes[], int offset, int length, String charsetName) + throws UnsupportedEncodingException + { + if (charsetName == null) + throw new NullPointerException("charsetName"); + checkBounds(bytes, offset, length); + char[] v = StringCoding.decode(charsetName, bytes, offset, length); + this.offset = 0; + this.count = v.length; + this.value = v; + } + + /** + * Constructs a new {@code String} by decoding the specified subarray of + * bytes using the specified {@linkplain java.nio.charset.Charset charset}. + * The length of the new {@code String} is a function of the charset, and + * hence may not be equal to the length of the subarray. + * + *

This method always replaces malformed-input and unmappable-character + * sequences with this charset's default replacement string. The {@link + * java.nio.charset.CharsetDecoder} class should be used when more control + * over the decoding process is required. + * + * @param bytes + * The bytes to be decoded into characters + * + * @param offset + * The index of the first byte to decode + * + * @param length + * The number of bytes to decode + * + * @param charset + * The {@linkplain java.nio.charset.Charset charset} to be used to + * decode the {@code bytes} + * + * @throws IndexOutOfBoundsException + * If the {@code offset} and {@code length} arguments index + * characters outside the bounds of the {@code bytes} array + * + * @since 1.6 + */ + public String(byte bytes[], int offset, int length, Charset charset) { + if (charset == null) + throw new NullPointerException("charset"); + checkBounds(bytes, offset, length); + char[] v = StringCoding.decode(charset, bytes, offset, length); + this.offset = 0; + this.count = v.length; + this.value = v; + } + + /** + * Constructs a new {@code String} by decoding the specified array of bytes + * using the specified {@linkplain java.nio.charset.Charset charset}. The + * length of the new {@code String} is a function of the charset, and hence + * may not be equal to the length of the byte array. + * + *

The behavior of this constructor when the given bytes are not valid + * in the given charset is unspecified. The {@link + * java.nio.charset.CharsetDecoder} class should be used when more control + * over the decoding process is required. + * + * @param bytes + * The bytes to be decoded into characters + * + * @param charsetName + * The name of a supported {@linkplain java.nio.charset.Charset + * charset} + * + * @throws UnsupportedEncodingException + * If the named charset is not supported + * + * @since JDK1.1 + */ + public String(byte bytes[], String charsetName) + throws UnsupportedEncodingException + { + this(bytes, 0, bytes.length, charsetName); + } + + /** + * Constructs a new {@code String} by decoding the specified array of + * bytes using the specified {@linkplain java.nio.charset.Charset charset}. + * The length of the new {@code String} is a function of the charset, and + * hence may not be equal to the length of the byte array. + * + *

This method always replaces malformed-input and unmappable-character + * sequences with this charset's default replacement string. The {@link + * java.nio.charset.CharsetDecoder} class should be used when more control + * over the decoding process is required. + * + * @param bytes + * The bytes to be decoded into characters + * + * @param charset + * The {@linkplain java.nio.charset.Charset charset} to be used to + * decode the {@code bytes} + * + * @since 1.6 + */ + public String(byte bytes[], Charset charset) { + this(bytes, 0, bytes.length, charset); + } + + /** + * Constructs a new {@code String} by decoding the specified subarray of + * bytes using the platform's default charset. The length of the new + * {@code String} is a function of the charset, and hence may not be equal + * to the length of the subarray. + * + *

The behavior of this constructor when the given bytes are not valid + * in the default charset is unspecified. The {@link + * java.nio.charset.CharsetDecoder} class should be used when more control + * over the decoding process is required. + * + * @param bytes + * The bytes to be decoded into characters + * + * @param offset + * The index of the first byte to decode + * + * @param length + * The number of bytes to decode + * + * @throws IndexOutOfBoundsException + * If the {@code offset} and the {@code length} arguments index + * characters outside the bounds of the {@code bytes} array + * + * @since JDK1.1 + */ + public String(byte bytes[], int offset, int length) { + checkBounds(bytes, offset, length); + char[] v = StringCoding.decode(bytes, offset, length); + this.offset = 0; + this.count = v.length; + this.value = v; + } + + /** + * Constructs a new {@code String} by decoding the specified array of bytes + * using the platform's default charset. The length of the new {@code + * String} is a function of the charset, and hence may not be equal to the + * length of the byte array. + * + *

The behavior of this constructor when the given bytes are not valid + * in the default charset is unspecified. The {@link + * java.nio.charset.CharsetDecoder} class should be used when more control + * over the decoding process is required. + * + * @param bytes + * The bytes to be decoded into characters + * + * @since JDK1.1 + */ + public String(byte bytes[]) { + this(bytes, 0, bytes.length); + } + + /** + * Allocates a new string that contains the sequence of characters + * currently contained in the string buffer argument. The contents of the + * string buffer are copied; subsequent modification of the string buffer + * does not affect the newly created string. + * + * @param buffer + * A {@code StringBuffer} + */ + public String(StringBuffer buffer) { + String result = buffer.toString(); + this.value = result.value; + this.count = result.count; + this.offset = result.offset; + } + + /** + * Allocates a new string that contains the sequence of characters + * currently contained in the string builder argument. The contents of the + * string builder are copied; subsequent modification of the string builder + * does not affect the newly created string. + * + *

This constructor is provided to ease migration to {@code + * StringBuilder}. Obtaining a string from a string builder via the {@code + * toString} method is likely to run faster and is generally preferred. + * + * @param builder + * A {@code StringBuilder} + * + * @since 1.5 + */ + public String(StringBuilder builder) { + String result = builder.toString(); + this.value = result.value; + this.count = result.count; + this.offset = result.offset; + } + + + // Package private constructor which shares value array for speed. + String(int offset, int count, char value[]) { + this.value = value; + this.offset = offset; + this.count = count; + } + + /** + * Returns the length of this string. + * The length is equal to the number of Unicode + * code units in the string. + * + * @return the length of the sequence of characters represented by this + * object. + */ + public int length() { + return count; + } + + /** + * Returns true if, and only if, {@link #length()} is 0. + * + * @return true if {@link #length()} is 0, otherwise + * false + * + * @since 1.6 + */ + public boolean isEmpty() { + return count == 0; + } + + /** + * Returns the char value at the + * specified index. An index ranges from 0 to + * length() - 1. The first char value of the sequence + * is at index 0, the next at index 1, + * and so on, as for array indexing. + * + *

If the char value specified by the index is a + * surrogate, the surrogate + * value is returned. + * + * @param index the index of the char value. + * @return the char value at the specified index of this string. + * The first char value is at index 0. + * @exception IndexOutOfBoundsException if the index + * argument is negative or not less than the length of this + * string. + */ + public char charAt(int index) { + if ((index < 0) || (index >= count)) { + throw new StringIndexOutOfBoundsException(index); + } + return value[index + offset]; + } + + /** + * Returns the character (Unicode code point) at the specified + * index. The index refers to char values + * (Unicode code units) and ranges from 0 to + * {@link #length()} - 1. + * + *

If the char value specified at the given index + * is in the high-surrogate range, the following index is less + * than the length of this String, and the + * char value at the following index is in the + * low-surrogate range, then the supplementary code point + * corresponding to this surrogate pair is returned. Otherwise, + * the char value at the given index is returned. + * + * @param index the index to the char values + * @return the code point value of the character at the + * index + * @exception IndexOutOfBoundsException if the index + * argument is negative or not less than the length of this + * string. + * @since 1.5 + */ + public int codePointAt(int index) { + if ((index < 0) || (index >= count)) { + throw new StringIndexOutOfBoundsException(index); + } + return Character.codePointAtImpl(value, offset + index, offset + count); + } + + /** + * Returns the character (Unicode code point) before the specified + * index. The index refers to char values + * (Unicode code units) and ranges from 1 to {@link + * CharSequence#length() length}. + * + *

If the char value at (index - 1) + * is in the low-surrogate range, (index - 2) is not + * negative, and the char value at (index - + * 2) is in the high-surrogate range, then the + * supplementary code point value of the surrogate pair is + * returned. If the char value at index - + * 1 is an unpaired low-surrogate or a high-surrogate, the + * surrogate value is returned. + * + * @param index the index following the code point that should be returned + * @return the Unicode code point value before the given index. + * @exception IndexOutOfBoundsException if the index + * argument is less than 1 or greater than the length + * of this string. + * @since 1.5 + */ + public int codePointBefore(int index) { + int i = index - 1; + if ((i < 0) || (i >= count)) { + throw new StringIndexOutOfBoundsException(index); + } + return Character.codePointBeforeImpl(value, offset + index, offset); + } + + /** + * Returns the number of Unicode code points in the specified text + * range of this String. The text range begins at the + * specified beginIndex and extends to the + * char at index endIndex - 1. Thus the + * length (in chars) of the text range is + * endIndex-beginIndex. Unpaired surrogates within + * the text range count as one code point each. + * + * @param beginIndex the index to the first char of + * the text range. + * @param endIndex the index after the last char of + * the text range. + * @return the number of Unicode code points in the specified text + * range + * @exception IndexOutOfBoundsException if the + * beginIndex is negative, or endIndex + * is larger than the length of this String, or + * beginIndex is larger than endIndex. + * @since 1.5 + */ + public int codePointCount(int beginIndex, int endIndex) { + if (beginIndex < 0 || endIndex > count || beginIndex > endIndex) { + throw new IndexOutOfBoundsException(); + } + return Character.codePointCountImpl(value, offset+beginIndex, endIndex-beginIndex); + } + + /** + * Returns the index within this String that is + * offset from the given index by + * codePointOffset code points. Unpaired surrogates + * within the text range given by index and + * codePointOffset count as one code point each. + * + * @param index the index to be offset + * @param codePointOffset the offset in code points + * @return the index within this String + * @exception IndexOutOfBoundsException if index + * is negative or larger then the length of this + * String, or if codePointOffset is positive + * and the substring starting with index has fewer + * than codePointOffset code points, + * or if codePointOffset is negative and the substring + * before index has fewer than the absolute value + * of codePointOffset code points. + * @since 1.5 + */ + public int offsetByCodePoints(int index, int codePointOffset) { + if (index < 0 || index > count) { + throw new IndexOutOfBoundsException(); + } + return Character.offsetByCodePointsImpl(value, offset, count, + offset+index, codePointOffset) - offset; + } + + /** + * Copy characters from this string into dst starting at dstBegin. + * This method doesn't perform any range checking. + */ + void getChars(char dst[], int dstBegin) { + System.arraycopy(value, offset, dst, dstBegin, count); + } + + /** + * Copies characters from this string into the destination character + * array. + *

+ * The first character to be copied is at index srcBegin; + * the last character to be copied is at index srcEnd-1 + * (thus the total number of characters to be copied is + * srcEnd-srcBegin). The characters are copied into the + * subarray of dst starting at index dstBegin + * and ending at index: + *

+     *     dstbegin + (srcEnd-srcBegin) - 1
+     * 
+ * + * @param srcBegin index of the first character in the string + * to copy. + * @param srcEnd index after the last character in the string + * to copy. + * @param dst the destination array. + * @param dstBegin the start offset in the destination array. + * @exception IndexOutOfBoundsException If any of the following + * is true: + * + */ + public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) { + if (srcBegin < 0) { + throw new StringIndexOutOfBoundsException(srcBegin); + } + if (srcEnd > count) { + throw new StringIndexOutOfBoundsException(srcEnd); + } + if (srcBegin > srcEnd) { + throw new StringIndexOutOfBoundsException(srcEnd - srcBegin); + } + System.arraycopy(value, offset + srcBegin, dst, dstBegin, + srcEnd - srcBegin); + } + + /** + * Copies characters from this string into the destination byte array. Each + * byte receives the 8 low-order bits of the corresponding character. The + * eight high-order bits of each character are not copied and do not + * participate in the transfer in any way. + * + *

The first character to be copied is at index {@code srcBegin}; the + * last character to be copied is at index {@code srcEnd-1}. The total + * number of characters to be copied is {@code srcEnd-srcBegin}. The + * characters, converted to bytes, are copied into the subarray of {@code + * dst} starting at index {@code dstBegin} and ending at index: + * + *

+     *     dstbegin + (srcEnd-srcBegin) - 1
+     * 
+ * + * @deprecated This method does not properly convert characters into + * bytes. As of JDK 1.1, the preferred way to do this is via the + * {@link #getBytes()} method, which uses the platform's default charset. + * + * @param srcBegin + * Index of the first character in the string to copy + * + * @param srcEnd + * Index after the last character in the string to copy + * + * @param dst + * The destination array + * + * @param dstBegin + * The start offset in the destination array + * + * @throws IndexOutOfBoundsException + * If any of the following is true: + * + */ + @Deprecated + public void getBytes(int srcBegin, int srcEnd, byte dst[], int dstBegin) { + if (srcBegin < 0) { + throw new StringIndexOutOfBoundsException(srcBegin); + } + if (srcEnd > count) { + throw new StringIndexOutOfBoundsException(srcEnd); + } + if (srcBegin > srcEnd) { + throw new StringIndexOutOfBoundsException(srcEnd - srcBegin); + } + int j = dstBegin; + int n = offset + srcEnd; + int i = offset + srcBegin; + char[] val = value; /* avoid getfield opcode */ + + while (i < n) { + dst[j++] = (byte)val[i++]; + } + } + + /** + * Encodes this {@code String} into a sequence of bytes using the named + * charset, storing the result into a new byte array. + * + *

The behavior of this method when this string cannot be encoded in + * the given charset is unspecified. The {@link + * java.nio.charset.CharsetEncoder} class should be used when more control + * over the encoding process is required. + * + * @param charsetName + * The name of a supported {@linkplain java.nio.charset.Charset + * charset} + * + * @return The resultant byte array + * + * @throws UnsupportedEncodingException + * If the named charset is not supported + * + * @since JDK1.1 + */ + public byte[] getBytes(String charsetName) + throws UnsupportedEncodingException + { + if (charsetName == null) throw new NullPointerException(); + return StringCoding.encode(charsetName, value, offset, count); + } + + /** + * Encodes this {@code String} into a sequence of bytes using the given + * {@linkplain java.nio.charset.Charset charset}, storing the result into a + * new byte array. + * + *

This method always replaces malformed-input and unmappable-character + * sequences with this charset's default replacement byte array. The + * {@link java.nio.charset.CharsetEncoder} class should be used when more + * control over the encoding process is required. + * + * @param charset + * The {@linkplain java.nio.charset.Charset} to be used to encode + * the {@code String} + * + * @return The resultant byte array + * + * @since 1.6 + */ + public byte[] getBytes(Charset charset) { + if (charset == null) throw new NullPointerException(); + return StringCoding.encode(charset, value, offset, count); + } + + /** + * Encodes this {@code String} into a sequence of bytes using the + * platform's default charset, storing the result into a new byte array. + * + *

The behavior of this method when this string cannot be encoded in + * the default charset is unspecified. The {@link + * java.nio.charset.CharsetEncoder} class should be used when more control + * over the encoding process is required. + * + * @return The resultant byte array + * + * @since JDK1.1 + */ + public byte[] getBytes() { + return StringCoding.encode(value, offset, count); + } + + /** + * Compares this string to the specified object. The result is {@code + * true} if and only if the argument is not {@code null} and is a {@code + * String} object that represents the same sequence of characters as this + * object. + * + * @param anObject + * The object to compare this {@code String} against + * + * @return {@code true} if the given object represents a {@code String} + * equivalent to this string, {@code false} otherwise + * + * @see #compareTo(String) + * @see #equalsIgnoreCase(String) + */ + public boolean equals(Object anObject) { + if (this == anObject) { + return true; + } + if (anObject instanceof String) { + String anotherString = (String)anObject; + int n = count; + if (n == anotherString.count) { + char v1[] = value; + char v2[] = anotherString.value; + int i = offset; + int j = anotherString.offset; + while (n-- != 0) { + if (v1[i++] != v2[j++]) + return false; + } + return true; + } + } + return false; + } + + /** + * Compares this string to the specified {@code StringBuffer}. The result + * is {@code true} if and only if this {@code String} represents the same + * sequence of characters as the specified {@code StringBuffer}. + * + * @param sb + * The {@code StringBuffer} to compare this {@code String} against + * + * @return {@code true} if this {@code String} represents the same + * sequence of characters as the specified {@code StringBuffer}, + * {@code false} otherwise + * + * @since 1.4 + */ + public boolean contentEquals(StringBuffer sb) { + synchronized(sb) { + return contentEquals((CharSequence)sb); + } + } + + /** + * Compares this string to the specified {@code CharSequence}. The result + * is {@code true} if and only if this {@code String} represents the same + * sequence of char values as the specified sequence. + * + * @param cs + * The sequence to compare this {@code String} against + * + * @return {@code true} if this {@code String} represents the same + * sequence of char values as the specified sequence, {@code + * false} otherwise + * + * @since 1.5 + */ + public boolean contentEquals(CharSequence cs) { + if (count != cs.length()) + return false; + // Argument is a StringBuffer, StringBuilder + if (cs instanceof AbstractStringBuilder) { + char v1[] = value; + char v2[] = ((AbstractStringBuilder)cs).getValue(); + int i = offset; + int j = 0; + int n = count; + while (n-- != 0) { + if (v1[i++] != v2[j++]) + return false; + } + return true; + } + // Argument is a String + if (cs.equals(this)) + return true; + // Argument is a generic CharSequence + char v1[] = value; + int i = offset; + int j = 0; + int n = count; + while (n-- != 0) { + if (v1[i++] != cs.charAt(j++)) + return false; + } + return true; + } + + /** + * Compares this {@code String} to another {@code String}, ignoring case + * considerations. Two strings are considered equal ignoring case if they + * are of the same length and corresponding characters in the two strings + * are equal ignoring case. + * + *

Two characters {@code c1} and {@code c2} are considered the same + * ignoring case if at least one of the following is true: + *

+ * + * @param anotherString + * The {@code String} to compare this {@code String} against + * + * @return {@code true} if the argument is not {@code null} and it + * represents an equivalent {@code String} ignoring case; {@code + * false} otherwise + * + * @see #equals(Object) + */ + public boolean equalsIgnoreCase(String anotherString) { + return (this == anotherString) ? true : + (anotherString != null) && (anotherString.count == count) && + regionMatches(true, 0, anotherString, 0, count); + } + + /** + * Compares two strings lexicographically. + * The comparison is based on the Unicode value of each character in + * the strings. The character sequence represented by this + * String object is compared lexicographically to the + * character sequence represented by the argument string. The result is + * a negative integer if this String object + * lexicographically precedes the argument string. The result is a + * positive integer if this String object lexicographically + * follows the argument string. The result is zero if the strings + * are equal; compareTo returns 0 exactly when + * the {@link #equals(Object)} method would return true. + *

+ * This is the definition of lexicographic ordering. If two strings are + * different, then either they have different characters at some index + * that is a valid index for both strings, or their lengths are different, + * or both. If they have different characters at one or more index + * positions, let k be the smallest such index; then the string + * whose character at position k has the smaller value, as + * determined by using the < operator, lexicographically precedes the + * other string. In this case, compareTo returns the + * difference of the two character values at position k in + * the two string -- that is, the value: + *

+     * this.charAt(k)-anotherString.charAt(k)
+     * 
+ * If there is no index position at which they differ, then the shorter + * string lexicographically precedes the longer string. In this case, + * compareTo returns the difference of the lengths of the + * strings -- that is, the value: + *
+     * this.length()-anotherString.length()
+     * 
+ * + * @param anotherString the String to be compared. + * @return the value 0 if the argument string is equal to + * this string; a value less than 0 if this string + * is lexicographically less than the string argument; and a + * value greater than 0 if this string is + * lexicographically greater than the string argument. + */ + public int compareTo(String anotherString) { + int len1 = count; + int len2 = anotherString.count; + int n = Math.min(len1, len2); + char v1[] = value; + char v2[] = anotherString.value; + int i = offset; + int j = anotherString.offset; + + if (i == j) { + int k = i; + int lim = n + i; + while (k < lim) { + char c1 = v1[k]; + char c2 = v2[k]; + if (c1 != c2) { + return c1 - c2; + } + k++; + } + } else { + while (n-- != 0) { + char c1 = v1[i++]; + char c2 = v2[j++]; + if (c1 != c2) { + return c1 - c2; + } + } + } + return len1 - len2; + } + + /** + * A Comparator that orders String objects as by + * compareToIgnoreCase. This comparator is serializable. + *

+ * Note that this Comparator does not take locale into account, + * and will result in an unsatisfactory ordering for certain locales. + * The java.text package provides Collators to allow + * locale-sensitive ordering. + * + * @see java.text.Collator#compare(String, String) + * @since 1.2 + */ + public static final Comparator CASE_INSENSITIVE_ORDER + = new CaseInsensitiveComparator(); + private static class CaseInsensitiveComparator + implements Comparator, java.io.Serializable { + // use serialVersionUID from JDK 1.2.2 for interoperability + private static final long serialVersionUID = 8575799808933029326L; + + public int compare(String s1, String s2) { + int n1 = s1.length(); + int n2 = s2.length(); + int min = Math.min(n1, n2); + for (int i = 0; i < min; i++) { + char c1 = s1.charAt(i); + char c2 = s2.charAt(i); + if (c1 != c2) { + c1 = Character.toUpperCase(c1); + c2 = Character.toUpperCase(c2); + if (c1 != c2) { + c1 = Character.toLowerCase(c1); + c2 = Character.toLowerCase(c2); + if (c1 != c2) { + // No overflow because of numeric promotion + return c1 - c2; + } + } + } + } + return n1 - n2; + } + } + + /** + * Compares two strings lexicographically, ignoring case + * differences. This method returns an integer whose sign is that of + * calling compareTo with normalized versions of the strings + * where case differences have been eliminated by calling + * Character.toLowerCase(Character.toUpperCase(character)) on + * each character. + *

+ * Note that this method does not take locale into account, + * and will result in an unsatisfactory ordering for certain locales. + * The java.text package provides collators to allow + * locale-sensitive ordering. + * + * @param str the String to be compared. + * @return a negative integer, zero, or a positive integer as the + * specified String is greater than, equal to, or less + * than this String, ignoring case considerations. + * @see java.text.Collator#compare(String, String) + * @since 1.2 + */ + public int compareToIgnoreCase(String str) { + return CASE_INSENSITIVE_ORDER.compare(this, str); + } + + /** + * Tests if two string regions are equal. + *

+ * A substring of this String object is compared to a substring + * of the argument other. The result is true if these substrings + * represent identical character sequences. The substring of this + * String object to be compared begins at index toffset + * and has length len. The substring of other to be compared + * begins at index ooffset and has length len. The + * result is false if and only if at least one of the following + * is true: + *

+ * + * @param toffset the starting offset of the subregion in this string. + * @param other the string argument. + * @param ooffset the starting offset of the subregion in the string + * argument. + * @param len the number of characters to compare. + * @return true if the specified subregion of this string + * exactly matches the specified subregion of the string argument; + * false otherwise. + */ + public boolean regionMatches(int toffset, String other, int ooffset, + int len) { + char ta[] = value; + int to = offset + toffset; + char pa[] = other.value; + int po = other.offset + ooffset; + // Note: toffset, ooffset, or len might be near -1>>>1. + if ((ooffset < 0) || (toffset < 0) || (toffset > (long)count - len) + || (ooffset > (long)other.count - len)) { + return false; + } + while (len-- > 0) { + if (ta[to++] != pa[po++]) { + return false; + } + } + return true; + } + + /** + * Tests if two string regions are equal. + *

+ * A substring of this String object is compared to a substring + * of the argument other. The result is true if these + * substrings represent character sequences that are the same, ignoring + * case if and only if ignoreCase is true. The substring of + * this String object to be compared begins at index + * toffset and has length len. The substring of + * other to be compared begins at index ooffset and + * has length len. The result is false if and only if + * at least one of the following is true: + *

+ * + * @param ignoreCase if true, ignore case when comparing + * characters. + * @param toffset the starting offset of the subregion in this + * string. + * @param other the string argument. + * @param ooffset the starting offset of the subregion in the string + * argument. + * @param len the number of characters to compare. + * @return true if the specified subregion of this string + * matches the specified subregion of the string argument; + * false otherwise. Whether the matching is exact + * or case insensitive depends on the ignoreCase + * argument. + */ + public boolean regionMatches(boolean ignoreCase, int toffset, + String other, int ooffset, int len) { + char ta[] = value; + int to = offset + toffset; + char pa[] = other.value; + int po = other.offset + ooffset; + // Note: toffset, ooffset, or len might be near -1>>>1. + if ((ooffset < 0) || (toffset < 0) || (toffset > (long)count - len) || + (ooffset > (long)other.count - len)) { + return false; + } + while (len-- > 0) { + char c1 = ta[to++]; + char c2 = pa[po++]; + if (c1 == c2) { + continue; + } + if (ignoreCase) { + // If characters don't match but case may be ignored, + // try converting both characters to uppercase. + // If the results match, then the comparison scan should + // continue. + char u1 = Character.toUpperCase(c1); + char u2 = Character.toUpperCase(c2); + if (u1 == u2) { + continue; + } + // Unfortunately, conversion to uppercase does not work properly + // for the Georgian alphabet, which has strange rules about case + // conversion. So we need to make one last check before + // exiting. + if (Character.toLowerCase(u1) == Character.toLowerCase(u2)) { + continue; + } + } + return false; + } + return true; + } + + /** + * Tests if the substring of this string beginning at the + * specified index starts with the specified prefix. + * + * @param prefix the prefix. + * @param toffset where to begin looking in this string. + * @return true if the character sequence represented by the + * argument is a prefix of the substring of this object starting + * at index toffset; false otherwise. + * The result is false if toffset is + * negative or greater than the length of this + * String object; otherwise the result is the same + * as the result of the expression + *
+     *          this.substring(toffset).startsWith(prefix)
+     *          
+ */ + public boolean startsWith(String prefix, int toffset) { + char ta[] = value; + int to = offset + toffset; + char pa[] = prefix.value; + int po = prefix.offset; + int pc = prefix.count; + // Note: toffset might be near -1>>>1. + if ((toffset < 0) || (toffset > count - pc)) { + return false; + } + while (--pc >= 0) { + if (ta[to++] != pa[po++]) { + return false; + } + } + return true; + } + + /** + * Tests if this string starts with the specified prefix. + * + * @param prefix the prefix. + * @return true if the character sequence represented by the + * argument is a prefix of the character sequence represented by + * this string; false otherwise. + * Note also that true will be returned if the + * argument is an empty string or is equal to this + * String object as determined by the + * {@link #equals(Object)} method. + * @since 1. 0 + */ + public boolean startsWith(String prefix) { + return startsWith(prefix, 0); + } + + /** + * Tests if this string ends with the specified suffix. + * + * @param suffix the suffix. + * @return true if the character sequence represented by the + * argument is a suffix of the character sequence represented by + * this object; false otherwise. Note that the + * result will be true if the argument is the + * empty string or is equal to this String object + * as determined by the {@link #equals(Object)} method. + */ + public boolean endsWith(String suffix) { + return startsWith(suffix, count - suffix.count); + } + + /** + * Returns a hash code for this string. The hash code for a + * String object is computed as + *
+     * s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
+     * 
+ * using int arithmetic, where s[i] is the + * ith character of the string, n is the length of + * the string, and ^ indicates exponentiation. + * (The hash value of the empty string is zero.) + * + * @return a hash code value for this object. + */ + public int hashCode() { + int h = hash; + if (h == 0 && count > 0) { + int off = offset; + char val[] = value; + int len = count; + + for (int i = 0; i < len; i++) { + h = 31*h + val[off++]; + } + hash = h; + } + return h; + } + + /** + * Returns the index within this string of the first occurrence of + * the specified character. If a character with value + * ch occurs in the character sequence represented by + * this String object, then the index (in Unicode + * code units) of the first such occurrence is returned. For + * values of ch in the range from 0 to 0xFFFF + * (inclusive), this is the smallest value k such that: + *
+     * this.charAt(k) == ch
+     * 
+ * is true. For other values of ch, it is the + * smallest value k such that: + *
+     * this.codePointAt(k) == ch
+     * 
+ * is true. In either case, if no such character occurs in this + * string, then -1 is returned. + * + * @param ch a character (Unicode code point). + * @return the index of the first occurrence of the character in the + * character sequence represented by this object, or + * -1 if the character does not occur. + */ + public int indexOf(int ch) { + return indexOf(ch, 0); + } + + /** + * Returns the index within this string of the first occurrence of the + * specified character, starting the search at the specified index. + *

+ * If a character with value ch occurs in the + * character sequence represented by this String + * object at an index no smaller than fromIndex, then + * the index of the first such occurrence is returned. For values + * of ch in the range from 0 to 0xFFFF (inclusive), + * this is the smallest value k such that: + *

+     * (this.charAt(k) == ch) && (k >= fromIndex)
+     * 
+ * is true. For other values of ch, it is the + * smallest value k such that: + *
+     * (this.codePointAt(k) == ch) && (k >= fromIndex)
+     * 
+ * is true. In either case, if no such character occurs in this + * string at or after position fromIndex, then + * -1 is returned. + * + *

+ * There is no restriction on the value of fromIndex. If it + * is negative, it has the same effect as if it were zero: this entire + * string may be searched. If it is greater than the length of this + * string, it has the same effect as if it were equal to the length of + * this string: -1 is returned. + * + *

All indices are specified in char values + * (Unicode code units). + * + * @param ch a character (Unicode code point). + * @param fromIndex the index to start the search from. + * @return the index of the first occurrence of the character in the + * character sequence represented by this object that is greater + * than or equal to fromIndex, or -1 + * if the character does not occur. + */ + public int indexOf(int ch, int fromIndex) { + if (fromIndex < 0) { + fromIndex = 0; + } else if (fromIndex >= count) { + // Note: fromIndex might be near -1>>>1. + return -1; + } + + if (ch < Character.MIN_SUPPLEMENTARY_CODE_POINT) { + // handle most cases here (ch is a BMP code point or a + // negative value (invalid code point)) + final char[] value = this.value; + final int offset = this.offset; + final int max = offset + count; + for (int i = offset + fromIndex; i < max ; i++) { + if (value[i] == ch) { + return i - offset; + } + } + return -1; + } else { + return indexOfSupplementary(ch, fromIndex); + } + } + + /** + * Handles (rare) calls of indexOf with a supplementary character. + */ + private int indexOfSupplementary(int ch, int fromIndex) { + if (Character.isValidCodePoint(ch)) { + final char[] value = this.value; + final int offset = this.offset; + final char hi = Character.highSurrogate(ch); + final char lo = Character.lowSurrogate(ch); + final int max = offset + count - 1; + for (int i = offset + fromIndex; i < max; i++) { + if (value[i] == hi && value[i+1] == lo) { + return i - offset; + } + } + } + return -1; + } + + /** + * Returns the index within this string of the last occurrence of + * the specified character. For values of ch in the + * range from 0 to 0xFFFF (inclusive), the index (in Unicode code + * units) returned is the largest value k such that: + *

+     * this.charAt(k) == ch
+     * 
+ * is true. For other values of ch, it is the + * largest value k such that: + *
+     * this.codePointAt(k) == ch
+     * 
+ * is true. In either case, if no such character occurs in this + * string, then -1 is returned. The + * String is searched backwards starting at the last + * character. + * + * @param ch a character (Unicode code point). + * @return the index of the last occurrence of the character in the + * character sequence represented by this object, or + * -1 if the character does not occur. + */ + public int lastIndexOf(int ch) { + return lastIndexOf(ch, count - 1); + } + + /** + * Returns the index within this string of the last occurrence of + * the specified character, searching backward starting at the + * specified index. For values of ch in the range + * from 0 to 0xFFFF (inclusive), the index returned is the largest + * value k such that: + *
+     * (this.charAt(k) == ch) && (k <= fromIndex)
+     * 
+ * is true. For other values of ch, it is the + * largest value k such that: + *
+     * (this.codePointAt(k) == ch) && (k <= fromIndex)
+     * 
+ * is true. In either case, if no such character occurs in this + * string at or before position fromIndex, then + * -1 is returned. + * + *

All indices are specified in char values + * (Unicode code units). + * + * @param ch a character (Unicode code point). + * @param fromIndex the index to start the search from. There is no + * restriction on the value of fromIndex. If it is + * greater than or equal to the length of this string, it has + * the same effect as if it were equal to one less than the + * length of this string: this entire string may be searched. + * If it is negative, it has the same effect as if it were -1: + * -1 is returned. + * @return the index of the last occurrence of the character in the + * character sequence represented by this object that is less + * than or equal to fromIndex, or -1 + * if the character does not occur before that point. + */ + public int lastIndexOf(int ch, int fromIndex) { + if (ch < Character.MIN_SUPPLEMENTARY_CODE_POINT) { + // handle most cases here (ch is a BMP code point or a + // negative value (invalid code point)) + final char[] value = this.value; + final int offset = this.offset; + int i = offset + Math.min(fromIndex, count - 1); + for (; i >= offset ; i--) { + if (value[i] == ch) { + return i - offset; + } + } + return -1; + } else { + return lastIndexOfSupplementary(ch, fromIndex); + } + } + + /** + * Handles (rare) calls of lastIndexOf with a supplementary character. + */ + private int lastIndexOfSupplementary(int ch, int fromIndex) { + if (Character.isValidCodePoint(ch)) { + final char[] value = this.value; + final int offset = this.offset; + char hi = Character.highSurrogate(ch); + char lo = Character.lowSurrogate(ch); + int i = offset + Math.min(fromIndex, count - 2); + for (; i >= offset; i--) { + if (value[i] == hi && value[i+1] == lo) { + return i - offset; + } + } + } + return -1; + } + + /** + * Returns the index within this string of the first occurrence of the + * specified substring. + * + *

The returned index is the smallest value k for which: + *

+     * this.startsWith(str, k)
+     * 
+ * If no such value of k exists, then {@code -1} is returned. + * + * @param str the substring to search for. + * @return the index of the first occurrence of the specified substring, + * or {@code -1} if there is no such occurrence. + */ + public int indexOf(String str) { + return indexOf(str, 0); + } + + /** + * Returns the index within this string of the first occurrence of the + * specified substring, starting at the specified index. + * + *

The returned index is the smallest value k for which: + *

+     * k >= fromIndex && this.startsWith(str, k)
+     * 
+ * If no such value of k exists, then {@code -1} is returned. + * + * @param str the substring to search for. + * @param fromIndex the index from which to start the search. + * @return the index of the first occurrence of the specified substring, + * starting at the specified index, + * or {@code -1} if there is no such occurrence. + */ + public int indexOf(String str, int fromIndex) { + return indexOf(value, offset, count, + str.value, str.offset, str.count, fromIndex); + } + + /** + * Code shared by String and StringBuffer to do searches. The + * source is the character array being searched, and the target + * is the string being searched for. + * + * @param source the characters being searched. + * @param sourceOffset offset of the source string. + * @param sourceCount count of the source string. + * @param target the characters being searched for. + * @param targetOffset offset of the target string. + * @param targetCount count of the target string. + * @param fromIndex the index to begin searching from. + */ + static int indexOf(char[] source, int sourceOffset, int sourceCount, + char[] target, int targetOffset, int targetCount, + int fromIndex) { + if (fromIndex >= sourceCount) { + return (targetCount == 0 ? sourceCount : -1); + } + if (fromIndex < 0) { + fromIndex = 0; + } + if (targetCount == 0) { + return fromIndex; + } + + char first = target[targetOffset]; + int max = sourceOffset + (sourceCount - targetCount); + + for (int i = sourceOffset + fromIndex; i <= max; i++) { + /* Look for first character. */ + if (source[i] != first) { + while (++i <= max && source[i] != first); + } + + /* Found first character, now look at the rest of v2 */ + if (i <= max) { + int j = i + 1; + int end = j + targetCount - 1; + for (int k = targetOffset + 1; j < end && source[j] == + target[k]; j++, k++); + + if (j == end) { + /* Found whole string. */ + return i - sourceOffset; + } + } + } + return -1; + } + + /** + * Returns the index within this string of the last occurrence of the + * specified substring. The last occurrence of the empty string "" + * is considered to occur at the index value {@code this.length()}. + * + *

The returned index is the largest value k for which: + *

+     * this.startsWith(str, k)
+     * 
+ * If no such value of k exists, then {@code -1} is returned. + * + * @param str the substring to search for. + * @return the index of the last occurrence of the specified substring, + * or {@code -1} if there is no such occurrence. + */ + public int lastIndexOf(String str) { + return lastIndexOf(str, count); + } + + /** + * Returns the index within this string of the last occurrence of the + * specified substring, searching backward starting at the specified index. + * + *

The returned index is the largest value k for which: + *

+     * k <= fromIndex && this.startsWith(str, k)
+     * 
+ * If no such value of k exists, then {@code -1} is returned. + * + * @param str the substring to search for. + * @param fromIndex the index to start the search from. + * @return the index of the last occurrence of the specified substring, + * searching backward from the specified index, + * or {@code -1} if there is no such occurrence. + */ + public int lastIndexOf(String str, int fromIndex) { + return lastIndexOf(value, offset, count, + str.value, str.offset, str.count, fromIndex); + } + + /** + * Code shared by String and StringBuffer to do searches. The + * source is the character array being searched, and the target + * is the string being searched for. + * + * @param source the characters being searched. + * @param sourceOffset offset of the source string. + * @param sourceCount count of the source string. + * @param target the characters being searched for. + * @param targetOffset offset of the target string. + * @param targetCount count of the target string. + * @param fromIndex the index to begin searching from. + */ + static int lastIndexOf(char[] source, int sourceOffset, int sourceCount, + char[] target, int targetOffset, int targetCount, + int fromIndex) { + /* + * Check arguments; return immediately where possible. For + * consistency, don't check for null str. + */ + int rightIndex = sourceCount - targetCount; + if (fromIndex < 0) { + return -1; + } + if (fromIndex > rightIndex) { + fromIndex = rightIndex; + } + /* Empty string always matches. */ + if (targetCount == 0) { + return fromIndex; + } + + int strLastIndex = targetOffset + targetCount - 1; + char strLastChar = target[strLastIndex]; + int min = sourceOffset + targetCount - 1; + int i = min + fromIndex; + + startSearchForLastChar: + while (true) { + while (i >= min && source[i] != strLastChar) { + i--; + } + if (i < min) { + return -1; + } + int j = i - 1; + int start = j - (targetCount - 1); + int k = strLastIndex - 1; + + while (j > start) { + if (source[j--] != target[k--]) { + i--; + continue startSearchForLastChar; + } + } + return start - sourceOffset + 1; + } + } + + /** + * Returns a new string that is a substring of this string. The + * substring begins with the character at the specified index and + * extends to the end of this string.

+ * Examples: + *

+     * "unhappy".substring(2) returns "happy"
+     * "Harbison".substring(3) returns "bison"
+     * "emptiness".substring(9) returns "" (an empty string)
+     * 
+ * + * @param beginIndex the beginning index, inclusive. + * @return the specified substring. + * @exception IndexOutOfBoundsException if + * beginIndex is negative or larger than the + * length of this String object. + */ + public String substring(int beginIndex) { + return substring(beginIndex, count); + } + + /** + * Returns a new string that is a substring of this string. The + * substring begins at the specified beginIndex and + * extends to the character at index endIndex - 1. + * Thus the length of the substring is endIndex-beginIndex. + *

+ * Examples: + *

+     * "hamburger".substring(4, 8) returns "urge"
+     * "smiles".substring(1, 5) returns "mile"
+     * 
+ * + * @param beginIndex the beginning index, inclusive. + * @param endIndex the ending index, exclusive. + * @return the specified substring. + * @exception IndexOutOfBoundsException if the + * beginIndex is negative, or + * endIndex is larger than the length of + * this String object, or + * beginIndex is larger than + * endIndex. + */ + public String substring(int beginIndex, int endIndex) { + if (beginIndex < 0) { + throw new StringIndexOutOfBoundsException(beginIndex); + } + if (endIndex > count) { + throw new StringIndexOutOfBoundsException(endIndex); + } + if (beginIndex > endIndex) { + throw new StringIndexOutOfBoundsException(endIndex - beginIndex); + } + return ((beginIndex == 0) && (endIndex == count)) ? this : + new String(offset + beginIndex, endIndex - beginIndex, value); + } + + /** + * Returns a new character sequence that is a subsequence of this sequence. + * + *

An invocation of this method of the form + * + *

+     * str.subSequence(begin, end)
+ * + * behaves in exactly the same way as the invocation + * + *
+     * str.substring(begin, end)
+ * + * This method is defined so that the String class can implement + * the {@link CharSequence} interface.

+ * + * @param beginIndex the begin index, inclusive. + * @param endIndex the end index, exclusive. + * @return the specified subsequence. + * + * @throws IndexOutOfBoundsException + * if beginIndex or endIndex are negative, + * if endIndex is greater than length(), + * or if beginIndex is greater than startIndex + * + * @since 1.4 + * @spec JSR-51 + */ + public CharSequence subSequence(int beginIndex, int endIndex) { + return this.substring(beginIndex, endIndex); + } + + /** + * Concatenates the specified string to the end of this string. + *

+ * If the length of the argument string is 0, then this + * String object is returned. Otherwise, a new + * String object is created, representing a character + * sequence that is the concatenation of the character sequence + * represented by this String object and the character + * sequence represented by the argument string.

+ * Examples: + *

+     * "cares".concat("s") returns "caress"
+     * "to".concat("get").concat("her") returns "together"
+     * 
+ * + * @param str the String that is concatenated to the end + * of this String. + * @return a string that represents the concatenation of this object's + * characters followed by the string argument's characters. + */ + public String concat(String str) { + int otherLen = str.length(); + if (otherLen == 0) { + return this; + } + char buf[] = new char[count + otherLen]; + getChars(0, count, buf, 0); + str.getChars(0, otherLen, buf, count); + return new String(0, count + otherLen, buf); + } + + /** + * Returns a new string resulting from replacing all occurrences of + * oldChar in this string with newChar. + *

+ * If the character oldChar does not occur in the + * character sequence represented by this String object, + * then a reference to this String object is returned. + * Otherwise, a new String object is created that + * represents a character sequence identical to the character sequence + * represented by this String object, except that every + * occurrence of oldChar is replaced by an occurrence + * of newChar. + *

+ * Examples: + *

+     * "mesquite in your cellar".replace('e', 'o')
+     *         returns "mosquito in your collar"
+     * "the war of baronets".replace('r', 'y')
+     *         returns "the way of bayonets"
+     * "sparring with a purple porpoise".replace('p', 't')
+     *         returns "starring with a turtle tortoise"
+     * "JonL".replace('q', 'x') returns "JonL" (no change)
+     * 
+ * + * @param oldChar the old character. + * @param newChar the new character. + * @return a string derived from this string by replacing every + * occurrence of oldChar with newChar. + */ + public String replace(char oldChar, char newChar) { + if (oldChar != newChar) { + int len = count; + int i = -1; + char[] val = value; /* avoid getfield opcode */ + int off = offset; /* avoid getfield opcode */ + + while (++i < len) { + if (val[off + i] == oldChar) { + break; + } + } + if (i < len) { + char buf[] = new char[len]; + for (int j = 0 ; j < i ; j++) { + buf[j] = val[off+j]; + } + while (i < len) { + char c = val[off + i]; + buf[i] = (c == oldChar) ? newChar : c; + i++; + } + return new String(0, len, buf); + } + } + return this; + } + + /** + * Tells whether or not this string matches the given regular expression. + * + *

An invocation of this method of the form + * str.matches(regex) yields exactly the + * same result as the expression + * + *

{@link java.util.regex.Pattern}.{@link + * java.util.regex.Pattern#matches(String,CharSequence) + * matches}(regex, str)
+ * + * @param regex + * the regular expression to which this string is to be matched + * + * @return true if, and only if, this string matches the + * given regular expression + * + * @throws PatternSyntaxException + * if the regular expression's syntax is invalid + * + * @see java.util.regex.Pattern + * + * @since 1.4 + * @spec JSR-51 + */ + public boolean matches(String regex) { + return Pattern.matches(regex, this); + } + + /** + * Returns true if and only if this string contains the specified + * sequence of char values. + * + * @param s the sequence to search for + * @return true if this string contains s, false otherwise + * @throws NullPointerException if s is null + * @since 1.5 + */ + public boolean contains(CharSequence s) { + return indexOf(s.toString()) > -1; + } + + /** + * Replaces the first substring of this string that matches the given regular expression with the + * given replacement. + * + *

An invocation of this method of the form + * str.replaceFirst(regex, repl) + * yields exactly the same result as the expression + * + *

+ * {@link java.util.regex.Pattern}.{@link java.util.regex.Pattern#compile + * compile}(regex).{@link + * java.util.regex.Pattern#matcher(java.lang.CharSequence) + * matcher}(str).{@link java.util.regex.Matcher#replaceFirst + * replaceFirst}(repl)
+ * + *

+ * Note that backslashes (\) and dollar signs ($) in the + * replacement string may cause the results to be different than if it were + * being treated as a literal replacement string; see + * {@link java.util.regex.Matcher#replaceFirst}. + * Use {@link java.util.regex.Matcher#quoteReplacement} to suppress the special + * meaning of these characters, if desired. + * + * @param regex + * the regular expression to which this string is to be matched + * @param replacement + * the string to be substituted for the first match + * + * @return The resulting String + * + * @throws PatternSyntaxException + * if the regular expression's syntax is invalid + * + * @see java.util.regex.Pattern + * + * @since 1.4 + * @spec JSR-51 + */ + public String replaceFirst(String regex, String replacement) { + return Pattern.compile(regex).matcher(this).replaceFirst(replacement); + } + + /** + * Replaces each substring of this string that matches the given regular expression with the + * given replacement. + * + *

An invocation of this method of the form + * str.replaceAll(regex, repl) + * yields exactly the same result as the expression + * + *

+ * {@link java.util.regex.Pattern}.{@link java.util.regex.Pattern#compile + * compile}(regex).{@link + * java.util.regex.Pattern#matcher(java.lang.CharSequence) + * matcher}(str).{@link java.util.regex.Matcher#replaceAll + * replaceAll}(repl)
+ * + *

+ * Note that backslashes (\) and dollar signs ($) in the + * replacement string may cause the results to be different than if it were + * being treated as a literal replacement string; see + * {@link java.util.regex.Matcher#replaceAll Matcher.replaceAll}. + * Use {@link java.util.regex.Matcher#quoteReplacement} to suppress the special + * meaning of these characters, if desired. + * + * @param regex + * the regular expression to which this string is to be matched + * @param replacement + * the string to be substituted for each match + * + * @return The resulting String + * + * @throws PatternSyntaxException + * if the regular expression's syntax is invalid + * + * @see java.util.regex.Pattern + * + * @since 1.4 + * @spec JSR-51 + */ + public String replaceAll(String regex, String replacement) { + return Pattern.compile(regex).matcher(this).replaceAll(replacement); + } + + /** + * Replaces each substring of this string that matches the literal target + * sequence with the specified literal replacement sequence. The + * replacement proceeds from the beginning of the string to the end, for + * example, replacing "aa" with "b" in the string "aaa" will result in + * "ba" rather than "ab". + * + * @param target The sequence of char values to be replaced + * @param replacement The replacement sequence of char values + * @return The resulting string + * @throws NullPointerException if target or + * replacement is null. + * @since 1.5 + */ + public String replace(CharSequence target, CharSequence replacement) { + return Pattern.compile(target.toString(), Pattern.LITERAL).matcher( + this).replaceAll(Matcher.quoteReplacement(replacement.toString())); + } + + /** + * Splits this string around matches of the given + * regular expression. + * + *

The array returned by this method contains each substring of this + * string that is terminated by another substring that matches the given + * expression or is terminated by the end of the string. The substrings in + * the array are in the order in which they occur in this string. If the + * expression does not match any part of the input then the resulting array + * has just one element, namely this string. + * + *

The limit parameter controls the number of times the + * pattern is applied and therefore affects the length of the resulting + * array. If the limit n is greater than zero then the pattern + * will be applied at most n - 1 times, the array's + * length will be no greater than n, and the array's last entry + * will contain all input beyond the last matched delimiter. If n + * is non-positive then the pattern will be applied as many times as + * possible and the array can have any length. If n is zero then + * the pattern will be applied as many times as possible, the array can + * have any length, and trailing empty strings will be discarded. + * + *

The string "boo:and:foo", for example, yields the + * following results with these parameters: + * + *

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
RegexLimitResult
:2{ "boo", "and:foo" }
:5{ "boo", "and", "foo" }
:-2{ "boo", "and", "foo" }
o5{ "b", "", ":and:f", "", "" }
o-2{ "b", "", ":and:f", "", "" }
o0{ "b", "", ":and:f" }
+ * + *

An invocation of this method of the form + * str.split(regex, n) + * yields the same result as the expression + * + *

+ * {@link java.util.regex.Pattern}.{@link java.util.regex.Pattern#compile + * compile}(regex).{@link + * java.util.regex.Pattern#split(java.lang.CharSequence,int) + * split}(str, n) + *
+ * + * + * @param regex + * the delimiting regular expression + * + * @param limit + * the result threshold, as described above + * + * @return the array of strings computed by splitting this string + * around matches of the given regular expression + * + * @throws PatternSyntaxException + * if the regular expression's syntax is invalid + * + * @see java.util.regex.Pattern + * + * @since 1.4 + * @spec JSR-51 + */ + public String[] split(String regex, int limit) { + /* fastpath if the regex is a + (1)one-char String and this character is not one of the + RegEx's meta characters ".$|()[{^?*+\\", or + (2)two-char String and the first char is the backslash and + the second is not the ascii digit or ascii letter. + */ + char ch = 0; + if (((regex.count == 1 && + ".$|()[{^?*+\\".indexOf(ch = regex.charAt(0)) == -1) || + (regex.length() == 2 && + regex.charAt(0) == '\\' && + (((ch = regex.charAt(1))-'0')|('9'-ch)) < 0 && + ((ch-'a')|('z'-ch)) < 0 && + ((ch-'A')|('Z'-ch)) < 0)) && + (ch < Character.MIN_HIGH_SURROGATE || + ch > Character.MAX_LOW_SURROGATE)) + { + int off = 0; + int next = 0; + boolean limited = limit > 0; + ArrayList list = new ArrayList<>(); + while ((next = indexOf(ch, off)) != -1) { + if (!limited || list.size() < limit - 1) { + list.add(substring(off, next)); + off = next + 1; + } else { // last one + //assert (list.size() == limit - 1); + list.add(substring(off, count)); + off = count; + break; + } + } + // If no match was found, return this + if (off == 0) + return new String[] { this }; + + // Add remaining segment + if (!limited || list.size() < limit) + list.add(substring(off, count)); + + // Construct result + int resultSize = list.size(); + if (limit == 0) + while (resultSize > 0 && list.get(resultSize-1).length() == 0) + resultSize--; + String[] result = new String[resultSize]; + return list.subList(0, resultSize).toArray(result); + } + return Pattern.compile(regex).split(this, limit); + } + + /** + * Splits this string around matches of the given regular expression. + * + *

This method works as if by invoking the two-argument {@link + * #split(String, int) split} method with the given expression and a limit + * argument of zero. Trailing empty strings are therefore not included in + * the resulting array. + * + *

The string "boo:and:foo", for example, yields the following + * results with these expressions: + * + *

+ * + * + * + * + * + * + * + * + *
RegexResult
:{ "boo", "and", "foo" }
o{ "b", "", ":and:f" }
+ * + * + * @param regex + * the delimiting regular expression + * + * @return the array of strings computed by splitting this string + * around matches of the given regular expression + * + * @throws PatternSyntaxException + * if the regular expression's syntax is invalid + * + * @see java.util.regex.Pattern + * + * @since 1.4 + * @spec JSR-51 + */ + public String[] split(String regex) { + return split(regex, 0); + } + + /** + * Converts all of the characters in this String to lower + * case using the rules of the given Locale. Case mapping is based + * on the Unicode Standard version specified by the {@link java.lang.Character Character} + * class. Since case mappings are not always 1:1 char mappings, the resulting + * String may be a different length than the original String. + *

+ * Examples of lowercase mappings are in the following table: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Language Code of LocaleUpper CaseLower CaseDescription
tr (Turkish)\u0130\u0069capital letter I with dot above -> small letter i
tr (Turkish)\u0049\u0131capital letter I -> small letter dotless i
(all)French Friesfrench frieslowercased all chars in String
(all)capiotacapchi + * capthetacapupsil + * capsigmaiotachi + * thetaupsilon + * sigmalowercased all chars in String
+ * + * @param locale use the case transformation rules for this locale + * @return the String, converted to lowercase. + * @see java.lang.String#toLowerCase() + * @see java.lang.String#toUpperCase() + * @see java.lang.String#toUpperCase(Locale) + * @since 1.1 + */ + public String toLowerCase(Locale locale) { + if (locale == null) { + throw new NullPointerException(); + } + + int firstUpper; + + /* Now check if there are any characters that need to be changed. */ + scan: { + for (firstUpper = 0 ; firstUpper < count; ) { + char c = value[offset+firstUpper]; + if ((c >= Character.MIN_HIGH_SURROGATE) && + (c <= Character.MAX_HIGH_SURROGATE)) { + int supplChar = codePointAt(firstUpper); + if (supplChar != Character.toLowerCase(supplChar)) { + break scan; + } + firstUpper += Character.charCount(supplChar); + } else { + if (c != Character.toLowerCase(c)) { + break scan; + } + firstUpper++; + } + } + return this; + } + + char[] result = new char[count]; + int resultOffset = 0; /* result may grow, so i+resultOffset + * is the write location in result */ + + /* Just copy the first few lowerCase characters. */ + System.arraycopy(value, offset, result, 0, firstUpper); + + String lang = locale.getLanguage(); + boolean localeDependent = + (lang == "tr" || lang == "az" || lang == "lt"); + char[] lowerCharArray; + int lowerChar; + int srcChar; + int srcCount; + for (int i = firstUpper; i < count; i += srcCount) { + srcChar = (int)value[offset+i]; + if ((char)srcChar >= Character.MIN_HIGH_SURROGATE && + (char)srcChar <= Character.MAX_HIGH_SURROGATE) { + srcChar = codePointAt(i); + srcCount = Character.charCount(srcChar); + } else { + srcCount = 1; + } + if (localeDependent || srcChar == '\u03A3') { // GREEK CAPITAL LETTER SIGMA + lowerChar = ConditionalSpecialCasing.toLowerCaseEx(this, i, locale); + } else if (srcChar == '\u0130') { // LATIN CAPITAL LETTER I DOT + lowerChar = Character.ERROR; + } else { + lowerChar = Character.toLowerCase(srcChar); + } + if ((lowerChar == Character.ERROR) || + (lowerChar >= Character.MIN_SUPPLEMENTARY_CODE_POINT)) { + if (lowerChar == Character.ERROR) { + if (!localeDependent && srcChar == '\u0130') { + lowerCharArray = + ConditionalSpecialCasing.toLowerCaseCharArray(this, i, Locale.ENGLISH); + } else { + lowerCharArray = + ConditionalSpecialCasing.toLowerCaseCharArray(this, i, locale); + } + } else if (srcCount == 2) { + resultOffset += Character.toChars(lowerChar, result, i + resultOffset) - srcCount; + continue; + } else { + lowerCharArray = Character.toChars(lowerChar); + } + + /* Grow result if needed */ + int mapLen = lowerCharArray.length; + if (mapLen > srcCount) { + char[] result2 = new char[result.length + mapLen - srcCount]; + System.arraycopy(result, 0, result2, 0, + i + resultOffset); + result = result2; + } + for (int x=0; xString to lower + * case using the rules of the default locale. This is equivalent to calling + * toLowerCase(Locale.getDefault()). + *

+ * Note: This method is locale sensitive, and may produce unexpected + * results if used for strings that are intended to be interpreted locale + * independently. + * Examples are programming language identifiers, protocol keys, and HTML + * tags. + * For instance, "TITLE".toLowerCase() in a Turkish locale + * returns "t\u005Cu0131tle", where '\u005Cu0131' is the + * LATIN SMALL LETTER DOTLESS I character. + * To obtain correct results for locale insensitive strings, use + * toLowerCase(Locale.ENGLISH). + *

+ * @return the String, converted to lowercase. + * @see java.lang.String#toLowerCase(Locale) + */ + public String toLowerCase() { + return toLowerCase(Locale.getDefault()); + } + + /** + * Converts all of the characters in this String to upper + * case using the rules of the given Locale. Case mapping is based + * on the Unicode Standard version specified by the {@link java.lang.Character Character} + * class. Since case mappings are not always 1:1 char mappings, the resulting + * String may be a different length than the original String. + *

+ * Examples of locale-sensitive and 1:M case mappings are in the following table. + *

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Language Code of LocaleLower CaseUpper CaseDescription
tr (Turkish)\u0069\u0130small letter i -> capital letter I with dot above
tr (Turkish)\u0131\u0049small letter dotless i -> capital letter I
(all)\u00df\u0053 \u0053small letter sharp s -> two letters: SS
(all)FahrvergnügenFAHRVERGNÜGEN
+ * @param locale use the case transformation rules for this locale + * @return the String, converted to uppercase. + * @see java.lang.String#toUpperCase() + * @see java.lang.String#toLowerCase() + * @see java.lang.String#toLowerCase(Locale) + * @since 1.1 + */ + public String toUpperCase(Locale locale) { + if (locale == null) { + throw new NullPointerException(); + } + + int firstLower; + + /* Now check if there are any characters that need to be changed. */ + scan: { + for (firstLower = 0 ; firstLower < count; ) { + int c = (int)value[offset+firstLower]; + int srcCount; + if ((c >= Character.MIN_HIGH_SURROGATE) && + (c <= Character.MAX_HIGH_SURROGATE)) { + c = codePointAt(firstLower); + srcCount = Character.charCount(c); + } else { + srcCount = 1; + } + int upperCaseChar = Character.toUpperCaseEx(c); + if ((upperCaseChar == Character.ERROR) || + (c != upperCaseChar)) { + break scan; + } + firstLower += srcCount; + } + return this; + } + + char[] result = new char[count]; /* may grow */ + int resultOffset = 0; /* result may grow, so i+resultOffset + * is the write location in result */ + + /* Just copy the first few upperCase characters. */ + System.arraycopy(value, offset, result, 0, firstLower); + + String lang = locale.getLanguage(); + boolean localeDependent = + (lang == "tr" || lang == "az" || lang == "lt"); + char[] upperCharArray; + int upperChar; + int srcChar; + int srcCount; + for (int i = firstLower; i < count; i += srcCount) { + srcChar = (int)value[offset+i]; + if ((char)srcChar >= Character.MIN_HIGH_SURROGATE && + (char)srcChar <= Character.MAX_HIGH_SURROGATE) { + srcChar = codePointAt(i); + srcCount = Character.charCount(srcChar); + } else { + srcCount = 1; + } + if (localeDependent) { + upperChar = ConditionalSpecialCasing.toUpperCaseEx(this, i, locale); + } else { + upperChar = Character.toUpperCaseEx(srcChar); + } + if ((upperChar == Character.ERROR) || + (upperChar >= Character.MIN_SUPPLEMENTARY_CODE_POINT)) { + if (upperChar == Character.ERROR) { + if (localeDependent) { + upperCharArray = + ConditionalSpecialCasing.toUpperCaseCharArray(this, i, locale); + } else { + upperCharArray = Character.toUpperCaseCharArray(srcChar); + } + } else if (srcCount == 2) { + resultOffset += Character.toChars(upperChar, result, i + resultOffset) - srcCount; + continue; + } else { + upperCharArray = Character.toChars(upperChar); + } + + /* Grow result if needed */ + int mapLen = upperCharArray.length; + if (mapLen > srcCount) { + char[] result2 = new char[result.length + mapLen - srcCount]; + System.arraycopy(result, 0, result2, 0, + i + resultOffset); + result = result2; + } + for (int x=0; xString to upper + * case using the rules of the default locale. This method is equivalent to + * toUpperCase(Locale.getDefault()). + *

+ * Note: This method is locale sensitive, and may produce unexpected + * results if used for strings that are intended to be interpreted locale + * independently. + * Examples are programming language identifiers, protocol keys, and HTML + * tags. + * For instance, "title".toUpperCase() in a Turkish locale + * returns "T\u005Cu0130TLE", where '\u005Cu0130' is the + * LATIN CAPITAL LETTER I WITH DOT ABOVE character. + * To obtain correct results for locale insensitive strings, use + * toUpperCase(Locale.ENGLISH). + *

+ * @return the String, converted to uppercase. + * @see java.lang.String#toUpperCase(Locale) + */ + public String toUpperCase() { + return toUpperCase(Locale.getDefault()); + } + + /** + * Returns a copy of the string, with leading and trailing whitespace + * omitted. + *

+ * If this String object represents an empty character + * sequence, or the first and last characters of character sequence + * represented by this String object both have codes + * greater than '\u0020' (the space character), then a + * reference to this String object is returned. + *

+ * Otherwise, if there is no character with a code greater than + * '\u0020' in the string, then a new + * String object representing an empty string is created + * and returned. + *

+ * Otherwise, let k be the index of the first character in the + * string whose code is greater than '\u0020', and let + * m be the index of the last character in the string whose code + * is greater than '\u0020'. A new String + * object is created, representing the substring of this string that + * begins with the character at index k and ends with the + * character at index m-that is, the result of + * this.substring(k, m+1). + *

+ * This method may be used to trim whitespace (as defined above) from + * the beginning and end of a string. + * + * @return A copy of this string with leading and trailing white + * space removed, or this string if it has no leading or + * trailing white space. + */ + public String trim() { + int len = count; + int st = 0; + int off = offset; /* avoid getfield opcode */ + char[] val = value; /* avoid getfield opcode */ + + while ((st < len) && (val[off + st] <= ' ')) { + st++; + } + while ((st < len) && (val[off + len - 1] <= ' ')) { + len--; + } + return ((st > 0) || (len < count)) ? substring(st, len) : this; + } + + /** + * This object (which is already a string!) is itself returned. + * + * @return the string itself. + */ + public String toString() { + return this; + } + + /** + * Converts this string to a new character array. + * + * @return a newly allocated character array whose length is the length + * of this string and whose contents are initialized to contain + * the character sequence represented by this string. + */ + public char[] toCharArray() { + char result[] = new char[count]; + getChars(0, count, result, 0); + return result; + } + + /** + * Returns a formatted string using the specified format string and + * arguments. + * + *

The locale always used is the one returned by {@link + * java.util.Locale#getDefault() Locale.getDefault()}. + * + * @param format + * A format string + * + * @param args + * Arguments referenced by the format specifiers in the format + * string. If there are more arguments than format specifiers, the + * extra arguments are ignored. The number of arguments is + * variable and may be zero. The maximum number of arguments is + * limited by the maximum dimension of a Java array as defined by + * The Java™ Virtual Machine Specification. + * The behaviour on a + * null argument depends on the conversion. + * + * @throws IllegalFormatException + * If a format string contains an illegal syntax, a format + * specifier that is incompatible with the given arguments, + * insufficient arguments given the format string, or other + * illegal conditions. For specification of all possible + * formatting errors, see the Details section of the + * formatter class specification. + * + * @throws NullPointerException + * If the format is null + * + * @return A formatted string + * + * @see java.util.Formatter + * @since 1.5 + */ + public static String format(String format, Object ... args) { + return new Formatter().format(format, args).toString(); + } + + /** + * Returns a formatted string using the specified locale, format string, + * and arguments. + * + * @param l + * The {@linkplain java.util.Locale locale} to apply during + * formatting. If l is null then no localization + * is applied. + * + * @param format + * A format string + * + * @param args + * Arguments referenced by the format specifiers in the format + * string. If there are more arguments than format specifiers, the + * extra arguments are ignored. The number of arguments is + * variable and may be zero. The maximum number of arguments is + * limited by the maximum dimension of a Java array as defined by + * The Java™ Virtual Machine Specification. + * The behaviour on a + * null argument depends on the conversion. + * + * @throws IllegalFormatException + * If a format string contains an illegal syntax, a format + * specifier that is incompatible with the given arguments, + * insufficient arguments given the format string, or other + * illegal conditions. For specification of all possible + * formatting errors, see the Details section of the + * formatter class specification + * + * @throws NullPointerException + * If the format is null + * + * @return A formatted string + * + * @see java.util.Formatter + * @since 1.5 + */ + public static String format(Locale l, String format, Object ... args) { + return new Formatter(l).format(format, args).toString(); + } + + /** + * Returns the string representation of the Object argument. + * + * @param obj an Object. + * @return if the argument is null, then a string equal to + * "null"; otherwise, the value of + * obj.toString() is returned. + * @see java.lang.Object#toString() + */ + public static String valueOf(Object obj) { + return (obj == null) ? "null" : obj.toString(); + } + + /** + * Returns the string representation of the char array + * argument. The contents of the character array are copied; subsequent + * modification of the character array does not affect the newly + * created string. + * + * @param data a char array. + * @return a newly allocated string representing the same sequence of + * characters contained in the character array argument. + */ + public static String valueOf(char data[]) { + return new String(data); + } + + /** + * Returns the string representation of a specific subarray of the + * char array argument. + *

+ * The offset argument is the index of the first + * character of the subarray. The count argument + * specifies the length of the subarray. The contents of the subarray + * are copied; subsequent modification of the character array does not + * affect the newly created string. + * + * @param data the character array. + * @param offset the initial offset into the value of the + * String. + * @param count the length of the value of the String. + * @return a string representing the sequence of characters contained + * in the subarray of the character array argument. + * @exception IndexOutOfBoundsException if offset is + * negative, or count is negative, or + * offset+count is larger than + * data.length. + */ + public static String valueOf(char data[], int offset, int count) { + return new String(data, offset, count); + } + + /** + * Returns a String that represents the character sequence in the + * array specified. + * + * @param data the character array. + * @param offset initial offset of the subarray. + * @param count length of the subarray. + * @return a String that contains the characters of the + * specified subarray of the character array. + */ + public static String copyValueOf(char data[], int offset, int count) { + // All public String constructors now copy the data. + return new String(data, offset, count); + } + + /** + * Returns a String that represents the character sequence in the + * array specified. + * + * @param data the character array. + * @return a String that contains the characters of the + * character array. + */ + public static String copyValueOf(char data[]) { + return copyValueOf(data, 0, data.length); + } + + /** + * Returns the string representation of the boolean argument. + * + * @param b a boolean. + * @return if the argument is true, a string equal to + * "true" is returned; otherwise, a string equal to + * "false" is returned. + */ + public static String valueOf(boolean b) { + return b ? "true" : "false"; + } + + /** + * Returns the string representation of the char + * argument. + * + * @param c a char. + * @return a string of length 1 containing + * as its single character the argument c. + */ + public static String valueOf(char c) { + char data[] = {c}; + return new String(0, 1, data); + } + + /** + * Returns the string representation of the int argument. + *

+ * The representation is exactly the one returned by the + * Integer.toString method of one argument. + * + * @param i an int. + * @return a string representation of the int argument. + * @see java.lang.Integer#toString(int, int) + */ + public static String valueOf(int i) { + return Integer.toString(i); + } + + /** + * Returns the string representation of the long argument. + *

+ * The representation is exactly the one returned by the + * Long.toString method of one argument. + * + * @param l a long. + * @return a string representation of the long argument. + * @see java.lang.Long#toString(long) + */ + public static String valueOf(long l) { + return Long.toString(l); + } + + /** + * Returns the string representation of the float argument. + *

+ * The representation is exactly the one returned by the + * Float.toString method of one argument. + * + * @param f a float. + * @return a string representation of the float argument. + * @see java.lang.Float#toString(float) + */ + public static String valueOf(float f) { + return Float.toString(f); + } + + /** + * Returns the string representation of the double argument. + *

+ * The representation is exactly the one returned by the + * Double.toString method of one argument. + * + * @param d a double. + * @return a string representation of the double argument. + * @see java.lang.Double#toString(double) + */ + public static String valueOf(double d) { + return Double.toString(d); + } + + /** + * Returns a canonical representation for the string object. + *

+ * A pool of strings, initially empty, is maintained privately by the + * class String. + *

+ * When the intern method is invoked, if the pool already contains a + * string equal to this String object as determined by + * the {@link #equals(Object)} method, then the string from the pool is + * returned. Otherwise, this String object is added to the + * pool and a reference to this String object is returned. + *

+ * It follows that for any two strings s and t, + * s.intern() == t.intern() is true + * if and only if s.equals(t) is true. + *

+ * All literal strings and string-valued constant expressions are + * interned. String literals are defined in section 3.10.5 of the + * The Java™ Language Specification. + * + * @return a string that has the same contents as this string, but is + * guaranteed to be from a pool of unique strings. + */ + public native String intern(); + +} diff --git a/notes/src/StringBuffer.java b/notes/src/StringBuffer.java new file mode 100644 index 00000000..b1d35977 --- /dev/null +++ b/notes/src/StringBuffer.java @@ -0,0 +1,527 @@ + + +package java.lang; + + public final class StringBuffer + extends AbstractStringBuilder + implements java.io.Serializable, CharSequence +{ + + /** use serialVersionUID from JDK 1.0.2 for interoperability */ + static final long serialVersionUID = 3388685877147921107L; + + /** + * Constructs a string buffer with no characters in it and an + * initial capacity of 16 characters. + */ + public StringBuffer() { + super(16); + } + + /** + * Constructs a string buffer with no characters in it and + * the specified initial capacity. + * + * @param capacity the initial capacity. + * @exception NegativeArraySizeException if the capacity + * argument is less than 0. + */ + public StringBuffer(int capacity) { + super(capacity); + } + + /** + * Constructs a string buffer initialized to the contents of the + * specified string. The initial capacity of the string buffer is + * 16 plus the length of the string argument. + * + * @param str the initial contents of the buffer. + * @exception NullPointerException if str is null + */ + public StringBuffer(String str) { + super(str.length() + 16); + append(str); + } + + /** + * Constructs a string buffer that contains the same characters + * as the specified CharSequence. The initial capacity of + * the string buffer is 16 plus the length of the + * CharSequence argument. + *

+ * If the length of the specified CharSequence is + * less than or equal to zero, then an empty buffer of capacity + * 16 is returned. + * + * @param seq the sequence to copy. + * @exception NullPointerException if seq is null + * @since 1.5 + */ + public StringBuffer(CharSequence seq) { + this(seq.length() + 16); + append(seq); + } + + public synchronized int length() { + return count; + } + + public synchronized int capacity() { + return value.length; + } + + + public synchronized void ensureCapacity(int minimumCapacity) { + if (minimumCapacity > value.length) { + expandCapacity(minimumCapacity); + } + } + + /** + * @since 1.5 + */ + public synchronized void trimToSize() { + super.trimToSize(); + } + + /** + * @throws IndexOutOfBoundsException {@inheritDoc} + * @see #length() + */ + public synchronized void setLength(int newLength) { + super.setLength(newLength); + } + + /** + * @throws IndexOutOfBoundsException {@inheritDoc} + * @see #length() + */ + public synchronized char charAt(int index) { + if ((index < 0) || (index >= count)) + throw new StringIndexOutOfBoundsException(index); + return value[index]; + } + + /** + * @since 1.5 + */ + public synchronized int codePointAt(int index) { + return super.codePointAt(index); + } + + /** + * @since 1.5 + */ + public synchronized int codePointBefore(int index) { + return super.codePointBefore(index); + } + + /** + * @since 1.5 + */ + public synchronized int codePointCount(int beginIndex, int endIndex) { + return super.codePointCount(beginIndex, endIndex); + } + + /** + * @since 1.5 + */ + public synchronized int offsetByCodePoints(int index, int codePointOffset) { + return super.offsetByCodePoints(index, codePointOffset); + } + + /** + * @throws NullPointerException {@inheritDoc} + * @throws IndexOutOfBoundsException {@inheritDoc} + */ + public synchronized void getChars(int srcBegin, int srcEnd, char[] dst, + int dstBegin) + { + super.getChars(srcBegin, srcEnd, dst, dstBegin); + } + + /** + * @throws IndexOutOfBoundsException {@inheritDoc} + * @see #length() + */ + public synchronized void setCharAt(int index, char ch) { + if ((index < 0) || (index >= count)) + throw new StringIndexOutOfBoundsException(index); + value[index] = ch; + } + + public synchronized StringBuffer append(Object obj) { + super.append(String.valueOf(obj)); + return this; + } + + public synchronized StringBuffer append(String str) { + super.append(str); + return this; + } + + /** + * Appends the specified StringBuffer to this sequence. + *

+ * The characters of the StringBuffer argument are appended, + * in order, to the contents of this StringBuffer, increasing the + * length of this StringBuffer by the length of the argument. + * If sb is null, then the four characters + * "null" are appended to this StringBuffer. + *

+ * Let n be the length of the old character sequence, the one + * contained in the StringBuffer just prior to execution of the + * append method. Then the character at index k in + * the new character sequence is equal to the character at index k + * in the old character sequence, if k is less than n; + * otherwise, it is equal to the character at index k-n in the + * argument sb. + *

+ * This method synchronizes on this (the destination) + * object but does not synchronize on the source (sb). + * + * @param sb the StringBuffer to append. + * @return a reference to this object. + * @since 1.4 + */ + public synchronized StringBuffer append(StringBuffer sb) { + super.append(sb); + return this; + } + + + /** + * Appends the specified CharSequence to this + * sequence. + *

+ * The characters of the CharSequence argument are appended, + * in order, increasing the length of this sequence by the length of the + * argument. + * + *

The result of this method is exactly the same as if it were an + * invocation of this.append(s, 0, s.length()); + * + *

This method synchronizes on this (the destination) + * object but does not synchronize on the source (s). + * + *

If s is null, then the four characters + * "null" are appended. + * + * @param s the CharSequence to append. + * @return a reference to this object. + * @since 1.5 + */ + public StringBuffer append(CharSequence s) { + // Note, synchronization achieved via other invocations + if (s == null) + s = "null"; + if (s instanceof String) + return this.append((String)s); + if (s instanceof StringBuffer) + return this.append((StringBuffer)s); + return this.append(s, 0, s.length()); + } + + /** + * @throws IndexOutOfBoundsException {@inheritDoc} + * @since 1.5 + */ + public synchronized StringBuffer append(CharSequence s, int start, int end) + { + super.append(s, start, end); + return this; + } + + public synchronized StringBuffer append(char[] str) { + super.append(str); + return this; + } + + /** + * @throws IndexOutOfBoundsException {@inheritDoc} + */ + public synchronized StringBuffer append(char[] str, int offset, int len) { + super.append(str, offset, len); + return this; + } + + public synchronized StringBuffer append(boolean b) { + super.append(b); + return this; + } + + public synchronized StringBuffer append(char c) { + super.append(c); + return this; + } + + public synchronized StringBuffer append(int i) { + super.append(i); + return this; + } + + /** + * @since 1.5 + */ + public synchronized StringBuffer appendCodePoint(int codePoint) { + super.appendCodePoint(codePoint); + return this; + } + + public synchronized StringBuffer append(long lng) { + super.append(lng); + return this; + } + + public synchronized StringBuffer append(float f) { + super.append(f); + return this; + } + + public synchronized StringBuffer append(double d) { + super.append(d); + return this; + } + + /** + * @throws StringIndexOutOfBoundsException {@inheritDoc} + * @since 1.2 + */ + public synchronized StringBuffer delete(int start, int end) { + super.delete(start, end); + return this; + } + + /** + * @throws StringIndexOutOfBoundsException {@inheritDoc} + * @since 1.2 + */ + public synchronized StringBuffer deleteCharAt(int index) { + super.deleteCharAt(index); + return this; + } + + /** + * @throws StringIndexOutOfBoundsException {@inheritDoc} + * @since 1.2 + */ + public synchronized StringBuffer replace(int start, int end, String str) { + super.replace(start, end, str); + return this; + } + + /** + * @throws StringIndexOutOfBoundsException {@inheritDoc} + * @since 1.2 + */ + public synchronized String substring(int start) { + return substring(start, count); + } + + /** + * @throws IndexOutOfBoundsException {@inheritDoc} + * @since 1.4 + */ + public synchronized CharSequence subSequence(int start, int end) { + return super.substring(start, end); + } + + /** + * @throws StringIndexOutOfBoundsException {@inheritDoc} + * @since 1.2 + */ + public synchronized String substring(int start, int end) { + return super.substring(start, end); + } + + /** + * @throws StringIndexOutOfBoundsException {@inheritDoc} + * @since 1.2 + */ + public synchronized StringBuffer insert(int index, char[] str, int offset, + int len) + { + super.insert(index, str, offset, len); + return this; + } + + /** + * @throws StringIndexOutOfBoundsException {@inheritDoc} + */ + public synchronized StringBuffer insert(int offset, Object obj) { + super.insert(offset, String.valueOf(obj)); + return this; + } + + /** + * @throws StringIndexOutOfBoundsException {@inheritDoc} + */ + public synchronized StringBuffer insert(int offset, String str) { + super.insert(offset, str); + return this; + } + + /** + * @throws StringIndexOutOfBoundsException {@inheritDoc} + */ + public synchronized StringBuffer insert(int offset, char[] str) { + super.insert(offset, str); + return this; + } + + /** + * @throws IndexOutOfBoundsException {@inheritDoc} + * @since 1.5 + */ + public StringBuffer insert(int dstOffset, CharSequence s) { + // Note, synchronization achieved via other invocations + if (s == null) + s = "null"; + if (s instanceof String) + return this.insert(dstOffset, (String)s); + return this.insert(dstOffset, s, 0, s.length()); + } + + /** + * @throws IndexOutOfBoundsException {@inheritDoc} + * @since 1.5 + */ + public synchronized StringBuffer insert(int dstOffset, CharSequence s, + int start, int end) + { + super.insert(dstOffset, s, start, end); + return this; + } + + /** + * @throws StringIndexOutOfBoundsException {@inheritDoc} + */ + public StringBuffer insert(int offset, boolean b) { + return insert(offset, String.valueOf(b)); + } + + /** + * @throws IndexOutOfBoundsException {@inheritDoc} + */ + public synchronized StringBuffer insert(int offset, char c) { + super.insert(offset, c); + return this; + } + + /** + * @throws StringIndexOutOfBoundsException {@inheritDoc} + */ + public StringBuffer insert(int offset, int i) { + return insert(offset, String.valueOf(i)); + } + + /** + * @throws StringIndexOutOfBoundsException {@inheritDoc} + */ + public StringBuffer insert(int offset, long l) { + return insert(offset, String.valueOf(l)); + } + + /** + * @throws StringIndexOutOfBoundsException {@inheritDoc} + */ + public StringBuffer insert(int offset, float f) { + return insert(offset, String.valueOf(f)); + } + + /** + * @throws StringIndexOutOfBoundsException {@inheritDoc} + */ + public StringBuffer insert(int offset, double d) { + return insert(offset, String.valueOf(d)); + } + + /** + * @throws NullPointerException {@inheritDoc} + * @since 1.4 + */ + public int indexOf(String str) { + return indexOf(str, 0); + } + + /** + * @throws NullPointerException {@inheritDoc} + * @since 1.4 + */ + public synchronized int indexOf(String str, int fromIndex) { + return String.indexOf(value, 0, count, + str.toCharArray(), 0, str.length(), fromIndex); + } + + /** + * @throws NullPointerException {@inheritDoc} + * @since 1.4 + */ + public int lastIndexOf(String str) { + // Note, synchronization achieved via other invocations + return lastIndexOf(str, count); + } + + /** + * @throws NullPointerException {@inheritDoc} + * @since 1.4 + */ + public synchronized int lastIndexOf(String str, int fromIndex) { + return String.lastIndexOf(value, 0, count, + str.toCharArray(), 0, str.length(), fromIndex); + } + + /** + * @since JDK1.0.2 + */ + public synchronized StringBuffer reverse() { + super.reverse(); + return this; + } + + public synchronized String toString() { + return new String(value, 0, count); + } + + /** + * Serializable fields for StringBuffer. + * + * @serialField value char[] + * The backing character array of this StringBuffer. + * @serialField count int + * The number of characters in this StringBuffer. + * @serialField shared boolean + * A flag indicating whether the backing array is shared. + * The value is ignored upon deserialization. + */ + private static final java.io.ObjectStreamField[] serialPersistentFields = + { + new java.io.ObjectStreamField("value", char[].class), + new java.io.ObjectStreamField("count", Integer.TYPE), + new java.io.ObjectStreamField("shared", Boolean.TYPE), + }; + + /** + * readObject is called to restore the state of the StringBuffer from + * a stream. + */ + private synchronized void writeObject(java.io.ObjectOutputStream s) + throws java.io.IOException { + java.io.ObjectOutputStream.PutField fields = s.putFields(); + fields.put("value", value); + fields.put("count", count); + fields.put("shared", false); + s.writeFields(); + } + + /** + * readObject is called to restore the state of the StringBuffer from + * a stream. + */ + private void readObject(java.io.ObjectInputStream s) + throws java.io.IOException, ClassNotFoundException { + java.io.ObjectInputStream.GetField fields = s.readFields(); + value = (char[])fields.get("value", null); + count = fields.get("count", 0); + } +} diff --git a/notes/src/StringBuilder.java b/notes/src/StringBuilder.java new file mode 100644 index 00000000..54f3b57f --- /dev/null +++ b/notes/src/StringBuilder.java @@ -0,0 +1,369 @@ + +package java.lang; + +public final class StringBuilder + extends AbstractStringBuilder + implements java.io.Serializable, CharSequence +{ + + /** use serialVersionUID for interoperability */ + static final long serialVersionUID = 4383685877147921099L; + + /** + * Constructs a string builder with no characters in it and an + * initial capacity of 16 characters. + */ + public StringBuilder() { + super(16); + } + + /** + * Constructs a string builder with no characters in it and an + * initial capacity specified by the capacity argument. + * + * @param capacity the initial capacity. + * @throws NegativeArraySizeException if the capacity + * argument is less than 0. + */ + public StringBuilder(int capacity) { + super(capacity); + } + + /** + * Constructs a string builder initialized to the contents of the + * specified string. The initial capacity of the string builder is + * 16 plus the length of the string argument. + * + * @param str the initial contents of the buffer. + * @throws NullPointerException if str is null + */ + public StringBuilder(String str) { + super(str.length() + 16); + append(str); + } + + /** + * Constructs a string builder that contains the same characters + * as the specified CharSequence. The initial capacity of + * the string builder is 16 plus the length of the + * CharSequence argument. + * + * @param seq the sequence to copy. + * @throws NullPointerException if seq is null + */ + public StringBuilder(CharSequence seq) { + this(seq.length() + 16); + append(seq); + } + + public StringBuilder append(Object obj) { + return append(String.valueOf(obj)); + } + + public StringBuilder append(String str) { + super.append(str); + return this; + } + + // Appends the specified string builder to this sequence. + private StringBuilder append(StringBuilder sb) { + if (sb == null) + return append("null"); + int len = sb.length(); + int newcount = count + len; + if (newcount > value.length) + expandCapacity(newcount); + sb.getChars(0, len, value, count); + count = newcount; + return this; + } + + /** + * Appends the specified StringBuffer to this sequence. + *

+ * The characters of the StringBuffer argument are appended, + * in order, to this sequence, increasing the + * length of this sequence by the length of the argument. + * If sb is null, then the four characters + * "null" are appended to this sequence. + *

+ * Let n be the length of this character sequence just prior to + * execution of the append method. Then the character at index + * k in the new character sequence is equal to the character at + * index k in the old character sequence, if k is less than + * n; otherwise, it is equal to the character at index k-n + * in the argument sb. + * + * @param sb the StringBuffer to append. + * @return a reference to this object. + */ + public StringBuilder append(StringBuffer sb) { + super.append(sb); + return this; + } + + /** + */ + public StringBuilder append(CharSequence s) { + if (s == null) + s = "null"; + if (s instanceof String) + return this.append((String)s); + if (s instanceof StringBuffer) + return this.append((StringBuffer)s); + if (s instanceof StringBuilder) + return this.append((StringBuilder)s); + return this.append(s, 0, s.length()); + } + + /** + * @throws IndexOutOfBoundsException {@inheritDoc} + */ + public StringBuilder append(CharSequence s, int start, int end) { + super.append(s, start, end); + return this; + } + + public StringBuilder append(char[] str) { + super.append(str); + return this; + } + + /** + * @throws IndexOutOfBoundsException {@inheritDoc} + */ + public StringBuilder append(char[] str, int offset, int len) { + super.append(str, offset, len); + return this; + } + + public StringBuilder append(boolean b) { + super.append(b); + return this; + } + + public StringBuilder append(char c) { + super.append(c); + return this; + } + + public StringBuilder append(int i) { + super.append(i); + return this; + } + + public StringBuilder append(long lng) { + super.append(lng); + return this; + } + + public StringBuilder append(float f) { + super.append(f); + return this; + } + + public StringBuilder append(double d) { + super.append(d); + return this; + } + + /** + * @since 1.5 + */ + public StringBuilder appendCodePoint(int codePoint) { + super.appendCodePoint(codePoint); + return this; + } + + /** + * @throws StringIndexOutOfBoundsException {@inheritDoc} + */ + public StringBuilder delete(int start, int end) { + super.delete(start, end); + return this; + } + + /** + * @throws StringIndexOutOfBoundsException {@inheritDoc} + */ + public StringBuilder deleteCharAt(int index) { + super.deleteCharAt(index); + return this; + } + + /** + * @throws StringIndexOutOfBoundsException {@inheritDoc} + */ + public StringBuilder replace(int start, int end, String str) { + super.replace(start, end, str); + return this; + } + + /** + * @throws StringIndexOutOfBoundsException {@inheritDoc} + */ + public StringBuilder insert(int index, char[] str, int offset, + int len) + { + super.insert(index, str, offset, len); + return this; + } + + /** + * @throws StringIndexOutOfBoundsException {@inheritDoc} + */ + public StringBuilder insert(int offset, Object obj) { + return insert(offset, String.valueOf(obj)); + } + + /** + * @throws StringIndexOutOfBoundsException {@inheritDoc} + */ + public StringBuilder insert(int offset, String str) { + super.insert(offset, str); + return this; + } + + /** + * @throws StringIndexOutOfBoundsException {@inheritDoc} + */ + public StringBuilder insert(int offset, char[] str) { + super.insert(offset, str); + return this; + } + + /** + * @throws IndexOutOfBoundsException {@inheritDoc} + */ + public StringBuilder insert(int dstOffset, CharSequence s) { + if (s == null) + s = "null"; + if (s instanceof String) + return this.insert(dstOffset, (String)s); + return this.insert(dstOffset, s, 0, s.length()); + } + + /** + * @throws IndexOutOfBoundsException {@inheritDoc} + */ + public StringBuilder insert(int dstOffset, CharSequence s, + int start, int end) + { + super.insert(dstOffset, s, start, end); + return this; + } + + /** + * @throws StringIndexOutOfBoundsException {@inheritDoc} + */ + public StringBuilder insert(int offset, boolean b) { + super.insert(offset, b); + return this; + } + + /** + * @throws IndexOutOfBoundsException {@inheritDoc} + */ + public StringBuilder insert(int offset, char c) { + super.insert(offset, c); + return this; + } + + /** + * @throws StringIndexOutOfBoundsException {@inheritDoc} + */ + public StringBuilder insert(int offset, int i) { + return insert(offset, String.valueOf(i)); + } + + /** + * @throws StringIndexOutOfBoundsException {@inheritDoc} + */ + public StringBuilder insert(int offset, long l) { + return insert(offset, String.valueOf(l)); + } + + /** + * @throws StringIndexOutOfBoundsException {@inheritDoc} + */ + public StringBuilder insert(int offset, float f) { + return insert(offset, String.valueOf(f)); + } + + /** + * @throws StringIndexOutOfBoundsException {@inheritDoc} + */ + public StringBuilder insert(int offset, double d) { + return insert(offset, String.valueOf(d)); + } + + /** + * @throws NullPointerException {@inheritDoc} + */ + public int indexOf(String str) { + return indexOf(str, 0); + } + + /** + * @throws NullPointerException {@inheritDoc} + */ + public int indexOf(String str, int fromIndex) { + return String.indexOf(value, 0, count, + str.toCharArray(), 0, str.length(), fromIndex); + } + + /** + * @throws NullPointerException {@inheritDoc} + */ + public int lastIndexOf(String str) { + return lastIndexOf(str, count); + } + + /** + * @throws NullPointerException {@inheritDoc} + */ + public int lastIndexOf(String str, int fromIndex) { + return String.lastIndexOf(value, 0, count, + str.toCharArray(), 0, str.length(), fromIndex); + } + + public StringBuilder reverse() { + super.reverse(); + return this; + } + + public String toString() { + // Create a copy, don't share the array + return new String(value, 0, count); + } + + /** + * Save the state of the StringBuilder instance to a stream + * (that is, serialize it). + * + * @serialData the number of characters currently stored in the string + * builder (int), followed by the characters in the + * string builder (char[]). The length of the + * char array may be greater than the number of + * characters currently stored in the string builder, in which + * case extra characters are ignored. + */ + private void writeObject(java.io.ObjectOutputStream s) + throws java.io.IOException { + s.defaultWriteObject(); + s.writeInt(count); + s.writeObject(value); + } + + /** + * readObject is called to restore the state of the StringBuffer from + * a stream. + */ + private void readObject(java.io.ObjectInputStream s) + throws java.io.IOException, ClassNotFoundException { + s.defaultReadObject(); + count = s.readInt(); + value = (char[]) s.readObject(); + } + +} diff --git a/notes/笔记/2016 校招真题题解.md.txt b/notes/笔记/2016 校招真题题解.md.txt deleted file mode 100644 index 86e92afa..00000000 --- a/notes/笔记/2016 校招真题题解.md.txt +++ /dev/null @@ -1,716 +0,0 @@ -[TOC] - -# 前言 - -省略的代码: - -```java -import java.util.*; -``` - -```java -public class Solution { -} -``` - -```java -public class Main { -    public static void main(String[] args) { -        Scanner in = new Scanner(System.in); -        while (in.hasNext()) { - } - } -} -``` - -# 1. 小米-小米Git - -- 重建多叉树 -- 使用 LCA - -```java -private class TreeNode { -    int id; -    List childs = new ArrayList<>(); - -    TreeNode(int id) { -        this.id = id; -    } -} - -public int getSplitNode(String[] matrix, int indexA, int indexB) { -    int n = matrix.length; -    boolean[][] linked = new boolean[n][n]; // 重建邻接矩阵 -    for (int i = 0; i < n; i++) { -        for (int j = 0; j < n; j++) { -            linked[i][j] = matrix[i].charAt(j) == '1'; -        } -    } -    TreeNode tree = constructTree(linked, 0); -    TreeNode ancestor = LCA(tree, new TreeNode(indexA), new TreeNode(indexB)); -    return ancestor.id; -} - -private TreeNode constructTree(boolean[][] linked, int root) { -    TreeNode tree = new TreeNode(root); -    for (int i = 0; i < linked[root].length; i++) { -        if (linked[root][i]) { -            linked[i][root] = false; //  因为题目给的邻接矩阵是双向的,在这里需要把它转为单向的 -            tree.childs.add(constructTree(links, i)); -        } -    } -    return tree; -} - -private TreeNode LCA(TreeNode root, TreeNode p, TreeNode q) { -    if (root == null || root.id == p.id || root.id == q.id) return root; -    TreeNode ancestor = null; -    int cnt = 0; -    for (int i = 0; i < root.childs.size(); i++) { -        TreeNode tmp = LCA(root.childs.get(i), p, q); -        if (tmp != null) { -            ancestor = tmp; -            cnt++; -        } -    } -    return cnt == 2 ? root : ancestor; -} -``` - -# 2. 小米-懂二进制 - -对两个数进行异或,结果的二进制表示为 1 的那一位就是两个数不同的位。 - -```java -public int countBitDiff(int m, int n) { -    return Integer.bitCount(m ^ n); -} -``` - -# 3. 小米-中国牛市 - -背包问题,可以设一个大小为 2 的背包。 - -状态转移方程如下: - -```html -dp[i, j] = max(dp[i, j-1], prices[j] - prices[jj] + dp[i-1, jj]) { jj in range of [0, j-1] } = max(dp[i, j-1], prices[j] + max(dp[i-1, jj] - prices[jj])) -``` - -```java -public int calculateMax(int[] prices) { -    int n = prices.length; -    int[][] dp = new int[3][n]; -    for (int i = 1; i <= 2; i++) { -        int localMax = dp[i - 1][0] - prices[0]; -        for (int j = 1; j < n; j++) { -            dp[i][j] = Math.max(dp[i][j - 1], prices[j] + localMax); -            localMax = Math.max(localMax, dp[i - 1][j] - prices[j]); -        } -    } -    return dp[2][n - 1]; -} -``` - -# 4. 微软-LUCKY STRING - -- 斐波那契数列可以预计算; -- 从头到尾遍历字符串的过程,每一轮循环都使用一个 Set 来保存从 i 到 j 出现的字符,并且 Set 保证了字符都不同,因此 Set 的大小就是不同字符的个数。 - -```java -Set fibSet = new HashSet<>(Arrays.asList(1, 2, 3, 5, 8, 13, 21, 34, 55, 89)); -Scanner in = new Scanner(System.in); -String str = in.nextLine(); -int n = str.length(); -Set ret = new HashSet<>(); -for (int i = 0; i < n; i++) { -    Set set = new HashSet<>(); -    for (int j = i; j < n; j++) { -        set.add(str.charAt(j)); -        int cnt = set.size(); -        if (fibSet.contains(cnt)) { -            ret.add(str.substring(i, j + 1)); -        } -    } -} -String[] arr = ret.toArray(new String[ret.size()]); -Arrays.sort(arr); -for (String s : arr) { -    System.out.println(s); -} -``` - -# 5. 微软-Numeric Keypad - -```java -private static int[][] canReach = { -        {1, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // 0 -        {1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, // 1 -        {1, 0, 1, 1, 0, 1, 1, 0, 1, 1}, // 2 -        {0, 0, 0, 1, 0, 0, 1, 0, 0, 1}, // 3 -        {1, 0, 0, 0, 1, 1, 1, 1, 1, 1}, // 4 -        {1, 0, 0, 0, 0, 1, 1, 0, 1, 1}, // 5 -        {0, 0, 0, 0, 0, 0, 1, 0, 0, 1}, // 6 -        {1, 0, 0, 0, 0, 0, 0, 1, 1, 1}, // 7 -        {1, 0, 0, 0, 0, 0, 0, 0, 1, 1}, // 8 -        {0, 0, 0, 0, 0, 0, 0, 0, 0, 1}  // 9 -}; - -private static boolean isLegal(char[] chars, int idx) { -    if (idx >= chars.length || idx < 0) return true; -    int cur = chars[idx] - '0'; -    int next = chars[idx + 1] - '0'; -    return canReach[cur][next] == 1; -} - -public static void main(String[] args) { -    Scanner in = new Scanner(System.in); -    int T = Integer.valueOf(in.nextLine()); -    for (int i = 0; i < T; i++) { -        String line = in.nextLine(); -        char[] chars = line.toCharArray(); -        for (int j = 0; j < chars.length - 1; j++) { -            while (!isLegal(chars, j)) { -                if (--chars[j + 1] < '0') { -                    chars[j--]--; -                } -                for (int k = j + 2; k < chars.length; k++) { -                    chars[k] = '9'; -                } -            } -        } -        System.out.println(new String(chars)); -    } -} -``` - -# 6. 微软-Spring Outing - -下面以 N = 3,K = 4 来进行讨论。 - -初始时,令第 0 个地方成为待定地点,也就是呆在家里。 - -从第 4 个地点开始投票,每个人只需要比较第 4 个地方和第 0 个地方的优先级,里,如果超过半数的人选择了第 4 个地方,那么更新第 4 个地方成为待定地点。 - -从后往前不断重复以上步骤,不断更新待定地点,直到所有地方都已经投票。 - -上面的讨论中,先令第 0 个地点成为待定地点,是因为这样的话第 4 个地点就只需要和这个地点进行比较,而不用考虑其它情况。如果最开始先令第 1 个地点成为待定地点,那么在对第 2 个地点进行投票时,每个人不仅要考虑第 2 个地点与第 1 个地点的优先级,也要考虑与其后投票地点的优先级。 - -```java -int N = in.nextInt(); -int K = in.nextInt(); -int[][] votes = new int[N][K + 1]; -for (int i = 0; i < N; i++) { -    for (int j = 0; j < K + 1; j++) { -        int place = in.nextInt(); -        votes[i][place] = j; -    } -} -int ret = 0; -for (int place = K; place > 0; place--) { -    int cnt = 0; -    for (int i = 0; i < N; i++) { -        if (votes[i][place] < votes[i][ret]) { -            cnt++; -        } -    } -    if (cnt > N / 2) { -        ret = place; -    } -} -System.out.println(ret == 0 ? "otaku" : ret); -``` - -# 7. 微软-S-expression - -# 8. 华为-最高分是多少 - -```java -int N = in.nextInt(); -int M = in.nextInt(); -int[] scores = new int[N]; -for (int i = 0; i < N; i++) { -    scores[i] = in.nextInt(); -} -for (int i = 0; i < M; i++) { -    String str = in.next(); -    if (str.equals("U")) { -        int id = in.nextInt() - 1; -        int newScore = in.nextInt(); -        scores[id] = newScore; -    } else { -        int idBegin = in.nextInt() - 1; -        int idEnd = in.nextInt() - 1; -        int ret = 0; -        if (idBegin > idEnd) { -            int t = idBegin; -            idBegin = idEnd; -            idEnd = t; -        } -        for (int j = idBegin; j <= idEnd; j++) { -            ret = Math.max(ret, scores[j]); -        } -        System.out.println(ret); -    } -} -``` - -# 9. 华为-简单错误记录 - -```java -HashMap map = new LinkedHashMap<>(); -while (in.hasNextLine()) { -    String s = in.nextLine(); -    String key = s.substring(s.lastIndexOf('\\') + 1); -    map.put(key, map.containsKey(key) ? map.get(key) + 1 : 1); -} -List> list = new LinkedList<>(map.entrySet()); -Collections.sort(list, (o1, o2) -> o2.getValue() - o1.getValue()); -for (int i = 0; i < 8 && i < list.size(); i++) { -    String[] token = list.get(i).getKey().split(" "); -    String filename = token[0]; -    String line = token[1]; -    if (filename.length() > 16) filename = filename.substring(filename.length() - 16); -    System.out.println(filename + " " + line + " " + list.get(i).getValue()); -} -``` - -# 10. 华为-扑克牌大小 - -```java -public class Main { - -    private Map map = new HashMap<>(); - -    public Main() { -        map.put("3", 0); -        map.put("4", 1); -        map.put("5", 2); -        map.put("6", 3); -        map.put("7", 4); -        map.put("8", 5); -        map.put("9", 6); -        map.put("10", 7); -        map.put("J", 8); -        map.put("Q", 9); -        map.put("K", 10); -        map.put("A", 11); -        map.put("2", 12); -        map.put("joker", 13); -        map.put("JOKER ", 14); -    } - -    private String play(String s1, String s2) { -        String[] token1 = s1.split(" "); -        String[] token2 = s2.split(" "); -        CardType type1 = computeCardType(token1); -        CardType type2 = computeCardType(token2); -        if (type1 == CardType.DoubleJoker) return s1; -        if (type2 == CardType.DoubleJoker) return s2; -        if (type1 == CardType.Bomb && type2 != CardType.Bomb) return s1; -        if (type2 == CardType.Bomb && type1 != CardType.Bomb) return s2; -        if (type1 != type2 || token1.length != token2.length) return "ERROR"; -        for (int i = 0; i < token1.length; i++) { -            int val1 = map.get(token1[i]); -            int val2 = map.get(token2[i]); -            if (val1 != val2) return val1 > val2 ? s1 : s2; -        } -        return "ERROR"; -    } - -    private CardType computeCardType(String[] token) { -        boolean hasjoker = false, hasJOKER = false; -        for (int i = 0; i < token.length; i++) { -            if (token[i].equals("joker")) hasjoker = true; -            else if (token[i].equals("JOKER")) hasJOKER = true; -        } -        if (hasjoker && hasJOKER) return CardType.DoubleJoker; -        int maxContinueLen = 1; -        int curContinueLen = 1; -        String curValue = token[0]; -        for (int i = 1; i < token.length; i++) { -            if (token[i].equals(curValue)) curContinueLen++; -            else { -                curContinueLen = 1; -                curValue = token[i]; -            } -            maxContinueLen = Math.max(maxContinueLen, curContinueLen); -        } -        if (maxContinueLen == 4) return CardType.Bomb; -        if (maxContinueLen == 3) return CardType.Triple; -        if (maxContinueLen == 2) return CardType.Double; -        boolean isStraight = true; -        for (int i = 1; i < token.length; i++) { -            if (map.get(token[i]) - map.get(token[i - 1]) != 1) { -                isStraight = false; -                break; -            } -        } -        if (isStraight && token.length == 5) return CardType.Straight; -        return CardType.Sigal; -    } - -    private enum CardType { -        DoubleJoker, Bomb, Sigal, Double, Triple, Straight; -    } - -    public static void main(String[] args) { -        Main main = new Main(); -        Scanner in = new Scanner(System.in); -        while (in.hasNextLine()) { -            String s = in.nextLine(); -            String[] token = s.split("-"); -            System.out.println(main.play(token[0], token[1])); -        } -    } -} -``` - -# 11. 去哪儿-二分查找 - -对于有重复元素的有序数组,二分查找需要注意以下要点: - -- if (val <= A[m]) h = m; -- 因为 h 的赋值为 m 而不是 m - 1,因此 while 循环的条件也就为 l < h。(如果是 m - 1 循环条件为 l <= h) - -```java -public int getPos(int[] A, int n, int val) { -    int l = 0, h = n - 1; -    while (l < h) { -        int m = l + (h - l) / 2; -        if (val <= A[m]) h = m; -        else l = m + 1; -    } -    return A[h] == val ? h : -1; -} -``` - -# 12. 去哪儿-首个重复字符 - -```java -public char findFirstRepeat(String A, int n) { -    boolean[] hasAppear = new boolean[256]; -    for (int i = 0; i < n; i++) { -        char c = A.charAt(i); -        if(hasAppear[c]) return c; -        hasAppear[c] = true; -    } -    return ' '; -} -``` - -# 13. 去哪儿-寻找Coder - -```java -public String[] findCoder(String[] A, int n) { -    List> list = new ArrayList<>(); -    for (String s : A) { -        int cnt = 0; -        String t = s.toLowerCase(); -        int idx = -1; -        while (true) { -            idx = t.indexOf("coder", idx + 1); -            if (idx == -1) break; -            cnt++; -        } -        if (cnt != 0) { -            list.add(new Pair<>(s, cnt)); -        } -    } -    Collections.sort(list, (o1, o2) -> (o2.getValue() - o1.getValue())); -    String[] ret = new String[list.size()]; -    for (int i = 0; i < list.size(); i++) { -        ret[i] = list.get(i).getKey(); -    } -    return ret; -} - -// 牛客网无法导入 javafx.util.Pair,这里就自己实现一下 Pair 类 -private class Pair { -    T t; -    K k; - -    Pair(T t, K k) { -        this.t = t; -        this.k = k; -    } - -    T getKey() { -        return t; -    } - -    K getValue() { -        return k; -    } -} -``` - -# 14. 美团-最大差值 - -贪心策略。 - -```java -public int getDis(int[] A, int n) { -    int max = 0; -    int soFarMin = A[0]; -    for (int i = 1; i < n; i++) { -        if(soFarMin > A[i]) soFarMin = A[i]; -        else max = Math.max(max, A[i]- soFarMin); -    } -    return max; -} -``` - -# 15. 美团-棋子翻转 - -```java -public int[][] flipChess(int[][] A, int[][] f) { -    int[][] direction = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}}; -    for (int[] ff : f) { -        for (int[] dd : direction) { -            int r = ff[0] + dd[0] - 1, c = ff[1] + dd[1] - 1; -            if(r < 0 || r > 3 || c < 0 || c > 3) continue; -            A[r][c] ^= 1; -        } -    } -    return A; -} -``` - -# 16. 美团-拜访 - -```java -private Set paths; -private List curPath; - -public int countPath(int[][] map, int n, int m) { -    paths = new HashSet<>(); -    curPath = new ArrayList<>(); -    for (int i = 0; i < n; i++) { -        for (int j = 0; j < m; j++) { -            if (map[i][j] == 1) { -                map[i][j] = -1; -                int[][] leftRightDirection = {{1, 0}, {-1, 0}}; -                int[][] topDownDirection = {{0, 1}, {0, -1}}; -                for (int[] lr : leftRightDirection) { -                    for (int[] td : topDownDirection) { -                        int[][] directions = {lr, td}; -                        backtracking(map, n, m, i, j, directions); -                    } -                } -                return paths.size(); -            } -        } -    } -    return 0; -} - -private void backtracking(int[][] map, int n, int m, int r, int c, int[][] directions) { -    if (map[r][c] == 2) { -        String path = ""; -        for (int num : curPath) { -            path += num; -        } -        paths.add(path); -        return; -    } -    for (int i = 0; i < directions.length; i++) { -        int nextR = r + directions[i][0]; -        int nextC = c + directions[i][1]; -        if (nextR < 0 || nextR >= n || nextC < 0 || nextC >= m || map[nextR][nextC] == -1) continue; -        map[nextR][nextC] = map[nextR][nextC] == 2 ? 2 : -1; -        curPath.add(nextR); -        curPath.add(nextC); -        backtracking(map, n, m, nextR, nextC, directions); -        curPath.remove(curPath.size() - 1); -        curPath.remove(curPath.size() - 1); -        map[nextR][nextC] = map[nextR][nextC] == 2 ? 2 : 0; -    } -} -``` - -# 17. 美团-直方图内最大矩形 - -```java -public int countArea(int[] A, int n) { -    int max = 0; -    for (int i = 0; i < n; i++) { -        int min = A[i]; -        for (int j = i; j < n; j++) { -            min = Math.min(min, A[j]); -            max = Math.max(max, min * (j - i + 1)); -        } -    } -    return max; -} -``` - -# 18. 美团-字符串计数 - -字符串都是小写字符,可以把字符串当成是 26 进制。但是字典序的比较和普通的整数比较不同,是从左往右进行比较,例如 "ac" 和 "abc",字典序的比较结果为 "ac" > "abc",如果按照整数方法比较,因为 "abc" 是三位数,显然更大。 - -由于两个字符串的长度可能不想等,在 s1 空白部分和 s2 对应部分进行比较时,应该把 s1 的空白部分看成是 'a' 字符进行填充的。 - -还有一点要注意的是,s1 到 s2 长度为 leni 的字符串个数只比较前面 i 个字符。例如 'aaa' 和 'bbb' ,长度为 2 的个数为 'aa' 到 'bb' 的字符串个数,不需要考虑后面部分的字符。 - -在统计个数时,从 len1 开始一直遍历到最大合法长度,每次循环都统计长度为 i 的子字符串个数。 - -```java -String s1 = in.next(); -String s2 = in.next(); -int len1 = in.nextInt(); -int len2 = in.nextInt(); -int len = Math.min(s2.length(), len2); -int[] subtractArr = new int[len]; -for (int i = 0; i < len; i++) { -    char c1 = i < s1.length() ? s1.charAt(i) : 'a'; -    char c2 = s2.charAt(i); -    subtractArr[i] = c2 - c1; -} -int ret = 0; -for (int i = len1; i <= len; i++) { -    for (int j = 0; j < i; j++) { -        ret += subtractArr[j] * Math.pow(26, i - j - 1); -    } -} -System.out.println(ret - 1); -``` - -# 19. 美团-平均年龄 - -```java -int W = in.nextInt(); -double Y = in.nextDouble(); -double x = in.nextDouble(); -int N = in.nextInt(); -while (N-- > 0) { -    Y++; // 老员工每年年龄都要加 1 -    Y += (21 - Y) * x; -} -System.out.println((int) Math.ceil(Y)); -``` - -# 20. 百度-罪犯转移 - -部分和问题,将每次求的部分和缓存起来。 - -```java -int n = in.nextInt(); -int t = in.nextInt(); -int c = in.nextInt(); -int[] values = new int[n]; -for (int i = 0; i < n; i++) { -    values[i] = in.nextInt(); -} -int cnt = 0; -int totalValue = 0; -for (int s = 0, e = c - 1; e < n; s++, e++) { -    if (s == 0) { -        for (int j = 0; j < c; j++) totalValue += values[j]; -    } else { -        totalValue = totalValue - values[s - 1] + values[e]; -    } -    if (totalValue <= t) cnt++; -} -System.out.println(cnt); -``` - -# 22. 百度-裁减网格纸 - -```java -int n = in.nextInt(); -int minX, minY, maxX, maxY; -minX = minY = Integer.MAX_VALUE; -maxX = maxY = Integer.MIN_VALUE; -for (int i = 0; i < n; i++) { -    int x = in.nextInt(); -    int y = in.nextInt(); -    minX = Math.min(minX, x); -    minY = Math.min(minY, y); -    maxX = Math.max(maxX, x); -    maxY = Math.max(maxY, y); -} -System.out.println((int) Math.pow(Math.max(maxX - minX, maxY - minY), 2)); -``` - -# 23. 百度-钓鱼比赛 - -P ( 至少钓一条鱼 ) = 1 - P ( 一条也钓不到 ) - -坑:读取概率矩阵的时候,需要一行一行进行读取,而不能直接用 in.nextDouble()。 - -```java -public static void main(String[] args) { -    Scanner in = new Scanner(System.in); -    while (in.hasNext()) { -        int n = in.nextInt(); -        int m = in.nextInt(); -        int x = in.nextInt(); -        int y = in.nextInt(); -        int t = in.nextInt(); -        in.nextLine(); // 坑 -        double pcc = 0.0; -        double sum = 0.0; -        for (int i = 1; i <= n; i++) { -            String[] token = in.nextLine().split(" "); // 坑 -            for (int j = 1; j <= m; j++) { -                double p = Double.parseDouble(token[j - 1]); -                //  double p = in.nextDouble(); -                sum += p; -                if (i == x && j == y) { -                    pcc = p; -                } -            } -        } -        double pss = sum / (n * m); -        pcc = computePOfIRT(pcc, t); -        pss = computePOfIRT(pss, t); -        System.out.println(pcc > pss ? "cc" : pss > pcc ? "ss" : "equal"); -        System.out.printf("%.2f\n", Math.max(pcc, pss)); -    } -} - -// compute probability of independent repeated trials -private static double computePOfIRT(double p, int t) { -    return 1 - Math.pow((1 - p), t); -} -``` - -# 24. 百度-蘑菇阵 - -这题用回溯会超时,需要用 DP。 - -dp[i][j] 表示到达 (i,j) 位置不会触碰蘑菇的概率。对于 N\*M 矩阵,如果 i == N || j == M,那么 (i,j) 只能有一个移动方向;其它情况下能有两个移动方向。 - -考虑以下矩阵,其中第 3 行和第 3 列只能往一个方向移动,而其它位置可以有两个方向移动。 - - -```java -int N = in.nextInt(); -int M = in.nextInt(); -int K = in.nextInt(); -boolean[][] mushroom = new boolean[N][M]; -while (K-- > 0) { -    int x = in.nextInt(); -    int y = in.nextInt(); -    mushroom[x - 1][y - 1] = true; -} -double[][] dp = new double[N][M]; -dp[0][0] = 1; -for (int i = 0; i < N; i++) { -    for (int j = 0; j < M; j++) { -        if (mushroom[i][j]) dp[i][j] = 0; -        else { -            double cur = dp[i][j]; -            if (i == N - 1 && j == M - 1) break; -            if (i == N - 1) dp[i][j + 1] += cur; -            else if (j == M - 1) dp[i + 1][j] += cur; -            else { -                dp[i][j + 1] += cur / 2; -                dp[i + 1][j] += cur / 2; -            } -        } -    } -} -System.out.printf("%.2f\n", dp[N - 1][M - 1]); -``` \ No newline at end of file diff --git a/notes/笔记/2017 校招真题题解.md.txt b/notes/笔记/2017 校招真题题解.md.txt deleted file mode 100644 index 8b137891..00000000 --- a/notes/笔记/2017 校招真题题解.md.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/notes/笔记/C++.md.txt b/notes/笔记/C++.md.txt deleted file mode 100644 index 7c0d450e..00000000 --- a/notes/笔记/C++.md.txt +++ /dev/null @@ -1,339 +0,0 @@ -[TOC] - -# const 关键字 - -用于定义常量,一旦初始化之后就无法改变。 - -常量必须在定义的时候进行初始化,因此用 const 修饰成员变量,必须在构造函数列表中初始化。 - -const 修饰指针时,分为顶层 const 和底层 const。 - -顶层 const 表示指针本身是个常量。 - -```c++ -int i = 0; -int* const p = &i; -``` - -底层 const 表示指针所指向的对象时一个常量。 - -```c++ -const int i = 0; -const int* p = &i; -``` - -const 修饰成员函数,说明该函数不应该修改非静态成员,但是这并不是十分可靠的,指针所指的非成员对象值可能会被改变。 - -# static 关键字 - -[static详解](http://blog.csdn.net/shanghairuoxiao/article/details/72904292) - -# extern 关键字 - -[C/C++中extern关键字详解](http://www.cnblogs.com/yc_sunniwell/archive/2010/07/14/1777431.html) - -# volatile 关键字 - -关键字volatile的作用是指示编译器,即使代码不对变量做任何改动,该变量的值仍可能会被外界修改。操作系统、硬件或其他线程都有可能修改该变量。该变量的值有可能遭受意料之外的修改,因此,每一次使用时,编译器都会重新从内存中获取这个值,而不是从寄存器中获取。 - -volatile变量不会被优化掉,这非常有用。设想有下面这个函数: - -```c++ -int opt = 1; -void Fn(void) { -    start: -        if (opt == 1) goto start; -        else break; -} -``` - -乍一看,上面的代码好像会进入无限循环,编译器可能会将这段代码优化成: -```c++ -void Fn(void) { -    start: -        int opt = 1; -        if (true) -        goto start; -} -``` - -这样就变成了无限循环。然后,外部操作可能会将 0 写入变量 opt 的位置,从而终止循环。 - -为了防止编译器执行这类优化,我们需要设法通知编译器,系统其他部分可能会修改这个变量。具体做法就是使用 volatile 关键字, - -# new 与 maclloc 区别 - -1. new 分配内存按照数据类型进行分配,malloc 分配内存按照大小分配; -2. new 不仅分配一段内存,而且会调用构造函数,但是 malloc 则不会。 -3. new 返回的是指定对象的指针,而 malloc 返回的是 void\*,因此 malloc 的返回值一般都需要进行类型转化; -4. new 是一个操作符可以重载,malloc 是一个库函数; -5. new 分配的内存要用 delete 销毁,malloc 要用 free 来销毁;delete 销毁的时候会调用对象的析构函数,而 free 则不会; -6. malloc 分配的内存不够的时候,可以用 realloc 扩容。扩容的原理?new 没用这样操作; -7. new 如果分配失败了会抛出 bad_malloc 的异常,而 malloc 失败了会返回 NULL。因此对于 new,正确的姿势是采用 try...catch 语法,而 malloc 则应该判断指针的返回值。为了兼容很多 c 程序员的习惯,C++ 也可以采用 new nothrow 的方法禁止抛出异常而返回 NULL; -8. new 和 new[] 的区别,new[] 一次分配所有内存,多次调用构造函数,分别搭配使用 delete 和 delete[],同理,delete[] 多次调用析构函数,销毁数组中的每个对象。而 malloc 则只能 sizeof(int) * n; -9. 如果不够可以继续谈 new 和 malloc 的实现,空闲链表,分配方法 ( 首次适配原则,最佳适配原则,最差适配原则,快速适配原则 )。delete 和 free 的实现原理,free 为什么直到销毁多大的空间? - -# 四种类型转换 - -**const_cast** 用于将 const 变量转为非 const - -**static_cast** 用的最多,对于各种隐式转换,非 const 转 const,void\* 转指针等 , static_cast 能用于多态想上转化,如果向下转能成功但是不安全,结果未知; - -**dynamic_cast** 用于动态类型转换。只能用于含有虚函数的类,用于类层次间的向上和向下转化。只能转指针或引用。向下转化时,如果是非法的对于指针返回 NULL,对于引用抛异常。要深入了解内部转换的原理。 - -**reinterpret_cast** 几乎什么都可以转,比如将 int 转指针,可能会出问题,尽量少用; - -为什么不使用 C 的强制转换?C 的强制转换表面上看起来功能强大什么都能转,但是转化不够明确,不能进行错误检查,容易出错。 - -# 指针和引用的区别 - -指针保存的是所指对象的地址,引用是所指对象的别名,指针需要通过解引用间接访问,而引用是直接访问; - -指针可以改变地址,从而改变所指的对象,而引用必须从一而终; - -引用在定义的时候必须初始化,而指针则不需要; - -指针有指向常量的指针和指针常量,而引用没有常量引用; - -指针更灵活,用的好威力无比,用的不好处处是坑,而引用用起来则安全多了,但是比较死板。 - -# 指针和数组的联系 - -一个一维 int 数组的数组名实际上是一个 int\* const 类型; -一个二维 int 数组的数组名实际上是一个 int (\*const p)[n]; -数组名做参数会退化为指针 - -# 智能指针 - -构造函数中计数初始化为1; -拷贝构造函数中计数值加1; -赋值运算符中,左边的对象引用计数减一,右边的对象引用计数加一; -析构函数中引用计数减一; -在赋值运算符和析构函数中,如果减一后为0,则调用delete释放对象。 -share_prt与weak_ptr的区别? - -```c++ -//share_ptr可能出现循环引用,从而导致内存泄露 -class A -{ -public: -    share_ptr p; -}; -class B -{ -public: -    share_ptr p; -} -int main() -{ -    while(true) -    { -        share_prt pa(new A()); //pa的引用计数初始化为1 -        share_prt pb(new B()); //pb的引用计数初始化为1 -        pa->p = pb; //pb的引用计数变为2 -        pb->p = pa; //pa的引用计数变为2 -    } -    //假设pa先离开,引用计数减一变为1,不为0因此不会调用class A的析构函数,因此其成员p也不会被析构,pb的引用计数仍然为2; -    //同理pb离开的时候,引用计数也不能减到0 -    return 0; -} -/* -** weak_ptr是一种弱引用指针,其存在不会影响引用计数,从而解决循环引用的问题 -``` - - - - -# 多态性 - -作者:oscarwin -链接:https://www.nowcoder.com/discuss/59394 -来源:牛客网 - -C++多态性与虚函数表 - -C++多态的实现? -多态分为静态多态和动态多态。静态多态是通过重载和模板技术实现,在编译的时候确定。动态多态通过虚函数和继承关系来实现,执行动态绑定,在运行的时候确定。 -动态多态实现有几个条件: -(1) 虚函数; -(2) 一个基类的指针或引用指向派生类的对象; -基类指针在调用成员函数(虚函数)时,就会去查找该对象的虚函数表。虚函数表的地址在每个对象的首地址。查找该虚函数表中该函数的指针进行调用。 -每个对象中保存的只是一个虚函数表的指针,C++内部为每一个类维持一个虚函数表,该类的对象的都指向这同一个虚函数表。 -虚函数表中为什么就能准确查找相应的函数指针呢?因为在类设计的时候,虚函数表直接从基类也继承过来,如果覆盖了其中的某个虚函数,那么虚函数表的指针就会被替换,因此可以根据指针准确找到该调用哪个函数。 -虚函数的作用? - -虚函数用于实现多态,这点大家都能答上来 -但是虚函数在设计上还具有封装和抽象的作用。比如抽象工厂模式。 -动态绑定是如何实现的? -第一个问题中基本回答了,主要都是结合虚函数表来答就行。 - -静态多态和动态多态。静态多态是指通过模板技术或者函数重载技术实现的多态,其在编译器确定行为。动态多态是指通过虚函数技术实现在运行期动态绑定的技术。 - -虚函数表 - -虚函数表是针对类的还是针对对象的?同一个类的两个对象的虚函数表是怎么维护的? - -编译器为每一个类维护一个虚函数表,每个对象的首地址保存着该虚函数表的指针,同一个类的不同对象实际上指向同一张虚函数表。 -纯虚函数如何定义,为什么对于存在虚函数的类中析构函数要定义成虚函数 -为了实现多态进行动态绑定,将派生类对象指针绑定到基类指针上,对象销毁时,如果析构函数没有定义为析构函数,则会调用基类的析构函数,显然只能销毁部分数据。如果要调用对象的析构函数,就需要将该对象的析构函数定义为虚函数,销毁时通过虚函数表找到对应的析构函数。 - -1 -2 -//纯虚函数定义 -virtual ~myClass() = 0; -析构函数能抛出异常吗 -答案肯定是不能。 - -C++标准指明析构函数不能、也不应该抛出异常。C++异常处理模型最大的特点和优势就是对C++中的面向对象提供了最强大的无缝支持。那么如果对象在运行期间出现了异常,C++异常处理模型有责任清除那些由于出现异常所导致的已经失效了的对象(也即对象超出了它原来的作用域),并释放对象原来所分配的资源, 这就是调用这些对象的析构函数来完成释放资源的任务,所以从这个意义上说,析构函数已经变成了异常处理的一部分。 - -(1) 如果析构函数抛出异常,则异常点之后的程序不会执行,如果析构函数在异常点之后执行了某些必要的动作比如释放某些资源,则这些动作不会执行,会造成诸如资源泄漏的问题。 - -(2) 通常异常发生时,c++的机制会调用已经构造对象的析构函数来释放资源,此时若析构函数本身也抛出异常,则前一个异常尚未处理,又有新的异常,会造成程序崩溃的问题。 - -构造函数和析构函数中调用虚函数吗? - - -# 内存对齐 - -作者:oscarwin -链接:https://www.nowcoder.com/discuss/59394 -来源:牛客网 - -从0位置开始存储; -变量存储的起始位置是该变量大小的整数倍; -结构体总的大小是其最大元素的整数倍,不足的后面要补齐; -结构体中包含结构体,从结构体中最大元素的整数倍开始存; -如果加入pragma pack(n) ,取n和变量自身大小较小的一个。 - -# 内联函数 - -宏定义在预编译的时候就会进行宏替换; - -内联函数在编译阶段,在调用内联函数的地方进行替换,减少了函数的调用过程,但是使得编译文件变大。因此,内联函数适合简单函数,对于复杂函数,即使定义了内联编译器可能也不会按照内联的方式进行编译。 - -内联函数相比宏定义更安全,内联函数可以检查参数,而宏定义只是简单的文本替换。因此推荐使用内联函数,而不是宏定义。 - -使用宏定义函数要特别注意给所有单元都加上括号,#define MUL(a, b) a b,这很危险,正确写法:#define MUL(a, b) ((a) (b)) - -# 内存管理 - -C++ 内存分为那几块?(堆区,栈区,常量区,静态和全局区) -每块存储哪些变量? -学会迁移,可以说到 malloc,从 malloc 说到操作系统的内存管理,说道内核态和用户态,然后就什么高端内存,slab 层,伙伴算法,VMA 可以巴拉巴拉了,接着可以迁移到 fork()。 - -# STL 的内存池实现 - -STL 内存分配分为一级分配器和二级分配器,一级分配器就是采用 malloc 分配内存,二级分配器采用内存池。 - -二级分配器设计的非常巧妙,分别给 8k,16k,..., 128k 等比较小的内存片都维持一个空闲链表,每个链表的头节点由一个数组来维护。需要分配内存时从合适大小的链表中取一块下来。假设需要分配一块 10K 的内存,那么就找到最小的大于等于 10k 的块,也就是 16K,从16K 的空闲链表里取出一个用于分配。释放该块内存时,将内存节点归还给链表。如果要分配的内存大于 128K 则直接调用一级分配器。 - -为了节省维持链表的开销,采用了一个 union 结构体,分配器使用 union 里的 next 指针来指向下一个节点,而用户则使用 union 的空指针来表示该节点的地址。 - -# STL 里的 set 和 map 实现 - -set 和 map 都是基于红黑树实现的。 - -红黑树是一种平衡二叉查找树,AVL 树是完全平衡的,红黑树基本上是平衡的。 - -与 AVL 相比红黑数插入和删除最多只需要 3 次旋转,而 AVL 树为了维持其完全平衡性,在坏的情况下要旋转的次数太多。 - -# 必须在构造函数初始化列表里进行初始化的数据成员 - -1. 常量成员,因为常量只能初始化不能赋值,所以必须放在初始化列表里面; -2. 引用类型,引用必须在定义的时候初始化,并且不能重新赋值,所以也要写在初始化列表里面; -3. 没有默认构造函数的类类型,因为使用初始化列表可以不必调用默认构造函数来初始化,而是直接调用拷贝构造函数初始化。 - -# 手写 strcpy() - -```c++ -char* strcpy(char* dest, const char* src) -{ -    char *save = dest; -    while (*dest++ = *src++){} -    return save; -} -``` - -```c++ -char src[] = "abc"; -char dest[10]; // dest 必须大于 src -strcpy(dest, src); -cout << dest << endl; -``` - -# 手写 strcat() - -``` -char* strcat(char* dest, const char* src) -{ -    char* save = dest; -    while (*dest) dest++; -    while(*dest++ = *src++){} -    return save; -} -``` - -# 手写 strcmp() - -```c++ -int strcmp(const char* s1, const char* s2) -{ -    while(*s1 == *s2 && *s1) -    { -        s1++; -        s2++; -    } -    return *s1 - *s2; -} -``` - -# 手写 strstr() - -要求不能用其它库函数。 - -直接使用暴力方法,每次比较 src 的一部分是否与 target 相等。 - -循环体只需要执行 N - M + 1 次即可,由于不能用 strlen() ,因此无法直接计算出 N 与 M,可以使用一个额外的指针,令它先从 src 头部移动 M - 1 次,这样它还可以再移动的次数就为 N - M + 1 次。 - -```c++ -char* strstr(const char* src, const char* target) -{ -    if (!*target) return (char*)src; -    char *p1 = (char*)src, *p2 = (char*)target; -    char* p1_ahead = (char*)src; -    while (*++p2) -        p1_ahead++; -    while (*p1_ahead++) -    { -        char* p1_begin = p1; -        p2 = (char*)target; -        while (*p1 && *p2 && *p1++ == *p2++) -        { -        } -        if (!*p2) return p1_begin; -        p1 = p1_begin + 1; -    } -    return nullptr; -} -``` - - -# 手写 memcpy() - -```c++ -void* memcpy(void* dest, const void* src, size_t len) -{ -    char* d = (char *)dest; -    const char* s = (char *)src; -    while (len--) -        *d++ = *s++; -    return dest; -} -``` - -# 参考资料 - -- [C++后台开发面试常见问题汇总](https://www.nowcoder.com/discuss/59394) -- [函数strcpy、strcat和strcmp实现源码](http://blog.csdn.net/wangningyu/article/details/4662891) -- [Github : gcc/libgcc/memcpy.c](https://github.com/gcc-mirror/gcc/blob/master/libgcc/memcpy.c) -- [Leetcode : Implement strstr() to Find a Substring in a String](https://articles.leetcode.com/implement-strstr-to-find-substring-in/) \ No newline at end of file diff --git a/notes/笔记/Git.md.txt b/notes/笔记/Git.md.txt deleted file mode 100644 index c399f20c..00000000 --- a/notes/笔记/Git.md.txt +++ /dev/null @@ -1,164 +0,0 @@ -![](index_files/0f6fe85a-b680-47ea-af67-21ab98a62f8c.jpg) - -[TOC] - -# 学习资料 - -> [git - 简明指南](http://rogerdudler.github.io/git-guide/index.zh.html) - -图文式简介 - -> [Github : my-git](https://github.com/xirong/my-git) - -汇总式的资料收集和简介,下面的很多资料都是从这里找到的。 - -> [图解 Git](http://marklodato.github.io/visual-git-guide/index-zh-cn.html) - -简洁明了。 - -> [廖雪峰 : Git教程](https://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000) - -比较系统的对 git 进行介绍,很适合学习 - -> [Learn Git Branching](https://learngitbranching.js.org/) - -交互式学习 - -# 集中式与分布式 - -Git 属于分布式版本控制系统,而 SVN 属于集中式。 - -集中式版本控制只有中心服务器拥有一份代码,而分布式版本控制每个人的电脑上就有一份完整的代码。 - -集中式版本控制有安全性问题,当中心服务器挂了所有人都没办法工作了。 - -集中式版本控制需要连网才能工作,如果网速过慢,那么提交一个文件的会慢的无法让人忍受。而分布式版本控制不需要连网就能工作。 - -分布式版本控制新建分支、合并分支操作速度非常快,而集中式版本控制新建一个分支相当于复制一份完整代码。 - -# Git 的中心服务器 - -Git 的中心服务器用来交换每个用户的修改。没有中心服务器也能工作,但是中心服务器能够 24 小时保持开机状态,这样就能更方便的交换修改。Github 就是一种 Git 中心服务器。 - -# Git 工作流 - -![](index_files/a1198642-9159-4d88-8aec-c3b04e7a2563.jpg) - -新建一个版本库之后,当前目录就成为了工作区,工作区下有一个隐藏目录 .git,它属于 Git 的版本库。 - -Git 版本库有一个称为 Stage 的暂存区,还有自动创建的 master 分支以及指向分支的 Head 指针。 - -![](index_files/46f66e88-e65a-4ad0-a060-3c63fe22947c.png) - - -- git add files 把文件的修改添加到暂存区; -- git commit 把暂存区的修改提交到当前分支,提交之后暂存区就被清空了; -- git reset -- files 用来撤销最后一次 git add files,也可以用 git reset 撤销所有暂存区。 -- git checkout -- files 把文件从暂存区复制到工作目录,用来丢弃本地修改。 - -![](index_files/17976404-95f5-480e-9cb4-250e6aa1d55f.png) - -可以跳过暂存区域直接从仓库取出文件或者直接提交代码。 - -- git commit -a 直接把所有文件的修改添加到暂缓区然后执行提交。 -- git checkout HEAD -- files 回滚到复制最后一次提交。 - -# 分支实现 - -Git 把每次提交都连成一条时间线。 - -分支使用指针来实现,例如 master 分支指向时间线的最后一个节点,也就是最后一次提交。 - -HEAD 指针指向的是当前分支。 - -![](index_files/fb546e12-e1fb-4b72-a1fb-8a7f5000dce6.jpg) - -新建分支是新建一个指针指向时间线的最后一个节点,并让 HEAD 指针指向新分支表示新分支成为当前分支。 - -![](index_files/bc775758-89ab-4805-9f9c-78b8739cf780.jpg) - -每次提交只会让当前分支向前移动,而其它分支不会移动。 - -![](index_files/5292faa6-0141-4638-bf0f-bb95b081dcba.jpg) - -合并分支也只需要改变指针即可。 - -![](index_files/1164a71f-413d-494a-9cc8-679fb6a2613d.jpg) - -# 冲突 - -当两个分支都对同一个文件进行了修改,在分支合并时就会产生冲突。 - -![](index_files/58e57a21-6b6b-40b6-af85-956dd4e0f55a.jpg) - -Git 会使用 <<<<<<< ,======= ,>>>>>>> 标记出不同分支的内容,只需要把不同分支中冲突部分修改成一样就能解决冲突。 - -``` -<<<<<<< HEAD -Creating a new branch is quick & simple. -======= -Creating a new branch is quick AND simple. ->>>>>>> feature1 -``` - -# Fast forward - -"快进式合并"(fast-farward merge),会直接将 master 分支指向合并的分支,这种模式下进行分支合并会丢失分支信息,也就不能在分支历史上看出分支信息。 - -可以在合并时加上 --no-ff 参数来禁用 Fast forward 模式,并且加上 -m 参数让合并时产生一个新的 commit。 - -``` -$ git merge --no-ff -m "merge with no-ff" dev -``` - -![](index_files/dd78a1fe-1ff3-4bcf-a56f-8c003995beb6.jpg) - -# 分支管理策略 - -master 分支应该是非常稳定的,只用来发布新版本; - -日常开发在开发分支 dev 上进行。 - -![](index_files/245fd2fb-209c-4ad5-bc5e-eb5664966a0e.jpg) - -# 储藏(Stashing) - -在一个分支上操作之后,如果还没有将修改提交到分支上,此时进行切换分支,那么另一个分支上也能看到新的修改。这是因为所有分支都共用一个工作区的缘故。 - -可以使用 git stash 将当前分支的修改储藏起来,此时当前工作区的所有修改都会被存到栈上,也就是说当前工作区是干净的,没有任何未提交的修改。此时就可以安全的切换到其它分支上了。 - -``` -$ git stash -Saved working directory and index state \ "WIP on master: 049d078 added the index file" -HEAD is now at 049d078 added the index file (To restore them type "git stash apply") -``` - -该功能可以用于 bug 分支的实现。如果当前正在 dev 分支上进行开发,但是此时 master 上有个 bug 需要修复,但是 dev 分支上的开发还未完成,不想立即提交。在新建 bug 分支并切换到 bug 分支之前就需要使用 git stash 将 dev 分支的未提交修改储藏起来。 - -# SSH 传输设置 - -Git 仓库和 Github 中心仓库之间是通过 SSH 加密。 - -如果工作区下没有 .ssh 目录,或者该目录下没有 id_rsa 和 id_rsa.pub 这两个文件,可以通过以下命令来创建 SSH Key: - -``` -$ ssh-keygen -t rsa -C "youremail@example.com" -``` - -然后把公钥 id_rsa.pub 的内容复制到 Github "Account settings" 的 SSH Keys 中。 - -# .gitignore 文件 - -忽略以下文件: - -1. 操作系统自动生成的文件,比如缩略图; -2. 编译生成的中间文件,比如 Java 编译产生的 .class 文件; -3. 自己的敏感信息,比如存放口令的配置文件。 - -不需要全部自己编写,可以到 [https://github.com/github/gitignore](https://github.com/github/gitignore) 中进行查询。 - -# Git 命令一览 - -![](index_files/7a29acce-f243-4914-9f00-f2988c528412.jpg) - -比较详细的地址:http://www.cheat-sheets.org/saved-copy/git-cheat-sheet.pdf \ No newline at end of file diff --git a/notes/笔记/Github.md.txt b/notes/笔记/Github.md.txt deleted file mode 100644 index d34332ab..00000000 --- a/notes/笔记/Github.md.txt +++ /dev/null @@ -1,143 +0,0 @@ -[TOC] - -# 第 1 章 Github 课程介绍 - -Git 是一个版本控制工具,由 Linux 之父 Linus 编写完成。相比于 CVS 和 SVN 等集中式版本管理工具,Git 分布式的特点给它带来了很大的优势:首先它不需要像集中式的版本管理工具一样只有连网才能进行操作,其次它的分布式特点使得每个项目合作者本地都有一个项目的完整版本,这避免了集中式版本管理工具会出现的因为中央服务器故障而导致的代码丢失的问题。需要注意的是,Git 也有一个中央服务器,只不过这个中央服务器是用来分发各个合作者的版本的中介,不像集中式那样起到所有版本的管理者的角色。 - -Git 能够火起来有一个主要的原因是它体现了互联网的开源文化,GitHub 这个网站是基于 Git 而创建的,将这种开源文化发扬光大。GitHub 现在是全球最大的开源代码托管平台,它重新定义了软件开发的流程,因为很多软件功能都可以在 GitHub 上找到相应的源代码,而不需要重新编写,这大大减低了软件开发时间和成本。 - -Git 是一个命令行的工具,GitHub 的图形化界面让 Git 的使用更简单,可以结合 GitHub 的桌面客户端和 GitHub 的网站来一起使用。 - -# 第 2 章 浏览器中使用 Github - -**commit** :GitHub 以一个 commit 作为一个版本。每当项目被修改后,需要进行一次 commit 操作。可以查看每个 commit 做的什么样的修改,包括修改时间,修改人,修改内容。一个 commit 在 GitHub 中 具有一个版本号,还有父版本号,父版本号是为了记录版本的历史线。 - -![](index_files/25c861a6-e70e-47b7-9447-df17d74b75a1.jpg) - -![](index_files/d0bc7eca-bd58-4fab-b559-7ebdd508702f.jpg) - -# 第 3 章 GitHub 客户端的使用 - -本地有更好用的编辑器和 IDE ,因此基本上需要在本地使用 GitHub。使用桌面客户端可以更简单方便地使用 GitHub,如果直接使用 Git 命令行工具,会暴露在复杂的概念面前,不容易掌握。 - -在本地的仓库里面进行修改后,会在桌面客户端自动显示 change。 - -![](index_files/02e1268b-4e4e-46d4-a437-61f586f8d939.jpg) - -一个 commit 需要有 Summary 和 Description,以便以后回头看这个 commit 能够知道进行了哪些修改。 - -Publish 用于发布一个本地的 repostory 到 GitHub 上。发布之后,可以用 Sync 同步本地的 commit 到 GitHub 服务器上。 - -客户端上的 Undo 只能是撤销还未同步的一个版本,而 Revert 才能撤销已经同步的版本,它本身也作为一个 commit 提交到服务器,抵消了上一次的 commit 做的修改。 - -Roll Back to this Commit 可以回滚到选定的一个版本,也是作为一个 commit 提交到服务器进行操作。 - -# 第 4 章 简单分支操作 - -分支是用指针来实现的,master 分支的指针指向最新的一个版本。 - -![](index_files/3f3d800e-7068-487c-a915-ec723d9c4d15.jpg) - -新建一个分支,没有复制各个版本,而是新建的一个指针和 master 指向同一个版本,在这个分支上的操作只是移动这个分支的指针。 - -![](index_files/a25b5785-ce00-4703-8462-0cfa06d485e6.jpg) - -![](index_files/a6fa121a-6a48-4da9-8a7a-4b477c397630.jpg) - -新建分支的目的是为了完成一个新想法,而不去污染 master 分支。 - -可以自由的切换分支,在不同分支上进行操作互不影响。 - -要发布一个新分支发布到 GitHub 上也是用 Publish。 - -删除一个分支:当前分支不可删除,需要切换到其它分支才可以删除。Unpublish 是不发布分支,也就是删除远程服务器上的分支,但是本地上的分支会保留;而 Delete 是直接删除本地分支,并且远程服务器上的分支也会被删除。 - -master 是默认分支,不能被删除,可以通过在设置中修改默认分支为其它分支然后再删除 master 分支。需要到 Github 网站上的 setting 中设置默认分支。 - -# 第 5 章 分支合并 - -通过 merge 合并分支之后,会以一个 commit 的形式提交,称为融合版本,它有两个父版本号,并且 master 指针会指向这个 commit。 - -![](index_files/e6124f92-51f5-4be7-8e03-aa9ea80db04d.jpg) - -在并行开发过程中,两个分支可能同时进行修改,如果对同一个文件进行了而修改,就会造成冲突。 - -本地 master 和远程 master 可以看成两个分支,同样也可能出现冲突的情况,使用 Sync 就是一个合并本地 master 分支和远端 master 分支的操作。 - -发生冲突的文件会自动添加冲突标识符,修改的时候也要把冲突标示符删除。 - -![](index_files/b4fd1a14-ca5f-4916-b6dd-c5c2784d762e.jpg) - -# 第 6 章 团队协作流程 - -通过 Github 网站的 Setting 中的 Collaborators 可以为一个仓库添加协作者,协作者对项目具有写权限。 - -每个协作者可以直接对仓库进行修改,但是很容易产生冲突,只有在做一点小修改时才这么做。当开发新功能时,一般是新建一个新分支,然后在新分支上进行修改。新分支要合并到 master 分支上之前,先提交一个 pull request 来引发讨论和代码审核,之后再进行合并操作。 - -新建分支之后,使用 Sync 就可以发布一个 pull request,而不是用 publish 该分支。 - -**Github Flow** - -1. 新建话题分支 -![](index_files/ba69b54f-aa39-4b5c-a474-d89448803f91.jpg) - -2. 实现功能,做成一个个新版本 -![](index_files/ab9ad765-a7b5-427d-ba6b-df0c23721fa7.jpg) - -3. 发起 pull request,就是请求项目参与者 pull 该分支并进行审查和讨论。 -![](index_files/f201cd06-a6ce-44b3-919b-3a956d9bd977.jpg) - -4. 审查和讨论。该过程的讨论和论坛上类似,而且该过程也可以继续提交新的 commit,而不用再次发 pull request。 -![](index_files/1cdbd78d-0043-40c4-aad5-400d6a74ec69.jpg) -![](index_files/a96896ee-f50c-4a32-947a-0cb949a5fc38.jpg) - -5. 最后协作者通过 merge pull request 来合并 -![](index_files/60f8eb23-f398-4987-9eb8-69c383d710d9.jpg) - -# 第 7 章 开源项目共享流程 - -![](index_files/1ae45cb9-6540-40a4-9edb-02a71172259e.jpg) - -开源项目以陌生人为主,不可能每个人都成为 Collaborators。 - -fork 之后相当于拷贝一份仓库到自己的 Github 上,类似于新建了一个分支。 - -如果只是对某个文件进行修改,那么可以在 Github 网站上通过修改按钮进行修改,会自动创建一个 pull request,而不用先 fork。 - - -# 第 8 章 GitHub Issues - -Github 提供了三种辅助设施:GitHub Pages:搭建项目主页、Wiki:知识库、Issues:事务卡片。 - -一个 Issues 相当于一个讨论区,可以 @ 某个人让他参与进来。每个 Issues 都有编号,用 #+ 编号 可以指定某个 Issues。当要回复前面某个评论时,可以用 > 引用前面的内容,然后再在下方写回复。> 是 markdown 的一个标记,其后面需要跟上引用内容。一个评论也有特定的链接,可以在新评论中插入该链接来快速定位。 - -![](index_files/cfdf5e0c-8860-4901-beef-c3d6ccaf4c6a.jpg) - -Pull Request 会创建一个 Issues。 - -在一个 commit 的 sumnary 内容后面加上 Issues 号,会自动在 Issues 后面新增这个 commit 内容。 - -![](index_files/b7307f8a-52be-4c84-babe-1362e3069086.jpg) - -# 第 9 章 GitHub Pages - -可以搭建的网站分为用户和组织、项目。 - -在项目中新建一个 gh-pages 分支,可以删除里面的其它文件,只放网页的文件,比如 index.html。域名为 用户名 .Github.io/ 项目名。 - -可以使用 Jekyll 等前端框架。 - -# 第 10 章 GitHub 的秘密机关 - -在 Github 网站的项目主页上按 t 快捷键可以打开搜索项目文件的界面。 - -![](index_files/454f65d3-d700-432e-9637-e7f7b8da44e6.jpg) - -# 第 11 章 Until Next Time,Goodbye - -GitLab 是一个开源免费的类似于 GitHub 的项目,可以用它来搭建私有仓库。 - -# 资源 - -- [ 课程地址 ](http://www.imooc.com/learn/390) -- [ 课程的文档地址 ](http://book.haoduoshipin.com/Gitbeijing/) diff --git a/notes/笔记/HTTP+.md.txt b/notes/笔记/HTTP+.md.txt deleted file mode 100644 index 7735c4db..00000000 --- a/notes/笔记/HTTP+.md.txt +++ /dev/null @@ -1,29 +0,0 @@ -# 各个 HTTP 版本的对比 - -1997 年 1 月公布的 HTTP/1.1 是目前主流的 HTTP 协议版本。HTTP/2 标准于 2015 年 5 月以 RFC 7540 正式发表。HTTP/2 的标准化工作由Chrome、Opera、Firefox、Internet Explorer 11、Safari、Amazon Silk 及 Edge 等浏览器提供支持。多数主流浏览器已经在 2015 年底支持了该协议。此外,根据 W3Techs 的数据,在 2017 年 5 月,在排名前一千万的网站中,有 13.7% 支持了 HTTP/2。 - -## HTTP/1.0 与 HTTP/1.1 的区别 - -- HTTP/1.1 默认是长连接; -- HTTP/1.1 提供了范围请求功能; -- HTTP/1.1 提供了虚拟主机的功能; -- HTTP/1.1 多了一些缓存处理字段; -- HTTP/1.1 多了一些状态码; - -## HTTP/1.1 与 HTTP/2.0 的区别 - -**多路复用** - -HTTP/2.0 使用多路复用技术,即使用同一个 TCP 连接来处理多个请求。 - -**首部压缩** - -HTTP1.1 的首部带有大量信息,而且每次都要重复发送,HTTP/2.0 要求通讯双方各自缓存一份首部字段表,从而避免了重复传输。 - -**服务端推送** - -在客户端请求一个资源时,会把相关的资源一起发送给客户端,客户端就不需要再次发起请求了。例如客户端请求 index.html 页面,服务端就把 index.js 一起发给客户端。 - -**二进制格式** - -HTTP1.1 的解析是基于文本的,而 HTTP2.0 采用二进制格式。 diff --git a/notes/笔记/HTTP.md.txt b/notes/笔记/HTTP.md.txt deleted file mode 100644 index cdb732a1..00000000 --- a/notes/笔记/HTTP.md.txt +++ /dev/null @@ -1,360 +0,0 @@ -[TOC] - -# 基础概念 - -## Web基础 - -HTTP(HyperText Transfer Protocol,超为本传输协议)。 - -WWW(Word Wide Web)的三种技术:HTML、HTTP、URL。 - -RFC(Request for Comments,征求修正意见书),互联网的设计文档。 - -## URL - -URI(Uniform Resource Indentifier,统一资源标识符),URL(Uniform Resource Locator,统一资源定位符),URN(Uniform Resource Name,统一资源名称),例如 urn:isbn:0-486-27557-4 。URI 包含 URL 和 URN,目前 WEB 只有 URL 比较流行,所以见到的基本都是 URL。 - -URL格式: - -![](index_files/4102b7d0-39b9-48d8-82ae-ac4addb7ebfb.jpg) - -## 请求和响应报文 - -**请求报文** - -![](index_files/9dbb5fc2-936b-4c6d-b3a7-9617aae45080.jpg) - -**响应报文** - -![](index_files/c634b5ed-a14b-4302-b40e-3ee387dd3c8a.jpg) - -# HTTP 方法 - -客户端发送的请求报文第一行为请求行,包含了方法字段。 - -## GET:获取资源 - -## POST:传输实体主体 - -POST 主要目的不是获取资源,而是传输实体主体数据。 - -GET 和 POST 的请求都能使用额外的参数,但是 GET 的参数是以查询字符串出现在 URL中,而 POST 的参数存储在实体主体部分。 - -``` -GET /test/demo_form.asp?name1=value1&name2=value2 HTTP/1.1 -``` -``` -POST /test/demo_form.asp HTTP/1.1 -Host: w3schools.com -name1=value1&name2=value2 -``` - -GET 的传参方式相比于 POST 安全性较差,因为 GET 传的参数在 URL 是可见的,可能会泄露私密信息。并且 GET 只支持 ASCII 字符,如果参数为中文则可能会出现乱码,而 POST 支持标准字符集。 - -## HEAD:获取报文首部 - -和 GET 方法一样,但是不返回报文实体主体部分。 - -主要用于确认 URL 的有效性以及资源更新的日期时间等。 - -## PUT:上传文件 - -由于自身不带验证机制,任何人都可以上传文件,因此存在安全性问题,一般 WEB 网站不使用该方法。 - -## DELETE:删除文件 - -与 PUT 功能相反,并且同样不带验证机制。 - -## OPTIONS:查询支持的方法 - -查询指定的 URL 能够支持的方法。 - -会返回 Allow: GET, POST, HEAD, OPTIONS 这样的内容。 - -## RACE:追踪路径 - -服务器会将通信路径返回给客户端。 - -发送请求时,在 Max-Forwards 首部字段中填入数值,每经过一个服务器就会减 1,当数值为 0 时就停止传输。 - -TRACE 一般不会使用,并且它容易受到 XST 攻击(Cross-Site Tracing,跨站追踪),因此更不会去使用它。 - -![](index_files/ca711108-e937-4d7d-99aa-61b325c61f1a.jpg) - -## CONNECT:要求用隧道协议连接代理 - -用隧道协议进行 TCP 通信。 - -主要使用 SSL(Secure Sokets Layer,安全套接字)和 TLS(Transport Layer Security,传输层安全)协议把通信内容加密后经网络隧道传输。 - -![](index_files/d8355d56-aa2b-4452-8001-8475cc095af1.jpg) - -# HTTP 状态码 - -服务器返回的响应报文中第一行为状态行,包含了状态码以及原因短语,来告知客户端请求的结果。 - -| 状态码 | 类别 | 原因短语 | -| --- | --- | --- | -| 1XX | Informational(信息性状态码) | 接收的请求正在处理 | -| 2XX | Success(成功状态码) | 请求正常处理完毕 | -| 3XX | Redirection(重定向状态码) | 需要进行附加操作以完成请求 | -| 4XX | Client Error(客户端错误状态码) | 服务器无法处理请求 | -| 5XX | Server Error(服务器错误状态码) | 服务器处理请求出错 | - -## 2XX 成功 - -**200 OK** - -**204 No Content**:请求已经成功处理,但是返回的响应报文不包含实体的主体部分。一般在只需要从客户端往服务器发送信息,而不需要返回数据时使用。 - -**206 Partial Content** - -## 3XX 重定向 - -**301 Moved Permanently**:永久性重定向 - -**302 Found**:临时性重定向 - -**303 See Other** - -注:虽然 HTTP 协议规定 301、302 状态下重定向时不允许把 POST 方法改成 GET 方法,但是大多数浏览器都会把 301、302 和 303 状态下的重定向把 POST 方法改成 GET 方法。 - -**304 Not Modified**:如果请求报文首部包含一些条件,例如:If-Match,If-ModifiedSince,If-None-Match,If-Range,If-Unmodified-Since,但是不满足条件,则服务器会返回 304 状态码。 - -**307 Temporary Redirect**:临时重定向,与 302 的含义类似,但是 307 要求浏览器不会把重定向请求的 POST 方法改成 GET 方法。 - -## 4XX 客户端错误 - -**400 Bad Request**:请求报文中存在语法错误 - -**401 Unauthorized**:该状态码表示发送的请求需要有通过 HTTP 认证(BASIC 认证、DIGEST 认证)的认证信息。如果之前已进行过一次请求,则表示用户认证失败。 - -![](index_files/b1b4cf7d-c54a-4ff1-9741-cd2eea331123.jpg) - -**403 Forbidden**:请求被拒绝,服务器端没有必要给出拒绝的详细理由。 - -**404 Not Found** - -## 5XX 服务器错误 - -**500 Internal Server Error**:服务器正在执行请求时发生错误 - -**503 Service Unavilable**:该状态码表明服务器暂时处于超负载或正在进行停机维护,现在无法处理请求。 - -# HTTP首部 - -HTTP 报文包含了首部和主体两部分。有 4 种类型的首部字段:通用首部字段、请求首部字段、响应首部字段和实体首部字段。各种首部字段及其含义如下(不需要全记,仅供查阅): - -## 通用首部字段 - -| 首部字段名 | 说明 | -| -- | -- | -| Cache-Control | 控制缓存的行为 | -| Connection | 逐跳首部、 连接的管理 | -| Date | 创建报文的日期时间 | -| Pragma | 报文指令 | -| Trailer | 报文末端的首部一览 | -| Transfer-Encoding | 指定报文主体的传输编码方式 | -| Upgrade | 升级为其他协议 | -| Via | 代理服务器的相关信息 | -| Warning | 错误通知 | - -## 请求首部字段 - -| 首部字段名 | 说明 | -| -- | -- | -| Accept | 用户代理可处理的媒体类型 | -| Accept-Charset | 优先的字符集 | -| Accept-Encoding | 优先的内容编码 | -| Accept-Language | 优先的语言(自然语言) | -| Authorization | Web认证信息 | -| Expect | 期待服务器的特定行为 | -| From | 用户的电子邮箱地址 | -| Host | 请求资源所在服务器 | -| If-Match | 比较实体标记(ETag) | -| If-Modified-Since | 比较资源的更新时间 | -| If-None-Match | 比较实体标记(与 If-Match 相反) | -| If-Range | 资源未更新时发送实体 Byte 的范围请求 | -| If-Unmodified-Since | 比较资源的更新时间(与If-Modified-Since相反) | -| Max-Forwards | 最大传输逐跳数 | -| Proxy-Authorization | 代理服务器要求客户端的认证信息 | -| Range | 实体的字节范围请求 | -| Referer | 对请求中 URI 的原始获取方 | -| TE | 传输编码的优先级 | -| User-Agent | HTTP 客户端程序的信息 | - -## 响应首部字段 - -| 首部字段名 | 说明 | -| -- | -- | -| Accept-Ranges | 是否接受字节范围请求 | -| Age | 推算资源创建经过时间 | -| ETag | 资源的匹配信息 | -| Location | 令客户端重定向至指定URI | -| Proxy-Authenticate | 代理服务器对客户端的认证信息 | -| Retry-After | 对再次发起请求的时机要求 | -| Server | HTTP服务器的安装信息 | -| Vary | 代理服务器缓存的管理信息 | -| WWW-Authenticate | 服务器对客户端的认证信息 | - -## 实体首部字段 - -| 首部字段名 | 说明 | -| -- | -- | -| Allow | 资源可支持的HTTP方法 | -| Content-Encoding | 实体主体适用的编码方式 | -| Content-Language | 实体主体的自然语言 | -| Content-Length | 实体主体的大小(单位: 字节) | -| Content-Location | 替代对应资源的URI | -| Content-MD5 | 实体主体的报文摘要 | -| Content-Range | 实体主体的位置范围 | -| Content-Type | 实体主体的媒体类型 | -| Expires | 实体主体过期的日期时间 | -| Last-Modified | 资源的最后修改日期时间 | - -# 具体应用 - -## Cookie - -HTTP 协议是无状态的,主要是为了让 HTTP 协议尽可能简单,使得它能够处理大量事务。HTTP/1.1 引入 Cookie 来保存状态信息。 - -服务器会发送的响应报文包含 Set-Cookie 字段,客户端得到该相应后把 Cookie 内容保存到浏览器中。下次再发送请求时,从浏览器中读出 Cookie 值,在请求报文中包含 Cookie 字段,这样服务器就知道客户端的状态信息了。Cookie 状态信息保存在客户端浏览器中,而不是服务器上。 - -![](index_files/ff17c103-750a-4bb8-9afa-576327023af9.png) - -Set-Cookie 字段有以下属性: - -| 属性 | 说明 | -| -- | -- | -| NAME=VALUE | 赋予 Cookie 的名称和其值(必需项) | -| expires=DATE | Cookie 的有效期(若不明确指定则默认为浏览器关闭前为止) | -| path=PATH | 将服务器上的文件目录作为 Cookie 的适用对象(若不指定则默认为文档所在的文件目录) | -| domain=域名 | 作为 Cookie 适用对象的域名(若不指定则默认为创建 Cookie 的服务器的域名) | -| Secure | 仅在 HTTPS 安全通信时才会发送 Cookie | -| HttpOnly | 加以限制,使 Cookie 不能被 JavaScript 脚本访问 | - -**Session 和 Cookie 区别** - -Session 是服务器用来跟踪用户的一种手段,每个 Session 都有一个唯一标识:Session ID。当服务器创建了一个 Session 时,给客户端发送的响应报文就包含了 Set-Cookie 字段,其中有一个名为 sid 的键值对,这个键值对就是 Session ID。客户端收到后就把 Cookie 保存在浏览器中,并且之后发送的请求报文都包含 Session ID。HTTP 就是 Session 和 Cookie 这两种方式一起合作来实现跟踪用户状态的,而 Session 用于服务器端,Cookie 用于客户端。 - -**浏览器禁用 Cookie 的情况** - -会使用 URL 重写技术,在 URL 后面加上 sid=xxx 。 - -**使用 Cookie 实现用户名和密码的自动填写** - -网站脚本会自动从 Cookie 中读取用户名和密码,从而实现自动填写。 - -## 缓存 - -有两种缓存方法:让代理服务器进行缓存和让客户端浏览器进行缓存。 - -Cache-Control 用于控制缓存的行为。 - -Cache-Control: no-cache 有两种含义,如果是客户端向缓存服务器发送的请求报文中含有该指令,表示客户端不想要缓存的资源;如果是源服务器向缓存服务器发送的响应报文中含有该指令,表示缓存服务器不能对资源进行缓存。 - -Expires 字段可以用于告知缓存服务器该资源什么时候会过期。当首部字段 Cache-Control 有指定 max-age 指令时,比起首部字段 Expires,会优先处理 max-age 指令。 - -## 持久连接 - -当浏览器访问一个包含多张图片的 HTML 页面时,除了请求访问 HTML 页面资源,还会请求图片资源,如果每进行一次 HTTP 通信就要断开一次 TCP 连接,连接建立和断开的开销会很大。**持久连接** 只需要进行一次 TCP 连接就能进行多次 HTTP 通信。HTTP/1.1开始,所有的连接默认都是持久连接。 - -![](index_files/c73a0b78-5f46-4d2d-a009-dab2a999b5d8.jpg) - -持久连接需要使用 Connection 首部字段进行管理。HTTP/1.1 开始HTTP 默认是持久化连接的,如果要断开 TCP 连接,需要由客户端或者服务器端提出断开,使用 Connection: close ;而在HTTP/1.1之前默认是非持久化连接的,如果要维持持续连接,需要使用 Keep-Alive。 - -管线化方式可以同时发送多个请求和响应,而不需要发送一个请求然后等待响应之后再发下一个请求。 - -![](index_files/6943e2af-5a70-4004-8bee-b33d60f39da3.jpg) - -## 编码 - -编码(Encoding)主要是为了对实体进行压缩。常用的编码有:gzip、compress、deflate、identity,其中 identity 表示不执行压缩的编码格式。 - -## 分块传输 - -分块传输(Chunked Transfer Coding)可以把数据分割成多块,让浏览器逐步显示页面。 - -## 多部分对象集合 - -一份报文主体内可含有多类型的实体同时发送,每个部分之间用 boundary 字段定义的分隔符进行分隔;每个部分都可以有首部字段。 - -例如,上传多个表单时可以使用如下方式: - -![](index_files/decb0936-e83c-4a55-840a-fe8aa101ac61.png) - -## 范围请求 - -如果网络出现中断,服务器只发送了一部分数据,范围请求使得客户端能够只请求未发送的那部分数据,从而避免服务器端重新发送所有数据。 - -在请求报文首部中添加 Range 字段,然后指定请求的范围,例如 Range : bytes = 5001-10000。请求成功的话服务器发送 206 Partial Content 状态。 - -## 内容协商 - -通过内容协商返回最合适的内容,例如根据浏览器的默认语言选择返回中文界面还是英文界面。 - -涉及以下首部字段:Accept、Accept-Charset、Accept-Encoding、Accept-Language、Content-Language。 - -## 虚拟主机 - -使用虚拟主机技术,使得一台服务器拥有多个域名,并且在逻辑上可以看成多个服务器。 - -## 通信数据转发 - -**代理** - -代理服务器接受客户端的请求,并且转发给其它服务器。代理服务器一般是透明的,不会改变 URL。 - -使用代理的主要目的是:缓存、网络访问控制以及记录访问日志。 - -![](index_files/c07035c3-a9ba-4508-8e3c-d8ae4c6ee9ee.jpg) - -**网关** - -与代理服务器不同的是,网关服务器会将 HTTP 转化为其它协议进行通信,从而其它非 HTTP 服务器的服务。 - -![](index_files/81375888-6be1-476f-9521-42eea3e3154f.jpg) - -**隧道** - -使用 SSL 等加密手段,为客户端和服务器之间建立一条安全的通信线路。 - -![](index_files/64b95403-d976-421a-8b45-bac89c0b5185.jpg) - -# HTTPs - -HTTP 有以下安全性问题: - -1. 通信使用明文,内容可能会被窃听; -2. 不验证通信方的身份,因此有可能遭遇伪装; -3. 无法证明报文的完整性,所以有可能已遭篡改。 - -HTTPs 并不是新协议,而是 HTTP 先和 SSL(Secure Socket Layer)通信,再由 SSL 和 TCP 通信。通过使用 SSL,HTTPs 提供了加密、认证和完整性保护。 - -## 加密 - -有两种加密方式:对称密钥加密和公开密钥加密。对称密钥加密的加密和解密使用同一密钥,而公开密钥加密使用一对密钥用于加密和解密,分别为公开密钥和私有密钥。公开密钥所有人都可以获得,通信发送方获得接收方的公开密钥之后,就可以使用公开密钥进行加密,接收方收到通信内容后使用私有密钥解密。 - -对称密钥加密的缺点:无法安全传输密钥;公开密钥加密的缺点:相对来说更耗时。 - -HTTPs 采用 **混合的加密机制**,使用公开密钥加密用于传输对称密钥,之后使用对称密钥加密进行通信。(下图中,共享密钥即对称密钥) - -![](index_files/110b1a9b-87cd-45c3-a21d-824623715b33.jpg) - -## 认证 - -通过使用 **证书** 来对通信方进行认证。证书中有公开密钥数据,如果可以验证公开密钥的确属于通信方的,那么就可以确定通信方是可靠的。 - -数字证书认证机构(CA,Certificate -Authority)颁发的公开密钥证书,可以通过 CA 对其进行验证。 - -进行 HTTPs 通信时,服务器会把证书发送给客户端,客户端取得其中的公开密钥之后,就可以开始加密过程。 - -使用 OpenSSL 这套开源程序,每个人都可以构建一套属于自己的认证机构,从而自己给自己颁发服务器证书。浏览器在访问该服务器时,会显示“无法确认连接安全性”或“该网站的安全证书存在问题”等警告消息。 - -客户端证书需要用户自行安装,只有在业务需要非常高安全性时才使用客户端证书,例如网上银行。 - -## 完整性 - -SSL 提供摘要功能来验证完整性。 - diff --git a/notes/笔记/JVM.md.txt b/notes/笔记/JVM.md.txt deleted file mode 100644 index 469ec6ec..00000000 --- a/notes/笔记/JVM.md.txt +++ /dev/null @@ -1,611 +0,0 @@ -[TOC] - -# 内存模型 - -![](index_files/dc695f48-4189-4fc7-b950-ed25f6c80f82.jpg) - -注:白色区域为线程私有的,蓝色区域为线程共享的。 - -## 1. 程序计数器 - -记录正在执行的虚拟机字节码指令的地址(如果正在执行的是 Native 方法则为空)。 - -## 2. Java 虚拟机栈 - -每个 Java 方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。 - -该区域可能抛出以下异常: - -1. 当线程请求的栈深度超过最大值,会抛出 StackOverflowError 异常; -2. 栈进行动态扩展时如果无法申请导足够内存,会抛出 OutOfMemoryError 异常。 - -## 3. 本地方法栈 - -与 Java 虚拟机栈类似,它们之间的区别只不过是本地方法栈为本地方法服务。 - -## 4. Java 堆 - -所有对象实例都在这里分配内存。 - -这块区域是垃圾收集器管理的主要区域("GC 堆 ")。现在收集器基本都是采用分代收集算法,Java 堆还可以分成:新生代和老年代(新生代还可以分成 Eden 空间、From Survivor 空间、To Survivor 空间等)。 - -不需要连续内存,可以通过 -Xmx 和 -Xms 来控制动态扩展内存大小,如果动态扩展失败会抛出 OutOfMemoryError 异常。 - -## 5. 方法区 - -用于存放已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。 - -和 Java 堆一样不需要连续的内存,并且可以动态扩展,动态扩展失败一样会抛出 OutOfMemoryError 异常。 - -对这块区域进行垃圾回收的主要目标是对常量池的回收和对类的卸载,但是一般比较难实现,HotSpot 虚拟机把它当成永久代来进行垃圾回收。 - -## 6. 运行时常量池 - -运行时常量池是方法区的一部分。 - -类加载后,Class 文件中的常量池(用于存放编译期生成的各种字面量和符号引用)就会被放到这个区域。 - -在运行期间也可以用过 String 类的 intern() 方法将新的常量放入该区域。 - -## 7. 直接内存 - -在 JDK 1.4 中新加入了 NIO 类,引入了一种基于通道(Channel)与缓冲区(Buffer)的 I/O 方式,它可以使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆里的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆中来回复制数据。 - -# 垃圾收集 - -程序计数器、虚拟机栈和本地方法栈这三个区域属于线程私有的,只存在于线程的生命周期内,线程结束之后也会消失,因此不需要对这三个区域进行垃圾回收。 - -垃圾回收主要是针对 Java 堆和方法区进行。 - -## 1. 判断一个对象是否可回收 - -### 1.1 引用计数 - -给对象添加一个引用计数器,当对象增加一个引用时计数器加 1,引用失效时计数器减 1。 - -引用计数为 0 的对象可被回收。 - -两个对象会出现循环引用问题,此时引用计数器永远不为 0,导致 GC 收集器无法回收。 - -```java -objA.instance = objB; -objB.instance = objA; -``` - -### 1.2 可达性 - -通过 GC Roots 作为起始点进行搜索,能够到达到的对象都是都是可用的,不可达的对象可被回收。 - -GC Roots 一般包含以下内容: - -1. 虚拟机栈中引用的对象 -2. 方法区中类静态属性引用的对象 -3. 方法区中的常量引用的对象 -4. 本地方法栈中引用的对象 - -### 1.3 引用类型 - -无论是通过引用计算算法判断对象的引用数量,还是通过可达性分析算法判断对象的引用链是否可达,判定独享是否存活都与“引用”有关。 - -#### 1.3.1 强引用 - -只要强引用存在,垃圾回收器永远不会回收调掉被引用的对象。 - -```java -Object obj = new Object(); -``` - -#### 1.3.2 软引用 - - -非必须引用,内存溢出之前进行回收。 - -```java -Object obj = new Object(); -SoftReference sf = new SoftReference(obj); -obj = null; -sf.get(); -``` - -sf 是对 obj 的一个软引用,通过 sf.get() 方法可以取到这个对象,当然,当这个对象被标记为需要回收的对象时,则返回 null; - -软引用主要用户实现类似缓存的功能,在内存足够的情况下直接通过软引用取值,无需从繁忙的真实来源查询数据,提升速度;当内存不足时,自动删除这部分缓存数据,从真正的来源查询这些数据。 - - -#### 1.3.3 弱引用 - -只能生存到下一次垃圾收集发生之前,当垃圾收集器工作时,无论当前内存是否足够,都会被回收。 - -```java -Object obj = new Object(); -WeakReference wf = new WeakReference(obj); -obj = null; -wf.get(); -wf.isEnQueued(); -``` - -#### 1.3.4 虚引用 - -又称为幽灵引用或者幻影引用,一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。 - -```java -Object obj = new Object(); -PhantomReference pf = new PhantomReference(obj); -obj=null; -pf.get(); -pf.isEnQueued(); -``` - -### 1.3 方法区的回收 - -在方法区主要是对常量池的回收和对类的卸载。 - -常量池的回收和堆中对象回收类似。 - -类的卸载条件很多,需要满足以下三个条件,并且满足了也不一定会被卸载: - -1. 该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。 -2. 加载该类的 ClassLoader 已经被回收。 -3. 该类对应的 java.lang.Class 对象没有在任何地方被引用,也就无法在任何地方通过反射访问该类方法。 - -可以通过 -Xnoclassgc 参数来控制是否对类进行卸载。 - -在大量使用反射、动态代理、CGLib 等 ByteCode 框架、动态生成 JSP 以及 OSGo 这类频繁自定义 ClassLoader 的场景都需要虚拟机具备类卸载功能,以保证不会出现内存溢出。 - -### 1.4 finalize() - -当一个对象可被回收时,如果该对象有必要执行 finalize() 方法,那么就有可能可能通过在该方法中让对象重新被引用,从而实现自救。 - -finalize() 类似 C++ 的虚构函数,用来做关闭外部资源等工作。但是 try-finally 等方式可以做的更好,并且该方法运行代价高昂,不确定性大,无法保证各个对象的调用顺序,因此最好不要使用。 - -## 2. 垃圾收集算法 - -### 2.1 标记 - 清除算法 - -![](index_files/a4248c4b-6c1d-4fb8-a557-86da92d3a294.jpg) - -将需要回收的对象进行标记,然后清除。 - -不足: - -1. 标记和清除过程效率都不高 -2. 会产生大量碎片 - -之后的算法都是基于该算法进行改进。 - -### 2.2 复制算法 - -![](index_files/e6b733ad-606d-4028-b3e8-83c3a73a3797.jpg) - -将内存划分为大小相等的两块,每次只使用其中一块,当这一块内存用完了就将还存活的对象复制到另一块上面,然后再把使用过的内存空间进行一次清理。 - -主要不足是只使用了内存的一半。 - -现在的商业虚拟机都采用这种收集算法来回收新生代,但是并不是将内存划分为大小相等的两块,而是分为一块较大的 Eden 空间和两块较小的 Survior 空间,每次使用 Eden 空间和其中一块 Survivor。在回收时,将 Eden 和 Survivor 中还存活着的对象一次性复制到另一块 Survivor 空间上,最后清理 Eden 和 Survivor。HotSpot 虚拟机的 Eden 和 Survivor 的大小比例默认为 8:1,保证了内存的利用率达到 90 %。如果每次回收有多于 10% 的对象存活,那么一块 Survivor 空间就不够用了,需要依赖于老年代进行分配担保,也就是借用老年代的空间。 - -### 2.3 标记 - 整理算法 - -![](index_files/902b83ab-8054-4bd2-898f-9a4a0fe52830.jpg) - -让所有存活的对象都向一段移动,然后直接清理掉端边界以外的内存。 - -### 2.4 分代收集算法 - -现在的商业虚拟机采用分代收集算法,它使用了前面介绍的几种收集算法,根据对象存活周期将内存划分为几块,不同块采用适当的收集算法。 - -一般将 Java 堆分为新生代和老年代。 - -1. 新生代使用:复制算法 -2. 老年代使用:标记 - 清理 或者 标记 - 整理 算法。 - -## 3. 垃圾收集器 - -![](index_files/c625baa0-dde6-449e-93df-c3a67f2f430f.jpg) - -以上是 HotSpot 虚拟机中的 7 个垃圾收集器,连线表示垃圾收集器可以配合使用。 - -### 3.1 Serial 收集器 - -![](index_files/22fda4ae-4dd5-489d-ab10-9ebfdad22ae0.jpg) - -它是单线程的收集器,不仅意味着只会使用一个线程进行垃圾收集工作,更重要的是它在进行垃圾收集时,必须暂停所有其他工作线程,往往造成过长的等待时间。 - -它的优点是简单高效,对于单个 CPU 环境来说,由于没有线程交互的开销,因此拥有最高的单线程收集效率。 - -在 Client 应用场景中,分配给虚拟机管理的内存一般来说不会很大,该收集器收集几十兆甚至一两百兆的新生代停顿时间可以控制在一百多毫秒以内,只要不是太频繁,这点停顿是可以接受的。 - -### 3.2 ParNew 收集器 - -![](index_files/81538cd5-1bcf-4e31-86e5-e198df1e013b.jpg) - -它是 Serial 收集器的多线程版本。 - -是 Server 模式下的虚拟机首选新生代收集器,除了性能原因外,主要是因为除了 Serial 收集器,只有它能与 CMS 收集器配合工作。 - -默认开始的线程数量与 CPU 数量相同,可以使用 -XX:ParallelGCThreads 参数来设置线程数。 - -### 3.3 Parallel Scavenge 收集器 - -是并行的多线程收集器。 - -其它收集器关注点是尽可能缩短垃圾收集时用户线程的停顿时间,而它的目标是达到一个可控制的吞吐量,它被称为“吞吐量优先”收集器。这里的吞吐量指 CPU 用于运行用户代码的时间占总时间的比值。 - -停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能提升用户体验。而高吞吐量则可以高效率地利用 CPU 时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。 - -提供了两个参数用于精确控制吞吐量,分别是控制最大垃圾收集停顿时间 -XX:MaxGCPauseMillis 参数以及直接设置吞吐量大小的 -XX:GCTimeRatio 参数(值为大于 0 且小于 100 的整数)。缩短停顿时间是以牺牲吞吐量和新生代空间来换取的:新生代空间变小,垃圾回收变得频繁,导致吞吐量下降。 - -还提供了一个参数 -XX:+UseAdaptiveSizePolicy,这是一个开关参数,打开参数后,就不需要手工指定新生代的大小(-Xmn)、Eden 和 Survivor 区的比例(-XX:SurvivorRatio)、晋升老年代对象年龄(-XX:PretenureSizeThreshold)等细节参数了,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量,这种方式称为 GC 自适应的调节策略(GC Ergonomics)。自适应调节策略也是它与 ParNew 收集器的一个重要区别。 - -### 3.4 Serial Old 收集器 - -![](index_files/08f32fd3-f736-4a67-81ca-295b2a7972f2.jpg) - -Serial Old 是 Serial 收集器的老年代版本,也是给 Client 模式下的虚拟机使用。如果用在 Server 模式下,它有两大用途: - -1. 在 JDK 1.5 以及之前版本(Parallel Old 诞生以前)中与 Parallel Scavenge 收集器搭配使用。 -2. 作为 CMS 收集器的后备预案,在并发收集发生 Concurrent Mode Failure 时使用。 - -### 3.5 Parallel Old 收集器 - -![](index_files/278fe431-af88-4a95-a895-9c3b80117de3.jpg) - -是 Parallel Scavenge 收集器的老年代版本。 - -在注重吞吐量以及 CPU 资源敏感的场合,都可以优先考虑 Parallel Scavenge 加 Parallel Old 收集器。 - -### 3.6 CMS 收集器 - -![](index_files/62e77997-6957-4b68-8d12-bfd609bb2c68.jpg) - -CMS(Concurrent Mark Sweep),从 Mark Sweep 可以知道它是基于 标记 - 清除 算法实现的。 - -特点:并发收集、低停顿。 - -分为以下四个流程: - -1. 初始标记:仅仅只是标记一下 GC Roots 能直接关联到的对象,速度很快,需要停顿。 -2. 并发标记:进行 GC Roots Tracing 的过程,它在整个回收过程中耗时最长,不需要停顿。 -3. 重新标记:为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,需要停顿。 -4. 并发清除:不需要停顿。 - -在整个过程中耗时最长的并发标记和并发清除过程中,收集器线程都可以与用户线程一起工作,不需要进行停顿。 - -具有以下缺点: - -1. 对 CPU 资源敏感。CMS 默认启动的回收线程数是 (CPU 数量 + 3) / 4,当 CPU 不足 4 个时,CMS 对用户程序的影响就可能变得很大,如果本来 CPU 负载就比较大,还要分出一半的运算能力去执行收集器线程,就可能导致用户程序的执行速度忽然降低了 50%,其实也让人无法接受。并且低停顿时间是以牺牲吞吐量为代价的,导致 CPU 利用率变低。 - -2. 无法处理浮动垃圾。由于并发清理阶段用户线程还在运行着,伴随程序运行自然就还会有新的垃圾不断产生。这一部分垃圾出现在标记过程之后,CMS 无法在当次收集中处理掉它们,只好留到下一次 GC 时再清理掉,这一部分垃圾就被称为“浮动垃圾”。也是由于在垃圾收集阶段用户线程还需要运行,那也就还需要预留有足够的内存空间给用户线程使用,因此它不能像其他收集器那样等到老年代几乎完全被填满了再进行收集,需要预留一部分空间提供并发收集时的程序运作使用。可以使用 -XX:CMSInitiatingOccupancyFraction 的值来改变触发收集器工作的内存占用百分比,JDK 1.5 默认设置下该值为 68,也就是当老年代使用了 68% 的空间之后会触发收集器工作。如果该值设置的太高,导致浮动垃圾无法保存,那么就会出现 Concurrent Mode Failure,此时虚拟机将启动后备预案:临时启用 Serial Old 收集器来重新进行老年代的垃圾收集。 - -3. 标记 - 清除算法导致的空间碎片,给大对象分配带来很大麻烦,往往出现老年代空间剩余,但无法找到足够大连续空间来分配当前对象,不得不提前出发一次 Full GC。 - -### 3.7 G1 收集器 - -![](index_files/f99ee771-c56f-47fb-9148-c0036695b5fe.jpg) - -G1(Garbage-First)收集器是当今收集器技术发展最前沿的成果之一,它是一款面向服务端应用的垃圾收集器,HotSpot 开发团队赋予它的使命是(在比较长期的)未来可以替换掉 JDK 1.5 中发布的 CMS 收集器。 - -具备如下特点: - -- 并行与并发:能充分利用多 CPU 环境下的硬件优势,使用多个 CPU 来缩短停顿时间; -- 分代收集:分代概念依然得以保留,虽然它不需要其它收集器配合就能独立管理整个 GC 堆,但它能够采用不同方式去处理新创建的对象和已存活一段时间、熬过多次 GC 的旧对象来获取更好的收集效果。 -- 空间整合:整体来看是基于“标记 - 整理”算法实现的收集器,从局部(两个 Region 之间)上来看是基于“复制”算法实现的,这意味着运行期间不会产生内存空间碎片。 -- 可预测的停顿:这是它相对 CMS 的一大优势,降低停顿时间是 G1 和 CMS 共同的关注点,但 G1 除了降低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为 M 毫秒的时间片段内,消耗在 GC 上的时间不得超过 N 毫秒,这几乎已经是实时 Java(RTSJ)的垃圾收集器的特征了。 - -在 G1 之前的其他收集器进行收集的范围都是整个新生代或者老生代,而 G1 不再是这样,Java 堆的内存布局与其他收集器有很大区别,将整个 Java 堆划分为多个大小相等的独立区域(Region)。虽然还保留新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,而都是一部分 Region(不需要连续)的集合。 - -之所以能建立可预测的停顿时间模型,是因为它可以有计划地避免在整个 Java 堆中进行全区域的垃圾收集。它跟踪各个 Region 里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的 Region(这也就是 Garbage-First 名称的来由)。这种使用 Region 划分内存空间以及有优先级的区域回收方式,保证了它在有限的时间内可以获取尽可能高的收集效率。 - -Region 不可能是孤立的,一个对象分配在某个 Region 中,可以与整个 Java 堆任意的对象发生引用关系。在做可达性分析确定对象是否存活的时候,需要扫描整个 Java 堆才能保证准确性,这显然是对 GC 效率的极大伤害。为了避免全堆扫描的发生,每个 Region 都维护了一个与之对应的 Remembered Set。虚拟机发现程序在对 Reference 类型的数据进行写操作时,会产生一个 Write Barrier 暂时中断写操作,检查 Reference 引用的对象是否处于不同的 Region 之中,如果是,便通过 CardTable 把相关引用信息记录到被引用对象所属的 Region 的 Remembered Set 之中。当进行内存回收时,在 GC 根节点的枚举范围中加入 Remembered Set 即可保证不对全堆扫描也不会有遗漏。 - -如果不计算维护 Remembered Set 的操作,G1 收集器的运作大致可划分为以下几个步骤: - -1. 初始标记 -2. 并发标记 -3. 最终标记:为了修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程的 Remembered Set Logs 里面,最终标记阶段需要把 Remembered Set Logs 的数据合并到 Remembered Set 中。这阶段需要停顿线程,但是可并行执行。 -4. 筛选回收:首先对各个 Region 中的回收价值和成本进行排序,根据用户所期望的 GC 停顿是时间来制定回收计划。此阶段其实也可以做到与用户程序一起并发执行,但是因为只回收一部分 Region,时间是用户可控制的,而且停顿用户线程将大幅度提高收集效率。 - -### 3.8 七种垃圾收集器的比较 - -| 收集器 | 串行、并行 or 并发 | 新生代 / 老年代 | 算法 | 目标 | 适用场景 | -| --- | --- | --- | --- | --- | --- | -| **Serial** | 串行 | 新生代 | 复制算法 | 响应速度优先 | 单 CPU 环境下的 Client 模式 | -| **Serial Old** | 串行 | 老年代 | 标记 - 整理 | 响应速度优先 | 单 CPU 环境下的 Client 模式、CMS 的后备预案 | -| **ParNew** | 并行 | 新生代 | 复制算法 | 响应速度优先 | 多 CPU 环境时在 Server 模式下与 CMS 配合 | -| **Parallel Scavenge** | 并行 | 新生代 | 复制算法 | 吞吐量优先 | 在后台运算而不需要太多交互的任务 | -| **Parallel Old** | 并行 | 老年代 | 标记 - 整理 | 吞吐量优先 | 在后台运算而不需要太多交互的任务 | -| **CMS** | 并发 | 老年代 | 标记 - 清除 | 响应速度优先 | 集中在互联网站或 B/S 系统服务端上的 Java 应用 | -| **G1** | 并发 | both | 标记 - 整理 + 复制算法 | 响应速度优先 | 面向服务端应用,将来替换 CMS | - -## 4. 内存分配与回收策略 - -### 4.1 优先在 Eden 分配 - -大多数情况下,对象在新生代 Eden 区分配,当 Eden 区空间不够时,发起 Minor GC; - -### 4.2 大对象直接进入老年代 - -提供 -XX:PretenureSizeThreshold 参数,大于此值的对象直接在老年代分配,避免在 Eden 区和 Survivor 区之间的大量内存复制; -### 4.3 长期存活的对象进入老年代 - -JVM 为对象定义年龄计数器,经过 Minor GC 依然存活且被 Survivor 区容纳的,移动到 Survivor 区,年龄加 1,每经历一次 Minor GC 不被清理则年龄加 1,增加到一定年龄则移动到老年区(默认 15 岁,通过 -XX:MaxTenuringThreshold 设置); - - -### 4.4 动态对象年龄判定 - -若 Survivor 区中同年龄所有对象大小总和大于 Survivor 空间一半,则年龄大于等于该年龄的对象可以直接进入老年代; - -### 4.5 空间分配担保 - -在发生 Minor GC 之前,JVM 先检查老年代最大可用连续空间是否大于新生代所有对象总空间,成立的话 Minor GC 确认是安全的;否则继续检查老年代最大可用连续空间是否大于历次晋升到老年代对象的平均大小,大于的话进行 Minor GC,小于的话进行 Full GC。 - -## 4.6 Full GC 的触发条件 - -对于 Minor GC,其触发条件非常简单,当 Eden 区空间满时,就将触发一次 Minor GC。而 Full GC 则相对复杂,有以下条件: - -### 4.6.1 调用 System.gc() - -此方法的调用是建议 JVM 进行 Full GC,虽然只是建议而非一定,但很多情况下它会触发 Full GC,从而增加 Full GC 的频率,也即增加了间歇性停顿的次数。因此强烈建议能不使用此方法就不要使用,让虚拟机自己去管理它的内存,可通过 -XX:+ DisableExplicitGC 来禁止 RMI 调用 System.gc()。 - -### 4.6.2 老年代空间不足 - -老年代空间不足的常见场景为前文所讲的大对象直接进入老年代、长期存活的对象进入老年代等,当执行 Full GC 后空间仍然不足,则抛出如下错误: Java.lang.OutOfMemoryError: Java heap space 为避免以上两种状况引起的 Full GC,调优时应尽量做到让对象在 Minor GC 阶段被回收、让对象在新生代多存活一段时间及不要创建过大的对象及数组。 - -### 4.6.3 空间分配担保失败 - -使用复制算法的 Minor GC 需要老年代的内存空间作担保,如果出现了 HandlePromotionFailure 担保失败,则会触发 Full GC。 - -### 4.6.4 JDK 1.7 及以前的永久代空间不足 - -在 JDK 1.7 及以前,HotSpot 虚拟机中的方法区是用永久代实现的,永久代中存放的为一些 class 的信息、常量、静态变量等数据,当系统中要加载的类、反射的类和调用的方法较多时,Permanet Generation 可能会被占满,在未配置为采用 CMS GC 的情况下也会执行 Full GC。如果经过 Full GC 仍然回收不了,那么 JVM 会抛出如下错误信息:java.lang.OutOfMemoryError: PermGen space 为避免 PermGen 占满造成 Full GC 现象,可采用的方法为增大 PermGen 空间或转为使用 CMS GC。 - -在 JDK 1.8 中用元空间替换了永久代作为方法区的实现,元空间是本地内存,因此减少了一种 Full GC 触发的可能性。 - -### 4.6.5 Concurrent Mode Failure - -执行 CMS GC 的过程中同时有对象要放入老年代,而此时老年代空间不足(有时候“空间不足”是 CMS GC 时当前的浮动垃圾过多导致暂时性的空间不足触发 Full GC),便会报 Concurrent Mode Failure 错误,并触发 Full GC。 - -# 类加载机制 - -类是在运行期间动态加载的。 - -## 1 类的生命周期 - -![](index_files/32b8374a-e822-4720-af0b-c0f485095ea2.jpg) - -包括以下 7 个阶段: - -- **加载(Loading)** -- **验证(Verification)** -- **准备(Preparation)** -- **解析(Resolution)** -- **初始化(Initialization)** -- 使用(Using) -- 卸载(Unloading) - -其中解析过程在某些情况下可以在初始化阶段之后再开始,这是为了支持 Java 的动态绑定。 - -## 2. 类初始化时机 - -虚拟机规范中并没有强制约束何时进行加载,但是规范严格规定了有且只有下列五种情况必须对类进行初始化:( 加载、验证、准备都会随着发生 ) - -1. 遇到 new、getstatic、putstatic、invokestatic 这四条字节码指令时,如果类没有进行过初始化,则必须先触发其初始化。最常见的生成这 4 条指令的场景是:使用 new 关键字实例化对象的时候;读取或设置一个类的静态字段(被 final 修饰、已在编译器把结果放入常量池的静态字段除外)的时候;以及调用一个类的静态方法的时候。 - -2. 使用 java.lang.reflect 包的方法对类进行反射调用的时候,如果类没有进行初始化,则需要先触发其初始化。 - -3. 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。 - -4. 当虚拟机启动时,用户需要指定一个要执行的主类(包含 main() 方法的那个类),虚拟机会先初始化这个主类; - -5. 当使用 jdk1.7 的动态语言支持时,如果一个 java.lang.invoke.MethodHandle 实例最后的解析结果为 REF_getStatic, REF_putStatic, REF_invokeStatic 的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化; - -以上 5 种场景中的行为称为对一个类进行主动引用。除此之外,所有引用类的方式都不会触发初始化,称为被动引用。被动引用的常见例子包括: - -1\. 通过子类引用父类的静态字段,不会导致子类初始化。 - -```java -System.out.println(SubClass.value); // value 字段在 SuperClass 中定义 -``` - -2\. 通过数组定义来引用类,不会触发此类的初始化。该过程会对数组类进行初始化,数组类是一个由虚拟机自动生成的、直接继承自 Object 的子类,其中包含了数组的属性和方法。 - -```java -SuperClass[] sca = new SuperClass[10]; -``` - -3\. 常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化。 - -```java -System.out.println(ConstClass.HELLOWORLD); -``` - -## 3. 类加载过程 - -包含了加载、验证、准备、解析和初始化这 5 个阶段。 - -### 3.1 加载 - -加载是类加载的一个阶段,注意不要混淆。 - -加载过程完成以下三件事: - -1. 通过一个类的全限定名来获取定义此类的二进制字节流。 -2. 将这个字节流所代表的静态存储结构转化为方法区的运行时存储结构。 -3. 在内存中生成一个代表这个类的 Class 对象,作为方法区这个类的各种数据的访问入口。 - -其中二进制字节流可以从以下方式中获取: - -- 从 ZIP 包读取,这很常见,最终成为日后 JAR、EAR、WAR 格式的基础。 -- 从网络中获取,这种场景最典型的应用是 Applet。 -- 运行时计算生成,这种场景使用得最多得就是动态代理技术,在 java.lang.reflect.Proxy 中,就是用了 ProxyGenerator.generateProxyClass 的代理类的二进制字节流。 -- 由其他文件生成,典型场景是 JSP 应用,即由 JSP 文件生成对应的 Class 类。 -- 从数据库读取,这种场景相对少见,例如有些中间件服务器(如 SAP Netweaver)可以选择把程序安装到数据库中来完成程序代码在集群间的分发。 -... - -### 3.2 验证 - -确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。 - -主要有以下 4 个阶段: - -1. 文件格式验证 -2. 元数据验证(对字节码描述的信息进行语义分析) -3. 字节码验证(通过数据流和控制流分析,确保程序语义是合法、符合逻辑的,将对类的方法体进行校验分析) -4. 符号引用验证 - -### 3.3 准备 - -类变量是被 static 修饰的变量,准备阶段为类变量分配内存并设置初始值,使用的是方法区的内存。 - -实例变量不会在这阶段分配内存,它将会在对象实例化时随着对象一起分配在 Java 堆中。 - -初始值一般为 0 值,例如下面的类变量 value 被初始化为 0 而不是 123。 - -```java -public static int value = 123; -``` - -如果类变量是常量,那么会按照表达式来进行初始化,而不是赋值为 0。 - -```java -public static final int value = 123; -``` - -### 3.4 解析 - -将常量池的符号引用替换为直接引用的过程。 - -### 3.5 初始化 - -初始化阶段即虚拟机执行类构造器 <clinit>() 方法的过程。 - -在准备阶段,类变量已经赋过一次系统要求的初始值,而在初始化阶段,根据程序员通过程序制定的主观计划去初始化类变量和其它资源。 - -<clinit>() 方法具有以下特点: - -- 是由编译器自动收集类中所有类变量的赋值动作和静态语句块(static{} 块)中的语句合并产生的,编译器收集的顺序由语句在源文件中出现的顺序决定。特别注意的是,静态语句块只能访问到定义在它之前的类变量,定义在它之后的类变量只能赋值,不能访问。例如以下代码: - -```java -public class Test { -    static { -        i = 0;                // 给变量赋值可以正常编译通过 -        System.out.print(i);  // 这句编译器会提示“非法向前引用” -    } -    static int i = 1; -} -``` - -- 与类的构造函数(或者说实例构造器 <init>())不同,不需要显式的调用父类的构造器。虚拟机会自动保证在子类的 <clinit>() 方法运行之前,父类的 <clinit>() 方法已经执行结束。因此虚拟机中第一个执行 <clinit>() 方法的类肯定为 java.lang.Object。 - -- 由于父类的 <clinit>() 方法先执行,也就意味着父类中定义的静态语句块要优于子类的变量赋值操作。例如以下代码: - -```java -static class Parent { -        public static int A = 1; -        static { -            A = 2; -        } -} - -static class Sub extends Parent { -        public static int B = A; -} - -public static void main(String[] args) { -        System.out.println(Sub.B);  // 输出结果是父类中的静态变量值 A,也就是 2 -} -``` - -- <clinit>() 方法对于类或接口不是必须的,如果一个类中不包含静态语句块,也没有对类变量的赋值操作,编译器可以不为该类生成 <clinit>() 方法。 - -- 接口中不可以使用静态语句块,但仍然有类变量初始化的赋值操作,因此接口与类一样都会生成 <clinit>() 方法。但接口与类不同的是,执行接口的 <clinit>() 方法不需要先执行父接口的 <clinit>() 方法。只有当父接口中定义的变量使用时,父接口才会初始化。另外,接口的实现类在初始化时也一样不会执行接口的 <clinit>() 方法。 - -- 虚拟机会保证一个类的 <clinit>() 方法在多线程环境下被正确的加锁和同步,如果多个线程同时初始化一个类,只会有一个线程执行这个类的 <clinit>() 方法,其它线程都会阻塞等待,直到活动线程执行 <clinit>() 方法完毕。如果在一个类的 <clinit>() 方法中有耗时的操作,就可能造成多个进程阻塞,在实际过程中此种阻塞很隐蔽。 - -## 4. 类加载器 - -虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流 ( 即字节码 )”这个动作放到 Java 虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称为“类加载器”。 - -### 4.1 类与类加载器 - -对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在 Java 虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。通俗而言:比较两个类是否“相等”(这里所指的“相等”,包括类的 Class 对象的 equals() 方法、isAssignableFrom() 方法、isInstance() 方法的返回结果,也包括使用 instanceof() 关键字对做对象所属关系判定等情况),只有在这两个类时由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个 Class 文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等。 - -### 4.2 类加载器分类 - -从 Java 虚拟机的角度来讲,只存在以下两种不同的类加载器: - -一种是启动类加载器(Bootstrap ClassLoader),这个类加载器用 C++ 实现,是虚拟机自身的一部分;另一种就是所有其他类的加载器,这些类由 Java 实现,独立于虚拟机外部,并且全都继承自抽象类 java.lang.ClassLoader。 - -从 Java 开发人员的角度看,类加载器可以划分得更细致一些: - -- 启动类加载器(Bootstrap ClassLoader) 此类加载器负责将存放在 \lib 目录中的,或者被 -Xbootclasspath 参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如 rt.jar,名字不符合的类库即使放在 lib 目录中也不会被加载)类库加载到虚拟机内存中。 启动类加载器无法被 Java 程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给引导类加载器,直接使用 null 代替即可。 - -- 扩展类加载器(Extension ClassLoader) 这个类加载器是由 ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的。它负责将 /lib/ext 或者被 java.ext.dir 系统变量所指定路径中的所有类库加载到内存中,开发者可以直接使用扩展类加载器。 - -- 应用程序类加载器(Application ClassLoader) 这个类加载器是由 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。由于这个类加载器是 ClassLoader 中的 getSystemClassLoader() 方法的返回值,因此一般称为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。 - -### 4.3 双亲委派模型 - -应用程序都是由三种类加载器相互配合进行加载的,如果有必要,还可以加入自己定义的类加载器。下图展示的类加载器之间的层次关系,称为类加载器的双亲委派模型(Parents Delegation Model)。该模型要求除了顶层的启动类加载器外,其余的类加载器都应有自己的父类加载器,这里类加载器之间的父子关系一般通过组合(Composition)关系来实现,而不是通过继承(Inheritance)的关系实现。 - -![](index_files/2cdc3ce2-fa82-4c22-baaa-000c07d10473.jpg) - -**工作过程** - -如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载,而是把这个请求委派给父类加载器,每一个层次的加载器都是如此,依次递归,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成此加载请求(它搜索范围中没有找到所需类)时,子加载器才会尝试自己加载。 - -**好处** - -使用双亲委派模型来组织类加载器之间的关系,使得 Java 类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类 java.lang.Object,它存放再 rt.jar 中,无论哪个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此 Object 类在程序的各种类加载器环境中都是同一个类。相反,如果没有双亲委派模型,由各个类加载器自行加载的话,如果用户编写了一个称为`java.lang.Object 的类,并放在程序的 ClassPath 中,那系统中将会出现多个不同的 Object 类,程序将变得一片混乱。如果开发者尝试编写一个与 rt.jar 类库中已有类重名的 Java 类,将会发现可以正常编译,但是永远无法被加载运行。 - -**实现** - -```java -protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException{ -    //check the class has been loaded or not -    Class c = findLoadedClass(name); -    if(c == null) { -        try{ -            if(parent != null) { -                c = parent.loadClass(name, false); -            } else{ -                c = findBootstrapClassOrNull(name); -            } -        } catch(ClassNotFoundException e) { -            //if throws the exception , the father can not complete the load -        } -        if(c == null) { -            c = findClass(name); -        } -    } -    if(resolve) { -        resolveClass(c); -    } -    return c; -} -``` - -# JVM 参数 - -## GC 优化配置 - -| 配置 | 描述 | -| --- | --- | -| -Xms | 初始化堆内存大小 | -| -Xmx | 堆内存最大值 | -| -Xmn | 新生代大小 | -| -XX:PermSize | 初始化永久代大小 | -| -XX:MaxPermSize | 永久代最大容量 | - -## GC 类型设置 - -| 配置 | 描述 | -| --- | --- | -| -XX:+UseSerialGC | 串行垃圾回收器 | -| -XX:+UseParallelGC | 并行垃圾回收器 | -| -XX:+UseConcMarkSweepGC | 并发标记扫描垃圾回收器 | -| -XX:ParallelCMSThreads= | 并发标记扫描垃圾回收器 = 为使用的线程数量 | -| -XX:+UseG1GC | G1 垃圾回收器 | - -```java -java -Xmx12m -Xms3m -Xmn1m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseSerialGC -jar java-application.jar -``` \ No newline at end of file diff --git a/notes/笔记/Java IO.md.txt b/notes/笔记/Java IO.md.txt deleted file mode 100644 index e5ad55b4..00000000 --- a/notes/笔记/Java IO.md.txt +++ /dev/null @@ -1,376 +0,0 @@ -[TOC] - -# 概览 - -Java 的 I/O 大概可以分成以下几类 - -1. 磁盘操作:File -2. 字节操作:InputStream 和 OutputStream -3. 字符操作:Reader 和 Writer -4. 对象操作:Serializable -5. 网络操作:Socket -6. 非阻塞式 IO:NIO - -# 磁盘操作 - -File 类可以用于表示文件和目录,但是它只用于表示文件的信息,而不表示文件的内容。 - -# 字节操作 - -![](index_files/8143787f-12eb-46ea-9bc3-c66d22d35285.jpg) - -Java I/O 使用了装饰者模式来实现。以 InputStream 为例,InputStream 是抽象组件,FileInputStream 是 InputStream 的子类,属于具体组件,提供了字节流的输入操作。FilterInputStream 属于抽象装饰者,装饰者用于装饰组件,为组件提供额外的功能,例如 BufferedInputStream 为 FileInputStream 提供缓存的功能。实例化一个具有缓存功能的字节流对象时,只需要在 FileInputStream 对象上再套一层 BufferedInputStream 对象即可。 - -```java -BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file)); -``` - -DataInputStream 装饰者提供了对更多数据类型进行输入的操作,比如 int、double 等基本类型。 - -批量读入文件中的内容到字节数组中 - -```java -byte[] buf = new byte[20*1024]; -int bytes = 0; -// 最多读取 buf.length 个字节,返回的是实际读取的个数,返回 -1 的时候表示读到 eof,即文件尾 -while((bytes = in.read(buf, 0 , buf.length)) != -1) { -    // ... -} -``` - -# 字符操作 - -不管是磁盘还是网络传输,最小的存储单元都是字节,而不是字符,所以 I/O 操作的都是字节而不是字符。但是在程序中操作的数据通常是字符形式,因此需要提供对字符进行操作的方法。 - -InputStreamReader 实现从文本文件的字节流解码成字符流;OutputStreamWriter 实现字符流编码成为文本文件的字节流。它们都继承自 Reader 和 Writer。 - -编码就是把字符转换为字节,而解码是把字节重新组合成字符。 - -```java -byte[] bytes = str.getBytes(encoding);     // 编码 -String str = new String(bytes, encoding); // 解码 -``` - -GBK 编码中,中文占 2 个字节,英文占 1 个字节;UTF-8 编码中,中文占 3 个字节,英文占 1 个字节;Java 使用双字节编码 UTF-16be,中文和英文都占 2 个字节。 - -如果编码和解码过程使用不同的编码方式那么就出现了乱码。 - -# 对象操作 - -序列化就是将一个对象转换成字节序列,方便存储和传输。 - -序列化:ObjectOutputStream.writeObject() - -反序列化:ObjectInputStream.readObject() - -序列化的类需要实现 Serializable 接口,它只是一个标准,没有任何方法需要实现。 - -transient 关键字可以使一些属性不会被序列化。 - -**ArrayList 序列化和反序列化的实现**:ArrayList 中存储数据的数组是用 transient 修饰的,因为这个数组是动态扩展的,并不是所有的空间都被使用,因此就不需要所有的内容都被序列化。通过重写序列化和反序列化方法,使得可以只序列化数组中有内容的那部分数据。 - -``` -private transient Object[] elementData; -``` - -# 网络操作 - -Java 中的网络支持: - -1. InetAddress:用于表示网络上的硬件资源,即 IP 地址; -2. URL:统一资源定位符,通过 URL 可以直接读取或者写入网络上的数据; -3. Sockets:使用 TCP 协议实现网络通信; -4. Datagram:使用 UDP 协议实现网络通信。 - -## 1. InetAddress - -没有公有构造函数,只能通过静态方法来创建实例,比如 InetAddress.getByName(String host)、InetAddress.getByAddress(byte[] addr)。 - -## 2. URL - -可以直接从 URL 中读取字节流数据 - -```java -URL url = new URL("http://www.baidu.com"); -InputStream is = url.openStream(); // 字节流 -InputStreamReader isr = new InputStreamReader(is, "utf-8");                              // 字符流 -BufferedReader br = new BufferedReader(isr); -String line = br.readLine(); -while (line != null) { -    System.out.println(line); -    line = br.readLine(); -} -br.close(); -isr.close(); -is.close(); -``` - -## 3. Sockets - -Socket 通信模型 - -![](index_files/fa4101d7-19ce-4a69-a84f-20bbe64320e5.jpg) - -- ServerSocket:服务器端类 -- Socket:客户端类 - -服务器和客户端通过 InputStream 和 OutputStream 进行输入输出。 - -## 4. Datagram - -- DatagramPacket:数据包类 -- DatagramSocket:通信类 - -# NIO - -NIO 将最耗时的 I/O 操作 ( 即填充和提取缓冲区 ) 转移回操作系统,因而 不需要程序员去控制就可以极大地提高运行速度。 - -## 1. 流与块 - -I/O 与 NIO 最重要的区别是数据打包和传输的方式。正如前面提到的,I/O 以流的方式处理数据,而 NIO 以块的方式处理数据。 - -面向流的 I/O 一次一个字节进行处理数据,一个输入流产生一个字节的数据,一个输出流消费一个字节的数据。为流式数据创建过滤器非常容易,链接几个过滤器,以便每个过滤器只负责单个复杂处理机制的一部分,这样也是相对简单的。不利的一面是,面向流的 I/O 通常相当慢。 - -一个面向块的 I/O 系统以块的形式处理数据,每一个操作都在一步中产生或者消费一个数据块。按块处理数据比按流处理数据要快得多。但是面向块的 I/O 缺少一些面向流的 I/O 所具有的优雅性和简单性。 - -I/O 包和 NIO 已经很好地集成了,java.io.\* 已经以 NIO 为基础重新实现了,所以现在它可以利用 NIO 的一些特性。例如, java.io.\* 包中的一些类包含以块的形式读写数据的方法,这使得即使在更面向流的系统中,处理速度也会更快。 - -## 2. 通道与缓冲区 - -### 2.1 通道 - -通道 Channel 是对原 I/O 包中的流的模拟,可以通过它读取和写入数据。 - -通道与流的不同之处在于,流只能在一个方向上移动,(一个流必须是 InputStream 或者 OutputStream 的子类), 而通道是双向的,可以用于读、写或者同时用于读写。 - -通道包括以下类型: - -- FileChannel:从文件中读写数据; -- DatagramChannel:通过 UDP 读写网络中数据; -- SocketChannel:通过 TCP 读写网络中数据; -- ServerSocketChannel:可以监听新进来的 TCP 连接,对每一个新进来的连接都会创建一个 SocketChannel。 - -### 2.2 缓冲区 - -发送给一个通道的所有对象都必须首先放到缓冲区中;同样地,从通道中读取的任何数据都要读到缓冲区中。也就是说,不会直接对通道进行读写数据,而是先经过缓冲区。 - -缓冲区实质上是一个数组,但它不仅仅是一个数组。缓冲区提供了对数据的结构化访问,而且还可以跟踪系统的读/写进程。 - -缓冲区包括以下类型: - -- ByteBuffer -- CharBuffer -- ShortBuffer -- IntBuffer -- LongBuffer -- FloatBuffer -- DoubleBuffer - - -## 3. 缓冲区状态变量 - -- capacity:最大容量; -- position:当前已经读写的字节数; -- limit:还可以读写的字节数。 - -状态变量的改变过程: - -1\. 新建一个大小为 8 个字节的缓冲区,此时 position 为 0,而 limit == capacity == 9。capacity 变量不会改变,下面的讨论会忽略它。 - -![](index_files/1bea398f-17a7-4f67-a90b-9e2d243eaa9a.png) - -2\. 从输入通道中读取 3 个字节数据写入缓冲区中,此时 position 移动设为 3,limit 保持不变。 - -![](index_files/4628274c-25b6-4053-97cf-d1239b44c43d.png) - -3\. 在将缓冲区的数据写到输出通道之前,需要先调用 flip() 方法,这个方法将 limit 设置为当前 position,并将 position 设置为 0。 - -![](index_files/952e06bd-5a65-4cab-82e4-dd1536462f38.png) - -4\. 从缓冲区中取 4 个字节到输出缓冲中,此时 position 设为 4。 - -![](index_files/b5bdcbe2-b958-4aef-9151-6ad963cb28b4.png) - -5\. 最后需要调用 clear() 方法来清空缓冲区,此时 position 和 limit 都被设置为最初位置。 - -![](index_files/67bf5487-c45d-49b6-b9c0-a058d8c68902.png) - -## 4. 读写文件实例 - -1\. 为要读取的文件创建 FileInputStream,之后通过 FileInputStream 获取输入 FileChannel; - -```java -FileInputStream fin = new FileInputStream("readandshow.txt"); -FileChannel fic = fin.getChannel(); -``` - -2\. 创建一个容量为 1024 的 Buffer - -```java -ByteBuffer buffer = ByteBuffer.allocate(1024); -``` - -3\. 将数据从输入 FileChannel 写入到 Buffer 中,如果没有数据的话, read() 方法会返回 -1 - -```java -int r = fcin.read(buffer); -if (r == -1) { -     break; -} -``` - -4\. 为要写入的文件创建 FileOutputStream,之后通过 FileOutputStream 获取输出 FileChannel - -```java -FileOutputStream fout = new FileOutputStream("writesomebytes.txt"); -FileChannel foc = fout.getChannel(); -``` - -5\. 调用 flip() 切换读写 - -```java -buffer.flip(); -``` - -6\. 把 Buffer 中的数据读取到输出 FileChannel 中 - -```java -foc.write(buffer); -``` - -7\. 最后调用 clear() 重置缓冲区 - -```java -buffer.clear(); -``` - -## 5. 阻塞与非阻塞 - -应当注意,FileChannel 不能切换到非阻塞模式,套接字 Channel 可以。 - -### 5.1 阻塞式 I/O - -阻塞式 I/O 在调用 InputStream.read() 方法时会一直等到数据到来时(或超时)才会返回,在调用 ServerSocket.accept() 方法时,也会一直阻塞到有客户端连接才会返回,每个客户端连接过来后,服务端都会启动一个线程去处理该客户端的请求。 - -![](index_files/edc23f99-c46c-4200-b64e-07516828720d.jpg) - -### 5.2 非阻塞式 I/O - -由一个专门的线程来处理所有的 I/O 事件,并负责分发。 - -事件驱动机制:事件到的时候触发,而不是同步的去监视事件。 - -线程通信:线程之间通过 wait()、notify() 等方式通信,保证每次上下文切换都是有意义的,减少无谓的线程切换。 - -![](index_files/7fcb2fb0-2cd9-4396-bc2d-282becf963c3.jpg) - -## 6. 套接字实例 - -### 6.1 ServerSocketChannel - -每一个端口都需要有一个 ServerSocketChannel 用来监听连接。 - -```java -ServerSocketChannel ssc = ServerSocketChannel.open(); -ssc.configureBlocking(false); // 设置为非阻塞 - -ServerSocket ss = ssc.socket(); -InetSocketAddress address = new InetSocketAddress(ports[i]); -ss.bind(address); // 绑定端口号 -``` - -### 6.2 Selectors - -异步 I/O 通过 Selector 注册对特定 I/O 事件的兴趣 ― 可读的数据的到达、新的套接字连接等等,在发生这样的事件时,系统将会发送通知。 - -创建 Selectors 之后,就可以对不同的通道对象调用 register() 方法。register() 的第一个参数总是这个 Selector。第二个参数是 OP_ACCEPT,这里它指定我们想要监听 accept 事件,也就是在新的连接建立时所发生的事件。 - -SelectionKey 代表这个通道在此 Selector 上的这个注册。当某个 Selector 通知您某个传入事件时,它是通过提供对应于该事件的 SelectionKey 来进行的。SelectionKey 还可以用于取消通道的注册。 - -```java -Selector selector = Selector.open(); -SelectionKey key = ssc.register(selector, SelectionKey.OP_ACCEPT); -``` - -### 6.3 主循环 - -首先,我们调用 Selector 的 select() 方法。这个方法会阻塞,直到至少有一个已注册的事件发生。当一个或者更多的事件发生时, select() 方法将返回所发生的事件的数量。 - -接下来,我们调用 Selector 的 selectedKeys() 方法,它返回发生了事件的 SelectionKey 对象的一个 集合 。 - -我们通过迭代 SelectionKeys 并依次处理每个 SelectionKey 来处理事件。对于每一个 SelectionKey,您必须确定发生的是什么 I/O 事件,以及这个事件影响哪些 I/O 对象。 - -```java -int num = selector.select(); -  -Set selectedKeys = selector.selectedKeys(); -Iterator it = selectedKeys.iterator(); -  -while (it.hasNext()) { -     SelectionKey key = (SelectionKey)it.next(); -     // ... deal with I/O event ... -} -``` - -### 6.4 监听新连接 - -程序执行到这里,我们仅注册了 ServerSocketChannel,并且仅注册它们“接收”事件。为确认这一点,我们对 SelectionKey 调用 readyOps() 方法,并检查发生了什么类型的事件: - -```java -if ((key.readyOps() & SelectionKey.OP_ACCEPT) -     == SelectionKey.OP_ACCEPT) { -     // Accept the new connection -     // ... -} -``` - -可以肯定地说, readOps() 方法告诉我们该事件是新的连接。 - -### 6.5 接受新的连接 - -因为我们知道这个服务器套接字上有一个传入连接在等待,所以可以安全地接受它;也就是说,不用担心 accept() 操作会阻塞: - -```java -ServerSocketChannel ssc = (ServerSocketChannel)key.channel(); -SocketChannel sc = ssc.accept(); -``` - -下一步是将新连接的 SocketChannel 配置为非阻塞的。而且由于接受这个连接的目的是为了读取来自套接字的数据,所以我们还必须将 SocketChannel 注册到 Selector上,如下所示: - -```java -sc.configureBlocking( false ); -SelectionKey newKey = sc.register( selector, SelectionKey.OP_READ ); -``` - -注意我们使用 register() 的 OP_READ 参数,将 SocketChannel 注册用于 读取 而不是 接受 新连接。 - -### 6.6 删除处理过的 SelectionKey - -在处理 SelectionKey 之后,我们几乎可以返回主循环了。但是我们必须首先将处理过的 SelectionKey 从选定的键集合中删除。如果我们没有删除处理过的键,那么它仍然会在主集合中以一个激活的键出现,这会导致我们尝试再次处理它。我们调用迭代器的 remove() 方法来删除处理过的 SelectionKey: - -```java -it.remove(); -``` - -现在我们可以返回主循环并接受从一个套接字中传入的数据(或者一个传入的 I/O 事件)了。 - -### 6.7 传入的 I/O - -当来自一个套接字的数据到达时,它会触发一个 I/O 事件。这会导致在主循环中调用 Selector.select(),并返回一个或者多个 I/O 事件。这一次, SelectionKey 将被标记为 OP_READ 事件,如下所示: - -```java -} else if ((key.readyOps() & SelectionKey.OP_READ) -     == SelectionKey.OP_READ) { -     // Read the data -     SocketChannel sc = (SocketChannel)key.channel(); -     // ... -} -``` - - -# 参考资料 - -- Eckel B, 埃克尔 , 昊鹏 , 等 . Java 编程思想 [M]. 机械工业出版社 , 2002. -- [IBM: NIO 入门](https://www.ibm.com/developerworks/cn/education/java/j-nio/j-nio.html) -- [ 深入分析 Java I/O 的工作机制 ](https://www.ibm.com/developerworks/cn/java/j-lo-javaio/index.html) -- [NIO 与传统 IO 的区别 ](http://blog.csdn.net/shimiso/article/details/24990499) \ No newline at end of file diff --git a/notes/笔记/Java 基础语法.md.txt b/notes/笔记/Java 基础语法.md.txt deleted file mode 100644 index 389b5c51..00000000 --- a/notes/笔记/Java 基础语法.md.txt +++ /dev/null @@ -1,128 +0,0 @@ -[TOC] - -# 基础 - -##  final - -**final 数据** - -声明数据为常量,可以是编译时常量,也可以是在运行时被初始化后不能被改变的常量。 - -对于基本类型,final 使数值不变;对于引用对象,final 使引用不变,也就不能引用其它对象,但是被引用的对象本身是可以修改的。 - -**final 方法** - -声明方法不能被子类覆盖。 - -private 方法隐式地被指定为 final,如果在子类中定义的方法和基类中的一个 private 方法签名相同,此时子类的方法不是覆盖基类方法,而是重载了。 - -**final 类** - -声明类不允许被继承。 - -## 初始化顺序 - -static 声明的静态数据在内存中只存在一份,只在类第一次实例化时初始化一次,优先于其它数据的初始化。 - -```java -public static String staticField = "静态变量"; -``` - -static 语句块和 static 数据一样在类第一次实例化时运行一次,具体哪个先运行取决于它们在代码中的顺序。 - -```java -static { -    System.out.println("静态初始化块"); -} -``` - -普通数据和普通语句块的初始化在静态数据和静态语句块初始化结束之后。 - -```java -public String field = "变量"; -``` - -```java -{ -    System.out.println("初始化块"); -} -``` - -最后才是构造函数中的数据进行初始化 - -```java -public InitialOrderTest() -{ -    System.out.println("构造器"); -} -``` - -存在继承的情况下,初始化顺序为: - -1. 父类(静态数据、静态语句块) -2. 子类(静态数据、静态语句块) -3. 父类(数据、语句块) -4. 父类(构造器) -5. 子类(数据、语句块) -6. 子类(构造器) - -## 访问权限 - -Java 中有三个访问权限修饰符:private、protected 以及 public,如果不加访问修饰符,表示包级可见。 - -可以对类或类中的成员(字段以及方法)加上访问修饰符。成员可见表示其它类可以用成员所在类的对象访问到该成员;类可见表示其它类可以用这个类创建对象,可以把类当做包中的一个成员,然后包表示一个类,这样就好理解了。 - -protected 用于修饰成员,表示在继承体系中成员对于子类可见。但是这个访问修饰符对于类没有意义,因为包没有继承体系。 - -# 容器 - -![](index_files/114c49a6-72e3-4264-ae07-c564127094ac.png) - -容器主要包括 Collection 和 Map 两种,Collection 又包含了 List、Set 以及 Queue。 - -## Set - -- HashSet:使用 Hash 实现,支持快速查找,但是失去有序性; - -- TreeSet:使用树实现,保持有序,但是查找效率不如 HashSet; - -- LinkedListHashSet:具有 HashSet 的查找效率,且内部使用链表维护元素的插入顺序,因此具有有序性。 - -## Queue - -只有两个实现:LinkedList 和 PriorityQueue,其中 LinkedList 支持双向队列。 - -## Map - -- HashMap:使用 Hash 实现 - -- LinkedHashMap:保持有序,顺序为插入顺序或者最近最少使用(LRU)顺序 - -- TreeMap:基于红黑树实现 - -- ConcurrentHashMap:线程安全 Map,不涉及同步加锁 - -# 反射 - -每个类都有一个 **Class** 对象,包含了与类有关的信息。当编译一个新类时,会产生一个同名的 .class 文件,该文件内容保存着 Class 对象。 - -类加载相当于 Class 对象的加载。类在第一次使用时才动态加载到 JVM 中,可以使用 Class.forName('com.mysql.jdbc.Driver.class') 这种方式来控制类的加载,该方法会返回一个 Class 对象。 - -反射可以提供运行时的类信息,并且这个类可以在运行时才加载进来,甚至在编译时期该类的 .class 不存在也可以加载进来。 - -Class 和 java.lang.reflect 一起对反射提供了支持,java.lang.reflect 类库包含了 **Field**、**Method** 以及 **Constructor** 类。可以使用 get() 和 set() 方法读取和修改 Field 对象关联的字段,可以使用 invoke() 方法调用与 Method 对象关联的方法,可以用 Constructor 创建新的对象。 - -IDE 使用反射机制获取类的信息,在使用一个类的对象时,能够把类的字段、方法和构造函数等信息列出来供用户选择。 - -# 异常 - -Throwable 可以用来表示任何可以作为异常抛出的类,分为两种:**Error** 和 **Exception**,其中 Error 用来表示编译时系统错误。 - -Exception 分为两种:**受检异常** 和 **非受检异常**。受检异常需要用 try...catch... 语句捕获并进行处理,并且可以从异常中恢复;非受检异常是程序运行时错误,例如除 0 会引发 Arithmetic Exception,此时程序奔溃并且无法恢复。 - -![](index_files/48f8f98e-8dfd-450d-8b5b-df4688f0d377.jpg) - -# 参考资料 - -- Eckel B, 埃克尔 , 昊鹏 , 等 . Java 编程思想 [M]. 机械工业出版社 , 2002. -- [Java 类初始化顺序 ](https://segmentfault.com/a/1190000004527951) diff --git a/notes/笔记/Java 容器.md.txt b/notes/笔记/Java 容器.md.txt deleted file mode 100644 index e625d4fa..00000000 --- a/notes/笔记/Java 容器.md.txt +++ /dev/null @@ -1,341 +0,0 @@ -[TOC] - -# 概览 - -![](index_files/ebf03f56-f957-4435-9f8f-0f605661484d.jpg) - -容器主要包括 Collection 和 Map 两种,Collection 又包含了 List、Set 以及 Queue。 - -## 1. List - -- ArrayList:基于动态数组实现,支持随机访问; - -- LinkedList:基于双向循环链表实现,只能顺序访问,但是可以快速地在链表中间插入和删除元素。不仅如此,LinkedList 还可以用作栈、队列和双端队列。 - -## 2. Set - -- HashSet:基于 Hash 实现,支持快速查找,但是失去有序性; - -- TreeSet:基于红黑树实现,保持有序,但是查找效率不如 HashSet; - -- LinkedListHashSet:具有 HashSet 的查找效率,且内部使用链表维护元素的插入顺序,因此具有有序性。 - -## 3. Queue - -只有两个实现:LinkedList 和 PriorityQueue,其中 LinkedList 支持双向队列,PriorityQueue 是基于堆结构实现。 - -## 4. Map - -- HashMap:基于 Hash 实现 - -- LinkedHashMap:使用链表来维护元素的顺序,顺序为插入顺序或者最近最少使用(LRU)顺序 - -- TreeMap:基于红黑树实现 - -- ConcurrentHashMap:线程安全 Map,不涉及类似于 HashTable 的同步加锁 - -## 5. Java 1.0/1.1 容器 - -对于旧的容器,我们决不应该使用它们,只需要对它们进行了解。 - -- Vector:和 ArrayList 类似,但它是线程安全的 - -- HashTable:和 HashMap 类似,但它是线程安全的 - -# 容器中的设计模式 - -## 1. 迭代器模式 - -从概览图可以看到,每个集合类都有一个 Iterator 对象,可以通过这个迭代器对象来遍历集合中的元素。 - -[Java 中的迭代器模式 ](https://github.com/CyC2018/InterviewNotes/blob/master/notes/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F.md#92-java-%E5%86%85%E7%BD%AE%E7%9A%84%E8%BF%AD%E4%BB%A3%E5%99%A8) - -## 2. 适配器模式 - -java.util.Arrays#asList() 可以把数组类型转换为 List 类型。 - -```java - List list = Arrays.asList(1, 2, 3); - int[] arr = {1, 2, 3}; - list = Arrays.asList(arr); -``` - -# 散列 - -使用 hasCode() 来返回散列值,使用的是对象的地址。 - -而 equals() 是用来判断两个对象是否相等的,相等的两个对象散列值一定要相同,但是散列值相同的两个对象不一定相等。 - -相等必须满足以下五个性质: - -1. 自反性 -2. 对称性 -3. 传递性 -4. 一致性(多次调用 x.equals(y),结果不变) -5. 对任何不是 null 的对象 x 调用 x.equals(nul) 结果都为 false - -# 源码分析 - -建议先阅读 [ 算法 - 查找 ](https://github.com/CyC2018/InterviewNotes/blob/master/notes/%E7%AE%97%E6%B3%95.md#%E7%AC%AC%E4%B8%89%E7%AB%A0-%E6%9F%A5%E6%89%BE) 部分,对集合类源码的理解有很大帮助。 - -源码下载:[OpenJDK 1.7](http://download.java.net/openjdk/jdk7) - -## 1. ArraList - -[ArraList.java](https://github.com/CyC2018/InterviewNotes/blob/master/notes/src/ArrayList.java) - -实现了 RandomAccess 接口,因此支持随机访问,这是理所当然的,因为 ArrayList 是基于数组实现的。 - -```java -public class ArrayList extends AbstractList -    implements List, RandomAccess, Cloneable, java.io.Serializable -``` - -基于数组实现,保存元素的数组使用 transient 修饰,这是因为该数组不一定所有位置都占满元素,因此也就没必要全部都进行序列化。需要重写 writeObject() 和 readObject()。 - -```java -private transient Object[] elementData; -``` - -数组的默认大小为 10 - -```java -public ArrayList(int initialCapacity) { -    super(); -    if (initialCapacity < 0) -        throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); -    this.elementData = new Object[initialCapacity]; -} - -public ArrayList() { -    this(10); -} -``` - -删除元素时调用 System.arraycopy() 对元素进行复制,因此删除操作成本很高,最好在创建时就指定大概的容量大小,减少复制操作的执行次数。 - -```java -public E remove(int index) { -    rangeCheck(index); - -    modCount++; -    E oldValue = elementData(index); - -    int numMoved = size - index - 1; -    if (numMoved > 0) -        System.arraycopy(elementData, index+1, elementData, index, numMoved); -    elementData[--size] = null; // Let gc do its work - -    return oldValue; -} -``` - -添加元素时使用 ensureCapacity() 方法来保证容量足够,如果不够时,需要进行扩容,使得新容量为旧容量的 1.5 倍。 - -modCount 用来记录 ArrayList 发生变化的次数,因为每次在进行 add() 和 addAll() 时都需要调用 ensureCapacity(),因此直接在 ensureCapacity() 中对 modCount 进行修改。 - -```java -public void ensureCapacity(int minCapacity) { -    if (minCapacity > 0) -        ensureCapacityInternal(minCapacity); -} - -private void ensureCapacityInternal(int minCapacity) { -    modCount++; -    // overflow-conscious code -    if (minCapacity - elementData.length > 0) -        grow(minCapacity); -} - -private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; - -private void grow(int minCapacity) { -    // overflow-conscious code -    int oldCapacity = elementData.length; -    int newCapacity = oldCapacity + (oldCapacity >> 1); -    if (newCapacity - minCapacity < 0) -        newCapacity = minCapacity; -    if (newCapacity - MAX_ARRAY_SIZE > 0) -        newCapacity = hugeCapacity(minCapacity); -    // minCapacity is usually close to size, so this is a win: -    elementData = Arrays.copyOf(elementData, newCapacity); -} - -private static int hugeCapacity(int minCapacity) { -    if (minCapacity < 0) // overflow -        throw new OutOfMemoryError(); -    return (minCapacity > MAX_ARRAY_SIZE) ? -        Integer.MAX_VALUE : -        MAX_ARRAY_SIZE; -} -``` - -在进行序列化或者迭代等操作时,需要比较操作前后 modCount 是否改变,如果改变了需要抛出 ConcurrentModificationException。 - -```java -private void writeObject(java.io.ObjectOutputStream s) -    throws java.io.IOException{ -    // Write out element count, and any hidden stuff -    int expectedModCount = modCount; -    s.defaultWriteObject(); - -    // Write out array length -    s.writeInt(elementData.length); - -    // Write out all elements in the proper order. -    for (int i=0; i()); 返回一个线程安全的 ArrayList,也可以使用 concurrent 并发包下的 CopyOnWriteArrayList 类; - -**和 LinkedList 的区别** - -1.  ArrayList 基于动态数组实现,LinkedList 基于双向循环链表实现; -2. ArrayList 支持随机访问,LinkedList 不支持; -3. LinkedList 在任意位置添加删除元素更快。 - -## 2. Vector 与 Stack - -[Vector.java](https://github.com/CyC2018/InterviewNotes/blob/master/notes/src/Vector.java) - -## 3. LinkedList - -[LinkedList.java](https://github.com/CyC2018/InterviewNotes/blob/master/notes/src/LinkedList.java) - -## 4. TreeMap - -[TreeMap.java](https://github.com/CyC2018/InterviewNotes/blob/master/notes/src/TreeMap.java) - -## 5. HashMap - -[HashMap.java](https://github.com/CyC2018/InterviewNotes/blob/master/notes/src/HashMap.java) - -使用拉链法来解决冲突。 - -默认容量 capacity 为 16,需要注意的是容量必须保证为 2 的次方。容量就是 Entry[] table 数组的长度,size 是数组的实际使用量。 - -threshold 规定了一个 size 的临界值,size 必须小于 threshold,如果大于等于,就必须进行扩容操作。 - -threshold = capacity * load_factor,其中 load_factor 为 table 数组能够使用的比例,load_factor 过大会导致聚簇的出现,从而影响查询和插入的效率,详见算法笔记。 - -```java -static final int DEFAULT_INITIAL_CAPACITY = 16; - -static final int MAXIMUM_CAPACITY = 1 << 30; - -static final float DEFAULT_LOAD_FACTOR = 0.75f; - -transient Entry[] table; - -transient int size; - -int threshold; - -final float loadFactor; - -transient int modCount; -``` - -从下面的添加元素代码中可以看出,当需要扩容时,令 capacity 为原来的两倍。 - -```java -void addEntry(int hash, K key, V value, int bucketIndex) { -    Entry e = table[bucketIndex]; -    table[bucketIndex] = new Entry<>(hash, key, value, e); -    if (size++ >= threshold) -        resize(2 * table.length); -} -``` - -Entry 用来表示一个键值对元素,其中的 next 指针在序列化时会使用。 - -```java -static class Entry implements Map.Entry { -    final K key; -    V value; -    Entry next; -    final int hash; -} -``` - -get() 操作需要分成两种情况,key 为 null 和 不为 null,从中可以看出 HashMap 允许插入 null 作为键。 - -```java -public V get(Object key) { -    if (key == null) -        return getForNullKey(); -    int hash = hash(key.hashCode()); -    for (Entry e = table[indexFor(hash, table.length)]; e != null; e = e.next) { -        Object k; -        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) -            return e.value; -    } -    return null; -} -``` - -put() 操作也需要根据 key 是否为 null 做不同的处理,需要注意的是如果本来没有 key 为 null 的键值对,新插入一个 key 为 null 的键值对时默认是放在数组的 0 位置,这是因为 null 不能计算 hash 值,也就无法知道应该放在哪个链表上。 - -```java -public V put(K key, V value) { -    if (key == null) -        return putForNullKey(value); -    int hash = hash(key.hashCode()); -    int i = indexFor(hash, table.length); -    for (Entry e = table[i]; e != null; e = e.next) { -        Object k; -        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { -            V oldValue = e.value; -            e.value = value; -            e.recordAccess(this); -            return oldValue; -        } -    } - -    modCount++; -    addEntry(hash, key, value, i); -    return null; -} -``` - -```java -private V putForNullKey(V value) { -    for (Entry e = table[0]; e != null; e = e.next) { -        if (e.key == null) { -            V oldValue = e.value; -            e.value = value; -            e.recordAccess(this); -            return oldValue; -        } -    } -    modCount++; -    addEntry(0, null, value, 0); -    return null; -} -``` - -## 6. LinkedHashMap - -[LinkedHashMap.java](https://github.com/CyC2018/InterviewNotes/blob/master/notes/src/HashMap.java) - -## 7. ConcurrentHashMap - -[ConcurrentHashMap.java](https://github.com/CyC2018/InterviewNotes/blob/master/notes/src/HashMap.java) - -[ 探索 ConcurrentHashMap 高并发性的实现机制 ](https://www.ibm.com/developerworks/cn/java/java-lo-concurrenthashmap/) - -# 参考资料 - -- Java 编程思想 \ No newline at end of file diff --git a/notes/笔记/Java 并发.md.txt b/notes/笔记/Java 并发.md.txt deleted file mode 100644 index 1aaa1254..00000000 --- a/notes/笔记/Java 并发.md.txt +++ /dev/null @@ -1,536 +0,0 @@ -[TOC] - -# 使用线程 - -有三种使用线程的方法: - -1. 实现 Runnable 接口; -2. 实现 Callable 接口; -3. 继承 Tread 类; - -实现 Runnable 和 Callable 接口的类只能当做一个可以在线程中运行的任务,不是真正意义上的线程,因此最后还需要 Thread 来调用。可以说任务是通过线程驱动从而执行的。 - -## 1. 实现 Runnable 接口 - -有一个 run() 方法需要实现 - -需要 Thread 调用 start() 来启动线程 - -```java -public class MyRunnable implements Runnable { -    public void run() { -        // ... -    } -    public static void main(String[] args) { -        MyRunnable instance = new MyRunnable(); -        Tread thread = new Thread(instance); -        thread.start(); -    } -} -``` - -## 2. 实现 Callable 接口 - -与 Runnable 相比,Callable 可以有返回值,返回值通过 FutureTask 进行封装。 - -```java -public  class  MyCallable  implements  Callable { -    public Integer call() { -        // ... -    } -    public  static  void  main(String[]  args) { -        MyCallable mc = new MyCallable(); -        FutureTask ft = new FutureTask<>(mc); -        Thread thread = new Thread(ft); -        thread.start(); -        System.out.println(ft.get()); -    } -} -``` - -## 3. 继承 Tread 类 - -同样也是需要实现 run() 方法,并且最后也是调用 start() 方法来启动线程。 - -```java -class MyThread extends Thread { -    public void run() { -        // ... -    } -    public  static  void  main(String[]  args) { -        MyThread mt = new MyThread(); -        mt.start(); -    } -} -``` - -## 4. 实现接口 vs 继承 Thread - -实现接口会更好一些,因为: - -1. Java 不支持多重继承,因此继承了 Thread 类就无法继承其它类,但是可以实现多个接口。 -2. 类可能只要求可执行即可,继承整个 Thread 类开销会过大。 - -# Executor - -Executor 管理多个异步任务的执行,而无需程序员显示地管理线程的生命周期。 - -主要有三种 Excutor: - -1. CachedTreadPool:一个任务创建一个线程; -2. FixedThreadPool:所有任务只能使用固定大小的线程; -3. SingleThreadExecutor:相当于大小为 1 的 FixedThreadPool。 - -```java -ExecutorService exec = Executors.newCachedThreadPool(); -for(int i = 0; i < 5; i++) { -    exec.execute(new MyRunnable()); -} -``` - -# 基础线程机制 - -## 1. sleep() - -**Thread.sleep(millisec)** 方法会休眠当前正在执行的线程,millisec 单位为毫秒。也可以使用 TimeUnit.TILLISECONDS.sleep(millisec)。 - -sleep() 可能会抛出 InterruptedException。因为异常不能跨线程传播回 main() 中,因此必须在本地进行处理。线程中抛出的其它异常也同样需要在本地进行处理。 - -```java -public void run() { -    try { -        // ... -        Thread.sleep(1000); -        // ... -    } catch(InterruptedException e) { -        System.err.println(e); -    } -} -``` - -## 2. yield() - -对静态方法 **Thread.yield()** 的调用声明了当前线程已经完成了生命周期中最重要的部分,可以切换给其它线程来执行。 - -```java -public void run() { -    // ... -    Thread.yield(); -} -``` - -## 3. join() - -在线程中调用另一个线程的 **join()** 方法,会将当前线程挂起,直到目标线程结束。 - -可以加一个超时参数。 - -## 4. deamon - -后台线程(**deamon**)是程序运行时在后台提供服务的线程,并不属于程序中不可或缺的部分。 - -当所有非后台线程结束时,程序也就终止,同时会杀死所有后台线程。 - -main() 属于非后台线程。 - -使用 setDaemon() 方法将一个线程设置为后台线程。 - -# 线程之间的协作 - -- **线程通信**:保证线程以一定的顺序执行; -- **线程同步**:保证线程对临界资源的互斥访问。 - -线程通信往往是基于线程同步的基础上完成的,因此很多线程通信问题也是线程同步问题。 - -## 1. 线程通信 - -**wait()、notify() 和 notifyAll()** 三者实现了线程之间的通信。 - -wait() 会在等待时将线程挂起,而不是忙等待,并且只有在 notify() 或者 notifyAll() 到达时才唤醒。 - -sleep() 和 yield() 并没有释放锁,但是 wait() 会释放锁。实际上,只有在同步控制方法或同步控制块里才能调用 wait() 、notify() 和 notifyAll()。 - -这几个方法属于基类的一部分,而不属于 Thread。 - -```java -private boolean flag = false; - -public synchronized void after() { -    while(flag == false) { -        wait(); -        // ... -    } -} - -public synchronized void before() { -    flag = true; -    notifyAll(); -} -``` - -**wait() 和 sleep() 的区别** - -1. wait() 是 Object 类的方法,而 sleep() 是 Thread 的静态方法; -2. wait() 会放弃锁,而 sleep() 不会。 - -## 2. 线程同步 - -给定一个进程内的所有线程,都共享同一存储空间,这样有好处又有坏处。这些线程就可以共享数据,非常有用。不过,在两个线程同时修改某一资源时,这也会造成一些问题。Java 提供了同步机制,以控制对共享资源的互斥访问。 - -### 2.1 synchronized - -**同步一个方法** - -使多个线程不能同时访问该方法。 - -```java -public synchronized void func(String name) { -    // ... -} -``` - -**同步一个代码块** - -```java -public void func(String name) { -    synchronized(this) { -        // ... -    } -} -``` - -### 2.2 Lock - -若要实现更细粒度的控制,我们可以使用锁(lock)。 - -```java -private Lock lock; -public int func(int value) { -    lock.lock(); -    // ... -    lock.unlock(); -} -``` - -### 2.3 BlockingQueue - -java.util.concurrent.BlockingQueue 接口有以下阻塞队列的实现: - -- **FIFO 队列**:LinkedBlockingQueue、ArrayListBlockingQueue(固定长度) -- **优先级队列**:PriorityBlockingQueue - -提供了阻塞的 take() 和 put() 方法:如果队列为空 take() 将一直阻塞到队列中有内容,如果队列为满  put() 将阻塞到队列有空闲位置。它们响应中断,当收到中断请求的时候会抛出 InterruptedException,从而提前结束阻塞状态。 - -**使用 BlockingQueue 实现生产者消费者问题** - -```java -// 生产者 -import java.util.concurrent.BlockingQueue; - -public class Producer implements Runnable { -    private BlockingQueue queue; - -    public Producer(BlockingQueue queue) { -        this.queue = queue; -    } - -    @Override -    public void run() { -        System.out.println(Thread.currentThread().getName() + " is making product..."); -        String product = "made by " + Thread.currentThread().getName(); -        try { -            queue.put(product); -        } catch (InterruptedException e) { -            e.printStackTrace(); -        } -    } -} -``` - -```java -// 消费者 -import java.util.concurrent.BlockingQueue; - -public class Consumer implements Runnable{ -    private BlockingQueue queue; - -    public Consumer(BlockingQueue queue) { -        this.queue = queue; -    } - -    @Override -    public void run() { -        try { -            String  product = queue.take(); -            System.out.println(Thread.currentThread().getName() + " is consuming product " + product + "..."); -        } catch (InterruptedException e) { -            e.printStackTrace(); -        } -    } -} -``` - -```java -// 客户端 -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; - -public class Client { -    public static void main(String[] args) { -        BlockingQueue queue = new LinkedBlockingQueue<>(5); -        for (int i = 0; i < 2; i++) { -            new Thread(new Consumer(queue), "Producer" + i).start(); -        } -        for (int i = 0; i < 5; i++) { -            // 只有两个 Product,因此只能消费两个,其它三个消费者被阻塞 -            new Thread(new Producer(queue), "Consumer" + i).start(); -        } -        for (int i = 2; i < 5; i++) { -            new Thread(new Consumer(queue), "Producer" + i).start(); -        } -    } -} -``` - -```html -// 运行结果 -Consumer0 is making product... -Producer0 is consuming product made by Consumer0... -Consumer1 is making product... -Producer1 is consuming product made by Consumer1... -Consumer2 is making product... -Consumer3 is making product... -Consumer4 is making product... -Producer2 is consuming product made by Consumer2... -Producer3 is consuming product made by Consumer3... -Producer4 is consuming product made by Consumer4... -``` - -# 线程状态 - -JDK 从 1.5 开始在 Thread 类中增添了 State 枚举,包含以下六种状态: - -1. **NEW**(新建) -2. **RUNNABLE**(当线程正在运行或者已经就绪正等待 CPU 时间片) -3. **BLOCKED**(阻塞,线程在等待获取对象同步锁) -4. **Waiting**(调用不带超时的 wait() 或 join()) -5. **TIMED_WAITING**(调用 sleep()、带超时的 wait() 或者 join()) -6. **TERMINATED**(死亡) - -![](index_files/19f2c9ef-6739-4a95-8e9d-aa3f7654e028.jpg) - -# 结束线程 - -## 1. 阻塞 - -一个线程进入阻塞状态可能有以下原因: - -1. 调用 Thread.sleep() 方法进入休眠状态; -2. 通过 wait() 使线程挂起,直到线程得到 notify() 或 notifyAll() 消息(或者 java.util.concurrent 类库中等价的 signal() 或 signalAll() 消息; -3. 等待某个 I/O 的完成; -4. 试图在某个对象上调用其同步控制方法,但是对象锁不可用,因为另一个线程已经获得了这个锁。 - -## 2. 中断 - -使用中断机制即可终止阻塞的线程。 - -使用 **interrupt()** 方法来中断某个线程,它会设置线程的中断状态。Object.wait(), Thread.join() 和 Thread.sleep() 三种方法在收到中断请求的时候会清除中断状态,并抛出 InterruptedException。 - -应当捕获这个 InterruptedException 异常,从而做一些清理资源的操作。 - -**不可中断的阻塞** - -不能中断 I/O 阻塞和 synchronized 锁阻塞。 - -**Executor 的中断操作** - -Executor 避免对 Thread 对象的直接操作,但是使用 interrupt() 方法必须持有 Thread 对象。Executor 使用 shutdownNow() 方法来中断所有它里面的所有线程,shutdownNow() 方法会发送 interrupt() 调用给所有线程。 - -如果只想中断一个线程,那么使用 Executor 的 submit() 而不是 executor() 来启动线程,就可以持有线程的上下文。submit() 将返回一个泛型 Futrue,可以在它之上调用 cancel(),如果将 true 传递给 cancel(),那么它将会发送 interrupt() 调用给特定的线程。 - -**检查中断** - -通过中断的方法来终止线程,需要线程进入阻塞状态才能终止。如果编写的 run() 方法循环条件为 true,但是该线程不发生阻塞,那么线程就永远无法终止。 - -interrupt() 方法会设置中断状态,可以通过 interrupted() 方法来检查中断状,从而判断一个线程是否已经被中断。 - -interrupted() 方法在检查完中断状态之后会清除中断状态,这样做是为了确保一次中断操作只会产生一次影响。 - -# 原子性 - -对于除 long 和 double 之外的基本类型变量的读写,可以看成是具有原子性的,以不可分割的步骤操作内存。 - -JVM 将 64 位变量(long 和 double)的读写当做两个分离的 32 位操作来执行,在两个操作之间可能会发生上下文切换,因此不具有原子性。可以使用 **volatile** 关键字来定义 long 和 double 变量,从而获得原子性。 - -**AtomicInteger、AtomicLong、AtomicReference** 等特殊的原子性变量类提供了下面形式的原子性条件更新语句,使得比较和更新这两个操作能够不可分割地执行。 - -```java -boolean compareAndSet(expectedValue, updateValue); -``` - -AtomicInteger 使用举例: - -```java -private AtomicInteger ai = new AtomicInteger(0); - -public int next() { -    return ai.addAndGet(2) -} -``` - -原子性具有很多复杂问题,应当尽量使用同步而不是原子性。 - -# volatile - -保证了内存可见性和禁止指令重排,没法保证原子性。 - -## 1. 内存可见性 - -普通共享变量被修改之后,什么时候被写入主存是不确定的。 - -volatile 关键字会保证每次修改共享变量之后该值会立即更新到内存中,并且在读取时会从内存中读取值。 - -synchronized 和 Lock 也能够保证内存可见性。它们能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。不过只有对共享变量的 set() 和 get() 方法都加上 synchronized 才能保证可见性,如果只有 set() 方法加了 synchronized,那么 get() 方法并不能保证会从内存中读取最新的数据。 - -## 2. 禁止指令重排 - -在 Java 内存模型中,允许编译器和处理器对指令进行重排序,重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。 - -volatile 关键字通过添加内存屏障的方式来进制指令重排,即重排序时不能把后面的指令放到内存屏障之前。 - -可以通过 synchronized 和 Lock 来保证有序性,它们保证每个时刻只有一个线程执行同步代码,相当于是让线程顺序执行同步代码,自然就保证了有序性。 - -# 可重入内置锁 - -每个Java对象都可以用做一个实现同步的锁,这些锁被称为内置锁或监视器锁。线程在进入同步代码块之前会自动获取锁,并且在退出同步代码块时会自动释放锁。获得内置锁的唯一途径就是进入由这个锁保护的同步代码块或方法。 - -当某个线程请求一个由其他线程持有的锁时,发出请求的线程就会阻塞。然而,由于内置锁是可重入的,因此如果某个线程试图获得一个已经由它自己持有的锁,那么这个请求就会成功。“重入”意味着获取锁的操作的粒度是“线程”,而不是调用。重入的一种实现方法是,为每个锁关联一个获取计数值和一个所有者线程。 - -重入进一步提升了加锁行为的封装性,因此简化了面向对象并发代码的开发。分析如下程序: - -```java -public class Father -{ -    public synchronized void doSomething(){ -        ...... -    } -} - -public class Child extends Father -{ -    public synchronized void doSomething(){ -        ...... -        super.doSomething(); -    } -} -``` - -子类覆写了父类的同步方法,然后调用父类中的方法,此时如果没有可重入的锁,那么这段代码件产生死锁。 - -由于Fither和Child中的doSomething()方法都是synchronized方法,因此每个doSomething()方法在执行前都会获取Child对象实例上的锁。如果内置锁不是可重入的,那么在调用super.doSomething()时将无法获得该Child对象上的互斥锁,因为这个锁已经被持有,从而线程会永远阻塞下去,一直在等待一个永远也无法获取的锁。重入则避免了这种死锁情况的发生。 - -同一个线程在调用本类中其他synchronized方法/块或父类中的synchronized方法/块时,都不会阻碍该线程地执行,因为互斥锁时可重入的。 - - -# Concurrent - -**Concurrent包里的其他东西:ArrayBlockingQueue、CountDownLatch等等。** - -CyclicBarrier 和 CountDownLatch 区别 - -这两个类非常类似,都在 java.util.concurrent 下,都可以用来表示代码运行到某个点上,二者的区别在于: - -*   CyclicBarrier 的某个线程运行到某个点上之后,该线程即停止运行,直到所有的线程都到达了这个点,所有线程才重新运行;CountDownLatch 则不是,某线程运行到某个点上之后,只是给某个数值 -1 而已,该线程继续运行 - -*   CyclicBarrier 只能唤起一个任务,CountDownLatch 可以唤起多个任务 - -*   CyclicBarrier 可重用,CountDownLatch 不可重用,计数值为 0 该 CountDownLatch 就不可再用了 - -# 免锁容器 - -免锁容器通用策略是:对容器的修改可以与读取操作同时发生,只要读取这只能看到完成修改地结果即可。修改实在容器数据结构的某个部分的一个单独的副本(又是是整个数据结构的副本)上执行的,并且这个副本在修改过程是不可视的。只有当修改完成后,被修改地结构才会自动地与主数据结构进行交换,之后读取者就可以看到这个修改版本了。 - -**CopyOnWriteArrayList** 和 **CopyOnWriteArraySet** 在写入时都会导致创建整个底层数组的副本,而 **ConcurrentHashMap** 和 **ConcurrentLinkedQueue** 只会创建部分副本。 - -1).Java.util 包中: -HashMap、 HashSet、 ArrayList 都不是线程安全的, Vector、 HashTable 是线程 -安全的。通过 Collections.synchronizedList/Set/Map/SortedSet/SortedMap 可以返回 -一个同步的对应集合 -通过对每一个公共方法进行同步实现线程安全。但是在多线程环境下对它们 -进行诸如“缺少即加入”这类符合操作时, 就会出现问题。【解决】: 客户端加锁, -要注意锁定的对象是对 List 等集合类,而不是客户端类 -【ConcurrentModificationException】 -使用 Vector 或者同步的 List,返回的 Iterator 迭代器都是 fail-fast 的, 这意味 -着如果在迭代过程中, 任何其他线程修改 List,都会失败, 抛出上异常,若想避 -免该异常则必须在迭代期间对容器加锁。 -2).Java.util.concurrent 包: -以下类在迭代期间都无需对容器加锁 -CopyOnWriteArrayList 、 CopyOnWriteArraySet 、 ConcurrentHashMap 、 -ConcurrentLinkedQueue 、 ConcurrentSkipListMap( 替 代 SortedMap) 、 -ConcurrentSkipListSet( 替代 SortedSet) -CopyOnWriteArrayList:底层维护一个 volatile 的基础数组, 某线程在对容器 -修改的时候先显示获取锁(只能有一个线程修改), 然后复制基础数组(保证了 -其他读线程不会出错), 最后更新原数组(因为是 volatile 的,所以对其他读线程 -立即可见)。 因为复制数组开销较大,所以适合元素少,修改操作少的情况。 - -## HashTable、HashMap、ConcurrentHashMap 的区别 - -1). 主要区别: -1). 直观使用上: HashTable 不接受 key 为 null, HashMap 接受 key 为 null -2). 哈希冲突的概率不同: 根据 Hash 值计算数组下标的算法不同, HashTable -直接使用对象的 hashCode, hashMap 做了重新计算, HashTable 的冲突几率比 -HashMap 高 -hashTable 默认的数组大小为 11, hashMap 默认数组大小为 16,他们的默认 -负载因子都是 0.75, HashTable 扩展为 2*size+1, HashMap 扩展为 2*size -3). 线程安全: HashTable 是线程安全的, HashMap 则不是线程安全的 , 但是 -仅仅是 Hashtable 仅仅是对每个方法进行了 synchronized 同步, 首先这样的效率 -会比较低;其次它本身的同步并不能保证程序在并发操作下的正确性(虽然每个 -方法都是同步的,但客户端调用可能在一个操作中调用多个方法,就不能保证操 -作原子性了),需要高层次的并发保护。 -2).ConcurrentHashMap 改进: - -并发效率问题: Hashtable 和 Collections.synchronizedMap 通过同步每个方 -法获得线程安全。 即当一个线程执行一个 Map 方法时,无论其他线程要对 Map -进行什么样操作,都不能执行,直到第一个线程结束才可以。对比来说, -ConcurrentHashMap 所使用的锁分段技术,首先将数据分成一段一段的存储,然 -后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他 -段的数据也能被其他线程访问。 从而可以有效提高并发效率。 - -迭代问题: ConcurrentHashMap 返回的 iterator 是弱一致性的,并不会抛出 -ConcurrentModifiedException。 弱一致性的迭代器可以容许并发修改,迭代器可以 -感应到迭代器在被创建后,对容器的修改。 -增加了常见的原子操作 API: 它的 API 中包含一些原子形式的“putIfAbsent()、 -相等便移除、 相等便替换”这些在 HashTable 中非原子操作 - -## ConcurrentHashMap 源码分析 - -[探索 ConcurrentHashMap 高并发性的实现机制](https://www.ibm.com/developerworks/cn/java/java-lo-concurrenthashmap/) - -# ThreadLocal - -重点:ThreadLocal的设计理念与作用,ThreadPool用法与优势 - -[ThreadLocal 源码深入分析 ](http://www.sczyh30.com/posts/Java/java-concurrent-threadlocal/) - -# 线程池 - -**线程池的底层实现和工作原理(建议写一个雏形简版源码实现)** - -[线程、多线程与线程池总结](https://www.jianshu.com/p/b8197dd2934c) - -# 多线程开发良好的实践 - -- 给线程命名; -- 最小化同步范围; -- 优先使用 volatile; -- 尽可能使用更高层次的并发工具而非 wait 和 notify() 来实现线程通信,如 BlockingQueue, Semeaphore; -- 多用并发容器,少用同步容器,并发容器壁同步容器的可扩展性更好。 -- 考虑使用线程池 -- 最低限度的使用同步和锁,缩小临界区。因此相对于同步方法,同步块会更好。 - -# 参考资料 - -- Java 编程思想 -- [Java 线程面试题 Top 50](http://www.importnew.com/12773.html) -- [Java 面试专题 - 多线程 & 并发编程 ](https://www.jianshu.com/p/e0c8d3dced8a) -- [可重入内置锁](https://github.com/francistao/LearningNotes/blob/master/Part2/JavaConcurrent/%E5%8F%AF%E9%87%8D%E5%85%A5%E5%86%85%E7%BD%AE%E9%94%81.md) \ No newline at end of file diff --git a/notes/笔记/Java 源码解析.md.txt b/notes/笔记/Java 源码解析.md.txt deleted file mode 100644 index 8b137891..00000000 --- a/notes/笔记/Java 源码解析.md.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/notes/笔记/Leetcode 题解.md.txt b/notes/笔记/Leetcode 题解.md.txt deleted file mode 100644 index 45346260..00000000 --- a/notes/笔记/Leetcode 题解.md.txt +++ /dev/null @@ -1,4844 +0,0 @@ -[TOC] - -# 算法思想 - -## 二分查找 - -二分查找思想简单,但是在实现时有一些需要注意的细节: - -1. 在计算 mid 时不能使用 mid = (l + h) / 2 这种方式,因为 l + h 可能会导致加法溢出,应该使用 mid = l + (h - l) / 2 。 - -2. 对 h 的赋值和循环条件有关,当循环条件为 l <= h 时,h = mid - 1;当循环条件为 l < h 时,h = mid。 -解释如下:在循环条件为 l <= h 时,如果 h = mid,会出现循环无法退出的情况,例如 l = 1,h = 1,此时 mid 也等于 1,如果此时继续执行 h = mid ,那么就会无限循环;在循环条件为 l < h ,如果 h = mid - 1,会错误跳过查找的数,例如对于数组 1,2,3 ,要查找 1 ,最开始 l = 0,h = 2,mid = 1,判断 key < arr[mid] 执行 h = mid - 1 = 0,此时循环退出,直接把查找的数跳过了。 - -3. l 的赋值一般都为 l = mid + 1。 - -```java -public int search(int key, int[] arr) { -    int l = 0, h = arr.length - 1; -    while (l <= h) { -        int mid = l + (h - l) / 2; -        if (key == arr[mid]) return mid; -        if (key < arr[mid]) h = mid - 1; -        else l = mid + 1; -    } -    return -1; -} -``` - -**求开方** - -[Leetcode : 69. Sqrt(x) (Easy)](https://leetcode.com/problems/sqrtx/description/) - -一个数 x 的开方 sqrt 一定在 0 ~ x 之间,并且满足 sqrt == x / sqrt 。可以利用二分查找在 0 ~ x 之间查找 sqrt。 - -```java -public int mySqrt(int x) { -    if(x <= 1) return x; -    int l = 1, h = x; -    while(l <= h){ -        int mid = l + (h - l) / 2; -        int sqrt = x / mid; -        if(sqrt == mid) return mid; -        else if(sqrt < mid) h = mid - 1; -        else l = mid + 1; -    } -    return h; -} -``` - -**摆硬币** - -[Leetcode : 441. Arranging Coins (Easy)](https://leetcode.com/problems/arranging-coins/description/) - -```html -n = 8 - -The coins can form the following rows: -¤ -¤ ¤ -¤ ¤ ¤ -¤ ¤ - -Because the 4th row is incomplete, we return 3. -``` - -题目描述:第 i 行摆 i 个,统计能够排列的行数。 - -返回 h 而不是 l,因为摆的硬币最后一行不能算进去。 - -```java -public int arrangeCoins(int n) { -    int l = 0, h = n; -    while(l <= h){ -        int m = l + (h - l) / 2; -        long x = m * (m + 1L) / 2; -        if(x == n) return m; -        else if(x < n) l = m + 1; -        else h = m - 1; -    } -    return h; -} -``` - -可以不用二分查找,更直观的解法如下: - -```java -public int arrangeCoins(int n) { -    int level = 1; -    while (n > 0) { -        n -= level; -        level++; -    } -    return n == 0 ? level - 1 : level - 2; -} -``` - -**有序数组的 Single Element** - -[Leetcode : 540. Single Element in a Sorted Array (Medium)](https://leetcode.com/problems/single-element-in-a-sorted-array/description/) - -题目描述:一个有序数组只有一个数不出现两次,找出这个数。 - -```java -public int singleNonDuplicate(int[] nums) { -    int l = 0, h = nums.length - 1; -    while(l < h) { -        int m = l + (h - l) / 2; -        if(m % 2 == 1) m--; // 保证 l/h/m 都在偶数位,使得查找区间大小一直都是 奇数 -        if(nums[m] == nums[m + 1]) l = m + 2; -        else h = m; -    } -    return nums[l]; -} -``` - -## 贪心思想 - -贪心思想保证每次操作都是局部最优的,并且最后得到的结果是全局最优的。 - -**分配饼干** - -[Leetcode : 455. Assign Cookies (Easy)](https://leetcode.com/problems/assign-cookies/description/) - -题目描述:每个孩子都有一个满足度,每个饼干都有一个大小,只有饼干的大小大于一个孩子的满足度,该孩子才会获得满足。求解最多可以获得满足的孩子数量。 - -因为最小的孩子最容易得到满足,因此先满足最小孩子。给一个孩子的饼干应当尽量小又能满足该孩子,这样大饼干就能拿来给满足度比较大的孩子。 - -证明:假设在某次选择中,贪心策略选择给第 i 个孩子分配第 m 个饼干,并且第 i 个孩子满足度最小,第 m 个饼干为可以满足第 i 个孩子的最小饼干,利用贪心策略最终可以满足 k 个孩子。假设最优策略在这次选择中给 i 个孩子分配第 n 个饼干,并且这个饼干大于第 m 个饼干,那么最优策略最终需要满足大于 k 个孩子。我们发现使用第 m 个饼干去替代第 n 个饼干完全不影响后续的结果,因此贪心策略就是最优策略,因此贪心策略是最优的。 - -```java -public int findContentChildren(int[] g, int[] s) { -    Arrays.sort(g); -    Arrays.sort(s); -    int i = 0, j = 0; -    while(i < g.length && j < s.length){ -        if(g[i] <= s[j]) i++; -        j++; -    } -    return i; -} -``` - -**投飞镖刺气球** - -[Leetcode : 452. Minimum Number of Arrows to Burst Balloons (Medium)](https://leetcode.com/problems/minimum-number-of-arrows-to-burst-balloons/description/) - -``` -Input: -[[10,16], [2,8], [1,6], [7,12]] - -Output: -2 -``` - -题目描述:气球在一个水平数轴上摆放,可以重叠,飞镖垂直射向坐标轴,使得路径上的气球都会刺破,求解最小的投飞镖次数。 - -```java -public int findMinArrowShots(int[][] points) { -    if(points.length == 0) return 0; -    Arrays.sort(points,(a,b) -> (a[1] - b[1])); -    int curPos = points[0][1]; -    int ret = 1; -    for (int i = 1; i < points.length; i++) { -        if(points[i][0] <= curPos) { -            continue; -        } -        curPos = points[i][1]; -        ret++; -    } -    return ret; - } -``` - -**股票的最大收益** - -[Leetcode : 122. Best Time to Buy and Sell Stock II (Easy)](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-ii/description/) - -先买入股票,然后出售股票,从而获得差价带来的收益。 - -对于一个交易 [a, b, c, d],如果有 a <= b <= c <= d ,那么最大收益为 d - a = (d - c) + (c - b) + (b - a) 。当访问到一个 prices[i] 且  prices[i] - prices[i-1] ,那么就把 prices[i] - prices[i-1] 加到收益中,从而在局部最优的情况下也保证全局最优。 - -```java -public int maxProfit(int[] prices) { -    int profit = 0; -    for(int i = 1; i < prices.length; i++){ -        if(prices[i] > prices[i-1]) profit += (prices[i] - prices[i-1]); -    } -    return profit; -} -``` - -**种植花朵** - -[Leetcode : 605. Can Place Flowers (Easy)](https://leetcode.com/problems/can-place-flowers/description/) - -```html -Input: flowerbed = [1,0,0,0,1], n = 1 -Output: True -``` - -题目描述:花朵之间至少需要一个单位的间隔。 - -```java -public boolean canPlaceFlowers(int[] flowerbed, int n) { -    int cnt = 0; -    for(int i = 0; i < flowerbed.length; i++){ -        if(flowerbed[i] == 1) continue; -        int pre = i == 0 ? 0 : flowerbed[i - 1]; -        int next = i == flowerbed.length - 1 ? 0 : flowerbed[i + 1]; -        if(pre == 0 && next == 0) { -            cnt++; -            flowerbed[i] = 1; -        } -    } -    return cnt >= n; -} -``` - -**修改一个数成为非递减数组** - -[Leetcode : 665. Non-decreasing Array (Easy)](https://leetcode.com/problems/non-decreasing-array/description/) - -题目描述:判断一个数组能不能只修改一个数就成为非递减数组。 - -在 nums[i] < nums[i - 1] 的情况下,会优先考虑令 nums[i - 1] = nums[i],因为如果修改 nums[i] = nums[i - 1] 的话,那么 nums[i] 这个数会变大,那么就有可能比 nums[i + 1] 大,我们要尽量使 nums[i] 更小。 - -但是在 nums[i] < nums[i - 2] 的情况下,只修改 nums[i - 1] 不能令数组成为非递减,只能通过修改 nums[i] = nums[i - 1] 才行。 - -```java -public boolean checkPossibility(int[] nums) { -    int cnt = 0; -    for(int i = 1; i < nums.length; i++){ -        if(nums[i] < nums[i - 1]){ -            cnt++; -            if(i - 2 >= 0 && nums[i - 2] > nums[i]) nums[i] = nums[i-1]; -            else nums[i - 1] = nums[i]; -        } -    } -    return cnt <= 1; -} -``` - -**判断是否为子串** - -[Leetcode : 392. Is Subsequence (Medium)](https://leetcode.com/problems/is-subsequence/description/) - -```html -s = "abc", t = "ahbgdc" -Return true. -``` - -```java -public boolean isSubsequence(String s, String t) { -    for (int i = 0, pos = 0; i < s.length(); i++, pos++) { -        pos = t.indexOf(s.charAt(i), pos); -        if(pos == -1) return false; -    } -    return true; -} -``` - -**分隔字符串使同种字符出现在一起** - -[Leetcode : 763. Partition Labels (Medium)](https://leetcode.com/problems/partition-labels/description/) - -```java -public List partitionLabels(String S) { -    List ret = new ArrayList<>(); -    int[] lastIdxs = new int[26]; -    for(int i = 0; i < S.length(); i++) lastIdxs[S.charAt(i) - 'a'] = i; -    int startIdx = 0; -    while(startIdx < S.length()) { -        int endIdx = startIdx; -        for(int i = startIdx; i < S.length() && i <= endIdx; i++) { -            int lastIdx = lastIdxs[S.charAt(i) - 'a']; -            if(lastIdx == i) continue; -            if(lastIdx > endIdx) endIdx = lastIdx; -        } -        ret.add(endIdx - startIdx + 1); -        startIdx = endIdx + 1; -    } -    return ret; -} -``` - -**根据身高和序号重组队列** - -```html -Input: -[[7,0], [4,4], [7,1], [5,0], [6,1], [5,2]] - -Output: -[[5,0], [7,0], [5,2], [6,1], [4,4], [7,1]] -``` - -一个学生用两个分量 (h, k) 描述,h 表示身高,k 表示排在前面的有 k 个学生的身高比他高或者和他一样高。 - -先排序:身高降序、k 值升序,然后按排好序的顺序插入队列的第 k 个位置中。 - -```java -public int[][] reconstructQueue(int[][] people) { -    if(people == null || people.length == 0 || people[0].length == 0) return new int[0][0]; -     -    Arrays.sort(people, new Comparator() { -       public int compare(int[] a, int[] b) { -           if(a[0] == b[0]) return a[1] - b[1]; -           return b[0] - a[0]; -       }  -    }); -     -    int n = people.length; -    List tmp = new ArrayList<>(); -    for(int i = 0; i < n; i++) { -        tmp.add(people[i][1], new int[]{people[i][0], people[i][1]}); -    } -     -    int[][] ret = new int[n][2]; -    for(int i = 0; i < n; i++) { -        ret[i][0] = tmp.get(i)[0]; -        ret[i][1] = tmp.get(i)[1]; -    } -    return ret; -} -``` - - -## 双指针 - -双指针主要用于遍历数组,两个指针指向不同的元素,从而协同完成任务。 - -**从一个已经排序的数组中查找出两个数,使它们的和为 0** - -[Leetcode :167. Two Sum II - Input array is sorted (Easy)](https://leetcode.com/problems/two-sum-ii-input-array-is-sorted/description/) - -使用双指针,一个指针指向元素较小的值,一个指针指向元素较大的值。指向较小元素的指针从头向尾遍历,指向较大元素的指针从尾向头遍历。 - -如果两个指针指向元素的和 sum == target,那么得到要求的结果;如果 sum > target,移动较大的元素,使 sum 变小一些;如果 sum < target,移动较小的元素,使 sum 变大一些。 - -```java -public int[] twoSum(int[] numbers, int target) { -    int i = 0, j = numbers.length - 1; -    while (i < j) { -        int sum = numbers[i] + numbers[j]; -        if (sum == target) return new int[]{i + 1, j + 1}; -        else if (sum < target) i++; -        else j--; -    } -    return null; -} -``` - -此问题可扩展成三个元素和为 0 的问题:[程序员代码面试指南 P351](#) - -**数组中累加和为给定值的最长子数组长度** - -[程序员代码面试指南 P354/P355/P358](#) - -**在行和列都排好序的矩阵中查找元素** - -[程序员代码面试指南 P347](#) - -**反转字符串中的元音字符** - -[Leetcode : 345. Reverse Vowels of a String (Easy)](https://leetcode.com/problems/reverse-vowels-of-a-string/description/) - -使用双指针,指向待反转的两个元音字符,一个指针从头向尾遍历,一个指针从尾到头遍历。 - -```java -private HashSet vowels = new HashSet<>(Arrays.asList('a','e','i','o','u','A','E','I','O','U')); - -public String reverseVowels(String s) { -    if(s.length() == 0) return s; -    int i = 0, j = s.length() - 1; -    char[] result = new char[s.length()]; -    while(i <= j){ -        char ci = s.charAt(i); -        char cj = s.charAt(j); -        if(!vowels.contains(ci)){ -            result[i] = ci; -            i++; -        } else if(!vowels.contains(cj)){ -            result[j] = cj; -            j--; -        } else{ -            result[i] = cj; -            result[j] = ci; -            i++; -            j--; -        } -    } -    return new String(result); -} -``` - -**两数平方和** - -[Leetcode : 633. Sum of Square Numbers (Easy)](https://leetcode.com/problems/sum-of-square-numbers/description/) - -判断一个数是否为两个数的平方和,例如 5 = 12 + 22。 - -```java -public boolean judgeSquareSum(int c) { -    int left = 0, right = (int) Math.sqrt(c); -    while(left <= right){ -        int powSum = left * left + right * right; -        if(powSum == c) return true; -        else if(powSum > c) right--; -        else left++; -    } -    return false; -} -``` - -**字符串回文数(可删除一个字符)** - -[Leetcode : 680. Valid Palindrome II (Easy)](https://leetcode.com/problems/valid-palindrome-ii/description/) - -```java -public boolean validPalindrome(String s) { -    int i = 0, j = s.length() -1; -    while(i < j){ -        if(s.charAt(i) != s.charAt(j)){ -            return isPalindrome(s, i, j - 1) || isPalindrome(s, i + 1, j); -        } -        i++; -        j--; -    } -    return true; -} - -private boolean isPalindrome(String s, int l, int r){ -    while(l < r){ -        if(s.charAt(l) != s.charAt(r)) -            return false; -        l++; -        r--; -    } -    return true; -} -``` - -**归并两个有序数组** - -[Leetcode : 88. Merge Sorted Array (Easy)](https://leetcode.com/problems/merge-sorted-array/description/) - -把归并结果存到第一个数组上。 - -```java -public void merge(int[] nums1, int m, int[] nums2, int n) { -    int i = m - 1, j = n - 1; // 需要从尾开始遍历,否则在 nums1 上归并得到的值会覆盖还未进行归并比较的值 -    int idx = m + n - 1; -    while(i >= 0 || j >= 0){ -        if(i < 0) nums1[idx] = nums2[j--]; -        else if(j < 0) nums1[idx] = nums1[i--]; -        else if(nums1[i] > nums2[j]) nums1[idx] = nums1[i--]; -        else nums1[idx] = nums2[j--]; -        idx--; -    } -} -``` - -**判断链表是否存在环** - -[Leetcode : 141. Linked List Cycle (Easy)](https://leetcode.com/problems/linked-list-cycle/description/) - -使用双指针,一个指针每次移动一个节点,一个指针每次移动两个节点,如果存在环,那么这两个指针一定会相遇。 - -```java -public boolean hasCycle(ListNode head) { -    if(head == null) return false; -    ListNode l1 = head, l2 = head.next; -    while(l1 != null && l2 != null){ -        if(l1 == l2) return true; -        l1 = l1.next; -        if(l2.next == null) break; -        l2 = l2.next.next; -    } -    return false; -} -``` - -**最长子序列** - -[Leetcode : 524. Longest Word in Dictionary through Deleting (Medium)](https://leetcode.com/problems/longest-word-in-dictionary-through-deleting/description/) - -``` -Input: -s = "abpcplea", d = ["ale","apple","monkey","plea"] - -Output: -"apple" -``` - -```java -public String findLongestWord(String s, List d) { -    String ret = ""; -    for (String str : d) { -        for (int i = 0, j = 0; i < s.length() && j < str.length(); i++) { -            if (s.charAt(i) == str.charAt(j)) j++; -            if (j == str.length()) { -                if (ret.length() < str.length() -                        || (ret.length() == str.length() && ret.compareTo(str) > 0)) { -                    ret = str; -                } -            } -        } -    } -    return ret; -} -``` - -## 排序 - -### 快速选择 - -一般用于求解 **Kth Element** 问题,可以在 O(n) 时间复杂度,O(1) 空间复杂度完成求解工作。 - -与快速排序一样,快速选择一般需要先打乱数组,否则最坏情况下时间复杂度为 O(n2)。 - -### 堆排序 - -堆排序用于求解 **TopK Elements** 问题,通过维护一个大小为 K 的堆,堆中的元素就是 TopK Elements。当然它也可以用于求解 Kth Element 问题,因为最后出堆的那个元素就是 Kth Element。快速选择也可以求解 TopK Elements 问题,因为找到 Kth Element 之后,再遍历一次数组,所有小于等于  Kth Element 的元素都是 TopK Elements。可以看到,快速选择和堆排序都可以求解 Kth Element 和 TopK Elements 问题,只是有可能需要间接求解。 - -**Kth Element** - -[Leetocde : 215. Kth Largest Element in an Array (Medium)](https://leetcode.com/problems/kth-largest-element-in-an-array/description/) - -解题参考:[Solution explained](https://leetcode.com/problems/kth-largest-element-in-an-array/discuss/60294/Solution-explained) - -- 时间复杂度 O(nlgn),空间复杂度 O(1) 解法:排序 -- 时间复杂度 O(nlgk),空间复杂度 O(k) 解法:堆排序 -- 时间复杂度 O(n),空间复杂度 O(1) 解法:QuickSelect - -**ToK Elements** - -[程序员代码面试指南 P336](#) - -### 桶排序 - - -**找出出现频率最多的 k 个数** - -[Leetcode : 347. Top K Frequent Elements (Medium)](https://leetcode.com/problems/top-k-frequent-elements/description/) - -桶排序 - -```java -public List topKFrequent(int[] nums, int k) { -    List ret = new ArrayList<>(); -    Map map = new HashMap<>(); -    for(int num : nums) { -        map.put(num, map.getOrDefault(num, 0) + 1); -    } -    List[] bucket = new List[nums.length + 1]; -    for(int key : map.keySet()) { -        int frequency = map.get(key); -        if(bucket[frequency] == null) { -            bucket[frequency] = new ArrayList<>(); -        } -        bucket[frequency].add(key); -    } -     -    for(int i = bucket.length - 1; i >= 0 && ret.size() < k; i--) { -        if(bucket[i] != null) { -            ret.addAll(bucket[i]); -        } -    } -    return ret; -} -``` - -## 搜索 - -深度优先搜索和广度优先搜索广泛运用于树和图中,但是它们的应用远远不止如此。 - -### BFS - -![](index_files/4ff355cf-9a7f-4468-af43-e5b02038facc.jpg) - -广度优先搜索的搜索过程有点像一层一层地进行遍历:从节点 0 出发,遍历到 6、2、1 和 5 这四个新节点。 - -继续从 6 开始遍历,得到节点 4 ;从 2 开始遍历,没有下一个节点;从 1 开始遍历,没有下一个节点;从 5 开始遍历,得到 3 和 4 节点。这一轮总共得到两个新节点:4 和 3 。 - -反复从新节点出发进行上述的遍历操作。 - -可以看到,每一轮遍历的节点都与根节点路径长度相同。设 di 表示第 i 个节点与根节点的路径长度,推导出一个结论:对于先遍历的节点 i 与后遍历的节点 j,有 di<=dj。利用这个结论,可以求解最短路径 **最优解** 问题:第一次遍历到目的节点,其所经过的路径为最短路径,如果继续遍历,之后再遍历到目的节点,所经过的路径就不是最短路径。 - -在程序实现 BFS 时需要考虑以下问题: - -- 队列:用来存储每一轮遍历的节点 -- 标记:对于遍历过得节点,应该将它标记,防止重复遍历; - - -**计算在网格中从原点到特定点的最短路径长度** - -```html -[[1,1,0,1], -[1,0,1,0], -[1,1,1,1], -[1,0,1,1]] -``` - -```java -public int minPathLength(int[][] grids, int tr, int tc) { -    int[][] next = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}}; -    int m = grids.length, n = grids[0].length; -    Queue queue = new LinkedList<>(); -    queue.add(new Position(0, 0, 1)); -    while (!queue.isEmpty()) { -        Position pos = queue.poll(); -        for (int i = 0; i < 4; i++) { -            Position nextPos = new Position(pos.r + next[i][0], pos.c + next[i][1], pos.length + 1); -            if (nextPos.r < 0 || nextPos.r >= m || nextPos.c < 0 || nextPos.c >= n) continue; -            if (grids[nextPos.r][nextPos.c] != 1) continue; -            grids[nextPos.r][nextPos.c] = 0; -            if (nextPos.r == tr && nextPos.c == tc) return nextPos.length; -            queue.add(nextPos); -        } -    } -    return -1; -} - -private class Position { -    int r, c, length; -    public Position(int r, int c, int length) { -        this.r = r; -        this.c = c; -        this.length = length; -    } -} -``` - - -### DFS - -![](index_files/f7f7e3e5-7dd4-4173-9999-576b9e2ac0a2.png) - -广度优先搜索一层一层遍历,每一层遍历到的所有新节点,要用队列先存储起来以备下一层遍历的时候再遍历;而深度优先搜索在遍历到一个新节点时立马对新节点进行遍历:从节点 0 出发开始遍历,得到到新节点 6 时,立马对新节点 6 进行遍历,得到新节点 4;如此反复以这种方式遍历新节点,直到没有新节点了,此时返回。返回到根节点 0 的情况是,继续对根节点 0 进行遍历,得到新节点 2,然后继续以上步骤。 - -从一个节点出发,使用 DFS 对一个图进行遍历时,能够遍历到的节点都是从初始节点可达的,DFS 常用来求解这种 **可达性** 问题。 - -在程序实现 DFS 时需要考虑以下问题: - -- 栈:用栈来保存当前节点信息,当遍历新节点返回时能够继续遍历当前节点。也可以使用递归栈。 -- 标记:和 BFS 一样同样需要对已经遍历过得节点进行标记。 - - -**查找最大的连通面积** - -[Leetcode : 695. Max Area of Island (Easy)](https://leetcode.com/problems/max-area-of-island/description/) - -```html -[[0,0,1,0,0,0,0,1,0,0,0,0,0], -[0,0,0,0,0,0,0,1,1,1,0,0,0], -[0,1,1,0,1,0,0,0,0,0,0,0,0], -[0,1,0,0,1,1,0,0,1,0,1,0,0], -[0,1,0,0,1,1,0,0,1,1,1,0,0], -[0,0,0,0,0,0,0,0,0,0,1,0,0], -[0,0,0,0,0,0,0,1,1,1,0,0,0], -[0,0,0,0,0,0,0,1,1,0,0,0,0]] -``` - -```java -public int maxAreaOfIsland(int[][] grid) { -    int m = grid.length, n = grid[0].length; -    int max = 0; -    for(int i = 0; i < m; i++){ -        for(int j = 0; j < n; j++){ -            if(grid[i][j] == 1) max = Math.max(max, dfs(grid, i, j)); -        } -    } -    return max; -} - -private int dfs(int[][] grid, int i, int j){ -    int m = grid.length, n = grid[0].length; -    if(i < 0 || i >= m || j < 0 || j >= n) return 0; -    if(grid[i][j] == 0) return 0; -    grid[i][j] = 0; -    return dfs(grid, i + 1, j) + dfs(grid, i - 1, j) + dfs(grid, i, j + 1) + dfs(grid, i, j - 1) + 1; -} -``` - -**图的连通分量** - -[Leetcode : 547. Friend Circles (Medium)](https://leetcode.com/problems/friend-circles/description/) - -```html -Input: -[[1,1,0], - [1,1,0], - [0,0,1]] -Output: 2 -Explanation:The 0th and 1st students are direct friends, so they are in a friend circle. -The 2nd student himself is in a friend circle. So return 2. -``` - -```java -public int findCircleNum(int[][] M) { -    int n = M.length; -    int ret = 0; -    boolean[] hasFind = new boolean[n]; -    for(int i = 0; i < n; i++) { -        if(!hasFind[i]) { -            dfs(M, i, hasFind); -            ret++; -        } - -    } -    return ret; -} - -private void dfs(int[][] M, int i, boolean[] hasFind) { -    hasFind[i] = true; -    int n = M.length; -    for(int k = 0; k < n; k++) { -        if(M[i][k] == 1 && !hasFind[k]) { -            dfs(M, k, hasFind); -        } -    } -} -``` - -**矩阵中的连通区域数量** - -[Leetcode : 200. Number of Islands (Medium)](https://leetcode.com/problems/number-of-islands/description/) - -```html -11110 -11010 -11000 -00000 -Answer: 1 -``` - -```java -private int m, n; -private int[][] direction = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}}; - -public int numIslands(char[][] grid) { -    if (grid == null || grid.length == 0) return 0; -    m = grid.length; -    n = grid[0].length; -    int ret = 0; -    for (int i = 0; i < m; i++) { -        for (int j = 0; j < n; j++) { -            if (grid[i][j] == '1') { -                dfs(grid, i, j); -                ret++; -            } -        } -    } -    return ret; -} - -private void dfs(char[][] grid, int i, int j) { -    if (i < 0 || i >= m || j < 0 || j >= n || grid[i][j] == '0') return; -    grid[i][j] = '0'; -    for (int k = 0; k < direction.length; k++) { -        dfs(grid, i + direction[k][0], j + direction[k][1]); -    } -} -``` - -**输出二叉树中所有从根到叶子的路径** - -[Leetcode : 257. Binary Tree Paths (Easy)](https://leetcode.com/problems/binary-tree-paths/description/) - -```html -  1 -/  \ -2    3 -\ -  5 -``` -```html -["1->2->5", "1->3"] -``` - -```java -public List binaryTreePaths(TreeNode root) { -    List ret = new ArrayList(); -    if(root == null) return ret; -    dfs(root, "", ret); -    return ret; -} - -private void dfs(TreeNode root, String prefix, List ret){ -    if(root == null) return; -    if(root.left == null && root.right == null){ -        ret.add(prefix + root.val); -        return; -    } -    prefix += (root.val + "->"); -    dfs(root.left, prefix, ret); -    dfs(root.right, prefix, ret); -} -``` - -**填充封闭区域** - -[Leetcode : 130. Surrounded Regions (Medium)](https://leetcode.com/problems/surrounded-regions/description/) - -```html -For example, -X X X X -X O O X -X X O X -X O X X - -After running your function, the board should be: -X X X X -X X X X -X X X X -X O X X -``` - -题目描述:使得被 'X' 的 'O' 转换为 'X'。 - -可以选择先填充最外侧,剩下的就是里侧了。 - -```java -private int[][] direction = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}}; -private int m, n; - -public void solve(char[][] board) { -    if (board == null || board.length == 0) return; -    m = board.length; -    n = board[0].length; -    for (int i = 0; i < m; i++) { -        dfs(board, i, 0); -        dfs(board, i, n - 1); -    } -    for (int i = 0; i < n; i++) { -        dfs(board, 0, i); -        dfs(board, m - 1, i); -    } -    for (int i = 0; i < m; i++) { -        for (int j = 0; j < n; j++) { -            if (board[i][j] == 'T') board[i][j] = 'O'; -            else if (board[i][j] == 'O') board[i][j] = 'X'; -        } -    } -} - -private void dfs(char[][] board, int r, int c) { -    if (r < 0 || r >= m || c < 0 || c >= n || board[r][c] != 'O') return; -    board[r][c] = 'T'; -    for (int i = 0; i < direction.length; i++) { -        dfs(board, r + direction[i][0], c + direction[i][1]); -    } -} -``` - -**从两个方向都能到达的区域** - -[Leetcode : 417. Pacific Atlantic Water Flow (Medium)](https://leetcode.com/problems/pacific-atlantic-water-flow/description/) - -```html -Given the following 5x5 matrix: - -  Pacific ~   ~   ~   ~   ~  -       ~  1   2   2   3  (5) * -       ~  3   2   3  (4) (4) * -       ~  2   4  (5)  3   1  * -       ~ (6) (7)  1   4   5  * -       ~ (5)  1   1   2   4  * -          *   *   *   *   * Atlantic - -Return: -[[0, 4], [1, 3], [1, 4], [2, 2], [3, 0], [3, 1], [4, 0]] (positions with parentheses in above matrix). -``` - -题目描述:左边和上边是太平洋,右边和下边是大西洋,内部的数字代表海拔,海拔高的地方的水能够流到低的地方,求解水能够流到太平洋和大西洋的所有地方。 - -```java -private int m, n; -private int[][] matrix; -private int[][] direction = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}}; - -public List pacificAtlantic(int[][] matrix) { -    List ret = new ArrayList<>(); -    if (matrix == null || matrix.length == 0) return ret; -    this.m = matrix.length; -    this.n = matrix[0].length; -    this.matrix = matrix; -    boolean[][] canReachP = new boolean[m][n]; -    boolean[][] canReachA = new boolean[m][n]; -    for (int i = 0; i < m; i++) { -        dfs(i, 0, canReachP); -        dfs(i, n - 1, canReachA); -    } -    for (int i = 0; i < n; i++) { -        dfs(0, i, canReachP); -        dfs(m - 1, i, canReachA); -    } -    for (int i = 0; i < m; i++) { -        for (int j = 0; j < n; j++) { -            if (canReachP[i][j] && canReachA[i][j]) { -                ret.add(new int[]{i, j}); -            } -        } -    } -    return ret; -} - -private void dfs(int r, int c, boolean[][] canReach) { -    if(canReach[r][c]) return; -    canReach[r][c] = true; -    for (int i = 0; i < direction.length; i++) { -        int nextR = direction[i][0] + r; -        int nextC = direction[i][1] + c; -        if (nextR < 0 || nextR >= m || nextC < 0 || nextC >= n -                || matrix[r][c] > matrix[nextR][nextC]) continue; -        dfs(nextR, nextC, canReach); -    } -} -``` - -**N 皇后** - -[Leetcode : 51. N-Queens (Hard)](https://leetcode.com/problems/n-queens/description/) - -![](index_files/1f080e53-4758-406c-bb5f-dbedf89b63ce.jpg) - -题目描述:在 n\*n 的矩阵中摆放 n 个皇后,并且每个皇后不能在同一行,同一列,同一对角线上,要求解所有的 n 皇后解。 - -一行一行地摆放,在确定一行中的那个皇后应该摆在哪一列时,需要用三个标记数组来确定某一列是否合法,这三个标记数组分别为:列标记数组、45 度对角线标记数组和 135 度对角线标记数组。 - -45 度对角线标记数组的维度为 2\*n - 1,通过下图可以明确 (r,c) 的位置所在的数组下标为 r + c。 - -![](index_files/85583359-1b45-45f2-9811-4f7bb9a64db7.jpg) - -135 度对角线标记数组的维度也是 2\*n - 1,(r,c) 的位置所在的数组下标为 n - 1 - (r - c)。 - -![](index_files/9e80f75a-b12b-4344-80c8-1f9ccc2d5246.jpg) - -```java -private List> ret; -private char[][] nQueens; -private boolean[] colUsed; -private boolean[] diagonals45Used; -private boolean[] diagonals135Used; -private int n; - -public List> solveNQueens(int n) { -    ret = new ArrayList<>(); -    nQueens = new char[n][n]; -    Arrays.fill(nQueens, '.'); -    colUsed = new boolean[n]; -    diagonals45Used = new boolean[2 * n - 1]; -    diagonals135Used = new boolean[2 * n - 1]; -    this.n = n; -    backstracking(0); -    return ret; -} - -private void backstracking(int row) { -    if (row == n) { -        List list = new ArrayList<>(); -        for (char[] chars : nQueens) { -            list.add(new String(chars)); -        } -        ret.add(list); -        return; -    } - -    for (int col = 0; col < n; col++) { -        int diagonals45Idx = row + col; -        int diagonals135Idx = n - 1 - (row - col); -        if (colUsed[col] || diagonals45Used[diagonals45Idx] || diagonals135Used[diagonals135Idx]) { -            continue; -        } -        nQueens[row][col] = 'Q'; -        colUsed[col] = diagonals45Used[diagonals45Idx] = diagonals135Used[diagonals135Idx] = true; -        backstracking(row + 1); -        colUsed[col] = diagonals45Used[diagonals45Idx] = diagonals135Used[diagonals135Idx] = false; -        nQueens[row][col] = '.'; -    } -} -``` - -### Backtracking - -回溯是 DFS 的一种,它不是用在遍历图的节点上,而是用于求解 **排列组合** 问题,例如有 { 'a','b','c' } 三个字符,求解所有由这三个字符排列得到的字符串。 - -在程序实现时,回溯需要注意对元素进行标记的问题。使用递归实现的回溯,在访问一个新元素进入新的递归调用,此时需要将新元素标记为已经访问,这样才能在继续递归调用时不用重复访问该元素;但是在递归返回时,需要将该元素标记为未访问,因为只需要保证在一个递归链中不同时访问一个元素,而在不同的递归链是可以访问已经访问过但是不在当前递归链中的元素。 - -**数字键盘组合** - -[Leetcode : 17. Letter Combinations of a Phone Number (Medium)](https://leetcode.com/problems/letter-combinations-of-a-phone-number/description/) - -![](index_files/a3f34241-bb80-4879-8ec9-dff2d81b514e.jpg) - -```html -Input:Digit string "23" -Output: ["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"]. -``` - -```java -private static final String[] KEYS = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"}; - -public List letterCombinations(String digits) { -    List ret = new ArrayList<>(); -    if (digits != null && digits.length() != 0) { -        combination("", digits, 0, ret); -    } -    return ret; -} - -private void combination(String prefix, String digits, int offset, List ret) { -    if (offset == digits.length()) { -        ret.add(prefix); -        return; -    } -    String letters = KEYS[digits.charAt(offset) - '0']; -    for (char c : letters.toCharArray()) { -        combination(prefix + c, digits, offset + 1, ret); -    } -} -``` - -**在矩阵中寻找字符串** - -[Leetcode : 79. Word Search (Medium)](https://leetcode.com/problems/word-search/description/) - -```html -For example, -Given board = -[ -  ['A','B','C','E'], -  ['S','F','C','S'], -  ['A','D','E','E'] -] -word = "ABCCED", -> returns true, -word = "SEE", -> returns true, -word = "ABCB", -> returns false. -``` - -```java -private static int[][] shift = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}}; -private static boolean[][] visited; -private int m; -private int n; - -public boolean exist(char[][] board, String word) { -    if (word == null || word.length() == 0) return true; -    if (board == null || board.length == 0 || board[0].length == 0) return false; -    m = board.length; -    n = board[0].length; -    visited = new boolean[m][n]; -    for (int i = 0; i < m; i++) { -        for (int j = 0; j < n; j++) { -            if (dfs(board, word, 0, i, j)) return true; -        } -    } -    return false; -} - -private boolean dfs(char[][] board, String word, int start, int r, int c) { -    if (start == word.length()) { -        return true; -    } -    if (r < 0 || r >= m || c < 0 || c >= n || board[r][c] != word.charAt(start) ||  visited[r][c] ) { -        return false; -    } -    visited[r][c] = true; -    for (int i = 0; i < shift.length; i++) { -        int nextR = r + shift[i][0]; -        int nextC = c + shift[i][1]; -        if (dfs(board, word, start + 1, nextR, nextC)) return true; -    } -    visited[r][c] = false; -    return false; -} -``` - -**IP 地址划分** - -[Leetcode : 93. Restore IP Addresses(Medium)](https://leetcode.com/problems/restore-ip-addresses/description/) - -```html -Given "25525511135", -return ["255.255.11.135", "255.255.111.35"]. -``` - -```java -private List ret; - -public List restoreIpAddresses(String s) { -    ret = new ArrayList<>(); -    doRestore(0, "", s); -    return ret; -} - -private void doRestore(int k, String path, String s) { -    if (k == 4 || s.length() == 0) { -        if (k == 4 && s.length() == 0) { -            ret.add(path); -        } -        return; -    } -    for (int i = 0; i < s.length() && i <= 2; i++) { -        if (i != 0 && s.charAt(0) == '0') break; -        String part = s.substring(0, i + 1); -        if (Integer.valueOf(part) <= 255) { -            doRestore(k + 1, path.length() != 0 ? path + "." + part : part, s.substring(i + 1)); -        } -    } -} -``` - -**排列** - -[Leetcode : 46. Permutations (Medium)](https://leetcode.com/problems/permutations/description/) - -题目描述:找出一组数的所有排列。 - -```java -public List> permute(int[] nums) { -    List> ret = new ArrayList<>(); -    List permuteList = new ArrayList<>(); -    boolean[] visited = new boolean[nums.length]; -    backtracking(permuteList, visited, nums, ret); -    return ret; -} - -private void backtracking(List permuteList, boolean[] visited, int[] nums, List> ret){ -    if(permuteList.size() == nums.length){ -        ret.add(new ArrayList(permuteList)); -        return; -    } - -    for(int i = 0; i < visited.length; i++){ -        if(visited[i]) continue; -        visited[i] = true; -        permuteList.add(nums[i]); -        backtracking(permuteList, visited, nums, ret); -        permuteList.remove(permuteList.size() - 1); -        visited[i] = false; -    } -} -``` - -**不能重复的排列** - -[Leetcode : 47. Permutations II (Medium)](https://leetcode.com/problems/permutations-ii/description/) - -```html -[1,1,2] have the following unique permutations: -[[1,1,2], [1,2,1], [2,1,1]] -``` - -在实现上,和 Permutations 不同的是要先排序,然后在添加一个元素时,判断这个元素是否等于前一个元素,如果等于,并且前一个元素还未访问,那么就跳过这个元素。 - -```java -public List> permuteUnique(int[] nums) { -    List> ret = new ArrayList<>(); -    List permuteList = new ArrayList<>(); -    Arrays.sort(nums); -    boolean[] visited = new boolean[nums.length]; -    backtracking(permuteList, visited, nums, ret); -    return ret; -} - -private void backtracking(List permuteList, boolean[] visited, int[] nums, List> ret) { -    if (permuteList.size() == nums.length) { -        ret.add(new ArrayList(permuteList)); -        return; -    } - -    for (int i = 0; i < visited.length; i++) { -        if (i != 0 && nums[i] == nums[i - 1] && !visited[i - 1]) continue; -        if (visited[i]) continue; -        visited[i] = true; -        permuteList.add(nums[i]); -        backtracking(permuteList, visited, nums, ret); -        permuteList.remove(permuteList.size() - 1); -        visited[i] = false; -    } -} -``` - -**组合** - -[Leetcode : 77. Combinations (Medium)](https://leetcode.com/problems/combinations/description/) - -题目描述:找出从 1 ~ n 中取出 k 个元素的所有可能的组合。 - -```java -public List> combine(int n, int k) { -    List> ret = new ArrayList<>(); -    List combineList = new ArrayList<>(); -    backtracking(1, n, k, combineList, ret); -    return ret; -} - -private void backtracking(int start, int n, int k, List combineList, List> ret){ -    if(k == 0){ -        ret.add(new ArrayList(combineList)); // 这里要重新构造一个 List -        return; -    } -     -    for(int i = start; i <= n - k + 1; i++){ // 剪枝 - -        combineList.add(i);                        // 把 i 标记为已访问 -        backtracking(i + 1, n, k - 1, combineList, ret); -        combineList.remove(combineList.size() - 1); // 把 i 标记为未访问 -    } -} -``` - -**组合求和** - -[Leetcode : 39. Combination Sum (Medium)](https://leetcode.com/problems/combination-sum/description/) - -```html -given candidate set [2, 3, 6, 7] and target 7, -A solution set is: -[[7],[2, 2, 3]] -``` - - -```java - private List> ret; - - public List> combinationSum(int[] candidates, int target) { -     ret = new ArrayList<>(); -     doCombination(candidates, target, 0, new ArrayList<>()); -     return ret; - } - - private void doCombination(int[] candidates, int target, int start, List list) { -     if (target == 0) { -         ret.add(new ArrayList<>(list)); -         return; -     } -     for (int i = start; i < candidates.length; i++) { -         if (candidates[i] <= target) { -             list.add(candidates[i]); -             doCombination(candidates, target - candidates[i], i, list); -             list.remove(list.size() - 1); -         } -     } - } -``` - -**不能重复的组合求和** - -[Leetcode : 40. Combination Sum II (Medium)](https://leetcode.com/problems/combination-sum-ii/description/) - -```html -For example, given candidate set [10, 1, 2, 7, 6, 1, 5] and target 8,  -A solution set is:  -[ -  [1, 7], -  [1, 2, 5], -  [2, 6], -  [1, 1, 6] -] -``` - -```java -private List> ret; - -public List> combinationSum2(int[] candidates, int target) { -    ret = new ArrayList<>(); -    Arrays.sort(candidates); -    doCombination(candidates, target, 0, new ArrayList<>(), new boolean[candidates.length]); -    return ret; -} - -private void doCombination(int[] candidates, int target, int start, List list, boolean[] visited) { -    if (target == 0) { -        ret.add(new ArrayList<>(list)); -        return; -    } -    for (int i = start; i < candidates.length; i++) { -        if (i != 0 && candidates[i] == candidates[i - 1] && !visited[i - 1]) continue; -        if (candidates[i] <= target) { -            list.add(candidates[i]); -            visited[i] = true; -            doCombination(candidates, target - candidates[i], i + 1, list, visited); -            visited[i] = false; -            list.remove(list.size() - 1); -        } -    } -} -``` - - -**子集** - -[Leetcode : 78. Subsets (Medium)](https://leetcode.com/problems/subsets/description/) - -题目描述:找出集合的所有子集,子集不能重复,[1, 2] 和 [2, 1] 这种子集算重复 - -```java -private List> ret; -private List subsetList; - -public List> subsets(int[] nums) { -    ret = new ArrayList<>(); -    subsetList = new ArrayList<>(); -    for (int i = 0; i <= nums.length; i++) { -        backtracking(0, i, nums); -    } -    return ret; -} - -private void backtracking(int startIdx, int size, int[] nums) { -    if (subsetList.size() == size) { -        ret.add(new ArrayList(subsetList)); -        return; -    } - -    for (int i = startIdx; i < nums.length; i++) { -        subsetList.add(nums[i]); -        backtracking(i + 1, size, nums); // startIdx 设为下一个元素,使 subset 中的元素都递增排序 -        subsetList.remove(subsetList.size() - 1); -    } -} -``` - -**不能重复的子集** - -[Leetcode : 90. Subsets II (Medium)](https://leetcode.com/problems/subsets-ii/description/) - -```html -For example, -If nums = [1,2,2], a solution is: - -[ -  [2], -  [1], -  [1,2,2], -  [2,2], -  [1,2], -  [] -] -``` - -```java -private List> ret; -private List subsetList; -private boolean[] visited; - -public List> subsetsWithDup(int[] nums) { -    ret = new ArrayList<>(); -    subsetList = new ArrayList<>(); -    visited = new boolean[nums.length]; -    Arrays.sort(nums); -    for (int i = 0; i <= nums.length; i++) { -        backtracking(0, i, nums); -    } -    return ret; -} - -private void backtracking(int startIdx, int size, int[] nums) { -    if (subsetList.size() == size) { -        ret.add(new ArrayList(subsetList)); -        return; -    } - -    for (int i = startIdx; i < nums.length; i++) { -        if (i != 0 && nums[i] == nums[i - 1] && !visited[i - 1]) continue; -        subsetList.add(nums[i]); -        visited[i] = true; -        backtracking(i + 1, size, nums); -        visited[i] = false; -        subsetList.remove(subsetList.size() - 1); -    } -} -``` - -**分割字符串使得每部分都是回文数** - -[Leetcode : 131. Palindrome Partitioning (Medium)](https://leetcode.com/problems/palindrome-partitioning/description/) - -```java -private List> ret; - -public List> partition(String s) { -    ret = new ArrayList<>(); -    doPartion(new ArrayList<>(), s); -    return ret; -} - -private void doPartion(List list, String s) { -    if (s.length() == 0) { -        ret.add(new ArrayList<>(list)); -        return; -    } -    for (int i = 0; i < s.length(); i++) { -        if (isPalindrome(s, 0, i)) { -            list.add(s.substring(0, i + 1)); -            doPartion(list, s.substring(i + 1)); -            list.remove(list.size() - 1); -        } -    } -} - -private boolean isPalindrome(String s, int begin, int end) { -    while (begin < end) { -        if (s.charAt(begin++) != s.charAt(end--)) return false; -    } -    return true; -} -``` - -**数独** - -[Leetcode : 37. Sudoku Solver (Hard)](https://leetcode.com/problems/sudoku-solver/description/) - -![](index_files/1ca52246-c443-48ae-b1f8-1cafc09ec75c.png) - -```java -private boolean[][] rowsUsed = new boolean[9][10]; -private boolean[][] colsUsed = new boolean[9][10]; -private boolean[][] cubesUsed = new boolean[9][10]; -private char[][] board; - -public void solveSudoku(char[][] board) { -    this.board = board; -    for (int i = 0; i < 9; i++) { -        for (int j = 0; j < 9; j++) { -            if (board[i][j] == '.') continue; -            int num = board[i][j] - '0'; -            rowsUsed[i][num] = true; -            colsUsed[j][num] = true; -            cubesUsed[cubeNum(i, j)][num] = true; -        } -    } -    for (int i = 0; i < 9; i++) { -        for (int j = 0; j < 9; j++) { -            backtracking(i, j); -        } -    } -} - -private boolean backtracking(int row, int col) { -    while (row < 9 && board[row][col] != '.') { -        row = col == 8 ? row + 1 : row; -        col = col == 8 ? 0 : col + 1; -    } -    if (row == 9) { -        return true; -    } -    for (int num = 1; num <= 9; num++) { -        if (rowsUsed[row][num] || colsUsed[col][num] || cubesUsed[cubeNum(row, col)][num]) continue; -        rowsUsed[row][num] = colsUsed[col][num] = cubesUsed[cubeNum(row, col)][num] = true; -        board[row][col] = (char) (num + '0'); -        if (backtracking(row, col)) return true; -        board[row][col] = '.'; -        rowsUsed[row][num] = colsUsed[col][num] = cubesUsed[cubeNum(row, col)][num] = false; -    } -    return false; -} - -private int cubeNum(int i, int j) { -    int r = i / 3; -    int c = j / 3; -    return r * 3 + c; -} -``` - -## 分治 - -**给表达式加括号** - -[Leetcode : 241. Different Ways to Add Parentheses (Medium)](https://leetcode.com/problems/different-ways-to-add-parentheses/description/) - -```html -Input: "2-1-1". - -((2-1)-1) = 0 -(2-(1-1)) = 2 - -Output : [0, 2] -``` - -```java -public List diffWaysToCompute(String input) { -    int n = input.length(); -    List ret = new ArrayList<>(); -    for (int i = 0; i < n; i++) { -        char c = input.charAt(i); -        if (c == '+' || c == '-' || c == '*') { -            List left = diffWaysToCompute(input.substring(0, i)); -            List right = diffWaysToCompute(input.substring(i + 1)); -            for (int l : left) { -                for (int r : right) { -                    switch (c) { -                        case '+': ret.add(l + r); break; -                        case '-': ret.add(l - r); break; -                        case '*': ret.add(l * r); break; -                    } -                } -            } -        } -    } -    if (ret.size() == 0) ret.add(Integer.valueOf(input)); -    return ret; -} -``` - -## 递归 - -将原始问题分解成较小的问题来求解。 - -## 动态规划 - -将原问题拆解成多个子问题,在求解子问题的时候保存子问题的解,使得子问题只求解一次。 - -### 分割整数 - -**分割整数的最大乘积** - -[Leetcode : 343. Integer Break (Medim)](https://leetcode.com/problems/integer-break/description/) - -题目描述:For example, given n = 2, return 1 (2 = 1 + 1); given n = 10, return 36 (10 = 3 + 3 + 4). - -```java -public int integerBreak(int n) { -    int[] dp = new int[n + 1]; -    dp[1] = 1; -    for(int i = 2; i <= n; i++) { -        for(int j = 1; j <= i - 1; j++) { -            dp[i] = Math.max(dp[i], Math.max(j * dp[i - j], j * (i - j))); -        } -    } -    return dp[n]; -} -``` - -**按平方数来分割整数** - -[Leetcode : 279. Perfect Squares(Medium)](https://leetcode.com/problems/perfect-squares/description/) - -题目描述:For example, given n = 12, return 3 because 12 = 4 + 4 + 4; given n = 13, return 2 because 13 = 4 + 9. - -```java -public int numSquares(int n) { -    List squares = new ArrayList<>(); // 存储小于 n 的平方数 -    int diff = 3; -    while(square <= n) { -        squares.add(square); -        square += diff; -        diff += 2; -    } -    int[] dp = new int[n + 1]; -    for(int i = 1; i <= n; i++) { -        int max = Integer.MAX_VALUE; -        for(int s : squares) { -            if(s > i) break; -            max = Math.min(max, dp[i - s] + 1); -        } -        dp[i] = max; -    } -    return dp[n]; -} -``` - -**分割整数构成字母字符串** - -[Leetcode : 91. Decode Ways (Medium)](https://leetcode.com/problems/decode-ways/description/) - -题目描述:Given encoded message "12", it could be decoded as "AB" (1 2) or "L" (12). - -```java -public int numDecodings(String s) { -    if(s == null || s.length() == 0) return 0; -    int n = s.length(); -    int[] dp = new int[n + 1]; -    dp[0] = 1; -    dp[1] = s.charAt(0) == '0' ? 0 : 1; -    for(int i = 2; i <= n; i++) { -        int one = Integer.valueOf(s.substring(i - 1, i)); -        if(one != 0) dp[i] += dp[i - 1]; -        if(s.charAt(i - 2) == '0') continue; -        int two = Integer.valueOf(s.substring(i - 2, i)); -        if(two <= 26) dp[i] += dp[i - 2]; -    } -    return dp[n]; -} -``` - -### 矩阵路径 - -**矩阵的总路径数** - -[Leetcode : 62. Unique Paths (Medium)](https://leetcode.com/problems/unique-paths/description/) - -题目描述:统计从矩阵左上角到右下角的路径总数,每次只能向左和向下移动。 - -```java -public int uniquePaths(int m, int n) { -    int[] dp = new int[n]; -    for (int i = 0; i < m; i++) { -        for (int j = 0; j < n; j++) { -            if(i == 0) dp[j] = 1; -            else if(j != 0) dp[j] = dp[j] + dp[j - 1]; -        } -    } -    return dp[n - 1]; -} -``` - -**矩阵的最小路径和** - -[Leetcode : 64. Minimum Path Sum (Medium)](https://leetcode.com/problems/minimum-path-sum/description/) - -题目描述:求从矩阵的左上角到右下角的最小路径和,每次只能向左和向下移动。 - -```java -public int minPathSum(int[][] grid) { -    if(grid.length == 0 || grid[0].length == 0) return 0; -    int m = grid.length, n = grid[0].length; -    int[] dp = new int[n]; -    for(int i = 0; i < m; i++) { -        for(int j = 0; j < n; j++) { -            if(j == 0) dp[0] = dp[0] + grid[i][0]; -            else if(i == 0) dp[j] = dp[j - 1] + grid[0][j]; -            else dp[j] = Math.min(dp[j - 1], dp[j]) + grid[i][j]; -        } -    } -    return dp[n - 1]; -} -``` - -### 斐波那契数列 - -程序员代码面试指南第 4 章的方法将求斐波那契第 N 项问题转换为矩阵乘法运算,从而将时间复杂度缩小为 O(lgN)。 - -**爬楼梯** - -[Leetcode : 70. Climbing Stairs (Easy)](https://leetcode.com/problems/climbing-stairs/description/) - -题目描述:有 N 阶楼梯,每次可以上一阶或者两阶,求有多少种上楼梯的方法。 - -定义一个数组 dp 存储上楼梯的方法数(为了方便讨论,数组下标从 1 开始),dp[i] 表示走到第 i 个楼梯的方法数目。第 i 个楼梯可以从第 i-1 和 i-2 个楼梯再走一步到达,走到第 i 个楼梯的方法数为走到第 i-1 和第 i-2 个楼梯的方法数之和。 - -
dp[i] = dp[i-1] + dp[i-2]
- -dp[N] 即为所求。 - -考虑到 dp[i] 只与 dp[i - 1] 和 dp[i - 2] 有关,因此可以只用两个变量来存储 dp[i - 1] 和 dp[i - 2] 即可,使得原来的 O(n) 空间复杂度优化为 O(1) 复杂度。 - -```java -public int climbStairs(int n) { -    if(n == 1) return 1; -    if(n == 2) return 2; -    // 前一个楼梯、后一个楼梯 -    int pre1 = 2, pre2 = 1; -    for(int i = 2; i < n; i++){ -        int cur = pre1 + pre2; -        pre2 = pre1; -        pre1 = cur; -    } -    return pre1; -} -``` - -**母牛生产** - -[程序员代码面试指南-P181](#) - -题目描述:假设农场中成熟的母牛每年都会生 1 头小母牛,并且永远不会死。第一年有 1 只小母牛,从第二年开始,母牛开始生小母牛。每只小母牛 3 年之后成熟又可以生小母牛。给定整数 N,求 N 年后牛的数量。 - -第 i 年成熟的牛的数量为: - -
dp[i] = dp[i-1] + dp[i-3]
- -**强盗抢劫** - -[Leetcode : 198. House Robber (Easy)](https://leetcode.com/problems/house-robber/description/) - -题目描述:抢劫一排住户,但是不能抢邻近的住户,求最大抢劫量。 - -定义 dp 数组用来存储最大的抢劫量,其中 dp[i] 表示抢到第 i 个住户时的最大抢劫量。由于不能抢劫邻近住户,因此如果抢劫了第 i 个住户那么只能抢劫 i - 2 和 i - 3 的住户,所以 - -$$ dp[i] = max(dp[i - 2], dp[i - 3]) + nums[i] $$ - -O(n) 空间复杂度实现方法: - -```java -public int rob(int[] nums) { -    int n = nums.length; -    if(n == 0) return 0; -    if(n == 1) return nums[0]; -    if(n == 2) return Math.max(nums[0], nums[1]); -    int[] dp = new int[n]; -    dp[0] = nums[0]; -    dp[1] = nums[1]; -    dp[2] = nums[0] + nums[2]; -    for(int i = 3; i < n; i++){ -        dp[i] = Math.max(dp[i -2], dp[i - 3]) + nums[i]; -    } -    return Math.max(dp[n - 1], dp[n - 2]); -} -``` - -O(1) 空间复杂度实现方法: - -```java -public int rob(int[] nums) { -    int n = nums.length; -    if(n == 0) return 0; -    if(n == 1) return nums[0]; -    if(n == 2) return Math.max(nums[0], nums[1]); -    int pre3 = nums[0], pre2 = nums[1], pre1 = nums[2] + nums[0]; -    for(int i = 3; i < n; i++){ -        int cur = Math.max(pre2, pre3) + nums[i]; -        pre3 = pre2; -        pre2 = pre1; -        pre1 = cur; -    } -    return Math.max(pre1, pre2); -} -``` - -**强盗在环形街区抢劫** - -[Leetcode : 213. House Robber II (Medium)](https://leetcode.com/problems/house-robber-ii/description/) - -```java -public int rob(int[] nums) { -    if(nums == null || nums.length == 0) return 0; -    int n = nums.length; -    if(n == 1) return nums[0]; -    return Math.max(rob(nums, 0, n - 2), rob(nums, 1, n - 1)); -} - -private int rob(int[] nums, int s, int e) { -    int n = nums.length; -    if(e - s == 0) return nums[s]; -    if(e - s == 1) return Math.max(nums[s], nums[s + 1]); -    int[] dp = new int[n]; -    dp[s] = nums[s]; -    dp[s + 1] = nums[s + 1]; -    dp[s + 2] = nums[s] + nums[s + 2]; -    for (int i = s + 3; i <= e; i++) { -        dp[i] = Math.max(dp[i - 2], dp[i - 3]) + nums[i]; -    } -    return Math.max(dp[e], dp[e - 1]); -} -``` - - -**信件错排** - -题目描述:有 N 个 信 和 信封,它们被打乱,求错误装信的方式数量。 - -定义一个数组 dp 存储错误方式数量,dp[i] 表示前 i 个信和信封的错误方式数量。假设第 i 个信装到第 j 个信封里面,而第 j 个信装到第 k 个信封里面。根据 i 和 k 是否相等,有两种情况: - -① i==k,交换 i 和 k 的信后,它们的信和信封在正确的位置,但是其余 i-2 封信有 dp[i-2] 种错误装信的方式。由于 j 有 i-1 种取值,因此共有 (i-1)\*dp[i-2] 种错误装信方式。 - -② i != k,交换 i 和 j 的信后,第 i 个信和信封在正确的位置,其余 i-1 封信有 dp[i-1] 种错误装信方式。由于 j 有 i-1 种取值,因此共有 (n-1)\*dp[i-1] 种错误装信方式。 - -综上所述,错误装信数量方式数量为: - -
 dp[i] = (i-1) \* dp[i-2] + (i-1) \* dp[i-1] 
- -dp[N] 即为所求。 - -和上楼梯问题一样,dp[i] 只与 dp[i-1] 和 dp[i-2] 有关,因此也可以只用两个变量来存储 dp[i-1] 和 dp[i-2]。 - -### 最长递增子序列 - -已知一个序列 {S1, S2,...,Sn} ,取出若干数组成新的序列 {Si1, Si2,..., Sim},其中 i1、i2 ... im 保持递增,即新序列中各个数仍然保持原数列中的先后顺序,称新序列为原序列的一个**子序列**。 - -如果在子序列中,当下标 ix > iy 时,Six > Siy,称子序列为原序列的一个**递增子序列**。 - -定义一个数组 dp 存储最长递增子序列的长度,dp[n] 表示以 Sn 结尾的序列的最长递增子序列长度。对于一个递增子序列 {Si1, Si2,...,Sim},如果 im < n 并且 Sim < Sn ,此时 {Si1, Si2,..., Sim, Sn} 为一个递增子序列,递增子序列的长度增加 1。满足上述条件的递增子序列中,长度最长的那个递增子序列就是要找的,在长度最长的递增子序列上加上 Sn 就构成了以 Sn 为结尾的最长递增子序列。因此 dp[n] = max{ dp[i]+1 | Si < Sn && i < n} 。 - -因为在求 dp[n] 时可能无法找到一个满足条件的递增子序列,此时 {Sn} 就构成了递增子序列,因此需要对前面的求解方程做修改,令 dp[n] 最小为 1,即: - -$$ dp[n] = max\{ 1, dp[i]+1 | S_i < S_n \&\& i < n \} $$ - -对于一个长度为 N 的序列,最长子序列并不一定会以 SN 为结尾,因此 dp[N] 不是序列的最长递增子序列的长度,需要遍历 dp 数组找出最大值才是所要的结果,即 max{ dp[i] | 1 <= i <= N} 即为所求。 - -**最长递增子序列** - -[Leetcode : 300. Longest Increasing Subsequence (Medium)](https://leetcode.com/problems/longest-increasing-subsequence/description/) - -```java -public int lengthOfLIS(int[] nums) { -    int n = nums.length; -    int[] dp = new int[n]; -    for(int i = 0; i < n; i++){ -        int max = 1; -        for(int j = 0; j < i; j++){ -            if(nums[i] > nums[j]) max = Math.max(max, dp[j] + 1); -        } -        dp[i] = max; -    } -    int ret = 0; -    for(int i = 0; i < n; i++){ -        ret = Math.max(ret, dp[i]); -    } -    return ret; -} -``` - -以上解法的时间复杂度为 O(n2) ,可以使用二分查找使得时间复杂度降低为 O(nlogn)。定义一个 tails 数组,其中 tails[i] 存储长度为 i + 1 的最长递增子序列的最后一个元素,例如对于数组 [4,5,6,3],有 - -```html -len = 1  :      [4], [5], [6], [3]  => tails[0] = 3 -len = 2  :      [4, 5], [5, 6]      => tails[1] = 5 -len = 3  :      [4, 5, 6]            => tails[2] = 6 -``` - -对于一个元素 x,如果它大于 tails 数组所有的值,那么把它添加到 tails 后面;如果 tails[i-1] < x <= tails[i],那么更新 tails[i] = x 。 - -可以看出 tails 数组保持有序,因此在查找 Si 位于 tails 数组的位置时就可以使用二分查找。 - -```java -public int lengthOfLIS(int[] nums) { -    int n = nums.length; -    int[] tails = new int[n]; -    int size = 0; -    for(int i = 0; i < n; i++){ -        int idx = binarySearch(tails, 0, size, nums[i]); -        tails[idx] = nums[i]; -        if(idx == size) size++; -    } -    return size; -} - -private int binarySearch(int[] nums, int sIdx, int eIdx, int key){ -    while(sIdx < eIdx){ -        int mIdx = sIdx + (eIdx - sIdx) / 2; -        if(nums[mIdx] == key) return mIdx; -        else if(nums[mIdx] > key) eIdx = mIdx; -        else sIdx = mIdx + 1; -    } -    return sIdx; -} -``` - -**最长摆动子序列** - -[Leetcode : 376. Wiggle Subsequence (Medium)](https://leetcode.com/problems/wiggle-subsequence/description/) - -要求:使用 O(n) 时间复杂度求解。 - -使用两个状态 up 和 down。 - -```java -public int wiggleMaxLength(int[] nums) { -    int len = nums.length; -    if (len == 0) return 0; -    int up = 1, down = 1; -    for (int i = 1; i < len; i++) { -        if (nums[i] > nums[i - 1]) up = down + 1; -        else if (nums[i] < nums[i - 1]) down = up + 1; -    } -    return Math.max(up, down); -} -``` - -### 最长公共子系列 - -对于两个子序列 S1 和 S2,找出它们最长的公共子序列。 - -定义一个二维数组 dp 用来存储最长公共子序列的长度,其中 dp[i][j] 表示 S1 的前 i 个字符与 S2 的前 j 个字符最长公共子序列的长度。考虑 S1i 与 S2j 值是否相等,分为两种情况: - -① 当 S1i==S2j 时,那么就能在 S1 的前 i-1 个字符与 S2 的前 j-1 个字符最长公共子序列的基础上再加上 S1i 这个值,最长公共子序列长度加 1 ,即 dp[i][j] = dp[i-1][j-1] + 1。 - -② 当 S1i != S2j 时,此时最长公共子序列为 S1 的前 i-1 个字符和 S2 的前 j 个字符最长公共子序列,与 S1 的前 i 个字符和 S2 的前 j-1 个字符最长公共子序列,它们的最大者,即 dp[i][j] = max{ dp[i-1][j], dp[i][j-1] }。 - -综上,最长公共子系列的状态转移方程为: - -$$ -dp[i][j]=\left\{ -\begin{array}{rcl} -dp[i-1][j-1]  &&  { S1_i==S2_j }\\ -max(dp[i-1][j], dp[i][j-1])  &&{ S1_i <> S2_j } -\end{array} \right. -$$ - -对于长度为 N 的序列 S1 和 长度为 M 的序列 S2,dp[N][M] 就是序列 S1 和序列 S2 的最长公共子序列长度。 - -与最长递增子序列相比,最长公共子序列有以下不同点: - -① 针对的是两个序列,求它们的最长公共子序列。 -② 在最长递增子序列中,dp[i] 表示以 Si 为结尾的最长递增子序列长度,子序列必须包含 Si ;在最长公共子序列中,dp[i][j] 表示 S1 中前 i 个字符与 S2 中前 j 个字符的最长公共子序列长度,不一定包含 S1i 和 S2j 。 -③ 由于 2 ,在求最终解时,最长公共子序列中 dp[N][M] 就是最终解,而最长递增子序列中 dp[N] 不是最终解,因为以 SN 为结尾的最长递增子序列不一定是整个序列最长递增子序列,需要遍历一遍 dp 数组找到最大者。 - -```java -public int lengthOfLCS(int[] nums1, int[] nums2) { -    int n1 = nums1.length, n2 = nums2.length; -    int[][] dp = new int[n1 + 1][n2 + 1]; -    for (int i = 1; i <= n1; i++) { -        for (int j = 1; j <= n2; j++) { -            if (nums1[i - 1] == nums2[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1; -            else dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]); -        } -    } -    return dp[n1][n2]; -} -``` - -### 0-1 背包 - -有一个容量为 N 的背包,要用这个背包装下物品的价值最大,这些物品有两个属性:体积 w 和价值 v。 - -定义一个二维数组 dp 存储最大价值,其中 dp[i][j] 表示体积不超过 j 的情况下,前 i 件物品能达到的最大价值。设第 i 件物品体积为 w,价值为 v,根据第 i 件物品是否添加到背包中,可以分两种情况讨论: - -① 第 i 件物品没添加到背包,总体积不超过 j 的前 i 件物品的最大价值就是总体积不超过 j 的前 i-1 件物品的最大价值,dp[i][j] = dp[i-1][j]。 -② 第 i 件物品添加到背包中,dp[i][j] = dp[i-1][j-w] + v。 - -第 i 件物品可添加也可以不添加,取决于哪种情况下最大价值更大。 - -综上,0-1 背包的状态转移方程为: - -$$ dp[i][j] = max ( dp[i-1][j], dp[i-1][j-w] + v ) $$ - -```java -public int knapsack(int W, int N, int[] weights, int[] values) { -    int[][] dp = new int[N][W]; -    for (int i = W - 1; i >= 0; i--) { -        dp[0][i] = i > weights[0] ? values[0] : 0; -    } -    for (int i = 1; i < N; i++) { -        for (int j = W - 1; j >= weights[i]; j--) { -            dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weights[i]] + values[i]); -        } -        for (int j = weights[i - 1] - 1; j >= 0; j--) { -            dp[i][j] = dp[i - 1][j]; -        } -    } -    return dp[N - 1][W - 1]; -} -``` - -**空间优化** - -在程序实现时可以对 0-1 背包做优化。观察状态转移方程可以知道,前 i 件物品的状态仅由前 i-1 件物品的状态有关,因此可以将 dp 定义为一维数组,其中 dp[j] 既可以表示 dp[i-1][j] 也可以表示 dp[i][j]。此时, - -$$ dp[j] = max ( dp[j], dp[j-w] + v ) $$ - -因为 dp[j-w] 表示 dp[i-1][j-w],因此不能先求 dp[i][j-w] 防止将 dp[i-1][j-w] 覆盖。也就是说要先计算 dp[i][j] 再计算 dp[i][j-w],在程序实现时需要按倒序来循环求解。 - - -**无法使用贪心算法的解释** - -0-1 背包问题无法使用贪心算法来求解,也就是说不能按照先添加性价比最高的物品来达到最优,这是因为这种方式可能造成背包空间的浪费,从而无法达到最优。考虑下面的物品和一个容量为 5 的背包,如果先添加物品 0 再添加物品 1,那么只能存放的价值为 16,浪费了大小为 2 的空间。最优的方式是存放物品 1 和物品 2,价值为 22. - -| id | w | v | v/w | -| --- | --- | --- | --- | -| 0 | 1 | 6 | 6 | -| 1 | 2 | 10 | 5 | -| 2 | 3 | 12 | 4 | - -**变种** - -完全背包:物品可以无限个,可以转换为 0-1 背包,令每种物品的体积和价值变为 1/2/4... 倍数,把它们都当成一个新物品,然后一种物品只能添加一次。 - -多重背包:物品数量有限制,同样可以转换为 0-1 背包。 - -多维费用背包:物品不仅有重量,还有体积,同时考虑这两种限制。 - -其它:物品之间相互约束或者依赖。 - -**划分数组为和相等的两部分** - -[Leetcode : 416. Partition Equal Subset Sum (Medium)](https://leetcode.com/problems/partition-equal-subset-sum/description/) - -可以看成一个背包大小为 sum/2 的 0-1 背包问题,但是也有不同的地方,这里没有价值属性,并且背包必须被填满。 - -以下实现使用了空间优化。 - -```java -public boolean canPartition(int[] nums) { -    int sum = 0; -    for (int num : nums) { -        sum += num; -    } -    if (sum % 2 != 0) { -        return false; -    } -    int W = sum / 2; -    boolean[] dp = new boolean[W + 1]; -    int n = nums.length; -    for(int i = 0; i <= W; i++) { -        if(nums[0] == i) dp[i] = true; -    } -    for(int i = 1; i < n; i++) { -        for(int j = W; j >= nums[i]; j--) { -            dp[j] = dp[j] || dp[j - nums[i]]; -        } -    } - -    return dp[W]; -} -``` - -**字符串按单词列表分割** - -[Leetcode : 139. Word Break (Medium)](https://leetcode.com/problems/word-break/description/) - -```html -s = "leetcode", -dict = ["leet", "code"]. -Return true because "leetcode" can be segmented as "leet code". -``` - -```java -public boolean wordBreak(String s, List wordDict) { -    int n = s.length(); -    boolean[] dp = new boolean[n + 1]; -    dp[0] = true; -    for (int i = 1; i <= n; i++) { -        for (String word : wordDict) { -            if (word.length() <= i -                    && word.equals(s.substring(i - word.length(), i))) { -                dp[i] = dp[i] || dp[i - word.length()]; -            } -        } -    } -    return dp[n]; -} -``` - -**改变一组数的正负号使得它们的和为一给定数** - -[Leetcode : 494. Target Sum (Medium)](https://leetcode.com/problems/target-sum/description/) - -```html -Input: nums is [1, 1, 1, 1, 1], S is 3.  -Output: 5 -Explanation:  - --1+1+1+1+1 = 3 -+1-1+1+1+1 = 3 -+1+1-1+1+1 = 3 -+1+1+1-1+1 = 3 -+1+1+1+1-1 = 3 - -There are 5 ways to assign symbols to make the sum of nums be target 3. -``` - -该问题可以转换为 subset sum 问题,从而使用 0-1 背包的方法来求解。可以将这组数看成两部分,P 和 N,其中 P 使用正号,N 使用负号,有以下推导: - -```html -                  sum(P) - sum(N) = target -sum(P) + sum(N) + sum(P) - sum(N) = target + sum(P) + sum(N) -                       2 * sum(P) = target + sum(nums) -``` - -因此只要找到一个子集,令它们都取正号,并且和等于 (target + sum(nums))/2,就证明存在解。 - -```java -public int findTargetSumWays(int[] nums, int S) { -    int sum = 0; -    for (int num : nums) { -        sum += num; -    } -    if (sum < S || (sum + S) % 2 == 1) { -        return 0; -    } -    return subsetSum(nums, (sum + S) >>> 1); -} - -private int subsetSum(int[] nums, int targetSum) { -    Arrays.sort(nums); -    int[] dp = new int[targetSum + 1]; -    dp[0] = 1; -    for (int i = 0; i < nums.length; i++) { -        int num = nums[i]; -        for (int j = targetSum; j >= num; j--) { -            dp[j] = dp[j] + dp[j - num]; -        } -    } -    return dp[targetSum]; -} -``` - -**01字符构成最多的字符串** - -[Leetcode : 474. Ones and Zeroes (Medium)](https://leetcode.com/problems/ones-and-zeroes/description/) - -```html -Input: Array = {"10", "0001", "111001", "1", "0"}, m = 5, n = 3 -Output: 4 - -Explanation: This are totally 4 strings can be formed by the using of 5 0s and 3 1s, which are “10,”0001”,”1”,”0” -``` - -这是一个多维费用的 0-1 背包问题,有两个背包大小,0 的数量和 1 的数量。 - -```java -public int findMaxForm(String[] strs, int m, int n) { -    if (strs == null || strs.length == 0) return 0; -    int l = strs.length; -    int[][] dp = new int[m + 1][n + 1]; -    for (int i = 0; i < l; i++) { -        String s = strs[i]; -        int ones = 0, zeros = 0; -        for (char c : s.toCharArray()) { -            if (c == '0') zeros++; -            else if (c == '1') ones++; -        } -        for (int j = m; j >= zeros; j--) { -            for (int k = n; k >= ones; k--) { -                if (zeros <= j && ones <= k) { -                    dp[j][k] = Math.max(dp[j][k], dp[j - zeros][k - ones] + 1); -                } -            } -        } -    } -    return dp[m][n]; -} -``` - -**找零钱** - -[Leetcode : 322. Coin Change (Medium)](https://leetcode.com/problems/coin-change/description/) - -题目描述:给一些面额的硬币,要求用这些硬币来组成给定面额的钱数,并且使得硬币数量最少。硬币可以重复使用。 - -这是一个完全背包问题,完全背包问题和 0-1背包问题在实现上唯一的不同是,第二层循环是从 0 开始的,而不是从尾部开始。 - -```java -public int coinChange(int[] coins, int amount) { -    int[] dp = new int[amount + 1]; -    Arrays.fill(dp, amount + 1); -    dp[0] = 0; -    for (int i = 1; i <= amount; i++) { -        for (int j = 0; j < coins.length; j++) { -            if (coins[j] <= i) { -                dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1); -            } -        } -    } -    return dp[amount] > amount ? -1 : dp[amount]; -} -``` - -**组合总和** - -[Leetcode : 377. Combination Sum IV (Medium)](https://leetcode.com/problems/combination-sum-iv/description/) - -```html -nums = [1, 2, 3] -target = 4 - -The possible combination ways are: -(1, 1, 1, 1) -(1, 1, 2) -(1, 2, 1) -(1, 3) -(2, 1, 1) -(2, 2) -(3, 1) - -Note that different sequences are counted as different combinations. - -Therefore the output is 7. -``` - -```java -public int combinationSum4(int[] nums, int target) { -    int[] dp = new int[target + 1]; -    dp[0] = 1; -    for (int i = 1; i <= target; i++) { -        for (int j = 0; j < nums.length; j++) { -            if(nums[j] <= i) { -                dp[i] += dp[i - nums[j]]; -            } -        } -    } -    return dp[target]; -} -``` - -**只能进行两次的股票交易** - -[Leetcode : 123. Best Time to Buy and Sell Stock III (Hard)](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-iii/description/) - -```java -public int maxProfit(int[] prices) { -    int firstBuy = Integer.MIN_VALUE, firstSell = 0; -    int secondBuy = Integer.MIN_VALUE, secondSell = 0; -    for (int curPrice : prices) { -        if (firstBuy < -curPrice) firstBuy = -curPrice; -        if (firstSell < firstBuy + curPrice) firstSell = firstBuy + curPrice; -        if (secondBuy < firstSell - curPrice) secondBuy = firstSell - curPrice; -        if (secondSell < secondBuy + curPrice) secondSell = secondBuy + curPrice; -    } -    return secondSell; -} -``` - -**只能进行 k 次的股票交易** - -[Leetcode : 188. Best Time to Buy and Sell Stock IV (Hard)](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-iv/description/) - -```html -dp[i, j] = max(dp[i, j-1], prices[j] - prices[jj] + dp[i-1, jj]) { jj in range of [0, j-1] } = max(dp[i, j-1], prices[j] + max(dp[i-1, jj] - prices[jj])) -``` - -```java -public int maxProfit(int k, int[] prices) { -    int n = prices.length; -    if (k >= n/2) { -        int maxPro = 0; -        for (int i = 1; i < n; i++) { -            if (prices[i] > prices[i-1]) -                maxPro += prices[i] - prices[i-1]; -        } -        return maxPro; -    } -    int[][] dp = new int[k + 1][n]; -    for (int i = 1; i <= k; i++) { -        int localMax = dp[i - 1][0] - prices[0]; -        for (int j = 1; j < n; j++) { -            dp[i][j] = Math.max(dp[i][j - 1], prices[j] + localMax); -            localMax = Math.max(localMax, dp[i - 1][j] - prices[j]); -        } -    } -    return dp[k][n - 1]; -} -``` - -### 数组区间 - -**数组区间和** - -[Leetcode : 303. Range Sum Query - Immutable (Easy)](https://leetcode.com/problems/range-sum-query-immutable/description/) - -求区间 i ~ j 的和,可以转换为 sum[j] - sum[i-1],其中 sum[i] 为 0 ~ j 的和。 - -```java -class NumArray { -     -    int[] nums; -     -    public NumArray(int[] nums) { -        for(int i = 1; i < nums.length; i++) -            nums[i] += nums[i - 1]; -        this.nums = nums; -    } -     -    public int sumRange(int i, int j) { -        return i == 0 ? nums[j] : nums[j] - nums[i - 1]; -    } -} -``` - -**子数组最大的和** - -[Leetcode : 53. Maximum Subarray (Easy)](https://leetcode.com/problems/maximum-subarray/description/) - -令 sum[i] 为以 num[i] 为结尾的子数组最大的和,可以由 sum[i-1] 得到 sum[i] 的值,如果 sum[i-1] 小于 0,那么以 num[i] 为结尾的子数组不能包含前面的内容,因为加上前面的部分,那么和一定会比 num[i] 还小。 - -```java -public int maxSubArray(int[] nums) { -    int n = nums.length; -    int[] sum = new int[n]; -    sum[0] = nums[0]; -    int max = sum[0]; -    for(int i = 1; i < n; i++){ -        sum[i] = (sum[i-1] > 0 ? sum[i-1] : 0) + nums[i]; -        max = Math.max(max, sum[i]); -    } -    return max; -} -``` - -**数组中等差递增子区间的个数** - -[Leetcode : 413. Arithmetic Slices (Medium)](https://leetcode.com/problems/arithmetic-slices/description/) - -```html -A = [1, 2, 3, 4] - -return: 3, for 3 arithmetic slices in A: [1, 2, 3], [2, 3, 4] and [1, 2, 3, 4] itself. -``` - -对于 (1,2,3,4),它有三种组成递增子区间的方式,而对于 (1,2,3,4,5),它组成递增子区间的方式除了 (1,2,3,4) 的三种外还多了一种,即 (1,2,3,4,5),因此 dp[i] = dp[i - 1] + 1。 - -```java -public int numberOfArithmeticSlices(int[] A) { -    int n = A.length; -    int[] dp = new int[n]; -    for(int i = 2; i < n; i++) { -        if(A[i] - A[i - 1] == A[i - 1] - A[i - 2]) { -            dp[i] = dp[i - 1] + 1; -        } -    } -    int ret = 0; -    for(int cnt : dp) { -        ret += cnt; -    } -    return ret; -} -``` - -### 字符串编辑 - -**删除两个字符串的字符使它们相等** - -[Leetcode : 583. Delete Operation for Two Strings (Medium)](https://leetcode.com/problems/delete-operation-for-two-strings/description/) - -可以转换为求两个字符串的最长公共子序列问题。 - -```java -public int minDistance(String word1, String word2) { -    int m = word1.length(), n = word2.length(); -    int[][] dp = new int[m + 1][n + 1]; -    for (int i = 0; i <= m; i++) { -        for (int j = 0; j <= n; j++) { -            if (i == 0 || j == 0) continue; -            dp[i][j] = word1.charAt(i - 1) == word2.charAt(j - 1) ? dp[i - 1][j - 1] + 1 -                    : Math.max(dp[i][j - 1], dp[i - 1][j]); -        } -    } -    return m + n - 2 * dp[m][n]; -} -``` - - -**修改一个字符串称为另一个字符串** // TODO - -[Leetcode : 72. Edit Distance (Hard)](https://leetcode.com/problems/edit-distance/description/) - -**修改一个字符串使它成为回文字符串**// TODO - -[程序员代码面试指南-字符串](#) - -### 其它问题 - -**需要冷却期的股票交易** - -[Leetcode : 309. Best Time to Buy and Sell Stock with Cooldown(Medium)](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-with-cooldown/description/) - -题目描述:交易之后需要有一天的冷却时间。 - -![](index_files/ac9b31ec-cef1-4880-a875-fc4571ca10e1.png) - -```html -s0[i] = max(s0[i - 1], s2[i - 1]); // Stay at s0, or rest from s2 -s1[i] = max(s1[i - 1], s0[i - 1] - prices[i]); // Stay at s1, or buy from s0 -s2[i] = s1[i - 1] + prices[i]; // Only one way from s1 -``` - - -```java -public int maxProfit(int[] prices) { -    if (prices == null || prices.length == 0) return 0; -    int n = prices.length; -    int[] s0 = new int[n]; -    int[] s1 = new int[n]; -    int[] s2 = new int[n]; -    s0[0] = 0; -    s1[0] = -prices[0]; -    s2[0] = Integer.MIN_VALUE; -    for (int i = 1; i < n; i++) { -        s0[i] = Math.max(s0[i - 1], s2[i - 1]); -        s1[i] = Math.max(s1[i - 1], s0[i - 1] - prices[i]); -        s2[i] = Math.max(s2[i - 1], s1[i - 1] + prices[i]); -    } -    return Math.max(s0[n - 1], s2[n - 1]); -} -``` - - -**统计从 0 ~ n 每个数的二进制表示中 1 的个数** - -[Leetcode : 338. Counting Bits (Medium)](https://leetcode.com/problems/counting-bits/description/) - -对于数字 6(110),它可以看成是数字 2(10) 前面加上一个 1 ,因此 dp[i] = dp[i&(i-1)] + 1; - -```java -    public int[] countBits(int num) { -        int[] ret = new int[num + 1]; -        for(int i = 1; i <= num; i++){ -            ret[i] = ret[i&(i-1)] + 1; -        } -        return ret; -    } -``` - -**一组整数对能够构成的最长链** - -[Leetcode : 646. Maximum Length of Pair Chain (Medium)](https://leetcode.com/problems/maximum-length-of-pair-chain/description/) - -对于 (a, b) 和 (c, d) ,如果 b < c,则它们可以构成一条链。 - -```java -public int findLongestChain(int[][] pairs) { -    if(pairs == null || pairs.length == 0) { -        return 0; -    } -    Arrays.sort(pairs, (a, b) -> (a[0] - b[0])); -    int n = pairs.length; -    int[] dp = new int[n]; -    Arrays.fill(dp, 1); -    for(int i = 0; i < n; i++) { -        for(int j = 0; j < i; j++) { -            if(pairs[i][0] > pairs[j][1]){ -                dp[i] = Math.max(dp[i], dp[j] + 1); -            } -        } -    } -     -    int ret = 0; -    for(int num : dp) { -        ret = Math.max(ret, num); -    } -    return ret; -} -``` - -**买入和售出股票最大的收益** - -[Leetcode : 121. Best Time to Buy and Sell Stock (Easy)](https://leetcode.com/problems/best-time-to-buy-and-sell-stock/description/) - -只进行一次交易。 - -只要记录前面的最小价格,将这个最小价格作为买入价格,然后将当前的价格作为售出价格,查看这个价格是否是当前的最大价格。 - -```java -public int maxProfit(int[] prices) { -    int n = prices.length; -    if(n == 0) return 0; -    int soFarMin = prices[0]; -    int max = 0; -    for(int i = 1; i < n; i++){ -        if(soFarMin > prices[i]) soFarMin = prices[i]; -        else max = Math.max(max, prices[i] - soFarMin); -    } -    return max; -} -``` - -**复制粘贴字符** - -[Leetcode : 650. 2 Keys Keyboard (Medium)](https://leetcode.com/problems/2-keys-keyboard/description/) - -```java -public int minSteps(int n) { -    int[] dp = new int[n + 1]; -    for (int i = 2; i <= n; i++) { -        dp[i] = i; -        for (int j = i - 1; j >= 0; j--) { -            if (i % j == 0) { -                dp[i] = dp[j] + dp[i / j]; -                break; -            } -        } -    } -    return dp[n]; -} -``` - -```java -public int minSteps(int n) { -    if (n == 1) return 0; -    for (int i = 2; i <= Math.sqrt(n); i++) { -        if (n % i == 0) return i + minSteps(n / i); -    } -    return n; -} -``` - -## 数学 - -### 素数 - -**素数分解** - -每一个数都可以分解成素数的乘积,例如 84 = 22 \* 31 \* 50 \* 71 \* 110 \* 130 \* 170 \* … - -**整除** - -令 x = 2m0 \* 3m1 \* 5m2 \* 7m3 \* 11m4 \* … -令 y = 2n0 \* 3n1 \* 5n2 \* 7n3 \* 11n4 \* … - -如果 x 整除 y(y mod x == 0),则对于所有 i,mi <= ni。 - -x 和 y 的 **最大公约数** 为:gcd(x,y) =  2min(m0,n0) \* 3min(m1,n1) \* 5min(m2,n2) \* ... - -x 和 y 的 **最小公倍数** 为:lcm(x,y) =  2max(m0,n0) \* 3max(m1,n1) \* 5max(m2,n2) \* ... - -**生成素数序列** - -[Leetcode : 204. Count Primes (Easy)](https://leetcode.com/problems/count-primes/description/) - -埃拉托斯特尼筛法在每次找到一个素数时,将能被素数整除的数排除掉。 - -```java -public int countPrimes(int n) { -    boolean[] notPrimes = new boolean[n + 1]; -    int cnt = 0; -    for(int i = 2; i < n; i++){ -        if(notPrimes[i]) continue; -        cnt++; -        // 从 i * i 开始,因为如果 k < i,那么 k * i 在之前就已经被去除过了 -        for(long j = (long) i * i; j < n; j += i){ -            notPrimes[(int) j] = true; -        } -    } -    return cnt; -} -``` - -### 最大公约数 - -```java -int gcd(int a, int b) { -    if (b == 0) return a; -    return gcd(b, a % b); -} -``` - -最大公倍数为两数的乘积除以最大公约数。 - -```java -int lcm(int a, int b){ -    return a * b / gcd(a, b); -} -``` - -对于最大公约数问题,因为需要计算 a % b ,而这个操作是比较耗时的,可以使用 [ 编程之美:2.7]() 的方法,利用减法和移位操作来替换它。 - -对于 a 和 b 的最大公约数 f(a, b),有: - -1\. 如果 a 和 b 均为偶数,f(a, b) = 2\*f(a/2, b/2); -2\. 如果 a 是偶数 b 是奇数,f(a, b) = f(a/2, b); -3\. 如果 b 是偶数 a 是奇数,f(a, b) = f(a, b/2); -4\. 如果 a 和 b 均为奇数,f(a, b) = f(a, a-b); - -乘 2 和除 2 都可以转换为移位操作。 - -### 进制转换 - -Java 中 static String toString(int num, int radix) 可以将一个整数装换为 redix 进制表示的字符串。 - -**7 进制** - -[Leetcode : 504. Base 7 (Easy)](https://leetcode.com/problems/base-7/description/) - -```java -public String convertToBase7(int num) { -    if (num < 0) { -        return '-' + convertToBase7(-num); -    } -    if (num < 7) { -        return num + ""; -    } -    return convertToBase7(num / 7) + num % 7; -} -``` - -**16 进制** - -[Leetcode : 405. Convert a Number to Hexadecimal (Easy)](https://leetcode.com/problems/convert-a-number-to-hexadecimal/description/) - -```java -public String toHex(int num) { -    char[] map = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'}; -    if(num == 0) return "0"; -    String ret = ""; -    while(num != 0){ -        ret = map[(num & 0b1111)] + ret; -        num >>>= 4; -    } -    return ret; -} -``` - -### 阶乘 - -**统计阶乘尾部有多少个 0** - -[Leetcode : 172. Factorial Trailing Zeroes (Easy)](https://leetcode.com/problems/factorial-trailing-zeroes/description/) - -尾部的 0 由 2 * 5 得来,2 的数量明显多于 5 的数量,因此只要统计有多少个 5 即可。 - -对于一个数 N,它所包含 5 的个数为:N/5 + N/52 + N/53 + ...,其中 N/5 表示不大于 N 的数中 5 的倍数贡献一个 5,N/52 表示不大于 N 的数中 52 的倍数再贡献一个 5 ...。 - -```java -public int trailingZeroes(int n) { -    return n == 0 ? 0 : n / 5 + trailingZeroes(n / 5); -} -``` - -如果统计的是 N! 的二进制表示中最低位 1 的位置,只要统计有多少个 2 即可,该题目出自 [ 编程之美:2.2](#) 。和求解有多少个 5 一样,2 的个数为 N/2 + N/22 + N/23 + ... - -### 字符串加法减法 - -**二进制加法** - -[Leetcode : 67. Add Binary (Easy)](https://leetcode.com/problems/add-binary/description/) - -```java -public String addBinary(String a, String b) { -    int i = a.length() - 1, j = b.length() - 1, carry = 0; -    String str = ""; -    while(i >= 0 || j >= 0){ -        if(i >= 0 && a.charAt(i--) == '1') carry++; -        if(j >= 0 && b.charAt(j--) == '1') carry++; -        str = (carry % 2) + str; -        carry /= 2; -    } -    if(carry == 1) str = "1" + str; -    return str; -} -``` - -**字符串加法** - -[Leetcode : 415. Add Strings (Easy)](https://leetcode.com/problems/add-strings/description/) - -题目描述:字符串的值为非负整数 - -```java -public String addStrings(String num1, String num2) { -    StringBuilder sb = new StringBuilder(); -    int carry = 0; -    for(int i = num1.length() - 1, j = num2.length() - 1; i >= 0 || j >= 0 || carry == 1; i--, j--){ -        int x = i < 0 ? 0 : num1.charAt(i) - '0'; -        int y = j < 0 ? 0 : num2.charAt(j) - '0'; -        sb.append((x + y + carry) % 10); -        carry = (x + y + carry) / 10; -    } -    return sb.reverse().toString(); -} -``` - -### 相遇问题 - -**改变数组元素使所有的数组元素都相等** - -[Leetcode : 462. Minimum Moves to Equal Array Elements II (Medium)](https://leetcode.com/problems/minimum-moves-to-equal-array-elements-ii/description/) - -题目描述:每次可以对一个数组元素加一或者减一,求最小的改变次数。 - -这是个典型的相遇问题,移动距离最小的方式是所有元素都移动到中位数。理由如下: - -设 m 为中位数。a 和 b 是 m 两边的两个元素,且 b > a。要使 a 和 b 相等,它们总共移动的次数为 b - a,这个值等于 (b - m) + (m - a),也就是把这两个数移动到中位数的移动次数。 - -设数组长度为 N,则可以找到 N/2 对 a 和 b 的组合,使它们都移动到 m 的位置。 - -**解法 1** - -先排序,时间复杂度:O(NlgN) - -```java -public int minMoves2(int[] nums) { -    Arrays.sort(nums); -    int ret = 0; -    int l = 0, h = nums.length - 1; -    while(l <= h) { -        ret += nums[h] - nums[l]; -        l++; -        h--; -    } -    return ret; -} -``` - -**解法 2** - -使用快速排序找到中位数,时间复杂度 O(N) - -```java -public int minMoves2(int[] nums) { -    int ret = 0; -    int n = nums.length; -    int median = quickSelect(nums, 0, n - 1, n / 2 + 1); -    for(int num : nums) ret += Math.abs(num - median); -    return ret; -} - -private int quickSelect(int[] nums, int start, int end, int k) { -    int l = start, r = end, privot = nums[(l + r) / 2]; -    while(l <= r) { -        while(nums[l] < privot) l++; -        while(nums[r] > privot) r--; -        if(l >= r) break; -        swap(nums, l, r); -        l++; r--; -    } -    int left = l - start + 1; -    if(left > k) return quickSelect(nums, start, l - 1, k); -    if(left == k && l == r) return nums[l]; -    int right = r - start + 1; -    return quickSelect(nums, r + 1, end, k - right); -} - -private void swap(int[] nums, int i, int j) { -    int tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; -} -``` - -### 多数投票问题 - -**数组中出现次数多于 n / 2 的元素** - -[Leetcode : 169. Majority Element (Easy)](https://leetcode.com/problems/majority-element/description/) - -先对数组排序,最中间那个数出现次数一定多于 n / 2 - -```java -public int majorityElement(int[] nums) { -    Arrays.sort(nums); -    return nums[nums.length / 2]; -} -``` - -可以利用 Boyer-Moore Majority Vote Algorithm 来解决这个问题,使得时间复杂度为 O(n)。可以这么理解该算法:使用 cnt 来统计一个元素出现的次数,当遍历到的元素和统计元素不想等时,令 cnt--。如果前面查找了 i 个元素,且 cnt == 0 ,说明前 i 个元素没有 majority,或者有 majority,但是出现的次数少于 i / 2 ,因为如果多于 i / 2 的话 cnt 就一定不会为 0 。此时剩下的 n - i 个元素中,majority 的数目多于 (n - i) / 2,因此继续查找就能找出 majority。 - -```java -public int majorityElement(int[] nums) { -    int cnt = 0, majority = 0; -    for(int i = 0; i < nums.length; i++){ -        if(cnt == 0) { -            majority = nums[i]; -            cnt++; -        } -        else if(majority == nums[i]) cnt++; -        else cnt--; -    } -    return majority; -} -``` - -**数组中出现次数多于 n / k 的元素** - -[程序员代码面试指南 P343](#) - -选 k - 1 个候选 - -### 其它 - -**平方数** - -[Leetcode : 367. Valid Perfect Square (Easy)](https://leetcode.com/problems/valid-perfect-square/description/) - -平方序列:1,4,9,16,.. -间隔:3,5,7,... - -间隔为等差数列,使用这个特性可以得到从 1 开始的平方序列。 - -```java -public boolean isPerfectSquare(int num) { -    int subNum = 1; -    while (num > 0) { -        num -= subNum; -        subNum += 2; -    } -    return num == 0; -} -``` - -**3 的 n 次方** - -[Leetcode : 326. Power of Three (Easy)](https://leetcode.com/problems/power-of-three/description/) - -```java -public boolean isPowerOfThree(int n) { -    return n > 0 && (1162261467 % n == 0); -} -``` - -**找出数组中的乘积最大的三个数** - -[Leetcode : 628. Maximum Product of Three Numbers (Easy)](https://leetcode.com/problems/maximum-product-of-three-numbers/description/) - -```java -public int maximumProduct(int[] nums) { -    int max1 = Integer.MIN_VALUE, max2 = Integer.MIN_VALUE, max3 = Integer.MIN_VALUE, min1 = Integer.MAX_VALUE, min2 = Integer.MAX_VALUE; -    for (int n : nums) { -        if (n > max1) { -            max3 = max2; -            max2 = max1; -            max1 = n; -        } else if (n > max2) { -            max3 = max2; -            max2 = n; -        } else if (n > max3) { -            max3 = n; -        } - -        if (n < min1) { -            min2 = min1; -            min1 = n; -        } else if (n < min2) { -            min2 = n; -        } -    } -    return Math.max(max1*max2*max3, max1*min1*min2); -} -``` - -**乘积数组** - -[Leetcode : 238. Product of Array Except Self (Medium)](https://leetcode.com/problems/product-of-array-except-self/description/) - -题目描述:给定一个数组,创建一个新数组,新数组的每个元素为原始数组中除了该位置上的元素之外所有元素的乘积。 - -题目要求:时间复杂度为 O(n),并且不能使用除法。 - -```java -public int[] productExceptSelf(int[] nums) { -    int n = nums.length; -    int[] ret = new int[n]; -    ret[0] = 1; -    for(int i = 1; i < n; i++) { -        ret[i] = ret[i - 1] * nums[i - 1]; -    } -    int right = 1; -    for(int i = n - 1; i >= 0; i--) { -        ret[i] *= right; -        right *= nums[i]; -    } -    return ret; -} -``` - -# 数据结构相关 - -## 栈和队列 - -**用栈实现队列** - -一个栈实现: - -```java -class  MyQueue { -    private Stack st = new Stack(); - -    public void push(int x) { -        Stack temp = new Stack(); -        while(!st.isEmpty()){ -            temp.push(st.pop()); -        } -        st.push(x); -        while(!temp.isEmpty()){ -            st.push(temp.pop()); -        } -    } - -    public int pop() { -        return st.pop(); -    } - -    public int peek() { -        return st.peek(); -    } - -    public boolean empty() { -        return st.isEmpty(); -    } -} -``` - -两个栈实现: - -```java -class  MyQueue { -    private Stack in = new Stack(); -    private Stack out = new Stack(); -   -    public void push(int x) { -        in.push(x); -    } - -    public int pop() { -        in2out(); -        return out.pop(); -    } - -    public int peek() { -        in2out(); -        return out.peek(); -    } -     -    private void in2out(){ -        if(out.isEmpty()){ -            while(!in.isEmpty()){ -                out.push(in.pop()); -            } -        } -    } - -    public boolean empty() { -        return in.isEmpty() && out.isEmpty(); -    } -} -``` - -**用队列实现栈** - -[Leetcode : 225. Implement Stack using Queues (Easy)](https://leetcode.com/problems/implement-stack-using-queues/description/) - -```java -class MyStack { -     -    private Queue queue; - -    public MyStack() { -        queue = new LinkedList<>(); -    } -     -    public void push(int x) { -        queue.add(x); -        for(int i = 1; i < queue.size(); i++){ // 翻转 -            queue.add(queue.remove()); -        } -    } -     -    public int pop() { -        return queue.remove(); -    } -     -    public int top() { -        return queue.peek(); -    } -     -    public boolean empty() { -        return queue.isEmpty(); -    } -} -``` - -**最小值栈** - -[Leetcode : 155. Min Stack (Easy)](https://leetcode.com/problems/min-stack/description/) - -用两个栈实现,一个存储数据,一个存储最小值。 - -```java -class MinStack { -     -    private Stack dataStack; -    private Stack minStack; -    private int min; - -    public MinStack() { -        dataStack = new Stack<>(); -        minStack = new Stack<>(); -        min = Integer.MAX_VALUE; -    } -     -    public void push(int x) { -        dataStack.add(x); -        if(x < min) { -            min = x; -        } -        minStack.add(min); -    } -     -    public void pop() { -        dataStack.pop(); -        minStack.pop(); -        if(!minStack.isEmpty()) { -            min = minStack.peek(); -        } else{ -            min = Integer.MAX_VALUE; -        } -    } -     -    public int top() { -        return dataStack.peek(); -    } -     -    public int getMin() { -        return min; -    } -} -``` - -对于实现最小值队列问题,可以先将队列使用栈来实现,然后就将问题转换为最小值栈,这个问题出现在 编程之美:3.7。 - -**用栈实现括号匹配** - -[Leetcode : 20. Valid Parentheses (Easy)](https://leetcode.com/problems/valid-parentheses/description/) - -```html -"()[]{}" - -Output : true -``` - -```java -public boolean isValid(String s) { -    Stack stack = new Stack<>(); -    for(int i = 0; i < s.length(); i++){ -        char c = s.charAt(i); -        if(c == '(' || c == '{' || c == '[') stack.push(c); -        else{ -            if(stack.isEmpty()) return false; -            char cStack = stack.pop(); -            if(c == ')' && cStack != '(' || -              c == ']' && cStack != '[' || -              c == '}' && cStack != '{' ) { -                return false; -            } -        } -    } -    return stack.isEmpty(); -} -``` - -**数组中比当前元素大的下一个数组元素的距离** - -```html -Input: [73, 74, 75, 71, 69, 72, 76, 73] -Output: [1, 1, 4, 2, 1, 1, 0, 0] -``` - -[Leetcode : 739. Daily Temperatures (Medium)](https://leetcode.com/problems/daily-temperatures/description/) - -使用栈来存储还未计算的元素。可以保证从栈顶向下元素递增,否则上面有一个比下面某个元素大的元素进入栈中,下面那个元素已经找到比它大的元素,因此会出栈。 - -```java -public int[] dailyTemperatures(int[] temperatures) { -    int n = temperatures.length; -    int[] ret = new int[n]; -    Stack stack = new Stack<>(); -    for(int i = 0; i < n; i++) { -        while(!stack.isEmpty() && temperatures[i] > temperatures[stack.peek()]) { -            int idx = stack.pop(); -            ret[idx] = i - idx; -        } -        stack.add(i); -    } -    return ret; -} -``` - -**数组中下一个比当前数大的数** - -[Leetcode : 496. Next Greater Element I (Easy)](https://leetcode.com/problems/next-greater-element-i/description/) - -```html -Input: nums1 = [4,1,2], nums2 = [1,3,4,2]. -Output: [-1,3,-1] -``` - -在遍历数组时用 Stack 把数组中的数存起来,如果当前遍历的数比栈顶元素来的大,说明栈顶元素的下一个比它大的数就是当前元素。 - -```java -public int[] nextGreaterElement(int[] nums1, int[] nums2) { -    Map map = new HashMap<>(); -    Stack stack = new Stack<>(); -    for(int num : nums2){ -        while(!stack.isEmpty() && num > stack.peek()){ -            map.put(stack.pop(), num); -        } -        stack.add(num); -    } -    int[] ret = new int[nums1.length]; -    for(int i = 0; i < nums1.length; i++){ -        if(map.containsKey(nums1[i])) ret[i] = map.get(nums1[i]); -        else ret[i] = -1; -    } -    return ret; -} -``` - -**循环数组中下一个比当前元素大的数** - -[Leetcode : 503. Next Greater Element II (Medium)](https://leetcode.com/problems/next-greater-element-ii/description/) - -```java -public int[] nextGreaterElements(int[] nums) { -    int n = nums.length, next[] = new int[n]; -    Arrays.fill(next, -1); -    Stack stack = new Stack<>(); -    for (int i = 0; i < n * 2; i++) { -        int num = nums[i % n]; -        while (!stack.isEmpty() && nums[stack.peek()] < num) -            next[stack.pop()] = num; -        if (i < n) stack.push(i); -    } -    return next; -} -``` - - -## 哈希表 - -利用 Hash Table 可以快速查找一个元素是否存在等问题,但是需要一定的空间来存储。在优先考虑时间复杂度的情况下,可以利用 Hash Table 这种空间换取时间的做法。 - -Java 中的 **HashSet** 用于存储一个集合,并以 O(1) 的时间复杂度查找元素是否在集合中。 - -如果元素有穷,并且范围不大,那么可以用一个布尔数组来存储一个元素是否存在,例如对于只有小写字符的元素,就可以用一个长度为 26 的布尔数组来存储一个字符集合,使得空间复杂度降低为 O(1)。 - -Java 中的 **HashMap** 主要用于映射关系,从而把两个元素联系起来。 - -在对一个内容进行压缩或者其它转换时,利用 HashMap 可以把原始内容和转换后的内容联系起来。例如在一个简化 url 的系统中([Leetcdoe : 535. Encode and Decode TinyURL (Medium)](https://leetcode.com/problems/encode-and-decode-tinyurl/description/)),利用 HashMap 就可以存储精简后的 url 到原始 url 的映射,使得不仅可以显示简化的 url,也可以根据简化的 url 得到原始 url 从而定位到正确的资源。 - -HashMap 也可以用来对元素进行计数统计,此时键为元素,值为计数。和 HashSet 类似,如果元素有穷并且范围不大,可以用整型数组来进行统计。 - - -**数组中的两个数和为给定值** - -[Leetcode : 1. Two Sum (Easy)](https://leetcode.com/problems/two-sum/description/) - -可以先对数组进行排序,然后使用双指针方法或者二分查找方法。这样做的时间复杂度为 O(nlgn),空间复杂度为 O(1)。 - -用 HashMap 存储数组元素和索引的映射,在访问到 nums[i] 时,判断 HashMap 中是否存在 target - nums[i] ,如果存在说明 target - nums[i] 所在的索引和 i 就是要找的两个数。该方法的时间复杂度为 O(n),空间复杂度为 O(n),使用空间来换取时间。 - -```java -public int[] twoSum(int[] nums, int target) { -    HashMap map = new HashMap<>(); -    for(int i = 0; i < nums.length; i++){ -        if(map.containsKey(target - nums[i])) return new int[]{map.get(target - nums[i]), i}; -        else map.put(nums[i], i); -    } -    return null; -} -``` - -**最长和谐序列** - -和谐序列中最大数和最小数只差正好为 1 - -[Leetcode : 594. Longest Harmonious Subsequence (Easy)](https://leetcode.com/problems/longest-harmonious-subsequence/description/) - -```java -public int findLHS(int[] nums) { -    Map map = new HashMap<>(); -    for (long num : nums) { -        map.put(num, map.getOrDefault(num, 0) + 1); -    } -    int result = 0; -    for (long key : map.keySet()) { -        if (map.containsKey(key + 1)) { -            result = Math.max(result, map.get(key + 1) + map.get(key)); -        } -    } -    return result; -} -``` - -## 字符串 - -**两个字符串的包含的字符是否完全相同** - -[Leetcode : 242. Valid Anagram (Easy)](https://leetcode.com/problems/valid-anagram/description/) - -字符串只包含小写字符,总共有 26 个小写字符。可以用 Hash Table 来映射字符与出现次数,因为键值范围很小,因此可以用数组来进行映射。 - -使用长度为 26 的整型数组对字符串出现的字符进行统计,比较两个字符串出现的字符数量是否相同。 - -```java -public boolean isAnagram(String s, String t) { -    int[] cnts = new int[26]; -    for(int i  = 0; i < s.length(); i++) cnts[s.charAt(i) - 'a'] ++; -    for(int i  = 0; i < t.length(); i++) cnts[t.charAt(i) - 'a'] --; -    for(int i  = 0; i < 26; i++) if(cnts[i] != 0) return false; -    return true; -} -``` - -**字符串同构** - -[Leetcode : 205. Isomorphic Strings (Easy)](https://leetcode.com/problems/isomorphic-strings/description/) - -例如 "egg" 和 "add" 就属于同构字符串。 - -记录一个字符上次出现的位置,如果两个字符串中某个字符上次出现的位置一样,那么就属于同构。 - -```java -public boolean isIsomorphic(String s, String t) { -    int[] m1 = new int[256]; -    int[] m2 = new int[256]; -    for(int i = 0; i < s.length(); i++){ -        if(m1[s.charAt(i)] != m2[t.charAt(i)]) { -            return false; -        } -        m1[s.charAt(i)] = i + 1; -        m2[t.charAt(i)] = i + 1; -    } -    return true; -} -``` - -**计算一组字符集合可以组成的回文字符串的最大长度** - -[Leetcode : 409. Longest Palindrome](https://leetcode.com/problems/longest-palindrome/description/) - -使用长度为 128 的整型数组来统计每个字符出现的个数,每个字符有偶数个可以用来构成回文字符串。因为回文字符串最中间的那个字符可以单独出现,所以如果有单独的字符就把它放到最中间。 - -```java -public int longestPalindrome(String s) { -    int[] cnts = new int[128]; // ascii 码总共 128 个 -    for(char c : s.toCharArray()) cnts[c]++; -    int ret = 0; -    for(int cnt : cnts)  ret += (cnt / 2) * 2; -    if(ret < s.length()) ret ++; // 这个条件下 s 中一定有单个未使用的字符存在,可以把这个字符放到回文的最中间 -    return ret; -} -``` - -**判断一个整数是否是回文数** - -[Leetcode : 9. Palindrome Number (Easy)](https://leetcode.com/problems/palindrome-number/description/) - -要求不能使用额外空间,也就不能将整数转换为字符串进行判断。 - -将整数分成左右两部分,右边那部分需要转置,然后判断这两部分是否相等。 - -```java -public boolean isPalindrome(int x) { -    if(x == 0) return true; -    if(x < 0) return false; -    if(x % 10 == 0) return false; -    int right = 0; -    while(x > right){ -        right = right * 10 + x % 10; -        x /= 10; -    } -    return x == right || x == right / 10; -} -``` - -**回文子字符串** - -[Leetcode : 647. Palindromic Substrings (Medium)](https://leetcode.com/problems/palindromic-substrings/description/) - -解决方案是从字符串的某一位开始,尝试着去扩展子字符串。 - -```java -private int cnt = 0; -public int countSubstrings(String s) { -    for(int i = 0; i < s.length(); i++) { -        extendSubstrings(s, i, i);    // 奇数长度 -        extendSubstrings(s, i, i + 1); // 偶数长度 -    } -    return cnt; -} - -private void extendSubstrings(String s, int start, int end) { -    while(start >= 0 && end < s.length() && s.charAt(start) == s.charAt(end)) { -        start--; -        end++; -        cnt++; -    } -} -``` - -**统计二进制字符串中连续 1 和 连续 0 数量相同的子字符串个数** - -```html -Input: "00110011" -Output: 6 -Explanation: There are 6 substrings that have equal number of consecutive 1's and 0's: "0011", "01", "1100", "10", "0011", and "01". -``` - -[Leetcode : 696. Count Binary Substrings (Easy)](https://leetcode.com/problems/count-binary-substrings/description/) - -```java -public int countBinarySubstrings(String s) { -    int preLen = 0, curLen = 1, ret = 0; -    for(int i = 1; i < s.length(); i++){ -        if(s.charAt(i) == s.charAt(i-1)) curLen++; -        else{ -            preLen = curLen; -            curLen = 1; -        } - -        if(preLen >= curLen) ret++; -    } -    return ret; -} -``` - -**字符串循环移位包含** - -[ 编程之美:3.1](#) - -给定两个字符串 s1 和 s2 ,要求判定 s2 是否能够被 s1 做循环移位得到的字符串包含。 - -```html -s1 = AABCD, s2 = CDAA -Return : true -``` - -s1 进行循环移位的结果是 s1s1 的子字符串,因此只要判断 s2 是否是 s1s1 的子字符串即可。 - -**字符串循环移位** - -[ 编程之美:2.17](#) - -将字符串向右循环移动 k 位。 - -例如 abcd123 向右移动 3 位 得到 123abcd - -将 abcd123 中的 abcd 和 123 单独逆序,得到 dcba321,然后对整个字符串进行逆序,得到123abcd。 - -**字符串中单词的翻转** - -[程序员代码面试指南](#) - -例如将 "I am a student" 翻转成 "student a am I" - -将每个单词逆序,然后将整个字符串逆序。 - -## 数组与矩阵 - -**矩阵旋转** // TODO - -[程序员面试金典 P114](#) - -**之字型打印** //TODO - -[程序员代码面试指南 P335](#) - -**把数组中的 0 移到末尾** - -[Leetcode : 283. Move Zeroes (Easy)](https://leetcode.com/problems/move-zeroes/description/) - -```java -    public void moveZeroes(int[] nums) { -        int n = nums.length; -        int idx = 0; -        for(int i = 0; i < n; i++){ -            if(nums[i] != 0) nums[idx++] = nums[i]; -        } -        while(idx < n){ -            nums[idx++] = 0; -        } -    } -``` - -**一个数组元素在 [1, n] 之间,其中一个数被替换为另一个数,找出丢失的数和重复的数** - -[Leetcode : 645. Set Mismatch (Easy)](https://leetcode.com/problems/set-mismatch/description/) - -最直接的方法是先对数组进行排序,这种方法时间复杂度为 O(nlogn),本题可以以 O(n) 的时间复杂度、O(1) 空间复杂度来求解。 - -主要思想是让通过交换数组元素,使得数组上的元素在正确的位置上。 - -遍历数组,如果第 i 位上的元素不是 i + 1 ,那么就交换第 i 位 和 nums[i] - 1 位上的元素,使得 num[i] - 1 的元素为 nums[i] ,也就是该位的元素是正确的。交换操作需要循环进行,因为一次交换没办法使得第 i 位上的元素是正确的。但是要交换的两个元素可能就是重复元素,那么循环就可能永远进行下去,终止循环的方法是加上 nums[i] != nums[nums[i] - 1 条件。 - -类似题目: - -- [Leetcode :448. Find All Numbers Disappeared in an Array (Easy)](https://leetcode.com/problems/find-all-numbers-disappeared-in-an-array/description/),寻找所有丢失的元素 -- [Leetcode : 442. Find All Duplicates in an Array (Medium)](https://leetcode.com/problems/find-all-duplicates-in-an-array/description/),寻找所有重复的元素。 - -```java -public int[] findErrorNums(int[] nums) { -    for(int i = 0; i < nums.length; i++){ -        while(nums[i] != i + 1 && nums[i] != nums[nums[i] - 1]) swap(nums, i, nums[i] - 1); -    } -     -    for(int i = 0; i < nums.length; i++){ -        if(i + 1 != nums[i]) return new int[]{nums[i], i + 1}; -    } -     -    return null; -} - -private void swap(int[] nums, int i, int j){ -    int tmp = nums[i]; -    nums[i] = nums[j]; -    nums[j] = tmp; -} -``` - -**找出数组中重复的数,数组值在 [0, n-1] 之间** - -[Leetcode : 287. Find the Duplicate Number (Medium)](https://leetcode.com/problems/find-the-duplicate-number/description/) - -二分查找解法: - -```java -public int findDuplicate(int[] nums) { -     int l = 1, h = nums.length - 1; -     while (l <= h) { -         int mid = l + (h - l) / 2; -         int cnt = 0; -         for (int i = 0; i < nums.length; i++) { -             if (nums[i] <= mid) cnt++; -         } -         if (cnt > mid) h = mid - 1; -         else l = mid + 1; -     } -     return l; -} -``` - -双指针解法,类似于有环链表中找出环的入口: - -```java -public int findDuplicate(int[] nums) { -      int slow = nums[0], fast = nums[nums[0]]; -      while (slow != fast) { -          slow = nums[slow]; -          fast = nums[nums[fast]]; -      } - -      fast = 0; -      while (slow != fast) { -          slow = nums[slow]; -          fast = nums[fast]; -      } -      return slow; -} -``` - -### 有序矩阵 - -有序矩阵指的是行和列分别有序的矩阵。 - -一般可以利用有序性使用二分查找方法。 - -```html -[ -   [ 1,  5,  9], -   [10, 11, 13], -   [12, 13, 15] -] -``` - -**有序矩阵查找** - -[Leetocde : 240. Search a 2D Matrix II (Medium)](https://leetcode.com/problems/search-a-2d-matrix-ii/description/) - -```java -public boolean searchMatrix(int[][] matrix, int target) { -    if (matrix == null || matrix.length == 0 || matrix[0].length == 0) return false; -    int m = matrix.length, n = matrix[0].length; -    int row = 0, col = n - 1; -    while (row < m && col >= 0) { -        if (target == matrix[row][col]) return true; -        else if (target < matrix[row][col]) col--; -        else row++; -    } -    return false; -} -``` - -**有序矩阵的 Kth Element** - -[Leetcode : 378. Kth Smallest Element in a Sorted Matrix ((Medium))](https://leetcode.com/problems/kth-smallest-element-in-a-sorted-matrix/description/) - -```html -matrix = [ -  [ 1,  5,  9], -  [10, 11, 13], -  [12, 13, 15] -], -k = 8, - -return 13. -``` - -解题参考:[Share my thoughts and Clean Java Code](https://leetcode.com/problems/kth-smallest-element-in-a-sorted-matrix/discuss/85173) - -二分查找解法: - -```java -public int kthSmallest(int[][] matrix, int k) { -    int m = matrix.length, n = matrix[0].length; -    int lo = matrix[0][0], hi = matrix[m - 1][n - 1]; -    while(lo <= hi) { -        int mid = lo + (hi - lo) / 2; -        int cnt = 0; -        for(int i = 0; i < m; i++) { -            for(int j = 0; j < n && matrix[i][j] <= mid; j++) { -                cnt++; -            } -        } -        if(cnt < k) lo = mid + 1; -        else hi = mid - 1; -    } -    return lo; -} -``` - -堆解法: - -```java -public int kthSmallest(int[][] matrix, int k) { -    int m = matrix.length, n = matrix[0].length; -    PriorityQueue pq = new PriorityQueue(); -    for(int j = 0; j < n; j++) pq.offer(new Tuple(0, j, matrix[0][j])); -    for(int i = 0; i < k - 1; i++) { // 小根堆,去掉 k - 1 个堆顶元素,此时堆顶元素就是第 k 的数 -        Tuple t = pq.poll(); -        if(t.x == m - 1) continue; -        pq.offer(new Tuple(t.x + 1, t.y, matrix[t.x + 1][t.y])); -    } -    return pq.poll().val; -} - -class Tuple implements Comparable { -    int x, y, val; -    public Tuple(int x, int y, int val) { -        this.x = x; this.y = y; this.val = val; -    } - -    @Override -    public int compareTo(Tuple that) { -        return this.val - that.val; -    } -} -``` - -## 链表 - -**判断两个链表的交点** - -[Leetcode : 160. Intersection of Two Linked Lists](https://leetcode.com/problems/intersection-of-two-linked-lists/description/) - -```html -A:          a1 → a2 -                  ↘ -                    c1 → c2 → c3 -                  ↗ -B:    b1 → b2 → b3 -``` - -要求:时间复杂度为 O(n) 空间复杂度为 O(1) - -设 A 的长度为 a + c,B 的长度为 b + c,其中 c 为尾部公共部分长度,可知 a + c + b = b + c + a。 - -当访问 A 链表的指针访问到链表尾部时,令它从链表 B 的头部开始访问链表 B;同样地,当访问 B 链表的指针访问到链表尾部时,令它从链表 A 的头部开始访问链表 A。这样就能控制访问 A 和 B 两个链表的指针能同时访问到交点。 - -```java -public ListNode getIntersectionNode(ListNode headA, ListNode headB) { -    if(headA == null || headB == null) return null; -    ListNode l1 = headA, l2 = headB; -    while(l1 != l2){ -        l1 = (l1 == null) ? headB : l1.next; -        l2 = (l2 == null) ? headA : l2.next; -    } -    return l1; -} -``` - -如果只是判断是否存在交点,那么就是另一个问题,即 编程之美:3.6 的问题。有两种解法:把第一个链表的结尾连接到第二个链表的开头,看第二个链表是否存在环;或者直接比较第一个链表最后一个节点和第二个链表最后一个节点是否相同。 - - - -**链表反转** - -[Leetcode : 206. Reverse Linked List](https://leetcode.com/problems/reverse-linked-list/description/) - -头插法能够按逆序构建链表。 - -```java -public ListNode reverseList(ListNode head) { -    ListNode newHead = null; // 设为 null ,作为新链表的结尾 -    while(head != null){ -        ListNode nextNode = head.next; -        head.next = newHead; -        newHead = head; -        head = nextNode; -    } -    return newHead; -} -``` - -**归并两个有序的链表** - -[Leetcode : 21. Merge Two Sorted Lists](https://leetcode.com/problems/merge-two-sorted-lists/description/) - -链表和树一样,可以用递归方式来定义:链表是空节点,或者有一个值和一个指向下一个链表的指针,因此很多链表问题可以用递归来处理。 - -```java -public ListNode mergeTwoLists(ListNode l1, ListNode l2) { -    if(l1 == null) return l2; -    if(l2 == null) return l1; -    ListNode newHead = null; -    if(l1.val < l2.val){ -        newHead = l1; -        newHead.next = mergeTwoLists(l1.next, l2); -    } else{ -        newHead = l2; -        newHead.next = mergeTwoLists(l1, l2.next); -    } -    return newHead; -} -``` - -**从有序链表中删除重复节点** - -[Leetcode : 83. Remove Duplicates from Sorted List (Easy)](https://leetcode.com/problems/remove-duplicates-from-sorted-list/description/) - -```java -public ListNode deleteDuplicates(ListNode head) { -    if(head == null || head.next == null) return head; -    head.next = deleteDuplicates(head.next); -    return head.next != null && head.val == head.next.val ? head.next : head; -} -``` - -**回文链表** - -[Leetcode : 234. Palindrome Linked List (Easy)](https://leetcode.com/problems/palindrome-linked-list/description/) - -切成两半,把后半段反转,然后比较两半是否相等。 - -```java -public boolean isPalindrome(ListNode head) { -    if(head == null || head.next == null) return true; -    ListNode slow = head, fast = head.next; -    while(fast != null && fast.next != null){ -        slow = slow.next; -        fast = fast.next.next; -    } - -    if(fast != null){  // 偶数节点,让 slow 指向下一个节点 -        slow = slow.next; -    } - -    cut(head, slow); // 切成两个链表 -    ListNode l1 = head, l2 = slow; -    l2 = reverse(l2); -    return isEqual(l1, l2); -} - -private void cut(ListNode head, ListNode cutNode){ -    while( head.next != cutNode ) head = head.next; -    head.next = null; -} - -private ListNode reverse(ListNode head){ -    ListNode newHead = null; -    while(head != null){ -        ListNode nextNode = head.next; -        head.next = newHead; -        newHead = head; -        head = nextNode; -    } -    return newHead; -} - -private boolean isEqual(ListNode l1, ListNode l2){ -    while(l1 != null && l2 != null){ -        if(l1.val != l2.val) return false; -        l1 = l1.next; -        l2 = l2.next; -    } -    return true; -} -``` - -**从链表中删除节点** - -[编程之美:3.4]() - -![](index_files/2c968ec5-0967-49ce-ac06-f8f5c9ab33bc.jpg) - -```java -B.val = C.val; -B.next = C.next; -``` - -**链表元素按奇偶聚集** - -[Leetcode : 328. Odd Even Linked List (Medium)](https://leetcode.com/problems/odd-even-linked-list/description/) - -```java -public ListNode oddEvenList(ListNode head) { -    if (head == null) { -        return head; -    } -    ListNode odd = head, even = head.next, evenHead = even; -    while (even != null && even.next != null) { -        odd.next = odd.next.next; -        odd = odd.next; -        even.next = even.next.next; -        even = even.next; -    } -    odd.next = evenHead; -    return head; -} -``` - -## 树 - -### 递归 - -一棵树要么是空树,要么有两个指针,每个指针指向一棵树。树是一种递归结构,很多树的问题可以使用递归来处理。 - -**树的高度** - -[Leetcode : 104. Maximum Depth of Binary Tree (Easy)](https://leetcode.com/problems/maximum-depth-of-binary-tree/description/) - -```java -public int maxDepth(TreeNode root) { -    if(root == null) return 0; -    return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1; -} -``` - -**翻转树** - -[Leetcode : 226. Invert Binary Tree (Easy)](https://leetcode.com/problems/invert-binary-tree/description/) - -```java -public TreeNode invertTree(TreeNode root) { -    if(root == null) return null; -    TreeNode left = root.left; // 后面的操作会改变 left 指针,因此先保存下来 -    root.left = invertTree(root.right); -    root.right = invertTree(left); -    return root; -} -``` - -**归并两棵树** - -[Leetcode : 617. Merge Two Binary Trees (Easy)](https://leetcode.com/problems/merge-two-binary-trees/description/) - -```java -public TreeNode mergeTrees(TreeNode t1, TreeNode t2) { -    if(t1 == null && t2 == null) return null; -    if(t1 == null) return t2; -    if(t2 == null) return t1; -    TreeNode root = new TreeNode(t1.val + t2.val); -    root.left = mergeTrees(t1.left, t2.left); -    root.right = mergeTrees(t1.right, t2.right); -    return root; -} -``` - -**判断路径和是否等于一个数** - -[Leetcdoe : 112. Path Sum (Easy)](https://leetcode.com/problems/path-sum/description/) - -题目描述:路径和定义为从 root 到 leaf 的所有节点的和 - -```java -public boolean hasPathSum(TreeNode root, int sum) { -    if(root == null) return false; -    if(root.left == null && root.right == null && root.val == sum) return true; -    return hasPathSum(root.left, sum - root.val) || hasPathSum(root.right, sum - root.val); -} -``` - -**统计路径和等于一个数的路径数量** - -[Leetcode : 437. Path Sum III (Easy)](https://leetcode.com/problems/path-sum-iii/description/) - -题目描述:路径不一定以 root 开头并以 leaf 结尾,但是必须连续 - -pathSumStartWithRoot() 方法统计以某个节点开头的路径个数。 - -```java -public int pathSum(TreeNode root, int sum) { -    if(root == null) return 0; -    int ret = pathSumStartWithRoot(root, sum) + pathSum(root.left, sum) + pathSum(root.right, sum); -    return ret; -} - -private int pathSumStartWithRoot(TreeNode root, int sum){ -    if(root == null) return 0; -    int ret = 0; -    if(root.val == sum) ret++; -    ret += pathSumStartWithRoot(root.left, sum - root.val) + pathSumStartWithRoot(root.right, sum - root.val); -    return ret; -} -``` - -**树的对称** - -[Leetcode : 101. Symmetric Tree (Easy)](https://leetcode.com/problems/symmetric-tree/description/) - -```java -public boolean isSymmetric(TreeNode root) { -    if(root == null) return true; -    return isSymmetric(root.left, root.right); -} - -private boolean isSymmetric(TreeNode t1, TreeNode t2){ -    if(t1 == null && t2 == null) return true; -    if(t1 == null || t2 == null) return false; -    if(t1.val != t2.val) return false; -    return isSymmetric(t1.left, t2.right) && isSymmetric(t1.right, t2.left); -} -``` - -**平衡树** - -[Leetcode : 110. Balanced Binary Tree (Easy)](https://leetcode.com/problems/balanced-binary-tree/description/) - -题目描述:左右子树高度差是否都小于等于 1 - -```java -private boolean result = true; - -public boolean isBalanced(TreeNode root) { -    maxDepth(root); -    return result; -} - -public int maxDepth(TreeNode root) { -    if (root == null) return 0; -    int l = maxDepth(root.left); -    int r = maxDepth(root.right); -    if (Math.abs(l - r) > 1) result = false; -    return 1 + Math.max(l, r); -} -``` - -**最小路径** - -[Leetcode : 111. Minimum Depth of Binary Tree (Easy)](https://leetcode.com/problems/minimum-depth-of-binary-tree/description/) - -题目描述:树的根节点到叶子节点的最小长度 - -```java -public int minDepth(TreeNode root) { -    if(root == null) return 0; -    int left = minDepth(root.left); -    int right = minDepth(root.right); -    if(left == 0 || right == 0) return left + right + 1; -    return Math.min(left, right) + 1; -} -``` - -**统计左叶子节点的和** - -[Leetcode : 404. Sum of Left Leaves (Easy)](https://leetcode.com/problems/sum-of-left-leaves/description/) - -```java -public int sumOfLeftLeaves(TreeNode root) { -    if(root == null) return 0; -    if(isLeaf(root.left)) return root.left.val +  sumOfLeftLeaves(root.right); -    return sumOfLeftLeaves(root.left) + sumOfLeftLeaves(root.right); -} - -private boolean isLeaf(TreeNode node){ -    if(node == null) return false; -    return node.left == null && node.right == null; -} -``` - -**修剪一棵树** - -[Leetcode : 669. Trim a Binary Search Tree (Easy)](https://leetcode.com/problems/trim-a-binary-search-tree/description/) - -题目描述:只保留值在 L ~ R 之间的节点 - -```java -public TreeNode trimBST(TreeNode root, int L, int R) { -    if(root == null) return null; -    if(root.val > R) return trimBST(root.left, L, R); -    if(root.val < L) return trimBST(root.right, L, R); -    root.left = trimBST(root.left, L, R); -    root.right = trimBST(root.right, L, R); -    return root; -} -``` - -**子树** - -[Leetcode : 572. Subtree of Another Tree (Easy)](https://leetcode.com/problems/subtree-of-another-tree/description/) - -```java -public boolean isSubtree(TreeNode s, TreeNode t) { -    if(s == null && t == null) return true; -    if(s == null || t == null) return false; -    if(s.val == t.val && isSame(s, t)) return true; -    return isSubtree(s.left, t) || isSubtree(s.right, t); -} - -private boolean isSame(TreeNode s, TreeNode t){ -    if(s == null && t == null) return true; -    if(s == null || t == null) return false; -    if(s.val != t.val) return false; -    return isSame(s.left, t.left) && isSame(s.right, t.right); -} -``` - -**从有序数组中构造二叉查找树** - -[Leetcode : 108. Convert Sorted Array to Binary Search Tree (Easy)](https://leetcode.com/problems/convert-sorted-array-to-binary-search-tree/description/) - -二叉查找树(BST):根节点大于等于左子树所有节点,小于等于右子树所有节点。 - -```java -public TreeNode sortedArrayToBST(int[] nums) { -    return toBST(nums, 0, nums.length - 1); -} - -private TreeNode toBST(int[] nums, int sIdx, int eIdx){ -    if(sIdx > eIdx) return null; -    int mIdx = (sIdx + eIdx) / 2; -    TreeNode root = new TreeNode(nums[mIdx]); -    root.left =  toBST(nums, sIdx, mIdx - 1); -    root.right = toBST(nums, mIdx + 1, eIdx); -    return root; -} -``` - -**两节点的最长路径** - -```html -          1 -        / \ -        2  3 -      / \ -      4  5 - -Return 3, which is the length of the path [4,2,1,3] or [5,2,1,3]. -``` - -```java -private int max = 0; - -public int diameterOfBinaryTree(TreeNode root) { -    depth(root); -    return max; -} - -private int depth(TreeNode root) { -    if (root == null) { -        return 0; -    } -    int leftDepth = depth(root.left); -    int rightDepth = depth(root.right); -    max = Math.max(max, leftDepth + rightDepth); -    return Math.max(leftDepth, rightDepth) + 1; -} -``` - -**找出二叉树中第二小的节点** - -[Leetcode : 671. Second Minimum Node In a Binary Tree (Easy)](https://leetcode.com/problems/second-minimum-node-in-a-binary-tree/description/) - -```html -Input: -    2 -  / \ -  2  5 -    / \ -    5  7 - -Output: 5 -``` - -一个节点要么具有 0 个或 2 个子节点,如果有子节点,那么根节点是最小的节点。 - -```java -public int findSecondMinimumValue(TreeNode root) { -    if(root == null) return -1; -    if(root.left == null && root.right == null) return -1; -    int leftVal = root.left.val; -    int rightVal = root.right.val; -    if(leftVal == root.val) leftVal = findSecondMinimumValue(root.left); -    if(rightVal == root.val) rightVal = findSecondMinimumValue(root.right); -    if(leftVal != -1 && rightVal != -1) return Math.min(leftVal, rightVal); -    if(leftVal != -1) return leftVal; -    return rightVal; -} -``` - -**寻找两个节点的最近公共祖先** - -[Leetcode : 235. Lowest Common Ancestor of a Binary Search Tree (Easy)](https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-search-tree/description/) - -```java -public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { -    if(root.val > p.val && root.val > q.val) return lowestCommonAncestor(root.left, p, q); -    if(root.val < p.val && root.val < q.val) return lowestCommonAncestor(root.right, p, q); -    return root; -} -``` - -**最近公共祖先** - -[Leetcode : 236. Lowest Common Ancestor of a Binary Tree (Medium) ](https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-tree/description/) - -```java -public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { -    if (root == null || root == p || root == q) return root; -    TreeNode left = lowestCommonAncestor(root.left, p, q); -    TreeNode right = lowestCommonAncestor(root.right, p, q); -    return left == null ? right : right == null ? left : root; -} -``` - -**最大相同节点值的路径长度** - -[Leetcode : 687. Longest Univalue Path (Easy)](https://pomotodo.com/app/) - -```html -              1 -            / \ -            4  5 -          / \  \ -          4  4  5 - -Output : 2 -``` - -```java -private int path = 0; -public int longestUnivaluePath(TreeNode root) { -    dfs(root); -    return path; -} - -private int dfs(TreeNode root){ -    if(root == null) return 0; -    int left = dfs(root.left); -    int right = dfs(root.right); -    int leftPath = root.left != null && root.left.val == root.val ? left + 1 : 0; -    int rightPath = root.right != null && root.right.val == root.val ? right + 1 : 0; -    path = Math.max(path, leftPath + rightPath); -    return Math.max(leftPath, rightPath); -} -``` - -**间隔遍历** - -[Leetcode : 337. House Robber III (Medium)](https://leetcode.com/problems/house-robber-iii/description/) - -```java -public int rob(TreeNode root) { -    if (root == null) return 0; -    int val1 = root.val; -    if (root.left != null) { -        val1 += rob(root.left.left) + rob(root.left.right); -    } -    if (root.right != null) { -        val1 += rob(root.right.left) + rob(root.right.right); -    } -    int val2 = rob(root.left) + rob(root.right); -    return Math.max(val1, val2); -} -``` - -### 层次遍历 - -使用 BFS,不需要使用两个队列来分别存储当前层的节点和下一层的节点, 因为在开始遍历一层的节点时,当前队列中的节点数就是当前层的节点数,只要控制遍历这么多节点数,就能保证这次遍历的都是当前层的节点。 - -**计算一棵树每层节点的平均数** - -[637. Average of Levels in Binary Tree (Easy)](https://leetcode.com/problems/average-of-levels-in-binary-tree/description/) - -```java -public List averageOfLevels(TreeNode root) { -    List ret = new ArrayList<>(); -    if(root == null) return ret; -    Queue queue = new LinkedList<>(); -    queue.add(root); -    while(!queue.isEmpty()){ -        int cnt = queue.size(); -        double sum = 0; -        for(int i = 0; i < cnt; i++){ -            TreeNode node = queue.poll(); -            sum += node.val; -            if(node.left != null) queue.add(node.left); -            if(node.right != null) queue.add(node.right); -        } -        ret.add(sum / cnt); -    } -    return ret; -} -``` - -**得到左下角的节点** - -[Leetcode : 513. Find Bottom Left Tree Value (Easy)](https://leetcode.com/problems/find-bottom-left-tree-value/description/) - -```java -public int findBottomLeftValue(TreeNode root) { -    Queue queue = new LinkedList<>(); -    queue.add(root); -    while(!queue.isEmpty()){ -        root = queue.poll(); -        if(root.right != null) queue.add(root.right); -        if(root.left != null) queue.add(root.left); -    } -    return root.val; -} -``` - -### 前中后序遍历 - -```html -   1 -  / \ -  2  3 - / \  \ -4  5  6 -``` - -层次遍历顺序:[1 2 3 4 5 6] -前序遍历顺序:[1 2 4 5 3 6] -中序遍历顺序:[4 2 5 1 3 6] -后序遍历顺序:[4 5 2 6 3 1] - -层次遍历使用 BFS 实现,利用的就是 BFS 一层一层遍历的特性;而前序、中序、后序遍历利用了 DFS 实现。 - -前序、中序、后序遍只是在对节点访问的顺序有一点不同,其它都相同。 - -① 前序 - -```java -void dfs(TreeNode root){ -    visit(root); -    dfs(root.left); -    dfs(root.right); -} -``` - -② 中序 - -```java -void dfs(TreeNode root){ -    dfs(root.left); -    visit(root); -    dfs(root.right); -} -``` - -③ 后序 - -```java -void dfs(TreeNode root){ -    dfs(root.left); -    dfs(root.right); -    visit(root); -} -``` - -**非递归实现二叉树的前序遍历** - -[Leetcode : 144. Binary Tree Preorder Traversal (Medium)](https://leetcode.com/problems/binary-tree-preorder-traversal/description/) - -```java -public List preorderTraversal(TreeNode root) { -    List ret = new ArrayList<>(); -    if (root == null) return ret; -    Stack stack = new Stack<>(); -    stack.push(root); -    while (!stack.isEmpty()) { -        TreeNode node = stack.pop(); -        ret.add(node.val); -        if (node.right != null) stack.push(node.right); -        if (node.left != null) stack.push(node.left); // 先添加右子树再添加左子树,这样是为了让左子树在栈顶 -    } -    return ret; -} -``` - -**非递归实现二叉树的后续遍历** - -[Leetcode : ### 145. Binary Tree Postorder Traversal (Medium)](https://leetcode.com/problems/binary-tree-postorder-traversal/description/) - -前序遍历为 root -> left -> right,后序遍历为 left -> right -> root,可以修改前序遍历成为 root -> right -> left,那么这个顺序就和后序遍历正好相反。 - -```java -public List postorderTraversal(TreeNode root) { -    List ret = new ArrayList<>(); -    if (root == null) return ret; -    Stack stack = new Stack<>(); -    stack.push(root); -    while (!stack.isEmpty()) { -        TreeNode node = stack.pop(); -        ret.add(node.val); -        if (node.left != null) stack.push(node.left); -        if (node.right != null) stack.push(node.right); -    } -    Collections.reverse(ret); -    return ret; -} -``` - -**非递归实现二叉树的中序遍历** - -[Leetcode : 94. Binary Tree Inorder Traversal (Medium)](https://leetcode.com/problems/binary-tree-inorder-traversal/description/) - -```java -public List inorderTraversal(TreeNode root) { -    List ret = new ArrayList<>(); -    Stack stack = new Stack<>(); -    TreeNode cur = root; -    while(cur != null || !stack.isEmpty()) { -        while(cur != null) { // 模拟递归栈的不断深入 -            stack.add(cur); -            cur = cur.left; -        } -        TreeNode node = stack.pop(); -        ret.add(node.val); -        cur = node.right; -    } -    return ret; -} -``` - -**使用中序遍历和前序遍历序列重建二叉树** //TODO - -### BST - -主要利用 BST 中序遍历有序的特点。 - -**在 BST 中寻找两个节点,使它们的和为一个给定值。** - -[653. Two Sum IV - Input is a BST](https://leetcode.com/problems/two-sum-iv-input-is-a-bst/description/) - -使用中序遍历得到有序数组之后,再利用双指针对数组进行查找。 - -应该注意到,这一题不能用分别在左右子树两部分来处理这种思想,因为两个待求的节点可能分别在左右子树中。 - -```java -public boolean findTarget(TreeNode root, int k) { -    List nums = new ArrayList<>(); -    inOrder(root, nums); -    int i = 0, j = nums.size() - 1; -    while(i < j){ -        int sum = nums.get(i) + nums.get(j); -        if(sum == k) return true; -        if(sum < k) i++; -        else j--; -    } -    return false; -} - -private void inOrder(TreeNode root, List nums){ -    if(root == null) return; -    inOrder(root.left, nums); -    nums.add(root.val); -    inOrder(root.right, nums); -} -``` - -**在 BST 中查找最小的两个节点之差的绝对值** - -[Leetcode : 530. Minimum Absolute Difference in BST (Easy)](https://leetcode.com/problems/minimum-absolute-difference-in-bst/description/) - -利用 BST 的中序遍历为有序的性质,计算中序遍历中临近的两个节点之差的绝对值,取最小值。 - -```java -private int minDiff = Integer.MAX_VALUE; -private int preVal = -1; - -public int getMinimumDifference(TreeNode root) { -    inorder(root); -    return minDiff; -} - -private void inorder(TreeNode node){ -    if(node == null) return; -    inorder(node.left); -    if(preVal != -1) minDiff = Math.min(minDiff, Math.abs(node.val - preVal)); -    preVal = node.val; -    inorder(node.right); -} -``` - -**把 BST 每个节点的值都加上比它大的节点的值** - -[Leetcode : Convert BST to Greater Tree (Easy)](https://leetcode.com/problems/convert-bst-to-greater-tree/description/) - -先遍历右子树。 - -```java -private int sum = 0; - -public TreeNode convertBST(TreeNode root) { -    traver(root); -    return root; -} - -private void traver(TreeNode root) { -    if (root == null) { -        return; -    } -    if (root.right != null) { -        traver(root.right); -    } -    sum += root.val; -    root.val = sum; -    if (root.left != null) { -        traver(root.left); -    } -} -``` - -**寻找 BST 中出现次数最多的节点** - -```java -private int cnt = 1; -private int maxCnt = 1; -private TreeNode preNode = null; -private List list; - -public int[] findMode(TreeNode root) { -    list = new ArrayList<>(); -    inorder(root); -    int[] ret = new int[list.size()]; -    int idx = 0; -    for(int num : list){ -        ret[idx++] = num; -    } -    return ret; -} - -private void inorder(TreeNode node){ -    if(node == null) return; -    inorder(node.left); -    if(preNode != null){ -        if(preNode.val == node.val) cnt++; -        else cnt = 1; -    } -    if(cnt > maxCnt){ -        maxCnt = cnt; -        list.clear(); -        list.add(node.val); -    } else if(cnt == maxCnt){ -        list.add(node.val); -    } -    preNode = node; -    inorder(node.right); -} -``` - -**寻找 BST 的第 k 个元素** - -[Leetcode : 230. Kth Smallest Element in a BST (Medium)](https://leetcode.com/problems/kth-smallest-element-in-a-bst/description/) - -递归解法: - -```java -public int kthSmallest(TreeNode root, int k) { -    int leftCnt = count(root.left); -    if(leftCnt == k - 1) return root.val; -    if(leftCnt > k - 1) return kthSmallest(root.left, k); -    return kthSmallest(root.right, k - leftCnt - 1); -} - -private int count(TreeNode node) { -    if(node == null) return 0; -    return 1 + count(node.left) + count(node.right); -} -``` - -中序遍历解法: - -```java -private int cnt = 0; -private int val; - -public int kthSmallest(TreeNode root, int k) { -    inorder(root, k); -    return val; -} - -private void inorder(TreeNode node, int k) { -    if(node == null) return; -    inorder(node.left, k); -    cnt++; -    if(cnt == k) { -        val = node.val; -        return; -    } -    inorder(node.right, k); -} -``` - - -### Trie - -![](index_files/5c638d59-d4ae-4ba4-ad44-80bdc30f38dd.jpg) - -Trie,又称前缀树或字典树,用于判断字符串是否存在或者是否具有某种字符串前缀。 - -**实现一个 Trie** - -[Leetcode : 208. Implement Trie (Prefix Tree) (Medium)](https://leetcode.com/problems/implement-trie-prefix-tree/description/) - -```java -class Trie { -     -    private class Node{ -        Node[] childs = new Node[26]; -        boolean isLeaf; -    } -     -    private Node root = new Node(); -     -    /** Initialize your data structure here. */ -    public Trie() { -    } -     -    /** Inserts a word into the trie. */ -    public void insert(String word) { -        int idx = word.charAt(0) - 'a'; -        insert(word, root); -    } -     -    private void insert(String word, Node node){ -        int idx = word.charAt(0) - 'a'; -        if(node.childs[idx] == null){ -            node.childs[idx] = new Node(); -        } -        if(word.length() == 1) node.childs[idx].isLeaf = true; -        else insert(word.substring(1), node.childs[idx]); -    } -     -    /** Returns if the word is in the trie. */ -    public boolean search(String word) { -        return search(word, root);  -    } -     -    private boolean search(String word, Node node){ -        if(node == null) return false; -        int idx = word.charAt(0) - 'a'; -        if(node.childs[idx] == null) return false; -        if(word.length() == 1) return node.childs[idx].isLeaf; -        return search(word.substring(1), node.childs[idx]); -    } -     -    /** Returns if there is any word in the trie that starts with the given prefix. */ -    public boolean startsWith(String prefix) { -        return startWith(prefix, root); -    } -     -    private boolean startWith(String prefix, Node node){ -        if(node == null) return false; -        if(prefix.length() == 0) return true; -        int idx = prefix.charAt(0) - 'a'; -        return startWith(prefix.substring(1), node.childs[idx]); -    } -} -``` - -**实现一个 Trie,用来求前缀和** - -[Leetcode : 677. Map Sum Pairs (Medium)](https://leetcode.com/problems/map-sum-pairs/description/) - -```java -class MapSum { -    private class Trie { -        int val; -        Map childs; -        boolean isWord; -         -        Trie() { -            childs = new HashMap<>(); -        } -    } -     -    private Trie root; - -    public MapSum() { -        root = new Trie(); -    } -     -    public void insert(String key, int val) { -        Trie cur = root; -        for(char c : key.toCharArray()) { -            if(!cur.childs.containsKey(c)) { -                Trie next = new Trie(); -                cur.childs.put(c, next); -            } -            cur = cur.childs.get(c); -        } -        cur.val = val; -        cur.isWord = true; -    } -     -    public int sum(String prefix) { -        Trie cur = root; -        for(char c : prefix.toCharArray()) { -            if(!cur.childs.containsKey(c)) return 0; -            cur = cur.childs.get(c); -        } -        return dfs(cur); -    } -     -    private int dfs(Trie cur) { -        int sum = 0; -        if(cur.isWord) { -            sum += cur.val; -        } -        for(Trie next : cur.childs.values()) { -            sum += dfs(next); -        } -        return sum; -    } -} -``` - -## 图 - -## 位运算 - -**1. 基本原理** - -0s 表示 一串 0 ,1s 表示一串 1。 - -``` -x ^ 0s = x      x & 0s = 0      x | 0s = x -x ^ 1s = ~x     x & 1s = x      x | 1s = 1s -x ^ x = 0       x & x = x       x | x = x -``` - -① 利用 x ^ 1s = ~x 的特点,可以将位级表示翻转;利用 x ^ x = 0 的特点,可以将三个数中重复的两个数去除,只留下另一个数; -② 利用 x & 0s = 0 和 x & 1s = x 的特点,可以实现掩码操作。一个数 num 与 mask :00111100 进行位与操作,只保留 num 中与 mask 的 1 部分相对应的位; -③ 利用 x | 0s = x 和 x | 1s = 1s 的特点,可以实现设置操作。一个数 num 与 mask:00111100 进行位或操作,将 num 中与 mask 的 1 部分相对应的位都设置为 1 。 - -\>\> n 为算术右移,相当于除以 2n; -\>\>\> n 为无符号右移,左边会补上 0。 -<< n 为算术左移,相当于乘以 2n。 - -n&(n-1) 该位运算是去除 n 的位级表示中最低的那一位。例如对于二进制表示 10110**100**,减去 1 得到 10110**011**,这两个数相与得到 10110**000**。 - -n-n&(~n+1) 概运算是去除 n 的位级表示中最高的那一位。 - -n&(-n) 该运算得到 n 的位级表示中最低的那一位。-n 得到 n 的反码加 1,对于二进制表示 10110**100**,-n 得到 01001**100**,相与得到 00000**100** - -**2. mask 计算** - -要获取 111111111,将 0 取反即可,~0。 - -要得到只有第 i 位为 1 的 mask,将 1 向左移动 i 位即可,1<<i 。例如 1<<5 得到只有第 5 位为 1 的 mask :00010000。 - -要得到 1 到 i 位为 1 的 mask,1<<(i+1)-1 即可,例如将 1<<(4+1)-1 = 00010000-1 = 00001111。 - -要得到 1 到 i 位为 0 的 mask,只需将 1 到 i 位为 1 的 mask 取反,即 ~(1<<(i+1)-1)。 - -**3. 位操作举例** - -① 获取第 i 位 - -num & 00010000 != 0 - -```java -(num & (1 << i)) != 0; -``` - -② 将第 i 位设置为 1 - -num | 00010000 - -```java -num | (1 << i); -``` - -③ 将第 i 位清除为 0 - -num & 11101111 - -```java -num & (~(1 << i)) -``` - -④ 将最高位到第 i 位清除为 0 - -num & 00001111 - -```java -num & ((1 << i) - 1); -``` - -⑤ 将第 0 位到第 i 位清除为 0 - -num & 11110000 - -```java -num & (~((1 << (i+1)) - 1)); -``` - -⑥ 将第 i 位设置为 0 或者 1 - -先将第 i 位清零,然后将 v 左移 i 位,执行“位或”运算。 - -```java -(num & (1 << i)) | (v << i); -``` - -**4. Java 中的位操作** - -```html -static int Integer.bitCount()            // 统计 1 的数量 -static int Integer.highestOneBit()       // 获得最高位 -static String toBinaryString(int i)      // 转换位二进制表示的字符串 -``` - -**统计两个数的二进制表示有多少位不同** - -[Leetcode : 461. Hamming Distance (Easy)](https://leetcode.com/problems/hamming-distance/) - -对两个数进行异或操作,不同的那一位结果为 1 ,统计有多少个 1 即可。 - -```java -public int hammingDistance(int x, int y) { -    int z = x ^ y; -    int cnt = 0; -    while(z != 0){ -        if((z & 1) == 1) cnt++; -        z = z >> 1; -    } -    return cnt; -} -``` - -可以使用 Integer.bitcount() 来统计 1 个的个数。 - -```java -public int hammingDistance(int x, int y) { -    return Integer.bitCount(x ^ y); -} -``` - -**翻转一个数的比特位** - -[Leetcode : 190. Reverse Bits (Easy)](https://leetcode.com/problems/reverse-bits/description/) - -```java -public int reverseBits(int n) { -    int ret = 0; -    for(int i = 0; i < 32; i++){ -        ret <<= 1; -        ret |= (n & 1); -        n >>>= 1; -    } -    return ret; -} -``` - -**不用额外变量交换两个整数** - -[程序员代码面试指南 :P317](#) - -```java -a = a ^ b; -b = a ^ b; -a = a ^ b; -``` - -将 c = a ^ b,那么 b ^ c = b ^ b ^ a = a,a ^ c = a ^ a ^ b = b。 - -**判断一个数是不是 4 的 n 次方** - -[Leetcode : 342. Power of Four (Easy)](https://leetcode.com/problems/power-of-four/) - -该数二进制表示有且只有一个奇数位为 1 ,其余的都为 0 ,例如 16 : 10000。可以每次把 1 向左移动 2 位,就能构造出这种数字,然后比较构造出来的数与要判断的数是否相同。 - -```java -public boolean isPowerOfFour(int num) { -    int i = 1; -    while(i > 0){ -        if(i == num) return true; -        i = i << 2; -    } -    return false; -} -``` - -也可以用 Java 的 Integer.toString() 方法将该数转换为 4 进制形式的字符串,然后判断字符串是否以 1 开头。 - -```java -public boolean isPowerOfFour(int num) { -    return Integer.toString(num, 4).matches("10*"); -} -``` - -**判断一个数是不是 2 的 n 次方** - -[Leetcode : 231. Power of Two (Easy)](https://leetcode.com/problems/power-of-two/description/) - -同样可以用 Power of Four 的方法,但是 2 的 n 次方更特殊,它的二进制表示只有一个 1 存在。 - -```java -public boolean isPowerOfTwo(int n) { -    return n > 0 && Integer.bitCount(n) == 1; -} -``` - -利用 1000 & 0111 == 0 这种性质,得到以下解法: - -```java -public boolean isPowerOfTwo(int n) { -    return n > 0 && (n & (n - 1)) == 0; -} -``` - -**数组中唯一一个不重复的元素** - -[Leetcode : 136. Single Number (Easy)](https://leetcode.com/problems/single-number/description/) - -两个相同的数异或的结果为 0,对所有数进行异或操作,最后的结果就是单独出现的那个数。 - -类似的有:[Leetcode : 389. Find the Difference (Easy)](https://leetcode.com/problems/find-the-difference/description/),两个字符串仅有一个字符不相同,使用异或操作可以以 O(1) 的空间复杂度来求解,而不需要使用 HashSet。 - -```java -public int singleNumber(int[] nums) { -    int ret = 0; -    for(int n : nums) ret = ret ^ n; -    return ret; -} -``` - -**数组中不重复的两个元素** - -[Leetcode : 260. Single Number III (Medium)](https://leetcode.com/problems/single-number-iii/description/) - -两个不相等的元素在位级表示上必定会有一位存在不同。 - -将数组的所有元素异或得到的结果为不存在重复的两个元素异或的结果。 - -diff &= -diff 得到出 diff 最右侧不为 0 的位,也就是不存在重复的两个元素在位级表示上最右侧不同的那一位,利用这一位就可以将两个元素区分开来。 - - -```java -public int[] singleNumber(int[] nums) { -    int diff = 0; -    for(int num : nums) diff ^= num; -    // 得到最右一位 -    diff &= -diff; -    int[] ret = new int[2]; -    for(int num : nums) { -        if((num & diff) == 0) ret[0] ^= num; -        else ret[1] ^= num; -    } -    return ret; -} -``` - -**判断一个数的位级表示是否不会出现连续的 0 和 1** - -[Leetcode : 693. Binary Number with Alternating Bits (Easy)](https://leetcode.com/problems/binary-number-with-alternating-bits/description/) - -对于 10101 这种位级表示的数,把它向右移动 1 位得到 1010 ,这两个数每个位都不同,因此异或得到的结果为 11111。 - -```java -public boolean hasAlternatingBits(int n) { -    int a = (n ^ (n >> 1)); -    return (a & (a + 1)) == 0; -} -``` - -**求一个数的补码** - -[Leetcode : 476. Number Complement (Easy)](https://leetcode.com/problems/number-complement/description/) - -不考虑二进制表示中的首 0 部分 - -对于 00000101,要求补码可以将它与 00000111 进行异或操作。那么问题就转换为求掩码 00000111。 - -```java -public int findComplement(int num) { -    if(num == 0) return 1; -    int mask = 1 << 30; -    while((num & mask) == 0) mask >>= 1; -    mask = (mask << 1) - 1; -    return num ^ mask; -} -``` - -可以利用 Java 的 Integer.highestOneBit() 方法来获得含有首 1 的数。 - -```java -public int findComplement(int num) { -    if(num == 0) return 1; -    int mask = Integer.highestOneBit(num); -    mask = (mask << 1) - 1; -    return num ^ mask; -} -``` - -对于 10000000 这样的数要扩展成 11111111,可以利用以下方法: - -```html -mask |= mask >> 1    11000000 -mask |= mask >> 2    11110000 -mask |= mask >> 4    11111111 -``` - -```java -public int findComplement(int num) { -    int mask = num; -    mask |= mask >> 1; -    mask |= mask >> 2; -    mask |= mask >> 4; -    mask |= mask >> 8; -    mask |= mask >> 16; -    return (mask ^ num); -} -``` - -**实现整数的加法** - -[Leetcode : 371. Sum of Two Integers (Easy)](https://leetcode.com/problems/sum-of-two-integers/description/) - -a ^ b 表示没有考虑进位的情况下两数的和,(a & b) << 1 就是进位。递归会终止的原因是 (a & b) << 1 最右边会多一个 0,那么继续递归,进位最右边的 0 会慢慢增多,最后进位会变为 0,递归终止。 - -```java -public int getSum(int a, int b) { -    return b == 0 ? a : getSum((a ^ b), (a & b) << 1); -} -``` - -**实现整数乘法** - -[程序员代码面试指南 P319](#) - -**字符串数组最大乘积** - -[Leetcode : 318. Maximum Product of Word Lengths (Medium)](https://leetcode.com/problems/maximum-product-of-word-lengths/description/) - -题目描述:字符串数组的字符串只含有小写字符。求解字符串数组中两个字符串长度的最大乘积,要求这两个字符串不能含有相同字符。 - -解题思路:本题主要问题是判断两个字符串是否含相同字符,由于字符串只含有小写字符,总共 26 位,因此可以用一个 32 位的整数来存储每个字符是否出现过。 - -```java -public int maxProduct(String[] words) { -    int n = words.length; -    if (n == 0) return 0; -    int[] val = new int[n]; -    for (int i = 0; i < n; i++) { -        for (char c : words[i].toCharArray()) { -            val[i] |= 1 << (c - 'a'); -        } -    } -    int ret = 0; -    for (int i = 0; i < n; i++) { -        for (int j = i + 1; j < n; j++) { -            if ((val[i] & val[j]) == 0) { -                ret = Math.max(ret, words[i].length() * words[j].length()); -            } -        } -    } -    return ret; -} -``` - -# 参考资料 - -- [Leetcode](https://leetcode.com/problemset/algorithms/?status=Todo) -- 剑指 Offer -- 程序员面试金典 -- 编程之美 -- 程序员代码面试指南 -- 数据结构与算法分析 -- 算法 -- 王道论坛计算机考研机试指南 - diff --git a/notes/笔记/Linux+.md.txt b/notes/笔记/Linux+.md.txt deleted file mode 100644 index fa95dbd5..00000000 --- a/notes/笔记/Linux+.md.txt +++ /dev/null @@ -1,17 +0,0 @@ -## 孤儿进程和僵死进程 - -### 孤儿进程 - -一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被 init 进程(进程号为 1)所收养,并由 init 进程对它们完成状态收集工作。由于孤儿进程会被 init 进程收养,所以孤儿进程不会对系统造成危害 - -### 僵尸进程 - -一个子进程的进程描述符在子进程退出时不会释放,只有当父进程通过 wait 或 waitpid 获取了子进程信息后才会释放。如果子进程退出,而父进程并没有调用 wait 或 waitpid,那么子进程的进程描述符仍然保存在系统中,这种进程称之为僵死进程。 - -僵死进程通过 ps 命令显示出来的状态为 Z。 - -系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程。 - -要消灭系统中大量的僵死进程,只需要将其父进程杀死,此时所有的僵死进程就会变成孤儿进程,从而被 init 所收养,这样 init 就会释放所有的僵死进程所占有的资源,从而结束僵死进程。 - -- [Linux 之守护进程、僵死进程与孤儿进程](http://liubigbin.github.io/2016/03/11/Linux-%E4%B9%8B%E5%AE%88%E6%8A%A4%E8%BF%9B%E7%A8%8B%E3%80%81%E5%83%B5%E6%AD%BB%E8%BF%9B%E7%A8%8B%E4%B8%8E%E5%AD%A4%E5%84%BF%E8%BF%9B%E7%A8%8B/) \ No newline at end of file diff --git a/notes/笔记/Linux.md.txt b/notes/笔记/Linux.md.txt deleted file mode 100644 index d4ec050a..00000000 --- a/notes/笔记/Linux.md.txt +++ /dev/null @@ -1,987 +0,0 @@ -[TOC] - -# 常用操作以及概念 - -## 求助 - -**1. --help** - -指令的基本用法与选项介绍。 - -**2. man** - -man 是 manual 的缩写,将指令的具体信息显示出来。 - -当执行 man date 时,有 DATE(1) 出现,其中的数字代表指令的类型,常用的数字及其类型如下: - -| 代号 | 类型 | -| -- | -- | -| 1 | 用户在 shell 环境中可以操作的指令或者可执行文件 | -| 5 | 配置文件 | -| 8 | 系统管理员可以使用的管理指令 | - -**3. info** - -info 与 man 类似,但是 info 将文档分成一个个页面,每个页面可以进行跳转。 - -## 关机 - -**1. 数据同步写入磁盘 sync** - -为了加快对磁盘上文件的读写速度,位于内存中的文件数据不会立即同步到磁盘上,因此关机之前需要先进行 sync 同步操作。 - -**2. shutdown** - -```html -# /sbin/shutdown [-krhc] [时间] [警告讯息] --k : 不会关机,只是发送警告讯息,通知所有在线的用户 --r : 将系统的服务停掉后就重新启动 --h : 将系统的服务停掉后就立即关机 --c : 取消已经在进行的 shutdown 指令内容 -``` - -**3. 其它关机指令** - -reboot、halt、poweroff。 - -## 查看进程 - -ps 指令。 - -例如查看 theadx 的相关信息: - -```html -ps aux | grep threadx -``` - -## 查看端口 - -netstat 指令。 - -例如查看端口 80 是否被占用: - -```html -netstat -anp | grep 80 -``` - -## PATH - -可以在环境变量 PATH 中声明可执行文件的路径,路径之间用 : 分隔。 - -```html -/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/dmtsai/.local/bin:/home/dmtsai/bin -``` - -## 运行等级 - -0:关机模式 -1:单用户模式(可用于破解root密码) -2:无网络支持的多用户模式 -3:有网络支持的多用户模式(文本模式,工作中最常用的模式) -4:保留,未使用 -5:有网络支持的 X-windows 支持多用户模式(桌面) -6:重新引导系统,即重启 - -## sudo - -使用 sudo 允许一般用户使用 root 可执行的命令。用户必须在 /etc/sudoers 中才能使用该指令。 - -## GNU - -GNU 计划,又译为革奴计划,它的目标是创建一套完全自由的操作系统,称为 GNU,其内容软件完全以 GPL 方式发布。其中 GPL 全称为 GNU 通用公共许可协议,包含了以下内容: - -- 以任何目的运行此程序的自由; -- 再复制的自由; -- 改进此程序,并公开发布改进的自由。 - -## 包管理工具 - -RPM 和 DPKG 为最常见的两类软件包管理工具。RPM 全称为 Redhat Package Manager,最早由 Red Hat 公司制定实施,随后被 GNU 开源操作系统接受并成为很多 Linux 系统 (RHEL) 的既定软件标准。与 RPM 进行竞争的是基于 Debian 操作系统 (UBUNTU) 的 DEB 软件包管理工具- DPKG,全称为 Debian Package,功能方面与 RPM 相似。 - -YUM 基于 RPM 包管理工具,能够从指定的源空间(服务器,本地目录等)自动下载目标 RPM 包并且安装,具有依赖管理功能,可以自动处理依赖关系并进行下载、安装,无须繁琐地手动下载、安装每一个需要的依赖包。此外,YUM 的另一个功能是进行系统中所有软件的升级。 - -## 常见发行版本 - -Linux 发行版是预先集成好的 Linux 内核及各种应用软件。 - -**基于 DPKG** - -商业发行版 - -- Ubuntu - -社区发行版 - -- Debian - -**基于 RPM** - -商业发行版 - -- Red Hat - -社区发行版 - -- Fedora -- CentOS - -# 分区 - -## 磁盘的文件名 - -Linux 中每个硬件都被当做一个文件。 - -常见磁盘的文件名: - -- SCSI/SATA/USB 磁盘:/dev/sd[a-p] -- IDE 磁盘:/dev/hd[a-d] - -其中文件名后面的序号的确定与磁盘插入的顺序有关,而与磁盘所插入的插槽位置无关。 - -## 分区表 - -磁盘分区表主要有两种格式,一种是限制较多的 MBR 分区表,一种是较新且限制较少的 GPT 分区表。 - -### 1. MBR - -MBR 中,第一个扇区最重要,里面有:主要开机记录(Master boot record, MBR)及分区表(partition table),其中 MBR 占 446 bytes,partition table 占 64 bytes。 - -分区表只有 64 bytes,最多只能存储 4 个分区,这 4 个分区为主分区(Primary)和扩展分区(Extended)。其中扩展分区只有一个,它将其它空间用来记录分区表,可以记录更多的分区,因此通过扩展分区可以分出更多区分,这些分区称为逻辑分区。 - -Linux 也把分区当成文件,分区文件的命名方式为:磁盘文件名+编号,例如 /dev/sda1。注意,逻辑分区的编号从 5 开始。 - -### 2. GPT - -不同的磁盘有不同的扇区大小,例如 512bytes 和最新磁盘的 4k。GPT 为了兼容所有磁盘,在定义扇区上使用逻辑区块地址(Logical Block Address, LBA)。 - -GPT 第 1 个区块记录了 MBR,紧接着是 33 个区块记录分区信息,并把最后的 33 个区块用于对分区信息进行备份。 - -GPT 没有扩展分区概念,都是主分区,最多可以分 128 个分区。 - -![](index_files/a5c25452-6fa5-49e7-9322-823077442775.jpg) - -## 开机检测程序 - -### 1. BIOS - -BIOS 是开机的时候计算机执行的第一个程序,这个程序知道可以开机的磁盘,并读取磁盘第一个扇区的 MBR,由 MBR 执行其中的开机管理程序,这个开机管理程序的会加载操作系统的核心文件。 - -MBR 中的开机管理程序提供以下功能:选单、载入核心文件以及转交其它开机管理程序。转交这个功能可以用来实现了多重引导,只需要将另一个操作系统的开机管理程序安装其它分区的启动扇区上,在启动 MBR 中的开机管理程序时,就可以选择启动当前的操作系统或者转交给其它开机管理程序从而启动另一个操作系统。 - -![](index_files/f900f266-a323-42b2-bc43-218fdb8811a8.jpg) - -安装多重引导,最好先安装 Windows 再安装 Linux。因为安装 Windows 时会覆盖掉 MBR,而 Linux 可以选择将开机管理程序安装在 MBR 或者其它分区的启动扇区,并且可以设置开机管理程序的选单。 - -### 2. UEFI - -UEFI 相比于 BIOS 来说功能更为全面,也更为安全。 - -## 挂载 - -挂载利用目录作为分区的进入点,也就是说,进入目录之后就可以读取分区的数据。 - -![](index_files/249f3bb1-feee-4805-a259-a72699d638ca.jpg) - -# 文件权限与目录配置 - -## 文件权限概念 - -把用户分为三种:文件拥有者、群组以及其它人,对不同的用户有不同的文件权限。 - -使用 ls 查看一个文件时,会出现一个文件的信息,例如 drwxr-xr-x. 3 root root 17 May 6 00:14 .config,对这个内容的解释如下: - -- drwxr-xr-x:文件类型以及权限,第 1 位为文件类型字段,后 9 位为文件权限字段。 -- 3:链接数; -- root:文件拥有者; -- root:所属群组; -- 17:文件大小; -- May 6 00:14:文件最后被修改的时间; -- .config:文件名。 - -常见的文件类型及其含义有: - -- d:目录; -- -:文件; -- l:链接文件; - -9 位的文件权限字段中,每 3 个为一组,共 3 组,每一组分别代表对文件拥有者、所属群组以及其它人的文件权限。一组权限中的 3 位分别为 r、w、x 权限,表示可读、可写、可执行。 - -## 文件属性以及权限的修改 - -### 1. 修改文件所属群组 - -```html -# chgrp [-R] groupname dirname/filename --R:递归修改 -``` - -### 2. 修改文件拥有者 - -不仅可以修改文件拥有者,也可以修改文件所属群组。 - -```html -# chown [-R] 用户名:群组名 dirname/filename -``` - -### 3. 修改权限 - -可以将一组权限用数字来表示,此时一组权限的 3 个位当做二进制数字的位,从左到右每个位的权值为 4、2、1,即每个权限对应的数字权值为 r:4、w:2、x:1。 - -```html -# chmod [-R] xyz dirname/filename -``` - -范例:将 .bashrc 文件的权限修改为 -rwxr-xr--。 - -```html -# chmod 754 .bashrc -``` - -也可以使用符号来设定权限。 - -```html -# chmod [ugoa] [+-=] [rwx] dirname/filename -- u:拥有者 -- g:所属群组 -- o:其他人 -- a:所有人 -- +:添加权限 -- -:移除权限 -- =:设定权限 -``` - -范例:为 .bashrc 文件的所有用户添加写权限。 - -```html -# chmod a+w .bashrc -``` - -## 目录的权限 - -文件名不是存储在一个文件的内容中,而是存储在一个文件所在的目录中。因此,拥有文件的 w 权限并不能对文件名进行修改。 - -目录存储文件列表,一个目录的权限也就是对其文件列表的权限。因此,目录的 r 权限表示可以读取文件列表;w 权限表示可以修改文件列表,具体来说,就是添加删除文件,对文件名进行修改;x 权限可以让该目录成为工作目录,x 权限是 r 和 w 权限的基础,如果不能使一个目录成为工作目录,也就没办法读取文件列表以及对文件列表进行修改了。 - -## 文件默认权限 - -文件默认权限:文件默认没有可执行权限,因此为 666 ,也就是 -rw-rw-rw- 。 -目录默认权限:目录必须要能够进入,也就是必须拥有可执行权限,因此为 777 ,也就是 drwxrwxrwx。 - -可以通过 umask 设置或者查看文件的默认权限,通常以掩码的形式来表示,例如 002 表示其它用户的权限去除了一个 2 的权限,也就是写权限,因此建立新文件时默认的权限为 -rw-rw-r-- 。 - -## 目录配置 - -为了使不同 Linux 发行版本的目录结构保持一致性,Filesystem Hierarchy Standard (FHS) 规定了 Linux 的目录结构。最基础的三个目录如下: - -- / (root, 根目录) -- /usr (unix software resource):所有系统默认软件都会安装到这个目录; -- /var (variable):存放系统或程序运行过程中的数据文件。 - -完整的目录树如下: - -![](index_files/27ace615-558f-4dfb-8ad4-7ac769c10118.jpg) - -# 文件与目录 - -## 文件时间 - -1. modification time (mtime):文件的内容更新就会更新; -2. status time (ctime):文件的状态(权限、属性)更新就会更新; -3. access time (atime):读取文件时就会更新。 - -## 文件与目录的基本操作 - -### 1. ls - -列出文件或者目录的信息,目录的信息就是其中包含的文件。 - -```html -# ls [-aAdfFhilnrRSt] file|dir --a :列出全部的文件 --d :仅列出目录本身 --l :以长数据串行列出,包含文件的属性与权限等等数据 -``` - -### 2. cp - -复制操作。 - -如果源文件有两个以上,则目的文件一定要是目录才行。 - -```html -cp [-adfilprsu] source destination --a :相当于 -dr --preserve=all 的意思,至于 dr 请参考下列说明 --d :若来源文件为链接文件,则复制链接文件属性而非文件本身 --i :若目标文件已经存在时,在覆盖前会先询问 --p :连同文件的属性一起复制过去 --r :递归持续复制 --u :destination 比 source 旧才更新 destination,或 destination 不存在的情况下才复制 ---preserve=all :除了 -p 的权限相关参数外,还加入 SELinux 的属性, links, xattr 等也复制了 -``` - -### 3. rm - -移除操作。 - -```html -# rm [-fir] 文件或目录 --r :递归删除 -``` - -### 4. mv - -移动操作。 - -```html -# mv [-fiu] source destination -# mv [options] source1 source2 source3 .... directory --f : force 强制的意思,如果目标文件已经存在,不会询问而直接覆盖 -``` - -## 获取文件内容 - -### 1. cat - -取得文件内容。 - -```html -# cat [-AbEnTv] filename --n :打印出行号, 连同空白行也会有行号,与 -b 的选项不同 -``` - -### 2. tac - -是 cat 的反向操作,从最后一行开始打印。 - -### 3. more - -可以一页一页查看文件内容,和文本编辑器类似。 - -### 4. less - -和 more 类似。 - -### 5. head - -可以取得文件前几行。 - -```html -# head [-n number] filename --n :后面接数字,代表显示几行的意思 -``` - -### 6. tail - -是 head 的反向操作,只是取得是后几行。 - -### 7. od - -可以以字符或者十六进制的形式显示二进制文件。 - -### 8. touch - -修改文件时间或者建立新文件。 - -```html -# touch [-acdmt] filename --a : 更新 atime --c : 更新 ctime,若该文件不存在则不建立新文件 --m : 更新 mtime --d : 后面可以接欲更新的日期而不用目前的日期,也可以使用 --date="日期或时间" --t :后面可以接欲更新的时间而不用目前的时间,格式为[YYYYMMDDhhmm] -``` - -## 指令与文件搜索 - -### 1. which - -指令搜索。 - -```html -# which [-a] command --a :将所有指令列出,而不是只列第一个 -``` - -### 2. whereis - -whereis 搜索文件的速度比较快,因为它只搜索几个特定的目录。 - -```html -# whereis [-bmsu] dirname/filename -``` - -### 3. locate - -locate 可以用关键字或者正则表达式进行搜索。 - -locate 使用 /var/lib/mlocate/ 这个数据库来进行搜索,它存储在内存中,并且每天更新一次,所以无法用 locate 搜索新建的文件。可以使用 updatedb 来立即更新数据库。 - -```html -# locate [-ir] keyword --r:接正则表达式 -``` - -### 4. find - -find 可以使用文件的属性和权限进行搜索。 - -```html -# find filename [option] -``` - -#### 4.1 与时间有关的选项 - -```html --mtime n :列出在 n 天前的那一天修改过内容的文件 --mtime +n :列出在 n 天之前(不含 n 天本身)修改过内容的文件 --mtime -n :列出在 n 天之内(含 n 天本身)修改过内容的文件 --newer file : 列出比 file 更新的文件 -``` - -+4、4 和 -4 的指示的时间范围如下: - -![](index_files/658fc5e7-79c0-4247-9445-d69bf194c539.png) - -#### 4.2 与文件拥有者和所属群组有关的选项 - -```html --uid n --gid n --user name --group name --nouser :搜索拥有者不存在 /etc/passwd 的文件 --nogroup:搜索所属群组不存在于 /etc/group 的文件 -``` - -#### 4.3 与文件权限和名称有关的选项 - -```html --name filename --size [+-]SIZE:搜寻比 SIZE 还要大(+)或小(-)的文件。这个 SIZE 的规格有:c: 代表 byte,k: 代表 1024bytes。所以,要找比 50KB还要大的文件,就是 -size +50k --type TYPE --perm mode :搜索权限等于 mode 的文件 --perm -mode :搜索权限包含 mode 的文件 --perm /mode :搜索权限包含任一 mode 的文件 -``` - -# 磁盘与文件系统 - -## 文件系统的组成 - -对分区进行格式化是为了在分区上建立文件系统。一个分区通常只能格式化为一个文件系统,但是磁盘阵列等技术可以将一个分区格式化为多个文件系统,因此只有文件系统能被挂载,而分区不能被挂载。 - -文件系统有以下三个结构: - -1. superblock:记录文件系统的整体信息,包括 inode 和 block 的总量、使用量、剩余量,以及文件系统的格式与相关信息等; -2. inode:一个文件占用一个 inode,记录文件的属性,同时记录此文件的数据所在的 block 号码; -3. block:记录文件的内容,若文件太大时,会占用多个 block。 - -当要读取一个文件的内容时,先在 inode 中去查找文件内容所在的所有 block,然后把所有 block 的内容读出来。 - -磁盘碎片是指一个文件内容所在的 block 过于分散。 - -Ext2 文件系统使用了上述的文件结构,并在此之上加入了 block 群组的概念,也就是将一个文件系统划分为多个 block 群组,方便管理。 - -![](index_files/1974a836-aa6b-4fb8-bce1-6eb11969284a.jpg) - -## inode - -Ext2 文件系统支持的 block 大小有 1k、2k 和 4k 三种,不同的 block 大小限制了单一文件的大小。而每个 inode 大小是固定为 128 bytes。 - -inode 中记录了文件内容所在的 block,但是每个 block 非常小,一个大文件随便都需要几十万的 block,而一个 inode 大小有限,无法直接引用这么多 block。因此引入了间接、双间接、三间接引用,使用用 block 来扩充大小,也就是让 block 来记录文件包含的 block。 - -![](index_files/89091427-7b2b-4923-aff6-44681319a8aa.jpg) - -inode 具体包含以下信息: - -- 该文件的存取模式(read/write/excute); -- 该文件的拥有者与群组(owner/group); -- 该文件的容量; -- 该文件建立或状态改变的时间(ctime); -- 最近一次的读取时间(atime); -- 最近修改的时间(mtime); -- 定义文件特性的旗标(flag),如 SetUID...; -- 该文件真正内容的指向 (pointer)。 - -## 目录的 inode 与 block - -建立一个目录时,会分配一个 inode 与至少一个 block。block 记录的内容是目录下所有文件的 inode 编号以及文件名,可以看出文件的 inode 本身不记录文件名,文件名记录在目录中,因此新增文件、删除文件、更改文件名这些操作与目录的 w 权限有关。 - -## 实体链接与符号链接 - -```html -# ln [-sf] source_filename dist_filename --s :默认是 hard link,加 -s 为 symbolic link --f :如果目标5文件存在时,先删除目标文件 -``` - -### 1. 实体链接 - -hard link 只是在某个目录下新增一个条目,使得新增的条目链接到文件的 inode 上。删除任意一个条目,文件还是存在,只要引用数量不为 0。 - -有以下限制:不能跨越 Filesystem;不能对目录进行链接。 - -```html -# ln /etc/crontab . -# ll -i /etc/crontab crontab -34474855 -rw-r--r--. 2 root root 451 Jun 10 2014 crontab -34474855 -rw-r--r--. 2 root root 451 Jun 10 2014 /etc/crontab -``` - -### 2. 符号链接 - -symbolic link 可以理解为 Windows 的快捷方式,通过建立一个独立的文件,这个文件的数据的读取指向链接的那个文件。当源文件被删除了,链接文件就打不开了。 - -symbolic link 可以为目录建立链接。 - -```html -# ll -i /etc/crontab /root/crontab2 -34474855 -rw-r--r--. 2 root root 451 Jun 10 2014 /etc/crontab -53745909 lrwxrwxrwx. 1 root root 12 Jun 23 22:31 /root/crontab2 -> /etc/crontab -``` - -# 压缩与打包 - -## 压缩 - -Linux 底下有很多压缩文件的扩展名,常见的如下: - -| 扩展名 | 压缩程序 | -| -- | -- | -| \*.Z | compress | -|\*.zip | zip | -|\*.gz | gzip| -|\*.bz2 | bzip2 | -|\*.xz | xz | -|\*.tar | tar 程序打包的数据,没有经过压缩 | -|\*.tar.gz | tar 程序打包的文件,经过 gzip 的压缩 | -|\*.tar.bz2 | tar 程序打包的文件,经过 bzip2 的压缩 | -|\*.tar.xz | tar 程序打包的文件,经过 xz 的压缩 | - -### 1. gzip - -gzip 是 Linux 使用最广的压缩指令,可以解开 compress、zip 与 gzip 所压缩的文件。 - -经过 gzip 压缩过,源文件就不存在了。 - -有 9 个不同的压缩等级可以使用。 - -可以使用 zcat、zmore、zless 来读取压缩文件的内容。 - -```html -$ gzip [-cdtv#] filename --c :将压缩的数据输出到屏幕上 --d :解压缩 --t :检验压缩文件是否出错 --v :显示压缩比等信息 --# : # 为数字的意思,代表压缩等级,数字越大压缩比越高,默认为6 -``` - -### 2. bzip2 - -提供比 gzip 更高的压缩比。 - -查看命令:bzcat、bzmore、bzless、bzgrep。 - -```html -$ bzip2 [-cdkzv#] filename --k :保留源文件 -``` - -### 3. xz - -提供比 bzip2 更佳的压缩比。 - -可以看到,gzip、bzip2、xz 的压缩比不断优化,不过要注意,压缩比越高,压缩的时间也越长。 - -查看命令:xzcat、xzmore、xzless、xzgrep。 - -```html -$ xz [-dtlkc#] filename -``` - -## 打包 - -压缩指令只能对一个文件进行压缩,而打包能够将多个文件打包成一个大文件。tar 不仅可以用于打包,也可以使用 gip、bzip2、xz 将打包文件进行压缩。 - -```html -$ tar [-z|-j|-J] [cv] [-f 新建的tar文件] filename... ==打包压缩 -$ tar [-z|-j|-J] [tv] [-f 已有的tar文件] ==查看 -$ tar [-z|-j|-J] [xv] [-f 已有的tar文件] [-C 目录] ==解压缩 --z :使用zip; --j :使用bzip2; --J :使用xz; --c :新建打包文件; --t :查看打包文件里面有哪些文件; --x :解打包或解压缩的功能; --v :在压缩/解压缩的过程中,显示正在处理的文件名; --f : filename:要处理的文件; --C 目录 : 在特定目录解压缩。 -``` - -最常用的方式如下: - -打包压缩 : tar -jcv -f filename.tar.bz2 要被压缩的文件或目录名称 -查 看       : tar -jtv -f filename.tar.bz2 -解压缩     :tar -jxv -f filename.tar.bz2 -C 要解压缩的目录 - -# BASH - -可以通过 shell 请求内核提供服务,Bash 正是 shell 的一种。 - -## Bash特性 - -**1. 命令历史** - -记录使用过的命令。本次登录所执行的命令都会暂时存放到内存中, ~/.bash_history 文件中记录的是前一次登录所执行过的命令。 - -**2. 命令与文件补全** - -快捷键:tab - -**3. 命名别名** - -例如 lm 是 ls -al 的别名。 - -**4. shell scripts** - -**5. 通配符** - -例如 ls -l /usr/bin/X\* 列出 /usr/bin 下面所有以 X 开头的文件。 - -## 变量操作 - -对一个变量赋值直接使用 = ,对变量取用需要在变量前加上 \$ ,也可以用 \${} 的形式,输出变量使用 echo 命令。 - -```bash -$ var=abc -$ echo $var -$ echo ${var} -``` - -变量内容如果有空格,需要使用双引号或者单引号。双引号内的特殊字符可以保留原本特性,例如var="lang is \$LANG",则var的值为 lang is zh_TW.UTF-8;而单引号内的特殊字符就是特殊字符本身,例如 var='lang is \$LANG',则 var 的值为 lang is \$LANG。 - - -可以使用 \`指令\` 或者 \$(指令) 的方式将指令的执行结果赋值给变量。例如 version=\$(uname -r),则 version 的值为 3.10.0-229.el7.x86_64。 - -可以使用 export 命令将自定义变量转成环境变量,环境变量可以在子程序中使用,所谓子程序就是由当前 Bash 而产生的子 Bash。 - -Bash 的变量可以声明为数组和整数数字。注意数字类型没有浮点数。如果不进行声明,默认是字符串类型。变量的声明使用 declare 命令: - -```html -$ declare [-aixr] variable --a : 定义为数组类型 --i : 定义为整数类型 --x : 定义为环境变量 --r : 定义为readonly类型 -``` - -使用 [ ] 来对数组进行操作: - -```bash -$ array[1]=a -$ array[2]=b -$ echo ${array[1]} -``` - -## 指令搜索顺序 - -1. 以绝对或相对路径来执行指令,例如 /bin/ls 或者 ./ls ; -2. 由别名找到该指令来执行; -3. 由 Bash 内建的指令来执行; -4. 按 \$PATH 变量指定的搜索路径的顺序找到第一个指令来执行。 - -## 数据流重定向 - -重定向就是使用文件代替标准输入、标准输出和标准错误输出。 - -1. 标准输入 (stdin)      :代码为 0 ,使用 < 或 << ; -2. 标准输出 (stdout)    :代码为 1 ,使用 > 或 >> ; -3. 标准错误输出(stderr):代码为 2 ,使用 2> 或 2>> ; - -其中,有一个箭头的表示以覆盖的方式重定向,而有两个箭头的表示以追加的方式重定向。 - -可以将不需要的标准输出以及标准错误输出重定向到 /dev/null ,相当于扔进垃圾箱。 - -如果需要将标准输出以及标准错误输出同时重定向到一个文件,需要将某个输出转换为另一个输出,例如 2>&1 表示将标准错误输出转换为标准输出。 - -```bash -$ find /home -name .bashrc > list 2>&1 -``` - -## 管线指令 - -管线是将一个命令的标准输出作为另一个命令的标准输入,在数据需要经过多个步骤的处理之后才能得到我们想要的格式时就可以使用管线。在命令之间使用 | 分隔各个管线命令。 - -```bash -$ ls -al /etc | less -``` - -### 1. 提取指令:cut - -提取过程一行一行地进行。 - -cut 对数据进行切分,取出想要的部分。 - -```html -$ cut --d :分隔符 --f :经过 -d 分隔后,使用 -f n 取出第 n 个区间 --c :以字符为单位取出区间 -``` - -范例1:last 将显示的登入者的信息,要求仅显示用户名。 - -```html -$ last -root pts/1 192.168.201.101 Sat Feb 7 12:35 still logged in -root pts/1 192.168.201.101 Fri Feb 6 12:13 - 18:46 (06:33) -root pts/1 192.168.201.254 Thu Feb 5 22:37 - 23:53 (01:16) - -$ last | cut -d ' ' -f 1 -``` - -范例2:将 export 输出的讯息,取得第 12 字符以后的所有字符串。 - -```html -$ export -declare -x HISTCONTROL="ignoredups" -declare -x HISTSIZE="1000" -declare -x HOME="/home/dmtsai" -declare -x HOSTNAME="study.centos.vbird" -.....(其他省略)..... - -$ export | cut -c 12 -``` - -### 2. 排序命令:sort、uniq - -**sort** 进行排序。 - -```html -$ sort [-fbMnrtuk] [file or stdin] --f :忽略大小写 --b :忽略最前面的空格 --M :以月份的名字来排序,例如 JAN, DEC --n :使用数字 --r :反向排序 --u :相当于 unique ,重复内容只出现一次 --t :分隔符,默认为tab --k :指定排序的区间 -``` - -范例:/etc/passwd 内容是以 : 来分隔的,以第三栏来排序。 - -```html -$ cat /etc/passwd | sort -t ':' -k 3 -root:x:0:0:root:/root:/bin/bash -dmtsai:x:1000:1000:dmtsai:/home/dmtsai:/bin/bash -alex:x:1001:1002::/home/alex:/bin/bash -arod:x:1002:1003::/home/arod:/bin/bash -``` - -**uniq** 可以将重复的数据只取一个。 - -```html -$ uniq [-ic] --i :忽略大小写 --c :进行计数 -``` - -范例:取得每个人的登录总次数 - -```html -$ last | cut -d ' ' -f 1 | sort | uniq -c -1 -6 (unknown -47 dmtsai -4 reboot -7 root -1 wtmp -``` - -### 3. 双向输出重定向:tee - -输出重定向会将输出内容重定向到文件中,而 **tee** 不仅能够完成这个功能,还能保留屏幕上的输出。也就是说,使用 tee 指令,一个输出会同时传送到文件和屏幕上。 - -```html -$ tee [-a] file -``` - -### 4. 字符转换指令:tr、col、expand、join、paste - - **tr** 用来删除一行中的字符,或者对字符进行替换。 - -```html -$ tr [-ds] SET1 ... --d : 删除行中 SET1 这个字符串 -``` - -范例,将 last 输出的信息所有小写转换为大写。 - -```html -$ last | tr '[a-z]' '[A-Z]' -``` - - **col** 将 tab 字符转为空格字符。 - -```html -$ col [-xb] --x : 将 tab 键转换成对等的空格键 -``` - -**expand** 将 tab 转换一定数量的空格,默认是 8 个。 - -```html -$ expand [-t] file --t :tab 转为空格的数量 -``` - -**join** 将有相同数据的那一行合并在一起。 - -```html -$ join [-ti12] file1 file2 --t :分隔符,默认为空格 --i :忽略大小写的差异 --1 :第一个文件所用的比较字段 --2 :第二个文件所用的比较字段 -``` - -**paste** 直接将两行粘贴在一起。 - -```html -$ paste [-d] file1 file2 --d :分隔符,默认为 tab -``` - -### 5. 分区指令:split - -**split** 将一个文件划分成多个文件。 - -```html -$ split [-bl] file PREFIX --b :以大小来进行分区,可加单位,例如 b, k, m 等 --l :以行数来进行分区。 -- PREFIX :分区文件的前导名称 -``` - -# 正规表示法与文件格式化处理 - -## grep - -使用正则表示式把匹配的行提取出来。 - -```html -$ grep [-acinv] [--color=auto] 搜寻字符串 filename --a : 将 binary 文件以 text 文件的方式进行搜寻 --c : 计算找到个数 --i : 忽略大小写 --n : 输出行号 --v : 反向选择,亦即显示出没有 搜寻字符串 内容的那一行 ---color=auto :找到的关键字加颜色显示 -``` - -范例:把含有 the 字符串的行提取出来(注意默认会有 --color=auto 选项,因此以下内容在 Linux 中有颜色显示 the 字符串) - -```html -$ grep -n 'the' regular_express.txt -8:I can't finish the test. -12:the symbol '*' is represented as start. -15:You are the best is mean you are the no. 1. -16:The world Happy is the same with "glad". -18:google is the best tools for search keyword -``` - -因为 { 与 } 的符号在 shell 是有特殊意义的,因此必须要使用使用转义字符进行转义。 - -```html -$ grep -n 'go\{2,5\}g' regular_express.txt -``` - -其它正则表达式请参考 [正则表达式](https://github.com/00000H/notes/blob/master/notes/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F.md). - -## printf - -用于格式化输出。 - -它不属于管道命令,在给 printf 传数据时需要使用 $( ) 形式。 - -```html -$ printf '%10s %5i %5i %5i %8.2f \n' $(cat printf.txt) - DmTsai 80 60 92 77.33 - VBird 75 55 80 70.00 - Ken 60 90 70 73.33 -``` - -## awk - -```html -$ awk '条件类型 1{动作 1} 条件类型 2{动作 2} ...' filename -``` - -awk 每次处理一行,处理的最小单位是字段,每个字段的命名方式为:\$n,n 为字段号,从 1 开始,\$0 表示一整行。 - -范例 1:取出登录用户的用户名和 ip - -```html -$ last -n 5 -dmtsai pts/0 192.168.1.100 Tue Jul 14 17:32 still logged in -dmtsai pts/0 192.168.1.100 Thu Jul 9 23:36 - 02:58 (03:22) -dmtsai pts/0 192.168.1.100 Thu Jul 9 17:23 - 23:36 (06:12) -dmtsai pts/0 192.168.1.100 Thu Jul 9 08:02 - 08:17 (00:14) -dmtsai tty1 Fri May 29 11:55 - 12:11 (00:15) - -$ last -n 5 | awk '{print $1 "\t" $3} -``` - -awk 变量: - -| 变量名称 | 代表意义 | -| -- | -- | -| NF | 每一行拥有的字段总数 | -| NR | 目前所处理的是第几行数据 | -| FS | 目前的分隔字符,默认是空格键 | - -范例 2:输出正在处理的行号,并显示每一行有多少字段 - -```html -$ last -n 5 | awk '{print $1 "\t lines: " NR "\t columns: " NF}' -dmtsai lines: 1 columns: 10 -dmtsai lines: 2 columns: 10 -dmtsai lines: 3 columns: 10 -dmtsai lines: 4 columns: 10 -dmtsai lines: 5 columns: 9 -``` - -可以使用大于等于逻辑,其中等于使用 ==。 - -范例 3:/etc/passwd 文件第三个字段为 UID,对 UID 小于 10 的数据进行处理。 - -```text -cat /etc/passwd | awk 'BEGIN {FS=":"} $3 < 10 {print $1 "\t " $3}' -root 0 -bin 1 -daemon 2 -``` - -# vim 三个模式 - -![](index_files/341c632a-1fc1-4068-9b9f-bf7ef68ebb4c.jpg) - -在指令列模式下,有以下命令用于离开或者存储文件。 - -| 命令 | 作用 | -| -- | -- | -| :w | 写入磁盘| -| :w! | 当文件为只读时,强制写入磁盘。到底能不能写入,与用户对该文件的权限有关 | -| :q | 离开| -| :q! | 强制离开不保存| -| :wq | 写入磁盘后离开| -| :wq!| 强制写入磁盘后离开| - - -# 参考资料 - -- 鸟哥. 鸟 哥 的 Linux 私 房 菜 基 础 篇 第 三 版[J]. 2009. -- [Linux 平台上的软件包管理](https://www.ibm.com/developerworks/cn/linux/l-cn-rpmdpkg/index.html) - diff --git a/notes/笔记/MapReduce.md.txt b/notes/笔记/MapReduce.md.txt deleted file mode 100644 index a7b08654..00000000 --- a/notes/笔记/MapReduce.md.txt +++ /dev/null @@ -1,176 +0,0 @@ -# 直观理解 - -统计一堆牌中有多少张黑桃,最简单的方法是一张一张去数。而 MapReduce 的方法是: - -1. 把这堆牌分配给多个玩家; -2. 让每个玩家数自己手中有多少张黑桃; -3. 把所有玩家数出的黑桃数加起来就是最后的结果。 - -通过让多个玩家来并行地进行统计,MapReduce 可以将统计时间大大缩短。 - -# 大数据 - -从大量数据中快速提取出有价值的信息。注意到这里的数据不一定需要同种类型的数据,可以是多种多样的数据,包括非结构化数据、半结构化数据以及结构化数据。 - -# 基本原理 - -MapReduce 用来对大数据中的海量数据进行离线分析。 - -MapReduce 分为两个阶段:Map 阶段和 Reduce 阶段。 - -在下图中,file 包含多个 block,每个块代表一个海量数据。file 被划分为多个 split,每个分片由一个 Mapper Task 去处理。Map 过程中输入的是 [K1, V1] 数据,这种是一种键值对形式的数据,键为泛型 K1 的类型,值为泛型 V1 的类型。输入也是一个泛型的键值对 [K2, V2]。Shuffle 过程主要是对 Map 阶段输出的键值对进行整合,将键相同的键值对整合到一组中,得到 [K2, {V2,...}] 的整合数据,作为 Reudce 阶段的输入。Reduce 处理完之后得到 [K3, V3] 键值对,Hadoop 会将输出的数据存到分布式文件系统 HDFS 中。 - -![](index_files/e2026020-d669-4faa-9e16-ca726619bb9f.jpg) - -# WordCount 实战 - -## 开发环境配置 - -创建 Maven 项目,在 pom.xml 中添加以下依赖: - -``` -     -        3.0.0 -     - -     -         -            org.apache.hadoop -            hadoop-common -            ${hadoopVersion} -         -         -            org.apache.hadoop -            hadoop-hdfs -            ${hadoopVersion} -         - -         -            org.apache.hadoop -            hadoop-mapreduce-client-core -            ${hadoopVersion} -         -         -            org.apache.hadoop -            hadoop-client -            ${hadoopVersion} -         -     -``` - -## 文本 - -```html -the weather is good -today is good -good weather is good -today has good weather -``` - -将文本的每一行分成一片,每一片的数据提供给 Mapper 进行处理,因此需要 4 个 Mapper。其中 K1 为行号,V1 位该行的字符串。 - -```html -Split-0: [0, "the weather is good] -Split-1: [1, "today is good"] -Split-2: [2, "good weather is good"] -Split-3: [3, "today has good weather] -``` - -## Mapper - -Mapper 类需要提供四个泛型,分别为 K1, V1, K2, V2。 - -```java -class WordCountMappper extends Mapper { -    @Override -    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { -        String line = value.toString(); -        String[] words = line.split(" "); -        for (String word : words) { -            context.write(new Text(word), new IntWritable(1)); -        } -    } -} -``` - -```html -Mapper-0: ["the", 1], ["weather", 1], ["is", 1], ["good", 1] -Mapper-1: ["today", 1], ["is", 1], ["good", 1] -Mapper-2: ["good", 2], ["weather", 1], ["is", 1] -Mapper-3: [today", 1], ["has", 1], ["good", 1], ["weater", 1] -``` - -## Shuffle - -排序并将拥有相同键的数据整合。 - -```html -["good", {1, 1, 2, 1}] -["has", {1}] -["is", {1, 1, 1}] -["the", {1}] -["today", {1, 2}] -["weater", {1,1}] -``` - -## Reducer - -Reducer 同样要指定 4 个泛型:K2, V2, K3, V3 - -```java -public class WordCountReducer extends Reducer { -    @Override -    protected void reduce(Text key, Iterable values, Context context) throws IOException, InterruptedException { -        Integer count = 0; -        for (IntWritable value : values) { -            count += value.get(); -        } -        context.write(key, new IntWritable(count)); -    } -} -``` - -```html -Reducer-0: ["good", 5] -Reducer-1: ["has", 1] -Reducer-2: ["is", 3] -Reducer-3: ["the", 1] -Reducer-4: ["today", 2] -Reducer-5: ["weather", 3] -``` - -## Job - -将项目打包成 jar 包后使用 Hadoop 来运行。 - -```java -public class WordCountMapReduce { -    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException { -        Configuration conf = new Configuration(); - // 新建 Job -        Job job = Job.getInstance(conf, "wordcount"); -        job.setJarByClass(WordCountMapReduce.class); -        job.setMapperClass(WordCountMappper.class); -        job.setReducerClass(WordCountReducer.class); -        // 设置 Mapper 和 Reducer 的 Key Value 类型 -        job.setMapOutputKeyClass(Text.class); -        job.setOutputValueClass(IntWritable.class); -        job.setOutputKeyClass(Text.class); -        job.setOutputValueClass(IntWritable.class); - // 设置 HDFS 路径 -        FileInputFormat.setInputPaths(job, new Path("hdfs://domain:8020/words")); -        FileOutputFormat.setOutputPath(job, new Path("hdfs://domain:8020/words")); - // 运行 -        boolean b = job.waitForCompletion(true); -        if (!b) { -            System.err.println("This task has failed!!!"); -        } -    } -} -``` - - -# 参考资料 - -- [Hadoop MapReduce 入门](http://www.jikexueyuan.com/course/2686.html) - diff --git a/notes/笔记/MySQL+.md.txt b/notes/笔记/MySQL+.md.txt deleted file mode 100644 index 627d1e8e..00000000 --- a/notes/笔记/MySQL+.md.txt +++ /dev/null @@ -1,28 +0,0 @@ -# 主从复制与读写分离 - -## 1. 主从复制 - -![](index_files/QQ_u622A_u56FE20180220095110.png) - -主要涉及三个线程:binlog 线程、I/O 线程和 SQL 线程。 - -1. **binlog 线程**:负责将主服务器上的数据更改写入二进制文件(binlog)中。 -2. **I/O 线程**:负责从主服务器上读取二进制日志文件,并写入中继日志中。 -3. **SQL 线程**:负责读取中继日志并重放其中的 SQL 语句。 - -## 2. 读写分离 - -![](index_files/QQ_u622A_u56FE20180220095116.png) - -主服务器用来处理写操作,而从服务器用来处理读操作。 - -读写分离常用代理方式来实现,代理服务器接收应用层传来的读写请求,然后决定转发到哪个服务器,例如 Amoeba 代理服务器。 - -MySQL 读写分离能提高性能的原因在于: - -1. 主从服务器负责各自的读和写,极大程度缓解了锁的争用; -2. 从服务器可以配置 MyISAM 引擎,提升查询技能以及节约系统开销; -3. 增加冗余,提高可用性。 - -- [MySQL 读写分离介绍及搭建 ](https://segmentfault.com/a/1190000003716617) -- [Mysql 分表和分区的区别、分库分表介绍与区别 ](http://www.cnblogs.com/langtianya/p/4997768.html) diff --git a/notes/笔记/MySQL.md.txt b/notes/笔记/MySQL.md.txt deleted file mode 100644 index 63e0cbff..00000000 --- a/notes/笔记/MySQL.md.txt +++ /dev/null @@ -1,382 +0,0 @@ -[TOC] - -# 存储引擎 - -## 1. InnoDB - -InnoDB 是 MySQL 的默认事务型引擎,只有在需要 InnoDB 不支持的特性时,才考虑使用其它存储引擎。 - -采用 MVCC 来支持高并发,并且实现了四个标准的隔离级别,默认级别是可重复读。 - -表是基于聚簇索引建立的,它对主键的查询性能有很高的提升。 - -内部做了很多优化,包括从磁盘读取数据时采用的可预测性读,能够自动在内存中创建 hash 索引以加速读操作的自适应哈希索引,以及能够加速插入操作的插入缓冲区等。 - -通过一些机制和工具支持真正的热备份。 - -## 2. MyISAM - -MyISAM 提供了大量的特性,包括全文索引、压缩、空间函数(GIS)等。但 MyISAM 不支持事务和行级锁,而且奔溃后无法安全恢复。 - -只能对整张表加锁,而不是针对行。 - -可以手工或者自动执行检查和修复操作,但是和事务恢复以及奔溃恢复不同,可能导致一些数据丢失,而且修复操作是非常慢的。 - -可以包含动态或者静态的行。 - -如果指定了 DELAY_KEY_WRITE 选项,在每次修改执行完成时,不会立即将修改的索引数据写入磁盘,而是会写到内存中的键缓冲区,只有在清理键缓冲区或者关闭表的时候才会将对应的索引块写入磁盘。这种方式可以极大的提升写入性能,但是在数据库或者主机奔溃时会造成索引损坏,需要执行修复操作。 - -如果表在创建并导入数据以后,不会再进行修改操作,那么这样的表适合采用 MyISAM 压缩表。 - -对于只读数据,或者表比较小、可以容忍修复操作,则依然可以继续使用 MyISAM。 - -MyISAM 设计简单,数据以紧密格式存储,所以在某些场景下性能很好。 - -## 3. InnoDB 与 MyISAM 的比较 - -**事务** - -InnoDB 是事务型的。 - -**备份** - -InnoDB 支持在线热备份。 - -**奔溃恢复** - -MyISAM 奔溃后发生损坏的概率比 InnoDB 高很多,而且恢复的速度也更慢。 - -**并发** - -MyISAM 只支持表级锁,而 InnoDB 还支持行级锁。 - -**其它特性** - -MyISAM 支持全文索引,地理空间索引; - -# 数据类型 - -## 1. 整型 - -TINYINT, SMALLINT, MEDIUMINT, INT, BIGINT 分别使用 8, 16, 24, 64 位存储空间,一般情况下越小的列越好。 - -INT(11) 中的数字只是规定了交互工具显示字符的个数,对于存储和计算来说是没有意义的。 - -## 2. 浮点数 - -FLOAT 和 DOUBLE 为浮点类型,DECIMAL 为高精度小数类型。CPU 原生支持浮点运算,但是不支持 DECIMAl 类型的计算,因此 DECIMAL 的计算比浮点类型需要更高的代价。 - -FLOAT、DOUBLE 和 DECIMAL 都可以指定列宽,例如 DECIMAL(18, 9) 表示总共 18 位,取 9 位存储小数部分,剩下 9 位存储整数部分。 - -## 3. 字符串 - -主要有 CHAR 和 VARCHAR 两种类型,一种是定长的,一种是变长的。 - -VARCHAR 这种变长类型能够节省空间,因为只需要存储必要的内容。但是在执行 UPDATE 时可能会使行变得比原来长,当超出一个页所能容纳的大小时,就要执行额外的操作,MyISAM 会将行拆成不同的片段存储,而 InnoDB 则需要分裂页来使行放进页内。 - -VARCHAR 会保留字符串末尾的空格,而 CHAR 会删除。 - -## 4. 时间和日期 - -MySQL 提供了两种相似的日期时间类型:DATATIME 和 TIMESTAMP。 - -**DATATIME** - -能够保存从 1001 年到 9999 年的日期和时间,精度为秒,使用 8 字节的存储空间。 - -它与时区无关。 - -默认情况下,MySQL 以一种可排序的、无歧义的格式显示 DATATIME 值,例如“2008-01016 22:37:08”,这是 ANSI 标准定义的日期和时间表示方法。 - -**TIMESTAMP** - -和 UNIX 时间戳相同,保存从 1970 年 1 月 1 日午夜(格林威治时间)以来的秒数,使用 4 个字节,只能表示从 1970 年 到 2038 年。 - -它和时区有关。 - -MySQL 提供了 FROM_UNIXTIME() 函数把 Unxi 时间戳转换为日期,并提供了 UNIX_TIMESTAMP() 函数把日期转换为 Unix 时间戳。 - -默认情况下,如果插入时没有指定 TIMESTAMP 列的值,会将这个值设置为当前时间。 - -应该尽量使用 TIMESTAMP,因为它比 DATETIME 空间效率更高。 - -# 索引 - -索引是在存储引擎层实现的,而不是在服务器层实现的,所以不同存储引擎具有不同的索引类型和实现。 - -索引能够轻易将查询性能提升几个数量级。 - -对于非常小的表、大部分情况下简单的全表扫描比建立索引更高效。对于中到大型的表,索引就非常有效。但是对于特大型的表,建立和使用索引的代价将会随之增长。这种情况下,需要用到一种技术可以直接区分出需要查询的一组数据,而不是一条记录一条记录地匹配,例如可以使用分区技术。 - -## 1. 索引分类 - -### 1.1 B-Tree 索引 - -B-Tree 索引是大多数 MySQL 存储引擎的默认索引类型。 - -因为不再需要进行全表扫描,只需要对树进行搜索即可,因此查找速度快很多。 - -可以指定多个列作为索引列,多个索引列共同组成键。B-Tree 索引适用于全键值、键值范围和键前缀查找,其中键前缀查找只适用于最左前缀查找。 - -除了用于查找,还可以用于排序和分组。 - -如果不是按照索引列的顺序进行查找,则无法使用索引。 - -### 1.2 哈希索引 - -基于哈希表实现,优点是查找非常快。 - -在 MySQL 中只有 Memory 引擎显式支持哈希索引。 - -InnoDB 引擎有一个特殊的功能叫“自适应哈希索引”,当某个索引值被使用的非常频繁时,会在 B-Tree 索引之上再创建一个哈希索引,这样就让 B-Tree 索引具有哈希索引的一些优点,比如快速的哈希查找。 - -限制:哈希索引只包含哈希值和行指针,而不存储字段值,所以不能使用索引中的值来避免读取行。不过,访问内存中的行的速度很快,所以大部分情况下这一点对性能影响并不明显;无法用于分组与排序;只支持精确查找,无法用于部分查找和范围查找;如果哈希冲突很多,查找速度会变得很慢。 - -### 1.3. 空间索引数据(R-Tree) - -MyISAM 存储引擎支持空间索引,可以用于地理数据存储。 - -空间索引会从所有维度来索引数据,可以有效地使用任意维度来进行组合查询。 - -### 1.4 全文索引 - -MyISAM 存储引擎支持全文索引,用于查找文本中的关键词,而不是直接比较索引中的值。 - -使用 MATCH AGAINST,而不是普通的 WHERE。 - -## 2. 索引的优点 - -- 大大减少了服务器需要扫描的数据量; - -- 帮助服务器避免进行排序和创建临时表; - -- 将随机 I/O 变为顺序 I/O。 - -## 3. 索引优化 - -### 3.1 独立的列 - -在进行查询时,索引列不能是表达式的一部分,也不能是函数的参数,否则无法使用索引。 - -例如下面的查询不能使用 actor_id 列的索引: - -```sql -SELECT actor_id FROM sakila.actor WHERE actor_id + 1 = 5; -``` - -### 3.2 前缀索引 - -对于 BLOB、TEXT 和 VARCHAR 类型的列,必须使用前缀索引,只索引开始的部分字符。 - -对于前缀长度的选取需要根据 **索引选择性** 来确定:不重复的索引值和记录总数的比值。选择性越高,查询效率也越高。最大值为 1 ,此时每个记录都有唯一的索引与其对应。 - -### 3.3 多列索引 - -在需要使用多个列作为条件进行查询时,使用多列索引比使用多个单列索引性能更好。例如下面的语句中,最好把 actor_id 和 file_id 设置为多列索引。 - -```sql -SELECT file_id, actor_ id FROM sakila.film_actor -WhERE actor_id = 1 OR film_id = 1; -``` - -### 3.4 索引列的顺序 - -让选择性最强的索引列放在前面,例如下面显示的结果中 customer_id 的选择性比 staff_id 更高,因此最好把 customer_id 列放在多列索引的前面。 - -```sql -SELECT COUNT(DISTINCT staff_id)/COUNT(*) AS staff_id_selectivity, -COUNT(DISTINCT customer_id)/COUNT(*) AS customer_id_selectivity, -COUNT(*) -FROM payment; -``` - -```html - staff_id_selectivity: 0.0001 -customer_id_selectivity: 0.0373 - COUNT(*): 16049 -``` - -### 3.5 聚簇索引 - -![](index_files/b9e9ae8c-e216-4c01-b267-a50dbeb98fa4.jpg) - -聚簇索引并不是一种索引类型,而是一种数据存储方式。 - -术语“聚簇”表示数据行和相邻的键值紧密地存储在一起,InnoDB 的聚簇索引的数据行存放在 B-Tree 的叶子页中。 - -因为无法把数据行存放在两个不同的地方,所以一个表只能有一个聚簇索引。 - -**优点** - -1. 可以把相关数据保存在一起,减少 I/O 操作; -2. 因为数据保存在 B-Tree 中,因此数据访问更快。 - -**缺点** - -1. 聚簇索引最大限度提高了 I/O 密集型应用的性能,但是如果数据全部放在内存,就没必要用聚簇索引。 -2. 插入速度严重依赖于插入顺序,按主键的顺序插入是最快的。 -3. 更新操作代价很高,因为每个被更新的行都会移动到新的位置。 -4. 当插入到某个已满的页中,存储引擎会将该页分裂成两个页面来容纳该行,页分裂会导致表占用更多的磁盘空间。 -5. 如果行比较稀疏,或者由于页分裂导致数据存储不连续时,聚簇索引可能导致全表扫描速度变慢。 - -### 3.6 覆盖索引 - -索引包含所有需要查询的字段的值。 - -## 4. B-Tree 和 B+Tree 原理 - -### 4. 1 B-Tree - -![](index_files/5ed71283-a070-4b21-85ae-f2cbfd6ba6e1.jpg) - -为了描述 B-Tree,首先定义一条数据记录为一个二元组 [key, data],key 为记录的键,data 为数据记录除 key 外的数据。 - -B-Tree 是满足下列条件的数据结构: - -- 所有叶节点具有相同的深度,也就是说 B-Tree 是平衡的; -- 一个节点中的 key 从左到右非递减排列; -- 如果某个指针的左右相邻 key 分别是 keyi 和 keyi+1,且不为 null,则该指针指向节点的所有 key 大于 keyi 且小于 keyi+1。 - -在 B-Tree 中按 key 检索数据的算法非常直观:首先从根节点进行二分查找,如果找到则返回对应节点的 data,否则对相应区间的指针指向的节点递归进行查找,直到找到节点或找到 null 指针,前者查找成功,后者查找失败。 - -由于插入删除新的数据记录会破坏 B-Tree 的性质,因此在插入删除时,需要对树进行一个分裂、合并、转移等操作以保持 B-Tree 性质。 - -### 4.2 B+Tree - -![](index_files/63cd5b50-d6d8-4df6-8912-ef4a1dd5ba13.jpg) - -与 B-Tree 相比,B+Tree 有以下不同点: - -- 每个节点的指针上限为 2d 而不是 2d+1; -- 内节点不存储 data,只存储 key,叶子节点不存储指针。 - -### 4.3 带有顺序访问指针的 B+Tree - -![](index_files/1ee5f0a5-b8df-43b9-95ab-c516c54ec797.jpg) - -一般在数据库系统或文件系统中使用的 B+Tree 结构都在经典 B+Tree 基础上进行了优化,在叶子节点增加了顺序访问指针,做这个优化的目的是为了提高区间访问的性能。 - -### 4.4 为什么使用 B-Tree 和 B+Tree - -红黑树等数据结构也可以用来实现索引,但是文件系统及数据库系统普遍采用 B-/+Tree 作为索引结构。 - -页是计算机管理存储器的逻辑块,硬件及操作系统往往将主存和磁盘存储区分割为连续的大小相等的块,每个存储块称为一页(在许多操作系统中,页得大小通常为 4k),主存和磁盘以页为单位交换数据。 - -一般来说,索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上。为了减少磁盘 I/O,磁盘往往不是严格按需读取,而是每次都会预读。这样做的理论依据是计算机科学中著名的局部性原理:当一个数据被用到时,其附近的数据也通常会马上被使用。数据库系统的设计者巧妙利用了磁盘预读原理,将一个节点的大小设为等于一个页,这样每个节点只需要一次 I/O 就可以完全载入。B-Tree 中一次检索最多需要 h-1 次 I/O(根节点常驻内存),渐进复杂度为 O(h)=O(logdN)。一般实际应用中,出度 d 是非常大的数字,通常超过 100,因此 h 非常小(通常不超过 3)。而红黑树这种结构,h 明显要深的多。并且于逻辑上很近的节点(父子)物理上可能很远,无法利用局部性,效率明显比 B-Tree 差很多。 - -B+Tree 更适合外存索引,原因和内节点出度 d 有关。由于 B+Tree 内节点去掉了 data 域,因此可以拥有更大的出度,拥有更好的性能。 - -# 查询性能优化 - -## 1. Explain - -用来分析 SQL 语句,分析结果中比较重要的字段有: - -- select_type : 查询类型,有简单查询、联合查询和子查询 - -- key : 使用的索引 - -- rows : 扫描的行数 - -## 2. 减少返回的列 - -慢查询主要是因为访问了过多数据,除了访问过多行之外,也包括访问过多列。 - -最好不要使用 SELECT * 语句,要根据需要选择查询的列。 - -## 3. 减少返回的行 - -最好使用 LIMIT 语句来取出想要的那些行。 - -还可以建立索引来减少条件语句的全表扫描。例如对于下面的语句,不适用索引的情况下需要进行全表扫描,而使用索引只需要扫描几行记录即可,使用 Explain 语句可以通过观察 rows 字段来看出这种差异。 - -```sql -SELECT * FROM sakila.film_actor WHERE film_id = 1; -``` - -## 4. 拆分大的 DELETE 或 INSERT 语句 - -如果一次性执行的话,可能一次锁住很多数据、占满整个事务日志、耗尽系统资源、阻塞很多小的但重要的查询。 - -```sql -DELEFT FROM messages WHERE create < DATE_SUB(NOW(), INTERVAL 3 MONTH); -``` -```sql -rows_affected = 0 -do { - rows_affected = do_query( - "DELETE FROM messages WHERE create < DATE_SUB(NOW(), INTERVAL 3 MONTH) LIMIT 10000") -} while rows_affected > 0 -``` - -# 分库与分表 - -**1. 分表与分区的不同** - -分表,就是讲一张表分成多个小表,这些小表拥有不同的表名;而分区是将一张表的数据分为多个区块,这些区块可以存储在同一个磁盘上,也可以存储在不同的磁盘上,这种方式下表仍然只有一个。 - -**2. 使用分库与分表的原因** - -随着时间和业务的发展,数据库中的表会越来越多,并且表中的数据量也会越来越大,那么读写操作的开销也会随着增大。 - -**3. 垂直切分** - -将表按功能模块、关系密切程度划分出来,部署到不同的库上。例如,我们会建立商品数据库 payDB、用户数据库 userDB 等,分别用来存储项目与商品有关的表和与用户有关的表。 - -**4. 水平切分** - -把表中的数据按照某种规则存储到多个结构相同的表中,例如按 id 的散列值、性别等进行划分, - -**5. 垂直切分与水平切分的选择** - -如果数据库中的表太多,并且项目各项业务逻辑清晰,那么垂直切分是首选。 - -如果数据库的表不多,但是单表的数据量很大,应该选择水平切分。 - -**6. 水平切分的实现方式** - -最简单的是使用 merge 存储引擎。 - -**7. 分库与分表存在的问题** - -(1) 事务问题 - -在执行分库分表之后,由于数据存储到了不同的库上,数据库事务管理出现了困难。如果依赖数据库本身的分布式事务管理功能去执行事务,将付出高昂的性能代价;如果由应用程序去协助控制,形成程序逻辑上的事务,又会造成编程方面的负担。 - -(2) 跨库跨表连接问题 - -在执行了分库分表之后,难以避免会将原本逻辑关联性很强的数据划分到不同的表、不同的库上。这时,表的连接操作将受到限制,我们无法连接位于不同分库的表,也无法连接分表粒度不同的表,导致原本只需要一次查询就能够完成的业务需要进行多次才能完成。 - -# 故障转移和故障恢复 - -故障转移也叫做切换,当主库出现故障时就切换到备库,使备库成为主库。故障恢复顾名思义就是从故障中恢复过来,并且保证数据的正确性。 - -## 1. 故障转移 - -**1.1 提升备库或切换角色** - -提升一台备库为主库,或者在一个主-主复制结构中调整主动和被动角色。 - -**1.2 虚拟 IP 地址和 IP 托管** - -为 MySQL 实例指定一个逻辑 IP 地址,当 MySQL 实例失效时,可以将 IP 地址转移到另一台 MySQL 服务器上。 - -**1.3 中间件解决方案** - -通过代理,可以路由流量到可以使用的服务器上。 - -![](index_files/fabd5fa0-b75e-48d0-9e2c-31471945ceb9.jpg) - -**1.4 在应用中处理故障转移** - -将故障转移整合到应用中可能导致应用变得太过笨拙。 - -## 2. 故障恢复 - - -# 参考资料 - -- 高性能 MySQL -- [MySQL 索引背后的数据结构及算法原理 ](http://blog.codinglabs.org/articles/theory-of-mysql-index.html) -- [MySQL 索引优化全攻略 ](http://www.runoob.com/w3cnote/mysql-index.html) -- [20+ 条 MySQL 性能优化的最佳经验 ](https://www.jfox.info/20-tiao-mysql-xing-nen-you-hua-de-zui-jia-jing-yan.html) \ No newline at end of file diff --git a/notes/笔记/PHP.md.txt b/notes/笔记/PHP.md.txt deleted file mode 100644 index 9556bd6d..00000000 --- a/notes/笔记/PHP.md.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/notes/笔记/Python 基本语法.md.txt b/notes/笔记/Python 基本语法.md.txt deleted file mode 100644 index be80c9a5..00000000 --- a/notes/笔记/Python 基本语法.md.txt +++ /dev/null @@ -1,548 +0,0 @@ -[TOC] - -# 第 1 章 作为命令行脚本 - -第一行注释告诉 Linux/Unix 系统当执行程序的时候,它应该使用哪个解释器。 - -```python -#!/usr/bin/python -# Filename : helloworld.py -print 'Hello World' -``` - -需要先给脚本设置可执行的权限。 - -```html -$ chmod a+x helloworld.py -$ ./helloworld.py -Hello World -``` - -通过以下命令可以把一个文件夹下的脚本都添加到 PATH 目录下,则可以在任何地方运行该脚本。 - -```html -PATH=$PATH:/home/username/dir -``` - -print 函数会自动加换行符,可以在 print 之后加逗号 , 来取消换行。 - -# 第 2 章 基本概念 - -## 2.1 复数 - -(2.3-4.6j) - -## 2.2 字符串 - -单引号和双引号表示的字符串完全相同,三引号表示的字符串中间可以有单引号和双引号,并且可以换行。 - -```html -'''This is the first line. -This is the second line. -"What's your name?," I asked. -He said "Bond, James Bond." -''' -``` - -字符串行末的反斜杠表示下一行继续。 - -```html -"This is the first sentence.\ -This is the second sentence." -``` - -自然字符串用 r 作为前缀,当不想让字符串中的内容转义时使用,比如正则表达式。 - -```html -r"Newlines are indicated by \n" -``` - -Unicode 是书写国际文本的标准方法。在处理文本文件的时候应该使用 Unicode 字符串,特别是这个文件含有用非英语的语言写的文本时使用。 - -```html -u"This is a Unicode string." -``` - -## 2.3 对象 - -Python 中用到的任何东西都被称为对象; - -## 2.4 逻辑行与物理行 - -可以将多个逻辑行写在一个物理行中,需要用分号分割;可以将一个逻辑行写在多个物理行中,需要在行末加 \ 。 - -## 2.5 缩进 - -同一层次的语句必须拥有相同的缩进。 - -# 第 3 章 运算符 - -|  运算符 | 说明  | -| --- | --- | -|  ** | 幂  | -|  // | 取整除  | -|  % | 取模,不取整  | -|  ~ | 按位翻转,-(x+1) | -|  and | 与  | -|  or | 或  | -|  not | 非  | - - -# 第 4 章 控制流 - -## 4.1 条件语句 - -Java 中的 else if 在 Python 中对应的语句为:elif。 - -True False - -## 4.2 控制台输入 - -从控制台中输入一行: - -``` -guess = int(raw_input('Enter an integer : ')) -``` - -## 4.3 循环 - -range(m, n, step),返回一个从 m 到 n,步长为 stap 的序列,不包含 n 。 - -for 后面的 else 语句在 break 退出的情况下不会执行: -``` -for i in range(1, 5): -      print i -else: -      print 'The for loop is over -``` - -len(str) 返回字符串的长度; - -# 第 5 章 函数 - -``` -def sayHello(): -    print 'hello world!' - -sayHello() -``` - -global 用来声明全局变量。 - -## 5.1 默认参数 - -``` -def say(message, times = 1): -    print message * times - -say('Hello') -say('World', 5) -``` - -## 5.2 关键参数 - -使用名字而不是位置来指定参数 - -``` -def func(a, b=5, c=10): -    print 'a is', a, 'and b is', b, 'and c is', c - -func(3, 7) -func(25, c=24) -func(c=50, a=100) -``` - -## 5.3 return - -没有 return 语句的函数默认 return None ,None 在 Python 中是指一个没有任何值的特殊类型。 - -## 5.4 DocStrings - -使用函数的 \_\_doc\_\_ 属性可以返回函数中的文档字符串。 - -``` -def printMax(x, y): -'''Prints the maximum of two numbers. -The two values must be integers.''' -x = int(x) -y = int(y) -if x > y: -    print x, 'is maximum' -else: -    print y, 'is maximum' - -printMax(3, 5) -print printMax.__doc__ -``` - -# 第 6 章 模块 - -模块基本上就是一个包含定义函数和变量的文件,使用 import 语句导入一个模块; - -``` -import sys -``` - -.pyc 是字节文件,会比源文件快很多,而且与平台无关。 - -使用 from sys import argv 之后可以直接在代码中使用 argv ,而不用 sys.argv。但是为了易读性,最好不要用这种方式。 - -\_\_name\_\_ 属性为一个模块的名字。 - -``` -if __name__ == '__main__': -    print 'This program is being run by itself' -else: -    print 'I am being imported from another module -``` - -创建一个 python 文件之后,要使得另一个 python 文件可以使用它,需要把它放在这个文件的同目录下,或者把它放到 sys.path 目录下。 - -使用 . 运算符来引用模块中的成员。 - -dir() 函数可以列出一个模块的函数、变量和类。 - -# 第 7 章 数据结构 - -## 7.1 列表 - -列表是处理一组有序的集合,并不说排好序了,而是有顺序。使用 [ ] 来定义一个列表。 - -```python -# This is my shopping list -shoplist = ['apple', 'mango', 'carrot', 'banana'] -print 'I have', len(shoplist),'items to purchase.' -print 'These items are:', # Notice the comma at end of the line -for item in shoplist: -     print item, -shoplist.append('rice') -shoplist.sort() -del shoplist[0] -``` - -## 7.2 元组 - -元组不可变。 - -一个元组可以包含另一个元组。 - -使用 ( ) 定义一个元组。 - -``` -zoo = ('wolf', 'elephant', 'penguin') -new_zoo = ('monkey', 'dolphin', zoo) -print 'Last animal brought from old zoo is', new_zoo[2][2] -``` - -元组可以用在打印语句中。 - -``` -age = 22 -name = 'Swaroop' -print '%s is %d years old' % (name, age) -print 'Why is %s playing with that python?' % name -``` - -## 7.3 字典 - -必须使用不可变的对象作为键。 - -d = {key1 : value1, key2 : value2 } 定义一个字典。 - -items() 方法返回所有 key。 - -has_key() 方法判断是否含有一个 key。 - -## 7.4 序列 - -字符串,元组和列表都是序列,序列主要具有索引和切片操作; - -简单复制语句不会创建拷贝,要拷贝一个序列时用切片操作 - -``` -mylist = shoplist[:] -``` - -## 7.5 字符串 - -startwith() - -in 操作符可以判断一个给定字符串是否在另一个字符串中。 - -find() 查找一个字符串在另一个字符串中的位置,如果查找失败返回 -1。 - -# 第 8 章 面向对象 - -## 8.1 self - -self 类似于 java 中的 this 指针。 - -## 8.2 类 - -``` -class Person: -    pass # An empty block - -p = Person() -print p -``` - -## 8.3 对象的方法 - -方法和函数最主要的区别是方法需要传入一个 self 变量。 - -``` -class Person: -    def sayHi(self): -       print 'Are you OK?' - -leijun = Person() -p.sayHi() -``` - -## 8.4 \_\_init\_\_() - -构造函数 - -``` -class Person: -  def __init__(self, name): -    self.name = name -  def sayHi(self): -    print 'Hello, my name is', self.name - -p = Person('Swaroop') -p.sayHi() -``` - -## 8.5 类与对象的方法 - -成员名有双下划线前缀 \_\_ ,比如 \_\_init\_\_(),则为私有成员。如果自己定义的类中某个成员想定义为私有成员,则在该成员名前加单下划线 \_,虽然 Python 没有规定这样做可以把成员变成私有成员,但是可以作为一种编程约定来使用。 - -## 8.6 \_\_del\_\_() - -析构函数 - -## 8.7 继承 - -``` -class Person: -    def __init__(self, name, age): -        self.__name = name -        self.__age = age - -class Worker(Person): -    def __init__(self, name, age, salary): -        Person.__init__(self, name, age) -        self.salary = salary -``` - -# 第 9 章 输入 / 输出 - -## 9.1 文件 - -``` -poem = '''\ -Programming is fun -When the work is done -if you wanna make your work also fun: -use Python! -''' -f = file('poem.txt', 'w') -f.write(poem) -f.close() - -f = file('poem.txt') -while True: -    line = f.readline() -    if len(line) == 0: -        break -    print line -f.close() - -``` - -## 9.2 序列化 - -pickle 模块可以用于存取一个对象。 - -cPickle 功能完全相同,但是用 c 语言编写的,速度快 1000 倍; - -``` -import cPickle as p - -shoplistfile = 'shoplist.data' -shoplist = ['apple', 'mango', 'carrot'] - -f = file(shoplistfile, 'w') -p.dump(shoplist, f) -f.close() - -del shoplist - -f = file(shoplistfile) -storedlist = p.load(f) -print storedlist - -``` - -# 第 10 章 异常 - -## 10.1 try-except - -可以跟上一个 else 从句,当没有异常发生时会执行; - -``` -import sys -try: -    s = raw_input('Enter something --> ') -except EOFError: -    print '\nWhy did you do an EOF on me?' -    sys.exit() # exit the program -except: -    print '\nSome error/exception occurred.' -# here, we are not exiting the program -print 'Done' - -``` - -## 10.2 引发异常 - -raise 语句可以引发一个异常,引发的异常应该是 Error 或者 Exception 的类或者子类; - -``` -class MyException(Exception): -    def __init__(self): -        self.name = 'MyException' - -try: -    raise MyException -except MyException, e: -    print e.name -else: -    print 'nothing' -``` - -## 10.3 finally - -finally 可以保证一个语句块无论如何都会执行。 - -# 第 11 章 Python 标准库 - -## 11.1 sys 模块 - -sys.argv 参数列表。 - -sys.stdin sys.stdout sys.stderr。 - -## 11.2 os 模块 - -该模块包含了普遍的操作系统功能,使程序能够与平台无关; - -os.name 字符串指示你正在使用的平台。比如对于 Windows,它是 'nt' ,而对于 Linux/Unix 用户,它是 'posix'。 - -os.getcwd() 函数得到当前工作目录,即当前 Python 脚本工作的目录路径。 - -os.getenv() 和 os.putenv() 函数分别用来读取和设置环境变量。 - -os.listdir() 返回指定目录下的所有文件和目录名。 - -os.remove() 函数用来删除一个文件。 - -os.system() 函数用来运行 shell 命令。 - -os.linesep 字符串给出当前平台使用的行终止符。例如,Windows 使用 '\r\n',Linux 用 '\n' 而 Mac 使用 '\r'。 - -os.path.split() 函数返回一个路径的目录名和文件名。 - -``` -os.path.split('/home/swaroop/byte/code/poem.txt') -('/home/swaroop/byte/code', 'poem.txt') -``` - -os.path.isfile() 和 os.path.isdir() 函数分别检验给出的路径是一个文件还是目录。类似地,os.path.exists() 函数用来检验给出的路径是否真地存在。 - -# 第 12 章 更多 Python 的内容 - -## 12.1 特殊的方法 - -特殊的方法主要用于模仿某个行为。 - -| 名称 | 说明 | -| ------------ | ------------ | -| __init__(self,...) | 这个方法在新建对象恰好要被返回使用之前被调用。 | -| __del__(self) | 恰好在对象要被删除之前调用。 | -| __str__(self) | 在我们对对象使用 print 语句或是使用 str() 的时候调用。 | -| __lt__(self, other) | 当使用 小于 运算符(<)的时候调用。类似地,对于所有的运算符(+,> 等等)都有特殊的方法。 | -| __getitem__(self, key) | 使用 x[key] 索引操作符的时候调用。 | -| __len__(self) | 对序列对象使用内建的 len() 函数的时候调用。 | - -## 12.2 列表综合 - -``` -listone = [2, 3, 4] -listtwo = [2*i for i in listone if i > 2] -print listtwo -``` - -## 12.3 在函数中接受元组和列表 - -``` -def powersum(power, *args): -  total = 0 -  for i in args: -  total += pow(i, power) -  return tota -``` - -由于在 args 变量前有 \* 前缀,所有多余的函数参数都会作为一个元组存储在 args 中。如果使用的是 \*\* 前缀,多余的参数则会被认为是一个字典的 键 / 值 对。 - -## 12.4 lambada 形式 - -lambda 语句被用来创建新的函数对象,并且在运行时返回它们。 - -``` -def make_repeater(n): -  return lambda s: s*n -twice = make_repeater(2) -print twice('word') -print twice(5) - -wordword -10 -``` - -## 12.5 exec 和 eval() - -``` -exec 'print "Hello World"' -eval('2*3') -``` - -## 12.6 assert 语句 - -asser t 断言在非真时会引发一个 AssertionErro。 - -``` -mylist = ['item'] -assert len(mylist) >= 1 -``` - -## 12.7 repr() 函数 - -repr() 函数和反引号用来获取对象的可打印的表示形式。 - -可以通过定义类的\_\_repr\_\_() 方法来控制你的对象在被 repr 函数调用的时候返回的内容。 - -``` ->>> i = [] ->>> i.append('item') ->>> `i` -"['item']" ->>> repr(i) -"['item'] -``` - -# 参考资料 - -1. 《简明 Python 教程》 \ No newline at end of file diff --git a/notes/笔记/Python 知识点扩展.md.txt b/notes/笔记/Python 知识点扩展.md.txt deleted file mode 100644 index dc4dbac2..00000000 --- a/notes/笔记/Python 知识点扩展.md.txt +++ /dev/null @@ -1,471 +0,0 @@ -[TOC] - - -# 实现单例模式 - -## 1 使用\_\_new\_\_方法 - -```python -class Singleton(object): -    def __new__(cls, *args, **kw): -        if not hasattr(cls, '_instance'): -            orig = super(Singleton, cls) -            cls._instance = orig.__new__(cls, *args, **kw) -        return cls._instance - -class MyClass(Singleton): -    a = 1 -``` - -## 2 共享属性 - -创建实例时把所有实例的`__dict__`指向同一个字典 , 这样它们具有相同的属性和方法 . - -```python - -class Borg(object): -    _state = {} -    def __new__(cls, *args, **kw): -        ob = super(Borg, cls).__new__(cls, *args, **kw) -        ob.__dict__ = cls._state -        return ob - -class MyClass2(Borg): -    a = 1 -``` - -## 3 装饰器版本 - -```python -def singleton(cls, *args, **kw): -    instances = {} -    def getinstance(): -        if cls not in instances: -            instances[cls] = cls(*args, **kw) -        return instances[cls] -    return getinstance - -@singleton -class MyClass: -  ... -``` - -## 4 import 方法 - -作为 python 的模块是天然的单例模式 - -```python -# mysingleton.py -class My_Singleton(object): -    def foo(self): -        pass - -my_singleton = My_Singleton() - -# to use -from mysingleton import my_singleton - -my_singleton.foo() - -``` - - - -# 垃圾回收机制 - -Python GC 主要使用引用计数(reference counting)来跟踪和回收垃圾。在引用计数的基础上,通过“标记 - 清除”(mark and sweep)解决容器对象可能产生的循环引用问题,通过“分代回收”(generation collection)以空间换时间的方法提高垃圾回收效率。 - -## 1 引用计数 - -PyObject 是每个对象必有的内容,其中`ob_refcnt`就是做为引用计数。当一个对象有新的引用时,它的`ob_refcnt`就会增加,当引用它的对象被删除,它的`ob_refcnt`就会减少 . 引用计数为 0 时,该对象生命就结束了。 - -优点 : - -1. 简单 -2. 实时性 - -缺点 : - -1. 维护引用计数消耗资源 -2. 循环引用 - -## 2 标记 - 清除机制 - -基本思路是先按需分配,等到没有空闲内存的时候从寄存器和程序栈上的引用出发,遍历以对象为节点、以引用为边构成的图,把所有可以访问到的对象打上标记,然后清扫一遍内存空间,把所有没标记的对象释放。 - -## 3 分代技术 - -分代回收的整体思想是:将系统中的所有内存块根据其存活时间划分为不同的集合,每个集合就成为一个“代”,垃圾收集频率随着“代”的存活时间的增大而减小,存活时间通常利用经过几次垃圾回收来度量。 - -Python 默认定义了三代对象集合,索引数越大,对象存活时间越长。 - -举例: -当某些内存块 M 经过了 3 次垃圾收集的清洗之后还存活时,我们就将内存块 M 划到一个集合 A 中去,而新分配的内存都划分到集合 B 中去。当垃圾收集开始工作时,大多数情况都只对集合 B 进行垃圾回收,而对集合 A 进行垃圾回收要隔相当长一段时间后才进行,这就使得垃圾收集机制需要处理的内存少了,效率自然就提高了。在这个过程中,集合 B 中的某些内存块由于存活时间长而会被转移到集合 A 中,当然,集合 A 中实际上也存在一些垃圾,这些垃圾的回收会因为这种分代的机制而被延迟。 - -# list 实现 - -https://www.jianshu.com/p/J4U6rR - -# Python 2 和 3 的区别 - -http://chenqx.github.io/2014/11/10/Key-differences-between-Python-2-7-x-and-Python-3-x/ - -# 去除列表中重复的数 - -```python -a = [1, 2, 4, 2, 4, 5, 6, 5, 7, 8, 9, 0] -a = list(set(a)) -``` - -# 替换字符串 - -使用 replace() 函数 - -```python -str = "aaa bbb ccc" -str = str.replace("aaa", "111") -``` - -使用 re 模块的 sub() 函数进行查询和替换。 - -```python -import re - -str = "aaa bbb ccc" -rex = r'(aaa|bbb)' -str = re.sub(rex, "111", str) -``` - -# read, readline 和 readlines - -- read 读取整个文件 -- readline 读取下一行 , 使用生成器方法 -- readlines 读取整个文件到一个迭代器以供我们遍历 - -# is 与 == - -is 比较地址,== 比较值 - - -# 可变对象与不可变对象 - -在 python 中,strings, tuples, 和 numbers 是不可更改的对象,而 list, dict, set 等则是可以修改的对象。 - -当一个变量引用了一个不可变对象时,变量值发生改变,变量所引用的对象也发生改变,也就是对象引用的地址也发生了改变。 - -# 静态方法和类方法 - -Python 其实有 3 个方法 , 即静态方法 (staticmethod), 类方法 (classmethod) 和对象方法。 - -其中对象方法的第一个参数 selft,在使用类对象调用时会将对象隐式传入;类方法的第一个参数 cls,在使用类调用时会将类隐式传入;而静态方法没有隐式参数。 - -```python -def foo(x): -    print "executing foo(%s)"%(x) - -class A(object): -    def foo(self, x): -        print "executing foo(%s,%s)"%(self, x) - -    @classmethod -    def class_foo(cls, x): -        print "executing class_foo(%s,%s)"%(cls, x) - -    @staticmethod -    def static_foo(x): -        print "executing static_foo(%s)"%x - -``` - -详情:[What is the difference between @staticmethod and @classmethod in Python? -](https://stackoverflow.com/questions/136097/what-is-the-difference-between-staticmethod-and-classmethod-in-python) - -# 拷贝 - -赋值创建了对象的一个新的引用;浅拷贝创建一个新的对象,但它包含的是对原始对象中包含项的引用,而深拷贝会递归的复制它所包含的对象。 - -创建浅拷贝的方式: - -- 完全切片方法; -- 工厂函数,如 list(); -- copy 模块的 copy() 函数 - -创建深拷贝的方式: - -- copy 模块的 deepcopy() 函数 - - -```python -import copy -a = [1, 2, 3, 4, ['a', 'b']]  # 原始对象 - -b = a  # 赋值,传对象的引用 -c = copy.copy(a)  # 对象拷贝,浅拷贝 -d = copy.deepcopy(a)  # 对象拷贝,深拷贝 - -a.append(5)  # 修改对象 a -a[4].append('c')  # 修改对象 a 中的 ['a', 'b'] 数组对象 - -print 'a = ', a -print 'b = ', b -print 'c = ', c -print 'd = ', d - -输出结果: -a =  [1, 2, 3, 4, ['a', 'b', 'c'], 5] -b =  [1, 2, 3, 4, ['a', 'b', 'c'], 5] -c =  [1, 2, 3, 4, ['a', 'b', 'c']] -d =  [1, 2, 3, 4, ['a', 'b']] -``` - -# 作用域 - -当 Python 遇到一个变量的话他会按照这样的顺序进行搜索: - -本地作用域(Local)→当前作用域被嵌入的本地作用域(Enclosing locals)→全局 / 模块作用域(Global)→内置作用域(Built-in) - - - -# 类变量与实例变量 - -```python -class Test(object): -    num_of_instance = 0 -    def __init__(self, name): -        self.name = name -        Test.num_of_instance += 1 - -if __name__ == '__main__': -    print Test.num_of_instance   # 0 -    t1 = Test('jack') -    print Test.num_of_instance   # 1 -    t2 = Test('lucy') -    print t1.name , t1.num_of_instance  # jack 2 -    print t2.name , t2.num_of_instance  # lucy 2 -``` - -# 自省 - -类似于 Java 的反射 - -```python -a = [1, 2, 3] -b = {'a':1,'b':2,'c':3} -c = True -print type(a), type(b), type(c) #    -print isinstance(a, list)  # True -``` - -# 字典推导式 - -```python -d = {key: value for (key, value) in iterable} -``` - -# 单下划线和双下划线 - -```python ->>> class MyClass(): -...     def __init__(self): -...             self.__superprivate = "Hello" -...             self._semiprivate = ", world!" -... ->>> mc = MyClass() ->>> print mc.__superprivate -Traceback (most recent call last): -  File "", line 1, in  -AttributeError: myClass instance has no attribute '__superprivate' ->>> print mc._semiprivate -, world! ->>> print mc.__dict__ -{'_MyClass__superprivate': 'Hello', '_semiprivate': ', world!'} -``` - -\_\_foo\_\_: 一种约定 , Python 内部的名字 , 用来区别其他用户自定义的命名 , 以防冲突,就是例如\_\_init\_\_(),\_\_del\__(),\__call\__() 这些特殊方法 - -\_foo: 一种约定 , 用来指定变量私有 . 程序员用来指定私有变量的一种方式 . 不能用 from module import * 导入,其他方面和公有一样访问; - -\_\_foo: 这个有真正的意义 : 解析器用\_classname\_\_foo 来代替这个名字 , 以区别和其他类相同的命名 , 它无法直接像公有成员一样随便访问 , 通过对象名 .\_类名\_\_xxx 这样的方式可以访问 . - -# 迭代器和生成器 - -这里有个关于生成器的创建问题面试官有考: 问: 将列表生成式中 [] 改成 () 之后数据结构是否改变? 答案:是,从列表变为生成器 - -```python ->>> L = [x*x for x in range(10)] ->>> L -[0, 1, 4, 9, 16, 25, 36, 49, 64, 81] ->>> g = (x*x for x in range(10)) ->>> g - at 0x0000028F8B774200> -``` - -通过列表生成式,可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含百万元素的列表,不仅是占用很大的内存空间,如:我们只需要访问前面的几个元素,后面大部分元素所占的空间都是浪费的。因此,没有必要创建完整的列表(节省大量内存空间)。在 Python 中,我们可以采用生成器:边循环,边计算的机制—>generator - -http://stackoverflow.com/questions/231767/what-does-the-yield-keyword-do-in-python - -10 \*args and \*\*kwargs -用\*args 和\*\*kwargs 只是为了方便并没有强制使用它们 . - -当你不确定你的函数里将要传递多少参数时你可以用\*args. 例如 , 它可以传递任意数量的参数 : -```python ->>> def print_everything(*args): -        for count, thing in enumerate(args): -...         print '{0}. {1}'.format(count, thing) -... ->>> print_everything('apple', 'banana', 'cabbage') -0. apple -1. banana -2. cabbage -``` -相似的 ,\*\*kwargs 允许你使用没有事先定义的参数名 : -```python ->>> def table_things(**kwargs): -...     for name, value in kwargs.items(): -...         print '{0} = {1}'.format(name, value) -... ->>> table_things(apple = 'fruit', cabbage = 'vegetable') -cabbage = vegetable -apple = fruit -``` -你也可以混着用 . 命名参数首先获得参数值然后所有的其他参数都传递给\*args 和\*\*kwargs. 命名参数在列表的最前端 . 例如 : - -```python -def table_things(titlestring, **kwargs) -``` -\*args 和*\\*kwargs 可以同时在函数的定义中 , 但是\*args 必须在\**\kwargs 前面 . - -当调用函数时你也可以用\*和\*\*语法 . 例如 : -```python ->>> def print_three_things(a, b, c): -...     print 'a = {0}, b = {1}, c = {2}'.format(a, b, c) -... ->>> mylist = ['aardvark', 'baboon', 'cat'] ->>> print_three_things(*mylist) - -a = aardvark, b = baboon, c = cat -``` - -就像你看到的一样 , 它可以传递列表 ( 或者元组 ) 的每一项并把它们解包 . 注意必须与它们在函数里的参数相吻合 . 当然 , 你也可以在函数定义或者函数调用时用*. - -http://stackoverflow.com/questions/3394835/args-and-kwargs - -# 面向切面编程 AOP 和装饰器 - -装饰器是一个很著名的设计模式,经常被用于有切面需求的场景,较为经典的有插入日志、性能测试、事务处理等。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量函数中与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。 - -这个问题比较大 , 推荐 : http://stackoverflow.com/questions/739654/how-can-i-make-a-chain-of-function-decorators-in-python - -# 鸭子类型 - -“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。” - -我们并不关心对象是什么类型,到底是不是鸭子,只关心行为。 - -比如在 python 中,有很多 file-like 的东西,比如 StringIO, GzipFile, socket。它们有很多相同的方法,我们把它们当作文件使用。 - -又比如 list.extend() 方法中 , 我们并不关心它的参数是不是 list, 只要它是可迭代的 , 所以它的参数可以是 list/tuple/dict/ 字符串 / 生成器等 . - -鸭子类型在动态语言中经常使用,非常灵活,使得 python 不想 java 那样专门去弄一大堆的设计模式。 - -# 重载 - -缺省参数实现重载。 - -http://www.zhihu.com/question/20053359 - -# 新式类和旧式类 - -这篇文章很好的介绍了新式类的特性 : http://www.cnblogs.com/btchenguang/archive/2012/09/17/2689146.html - -新式类很早在 2.2 就出现了 , 所以旧式类完全是兼容的问题 , Python3 里的类全部都是新式类 . 这里有一个 MRO 问题可以了解下 ( 新式类是广度优先 , 旧式类是深度优先 ), 里讲的也很多 . - -一个旧式类的深度优先的例子 - -```python -class A(): -    def foo1(self): -        print "A" -class B(A): -    def foo2(self): -        pass -class C(A): -    def foo1(self): -        print "C" -class D(B, C): -    pass - -d = D() -d.foo1() - -# A -``` - -**按照经典类的查找顺序`从左到右深度优先`的规则,在访问`d.foo1()`的时候 , D 这个类是没有的 .. 那么往上查找 , 先找到 B, 里面没有 , 深度优先 , 访问 A, 找到了 foo1(), 所以这时候调用的是 A 的 foo1(),从而导致 C 重写的 foo1() 被绕过** - -__new__是一个静态方法 , 而__init__是一个实例方法 . -__new__方法会返回一个创建的实例 , 而__init__什么都不返回 . -只有在__new__返回一个 cls 的实例时后面的__init__才能被调用 . -当创建一个新实例时调用__new__, 初始化一个实例时用__init__. - -# `__new__`和`__init__`的区别 - -这个`__new__`确实很少见到 , 先做了解吧 . - -1. `__new__`是一个静态方法 , 而`__init__`是一个实例方法 . -2. `__new__`方法会返回一个创建的实例 , 而`__init__`什么都不返回 . -3. 只有在`__new__`返回一个 cls 的实例时后面的`__init__`才能被调用 . -4. 当创建一个新实例时调用`__new__`, 初始化一个实例时用`__init__`. - -[stackoverflow](http://stackoverflow.com/questions/674304/pythons-use-of-new-and-init) - -# lambda 函数 - -其实就是一个匿名函数 , 为什么叫 lambda? 因为和后面的函数式编程有关 . - -推荐 : [ 知乎 ](http://www.zhihu.com/question/20125256) - -# Python 函数式编程 - -推荐 : [ 酷壳 ](http://coolshell.cn/articles/10822.html) - -python 中函数式编程支持 : - -filter 函数的功能相当于过滤器。调用一个布尔函数 bool_func 来迭代遍历每个 seq 中的元素;返回一个使 bool_seq 返回值为 true 的元素的序列。 - -```python ->>>a = [1, 2, 3, 4, 5, 6, 7] ->>>b = filter(lambda x: x > 5, a) ->>>print b ->>>[6, 7] -``` - -map 函数是对一个序列的每个项依次执行函数,下面是对一个序列每个项都乘以 2: - -```python ->>> a = map(lambda x:x*2,[1, 2, 3]) ->>> list(a) -[2, 4, 6] -``` - -reduce 函数是对一个序列的每个项迭代调用函数,下面是求 3 的阶乘: - -```python ->>> reduce(lambda x, y:x*y, range(1, 4)) -6 -``` - -# 闭包 - -闭包 (closure) 是函数式编程的重要的语法结构。 - -创建一个闭包必须满足以下几点 : - -1. 必须有一个内嵌函数 -2. 内嵌函数必须引用外部函数中的变量 -3. 外部函数的返回值必须是内嵌函数 - - -# 参考资料 - -- [Github:interview_python](https://github.com/taizilongxu/interview_python) \ No newline at end of file diff --git a/notes/笔记/Redis.md.txt b/notes/笔记/Redis.md.txt deleted file mode 100644 index 9c3bcc69..00000000 --- a/notes/笔记/Redis.md.txt +++ /dev/null @@ -1,284 +0,0 @@ -[TOC] - -# Redis是什么 - -Redis 是速度非常快的非关系型(nosql)内存键值数据库,可以存储键和五种不同类型的值之间的映射。 - -五种类型数据类型为:字符串、列表、集合、有序集合、散列表。 - -Redis 支持很多特性,例如可以将内存中的数据持久化到硬盘中,可以使用复制来扩展读性能,可以使用分片来扩展写性能。 - -# Redis的五种基本类型 - -| 数据类型 | 可以存储的值 | 操作 | -| -- | -- | -- | -| STRING | 字符串、整数或者浮点数 | 对整个字符串或者字符串的其中一部分执行操作;
 对整数和浮点数执行自增或者自减操作 | -| LIST | 链表 | 从两端压入或者弹出元素;
 读取单个或者多个元素;
 进行修剪,只保留一个范围内的元素。 | -| SET | 无序集合 | 添加、获取、移除单个元素;
 检查一个元素是否存在于集合中;
 计算交集、并集、差集;
 从集合里面随机获取元素。 | -| HASH | 包含键值对的无序散列表 | 添加、获取、移除单个键值对;
 获取所有键值对;
 检查某个键是否存在。| -| ZSET | 有序集合1 | 添加、获取、删除元素个元素;
 根据分值范围或者成员来获取元素;
 计算一个键的排名。 | - -注 1:有序集合的每个集合元素都对应一个分值,根据这个分值的大小来对集合元素进行排序。有因此有序集合相当于是有序的散列表,键是集合元素,值为元素对应的分值。 - -# 键的过期时间 - -Redis 可以为每个键设置过期时间,当键过期时,会自动删除该键。 - -对于散列表这种容器,只能为整个键设置过期时间(整个散列表),而不能为键里面的单个元素设置过期时间。 - -过期时间对于清理缓存数据非常有用。 - -# 发布与订阅 - -发布与订阅实际上是观察者模式,订阅者订阅了频道之后,发布者向频道发送字符串消息会被所有订阅者接收到。 - -发布与订阅有一些问题,很少使用它,而是使用替代的解决方案。问题如下: - -1. 如果订阅者读取消息的速度很慢,会使得消息不断积压在发布者的输出缓存区中,造成内存占用过多; -2. 如果订阅者在执行订阅的过程中网络出现问题,那么就会丢失断线期间发送的所有消息。 - -# 事务 - -Redis 最简单的事务实现方式是使用 MULTI 和 EXEC 命令将事务操作包围起来。 - -MULTI 和 EXEC 中的操作将会一次性发送给服务器,而不是一条一条发送,这种方式称为流水线,它可以减少客户端与服务器之间的网络通信次数从而提升性能。 - -# 持久化 - -Redis 是内存型数据库,为了保证数据在断电后不会丢失,需要将内存中的数据持久化到硬盘上。 - -## 1. 快照持久化 - -将某个时间点的所有数据都存放到硬盘上。 - -可以将快照复制到其它服务器从而创建具有相同数据的服务器副本。 - -如果系统发生故障,将会丢失最后一次创建快照之后的数据。并且如果数据量很大,保存快照的时间也会很长。 - -## 2. AOF 持久化 - -AOF 持久化将写命令添加到 AOF 文件(Append Only File)的末尾。 - -对硬盘的文件进行写入时,写入的内容首先会被存储到缓冲区,然后由操作系统决定什么时候将该内容同步到硬盘,用户可以调用 file.flush() 方法请求操作系统尽快将缓冲区存储的数据同步到硬盘。因此将写命令添加到 AOF 文件时,要根据需求来保证何时将添加的数据同步到硬盘上,有以下同步选项: - -| 选项 | 同步频率 | -| -- | -- | -| always | 每个写命令都同步 | -| everysec | 每秒同步一次 | -| no | 让操作系统来决定何时同步 | - -always 选项会严重减低服务器的性能;everysec 选项比较合适,可以保证系统奔溃时只会丢失一秒左右的数据,并且 Redis 每秒执行一次同步对服务器性能几乎没有任何影响;no 选项并不能给服务器性能带来多大的提升,而且也会增加系统奔溃时数据丢失的数量。 - -随着服务器写请求的增多,AOF 文件会越来越大;Redis 提供了一种将 AOF 重写的特性,能够去除 AOF 文件中的冗余写命令。 - -# 复制 - -通过使用 slaveof host port 命令来让一个服务器成为另一个服务器的从服务器。 - -一个从服务器只能有一个主服务器,并且不支持主主复制。 - -**1. 从服务器连接主服务器的过程** - -(1) 主服务器创建快照文件,发送给从服务器,并在发送期间使用缓冲区记录执行的写命令。快照文件发送完毕之后,开始向从服务器发送存储在缓冲区中的写命令; -(2) 从服务器丢弃所有旧数据,载入主服务器发来的快照文件,之后从服务器开始接受主服务器发来的写命令; -(3) 主服务器每执行一次写命令,就向从服务器发送相同的写命令。 - -**2. 主从链** - -随着负载不断上升,主服务器可能无法很快地更新所有从服务器,或者重新连接和重新同步从服务器而导致系统超载。为了解决这个问题,可以创建一个中间层来分担主服务器的复制工作。中间层的服务器是最上层服务器的从服务器,又是最下层服务器的主服务器。 - -![](index_files/b242fafc-5945-42a8-805e-6e3f1f2f89b4.jpg) - -# 处理故障 - -要用到持久化文件来恢复服务器的数据。 - -持久化文件可能因为服务器出错也有错误,因此要先对持久化文件进行验证和修复。对 AOF 文件就行验证和修复很容易,修复操作将第一个出错命令和其后的所有命令都删除;但是只能验证快照文件,无法对快照文件进行修复,因为快照文件进行了压缩,出现在快照文件中间的错误可能会导致整个快照文件的剩余部分无法读取。 - -当主服务器出现故障时,Redis 常用的做法是新开一台服务器作为主服务器,具体步骤如下:假设 A 为主服务器,B 为从服务器,当 A 出现故障时,让 B 生成一个快照文件,将快照文件发送给 C,并让 C 恢复快照文件的数据。最后,让 B 成为 C 的从服务器。 - -# 分片 - -Redis 中的分片类似于 MySQL 的分表操作,分片是将数据划分为多个部分的方法,对数据的划分可以基于键包含的 ID、基于键的哈希值,或者基于以上两者的某种组合。通过对数据进行分片,用户可以将数据存储到多台机器里面,也可以从多台机器里面获取数据,这种方法在解决某些问题时可以获得线性级别的性能提升。 - -假设有 4 个 Reids 实例 R0,R1,R2,R3,还有很多表示用户的键 user:1,user:2,... 等等,有不同的方式来选择一个指定的键存储在哪个实例中。最简单的方式是范围分片,例如用户 id 从 0~1000 的存储到实例 R0 中,用户 id 从 1001~2000 的存储到实例 R1 中,等等。但是这样需要维护一张映射范围表,维护操作代价很高。还有一种方式是哈希分片,使用 CRC32 哈希函数将键转换为一个数字,再对实例数量求模就能知道应该存储的实例。 - -**1. 客户端分片** - -客户端使用一致性哈希等算法决定键应当分布到哪个节点。 - -**2. 代理分片** - -将客户端请求发送到代理上,由代理转发请求到正确的节点上。 - -**3. 服务器分片** - -Redis Cluster。 - -# 事件 - -**1. 事件类型** - -(1) 文件事件:服务器有许多套接字,事件产生时会对这些套接字进行操作,服务器通过监听套接字来处理事件。常见的文件事件有:客户端的连接事件;客户端的命令请求事件;服务器向客户端返回命令结果的事件; - -(2) 时间事件:又分为两类,定时事件是让一段程序在指定的时间之内执行一次;周期性时间是让一段程序每隔指定时间就执行一次。 - -**2. 事件的调度与执行** - -服务器需要不断监听文件事件的套接字才能得到待处理的文件事件,但是不能监听太久,否则时间事件无法在规定的时间内执行,因此监听时间应该根据距离现在最近的时间事件来决定。 - -事件调度与执行由 aeProcessEvents 函数负责,伪代码如下: - -```python -def aeProcessEvents(): - -    # 获取到达时间离当前时间最接近的时间事件 -    time_event = aeSearchNearestTimer() - -    #计算最接近的时间事件距离到达还有多少毫秒 -    remaind_ms = time_event.when - unix_ts_now() - -    # 如果事件已到达,那么 remaind_ms 的值可能为负数,将它设为 0 -    if remaind_ms < 0: -        remaind_ms = 0 - -    # 根据 remaind_ms 的值,创建 timeval -    timeval = create_timeval_with_ms(remaind_ms) - -    # 阻塞并等待文件事件产生,最大阻塞时间由传入的 timeval 决定 -    aeApiPoll(timeval) - -    # 处理所有已产生的文件事件 -    procesFileEvents() - -    # 处理所有已到达的时间事件 -    processTimeEvents() -``` - -将 aeProcessEvents 函数置于一个循环里面,加上初始化和清理函数,就构成了 Redis 服务器的主函数,伪代码如下: - -```python -def main(): -     -    # 初始化服务器 -    init_server() -     -    # 一直处理事件,直到服务器关闭为止 -    while server_is_not_shutdown(): -        aeProcessEvents() -     -    # 服务器关闭,执行清理操作 -    clean_server() -``` - -事件处理的角度下服务器运行流程如下: - -![](index_files/73b73189-9e95-47e5-91d0-9378b8462e15.png) - -# Redis与Memcached的区别 - -两者都是非关系型内存键值数据库。有以下主要不同: - -**1. 数据类型** - -Memcached 仅支持字符串类型,而 Redis 支持五种不同种类的数据类型,使得它可以更灵活地解决问题。 - -**2. 数据持久化** - -Redis 支持两种持久化策略:RDB 快照和 AOF 日志,而 Memcached 不支持持久化。 - -**3. 分布式** - -Memcached 不支持分布式,只能通过在客户端使用像一致性哈希这样的分布式算法来实现分布式存储,这种方式在存储和查询时都需要先在客户端计算一次数据所在的节点。 - -Redis Cluster 实现了分布式的支持。 - -**4. 内存管理机制** - -在 Redis 中,并不是所有数据都一直存储在内存中,可以将一些很久没用的 value 交换到磁盘。而 Memcached 的数据则会一直在内存中。 - -Memcached 将内存分割成特定长度的块来存储数据,以完全解决内存碎片的问题,但是这种方式会使得内存的利用率不高,例如块的大小为 128 bytes,只存储 100 bytes 的数据,那么剩下的 28 bytes 就浪费掉了。 - -# Redis适用场景 - -**1. 缓存** - -适用 Redis 作为缓存,将热点数据放到内存中。 - -**2. 消息队列** - -Redis 的 list 类型是双向链表,很适合用于消息队列。 - -**3. 计数器** - -Redis 这种内存数据库才能支持计数器的频繁读写操作。 - -**4. 好友关系** - -使用 set 类型的交集很容易就可以知道两个用户的共同好友。 - -# 数据淘汰策略 - -可以设置内存最大使用量,当内存使用量超过时施行淘汰策略,具体有 6 种淘汰策略。 - -| 策略 | 描述 | -| -- | -- | -| volatile-lru | 从已设置过期时间的数据集中挑选最近最少使用的数据淘汰 | -| volatile-ttl | 从已设置过期时间的数据集中挑选将要过期的数据淘汰 | -|volatile-random | 从已设置过期时间的数据集中任意选择数据淘汰 | -| allkeys-lru | 从所有数据集中挑选最近最少使用的数据淘汰 | -| allkeys-random | 从所有数据集中任意选择数据进行淘汰 | -| no-envicition | 禁止驱逐数据 | - -如果使用 Redis 来缓存数据时,要保证所有数据都是热点数据,可以将内存最大使用量设置为热点数据占用的内存量,然后启用 allkeys-lru 淘汰策略,将最近最少使用的数据淘汰。 - -具体问题:MySQL 里有 2000w 数据,Redis 中只存 20w 的数据,如何保证 Redis 中的数据都是热点数据? - -# 一个简单的论坛系统分析 - -该论坛系统功能如下: - -1. 可以发布文章; -2. 可以对文章进行点赞; -3. 在首页可以按文章的发布时间或者文章的点赞数进行排序显示; - -**1. 文章信息** - -文章包括标题、作者、赞数等信息,在关系型数据库中很容易构建一张表来存储这些信息,在 Redis 中可以使用 HASH 来存储每种信息以及其对应的值的映射。 - -Redis 没有表的概念将同类型的数据存放在一起,而是使用命名空间的方式来实现这一功能。键名的前面部分存储命名空间,后面部分的内容存储 ID,通常使用 : 来进行分隔。例如下面的 HASH 的键名为 article:92617,其中 article 为命名空间,ID 为 92617。 - -![](index_files/2d078e08-3a49-46d0-b784-df780b7e4bc3.jpg) - -**2. 点赞功能** - -当有用户为一篇文章点赞时,除了要对该文章的 votes 字段进行加 1 操作,还必须记录该用户已经对该文章进行了点赞,防止用户不断点赞。可以建立文章的已投票用户集合来进行记录。 - -为了节约内存,规定一篇文章发布满一周之后,就不能再对它进行投票,而文章的已投票集合也会被删除,可以为文章的已投票集合设置一个一周的过期时间就能实现这个规定。 - -![](index_files/0e4c8a7f-f84c-4c4e-9544-49cd40167af8.png) - -**3. 对文章进行排序** - -为了按发布时间和点赞数进行排序,可以建立一个文章发布时间的有序集合和一个文章点赞数的有序集合。(下图中的 score 就是这里所说的点赞数;下面所示的有序集合分值并不直接是时间和点赞数,而是根据它们间接计算出来的) - -![](index_files/ea5e434a-a218-44b5-aa72-4cd08991abcf.jpg) - -# 案例分析 - -**1. 假设有一个博客系统,数据库存储采用 MySQL,用户数量为 1000 万,预计文章总数为 10 亿,每天有至少 10 万的更新量,每天访问量为 5000 万,对数据库的读写操作的比例超过 10:1,你如何设计该系统以确保系统高效、稳定的运行?** - -提示:可以从数据库设计、系统框架、及网络架构方面进行描述,可以自由发挥。 - -① 考虑到读操作比写操作多的多,因此可以使用主从架构,让主服务器处理写请求,从服务器处理读请求,并且从服务器可以使用读性能比较高的 MyISAM 存储引擎; -② 博客系统有个特点,就是热点数据特别明显,比如精华文章、推荐文章等,对这部分热点数据可以使用 Redis 进行缓存; -③ 由于文章总数很大,可以选择根据用户散列来进行分表操作。 - - -# 参考资料 - -1. Redis实战 -2. Reids设计与实现 -3. [论述Redis和Memcached的差异](http://www.cnblogs.com/loveincode/p/7411911.html) -4. [Redis 3.0 中文版- 分片](http://wiki.jikexueyuan.com/project/redis-guide) -5. [Redis应用场景](http://www.scienjus.com/redis-use-case/) \ No newline at end of file diff --git a/notes/笔记/SQL 实战.md.txt b/notes/笔记/SQL 实战.md.txt deleted file mode 100644 index 2fd030a5..00000000 --- a/notes/笔记/SQL 实战.md.txt +++ /dev/null @@ -1,225 +0,0 @@ -[TOC] - -# 查找最晚入职员工的所有信息 - -```sql -select * -from employees -order by hire_date desc -limit 1; -``` - -# 查找入职员工时间排名倒数第三的员工所有信息 - -```sql -select * -from employees -order by hire_date desc -limit 2, 1; -``` - -# 查找各个部门当前领导当前薪水详情以及其对应部门编号dept_no - -```sql -select s.emp_no, s.salary, s.from_date, s.to_date, d.dept_no -from salaries as s inner join dept_manager as d -on d.emp_no = s.emp_no -and d.to_date = '9999-01-01' -and s.to_date = '9999-01-01'; -``` - -# 查找所有已经分配部门的员工的last_name和first_name - -```sql -select e.last_name, e.first_name, d.dept_no -from employees as e inner join dept_emp as d -on e.emp_no = d.emp_no; -``` - -# 查找所有员工的last_name和first_name以及对应部门编号dept_no - -也包括展示没有分配具体部门的员工 - -```sql -select e.last_name, e.first_name, d.dept_no -from employees as e left outer join dept_emp as d -on e.emp_no = d.emp_no; -``` - -# 查找所有员工入职时候的薪水情况 - -```sql -select e.emp_no, s.salary -from employees as e inner join salaries as s -on e.emp_no = s.emp_no and e.hire_date = s.from_date -order by e.emp_no desc; -``` - -# 查找薪水涨幅超过15次的员工号emp_no以及其对应的涨幅次数t - -```sql -select emp_no, count(*) as t -from salaries -group by emp_no -having t > 15; -``` - -# 找出所有员工当前具体的薪水salary情况 - -```sql -select distinct salary -from salaries -where to_date = '9999-01-01' -order by salary desc; -``` - -# 获取所有部门当前manager的当前薪水情况 - -```sql -select d.dept_no, d.emp_no, s.salary -from salaries as s inner join dept_manager as d -on d.emp_no = s.emp_no -and d.to_date = '9999-01-01' -and s.to_date = '9999-01-01'; -``` - -# 获取所有非manager的员工emp_no - -```sql -select emp_no -from employees -where emp_no not in ( - select emp_no -    from dept_manager -) -``` - -# 获取所有员工当前的manager - -```sql -select d1.emp_no, d2.emp_no as manager_no -from dept_emp as d1 inner join dept_manager as d2 -on d1.dept_no = d2.dept_no -and d1.to_date = '9999-01-01' -and d2.to_date = '9999-01-01' -and d1.emp_no <> d2.emp_no -``` - -# 获取所有部门中当前员工薪水最高的相关信息 - -```sql -select d.dept_no, d.emp_no, MAX(s.salary) as salary -from dept_emp as d inner join salaries as s -on d.emp_no = s.emp_no -and d.to_date = '9999-01-01' -and s.to_date = '9999-01-01' -group by d.dept_no -``` - -# 从titles表获取按照title进行分组 - -```sql -select title, COUNT(*) as t -from titles -group by title -having t >= 2 -``` - -# 从titles表获取按照title进行分组,注意对于重复的emp_no进行忽略。 - -```sql -select title, COUNT(distinct emp_no) as t -from titles -group by title -having t >= 2 -``` - -# 查找employees表所有emp_no为奇数 - -```sql -select * -from employees -where emp_no % 2 = 1 and last_name != 'Mary' -order by hire_date desc -``` - -# 统计出当前各个title类型对应的员工当前薪水对应的平均工资 - -```sql -select t.title, AVG(s.salary) as avg -from titles as t inner join salaries as s -on t.emp_no = s.emp_no -and t.to_date = '9999-01-01' -and s.to_date = '9999-01-01' -group by t.title -``` - -# 获取当前薪水第二多的员工的emp_no以及其对应的薪水salary - -```sql -select emp_no, salary -from salaries -order by salary desc -limit 1, 1 -``` - -# 查找当前薪水排名第二多的员工编号emp_no - -```sql -select e.emp_no, MAX(s.salary) as salary, e.last_name, e.first_name -from employees as e, salaries as s -where e.emp_no = s.emp_no -and s.to_date = '9999-01-01' -and s.salary not in ( -    select MAX(salary) -    from salaries -    where s.to_date = '9999-01-01' -) -``` - -#  查找所有员工的last_name和first_name以及对应的dept_name - -查找所有员工的last_name和first_name以及对应的dept_name,也包括暂时没有分配部门的员工 - -本题思路为运用两次LEFT JOIN连接嵌套 -1、第一次LEFT JOIN连接employees表与dept_emp表,得到所有员工的last_name和first_name以及对应的dept_no,也包括暂时没有分配部门的员工 -2、第二次LEFT JOIN连接上表与departments表,即连接dept_no与dept_name,得到所有员工的last_name和first_name以及对应的dept_name,也包括暂时没有分配部门的员工 - -```sql -SELECT em.last_name, em.first_name, dp.dept_name -FROM (employees AS em LEFT JOIN dept_emp AS de ON em.emp_no = de.emp_no) -LEFT JOIN departments AS dp ON de.dept_no = dp.dept_no -``` - -# 查找员工编号emp_no为10001其自入职以来的薪水salary涨幅值growth - -```sql -SELECT (MAX(salary)-MIN(salary)) AS growth -FROM salaries WHERE emp_no = '10001' -``` - -# 查找所有员工自入职以来的薪水涨幅情况 - -```sql -select a.emp_no, (b.salary - c.salary) as growth -from - employees as a - inner join salaries as b - on a.emp_no = b.emp_no and b.to_date = '9999-01-01' - inner join salaries as c - on a.emp_no = c.emp_no and a.hire_date = c.from_date -order by growth asc -``` - -# 统计各个部门对应员工涨幅的次数总和 - -本题关键是要将 每个部门分组,并分别统计工资记录总数,思路如下: -1、用INNER JOIN连接dept_emp表和salaries表,并以dept_emp.no分组,统计每个部门所有员工工资的记录总数 -2、再将上表用INNER JOIN连接departments表,限制条件为两表的dept_no相等,找到dept_no与dept_name的对应关系,最后依次输出dept_no、dept_name、sum - -```sql -SELECT de.dept_no, dp.dept_name, COUNT(s.salary) AS sum -FROM (dept_emp AS de INNER JOIN salaries AS s ON de.emp_no = s.emp_no) -INNER JOIN departments AS dp ON de.dept_no = dp.dept_no -GROUP BY de.dept_no -``` \ No newline at end of file diff --git a/notes/笔记/SQL 语法.md.txt b/notes/笔记/SQL 语法.md.txt deleted file mode 100644 index c5d5c4af..00000000 --- a/notes/笔记/SQL 语法.md.txt +++ /dev/null @@ -1,702 +0,0 @@ -[TOC] - -# 基础 - -模式:定义了数据如何存储、存储什么样的数据以及数据如何分解等信息,数据库和表都有模式。 - -主键的值不允许修改,也不允许复用(不能使用已经删除的主键值赋给新数据行的主键)。 - -SQL(Structured Query Language),标准 SQL 由 ANSI 标准委员会管理,从而称为 ANSI SQL,各个 DBMS 都有自己的实现,如 PL/SQL、Transact-SQL 等。 - -# 查询 - -SQL 语句不区分大小写,但是数据库表名、列名和值是否区分依赖于具体的 DBMS 以及配置。 - -**DISTINCT** - -相同值只会出现一次。它作用于所有列,也就是说所有列的值都相同才算相同。 - -```sql -SELECT DISTINCT col1, col2 -FROM mytable; -``` - -**LIMIT** - -限制返回的行数。可以有两个参数,第一个参数为起始行,从 0 开始;第二个参数为返回的总行数。 - -返回前 5 行的 SQL: - -```sql -SELECT * -FROM mytable -LIMIT 5; -``` - -```sql -SELECT * -FROM mytable -LIMIT 0, 5; -``` - -返回第 3 ~ 5 行: - -```sql -SELECT * -FROM mytable -LIMIT 2, 3; -``` - -**注释** - -```sql -# 注释 -SELECT * -FROM mytable -- 注释 -/* 注释1 -   注释2 */ -``` - -# 排序 - -**ASC**:升序(默认) -**DESC**:降序 - -可以按多个列进行排序: - -```sql -SELECT * -FROM mytable -ORDER BY col1 DESC, col2 ASC; -``` - -# 过滤 - -在应用层也可以过滤数据,但是不在服务器端进行过滤的数据非常大,导致通过网络传输了很多多余的数据,从而浪费了网络带宽。 - -```sql -SELECT * -FROM mytable -WHERE col IS NULL; -``` - -下表显示了 WHERE 子句可用的操作符 - -|  操作符 | 说明  | -| ------------ | ------------ | -| = <  >  | 等于 小于 大于 | -| <> !=  | 不等于  | -| <= !> | 小于等于 | -| >= !< | 大于等于 | -| BETWEEN | 在两个值之间 | -| IS NULL | 为NULL值 | - -应该注意到,NULL 与 0 、空字符串都不同。 - -**AND OR** 用于连接多个过滤条件。优先处理 AND,因此当一个过滤表达式涉及到多个 AND 和 OR 时,应当使用 () 来决定优先级。 - -**IN** 操作符用于匹配一组值,其后也可以接一个 SELECT 子句,从而匹配子查询得到的一组值。 - -**NOT** 操作符用于否定一个条件。 - -# 通配符 - -通配符也是用在过滤语句中,只能用于文本字段。 - -- **%** 匹配 >=0 个任意字符,类似于 \*; - -- **\_** 匹配 ==1 个任意字符,类似于 \.; - -- **[ ]** 可以匹配集合内的字符,用脱字符 ^ 可以对其进行否定 - -使用 Like 来进行通配符匹配。 - -```sql -SELECT * -FROM mytable -WHERE col LIKE '[^AB]%' -- 不以AB开头的任意文本 -``` - -不要滥用通配符,通配符位于开头处匹配会非常慢。 - -# 计算字段 - -在数据库服务器上完成数据的转换和格式化的工作往往比客户端上快得多,并且转换和格式化后的数据量更少的话可以减少网络通信量。 - -计算字段通常需要使用 **AS** 来取别名,否则输出的时候字段名为计算表达式。 - -```sql -SELECT col1*col2 AS alias -FROM mytable -``` - -**Concat()** 用于连接两个字段。许多数据库会使用空格把一个值填充为列宽,因此连接的结果会出现一些不必要的空格,使用 **TRIM()** 可以去除首尾空格。 - -```sql -SELECT Concat(TRIM(col1), ' (', TRIM(col2), ')') -FROM mytable -``` - -# 函数 - -各个 DBMS 的函数都是不相同的,因此不可移植。 - -## 文本处理 - -| 函数  | 说明  | -| ------------ | ------------ | -|  LEFT() RIGHT() |  左边或者右边的字符 | -|  LOWER() UPPER() |  转换为小写或者大写 | -| LTRIM() RTIM() | 去除左边或者右边的空格 | -| LENGTH() | 长度 | -| SUNDEX() | 转换为语音值 | - -其中,**SOUNDEX()** 是将一个字符串转换为描述其语音表示的字母数字模式的算法,它是根据发音而不是字母比较。 - -```sql -SELECT * -FROM mytable -WHERE SOUNDEX(col1) = SOUNDEX('apple') -``` -## 日期和时间处理 - -日期格式:YYYY-MM-DD - -时间格式:HH:MM:SS - -|函 数 | 说 明| -| --- | --- | -| AddDate() | 增加一个日期(天、周等)| -| AddTime() | 增加一个时间(时、分等)| -| CurDate() | 返回当前日期 | -| CurTime() | 返回当前时间 | -|Date() |返回日期时间的日期部分| -|DateDiff() |计算两个日期之差| -|Date_Add() |高度灵活的日期运算函数| -|Date_Format() |返回一个格式化的日期或时间串| -|Day()| 返回一个日期的天数部分| -|DayOfWeek() |对于一个日期,返回对应的星期几| -|Hour() |返回一个时间的小时部分| -|Minute() |返回一个时间的分钟部分| -|Month() |返回一个日期的月份部分| -|Now() |返回当前日期和时间| -|Second() |返回一个时间的秒部分| -|Time() |返回一个日期时间的时间部分| -|Year() |返回一个日期的年份部分| - -```sql -mysql> SELECT NOW(); -        -> '2017-06-28 14:01:52' -``` - -## 数值处理 - -| 函数 | 说明 | -| --- | --- | -| SIN() | 正弦 | -|COS() | 余弦 | -| TAN() | 正切 | -| ABS() | 绝对值 | -| SQRT() | 平方根| -| MOD() | 余数| -| EXP() | 指数| -| PI() | 圆周率| -|RAND() | 随机数| - -## 汇总 - -|函 数 |说 明| -| --- | --- | -|AVG() |返回某列的平均值| -|COUNT()| 返回某列的行数| -|MAX()| 返回某列的最大值| -|MIN()| 返回某列的最小值| -|SUM() |返回某列值之和| - -AVG() 会忽略 NULL 行。 - -DISTINCT 关键字会只汇总不同的值。 - -```sql -SELECT AVG(DISTINCT col1) AS avg_col -FROM mytable -``` - -# 分组 - -分组就是把相同的数据放在同一组中。 - -可以对每组数据使用汇总函数进行处理,例如求每组数的平均值等。 - -按 col 排序并分组数据: - -```sql -SELECT col, COUNT(*) AS num -FROM mytable -GROUP BY col; -``` - -WHERE 过滤行,HAVING 过滤分组,行过滤应当先与分组过滤; - -```sql -SELECT col, COUNT(*) AS num -FROM mytable -WHERE col > 2 -GROUP BY col -HAVING COUNT(*) >= 2; -``` - -GROUP BY 的排序结果为分组字段,而 ORDER BY 也可以以聚集字段来进行排序。 - -```sql -SELECT col, COUNT(*) AS num -FROM mytable -GROUP BY col -ORDER BY num; -``` - -分组规定: - -1. GROUP BY 子句出现在 WHERE 子句之后,ORDER BY 子句之前; -2. 除了汇总计算语句之外,SELECT 语句中的每一列都必须在 GROUP BY 子句中给出; -3. NULL 的行会单独分为一组; -4. 大多数 SQL 实现不支持 GROUP BY 列具有可变长度的数据类型。 - -# 子查询 - -子查询中只能返回一个列。 - -可以将子查询的结果作为 WHRER 语句的过滤条件: - -``` -SELECT * -FROM mytable1 -WHERE col1 IN (SELECT col2 -                 FROM mytable2); -``` - -下面的语句可以检索出客户的订单数量。子查询语句会对检索出的每个客户执行一次: - -```sql -SELECT cust_name, (SELECT COUNT(*) -                   FROM Orders -                   WHERE Orders.cust_id = Customers.cust_id) -                   AS orders_num -FROM Customers -ORDER BY cust_name; -``` - -# 连接 - -连接用于连接多个表,使用 JOIN 关键字,并且条件语句使用 ON。 - -连接可以替换子查询,并且比子查询的效率一般会更快。 - -可以用 AS 给列名、计算字段和表名取别名,给表名取别名是为了简化 SQL 语句以及连接相同表。 - -## 内连接 - -内连接又称等值连接,使用 INNER JOIN 关键字。 - -``` -select a, b, c -from A inner join B -on A.key = B.key -``` - -可以不明确使用 INNER JOIN,而使用普通查询并在 WHERE 中将两个表中要连接的列用等值方法连接起来。 - -``` -select a, b, c -from A, B -where A.key = B.key -``` - -在没有条件语句的情况下返回笛卡尔积。 - -## 自连接 - -自连接可以看成内连接的一种,只是连接的表是自身而已。 - -一张员工表,包含员工姓名和员工所属部门,要找出与 Jim 处在同一部门的所有员工姓名。 - -**子查询版本** - -``` -select name -from employee -where department = ( -      select department -      from employee -      where name = "Jim"); -``` - -**自连接版本** - -``` -select name -from employee as e1, employee as e2 -where e1.department = e2.department -      and e1.name = "Jim"; -``` - -连接一般比子查询的效率高。 - -## 自然连接 - -自然连接是把同名列通过等值测试连接起来的,同名列可以有多个。 - -内连接和自然连接的区别:内连接提供连接的列,而自然连接自动连接所有同名列;内连接属于自然连接。 - -``` -select * -from employee natural join department; -``` - -## 外连接 - -外连接保留了没有关联的那些行。分为左外连接,右外连接以及全外连接,左外连接就是保留左表的所有行。 - -检索所有顾客的订单信息,包括还没有订单信息的顾客。 - -``` -select Customers.cust_id, Orders.order_num -   from Customers left outer join Orders -   on Customers.cust_id = Orders.curt_id -``` - -如果需要统计顾客的订单数,使用聚集函数。 - -``` -select Customers.cust_id, -       COUNT(Orders.order_num) as num_ord -from Customers left outer join Orders -on Customers.cust_id = Orders.curt_id -group by Customers.cust_id -``` - -# 组合查询 - -使用 **UNION** 来连接两个查询,每个查询必须包含相同的列、表达式或者聚集函数。 - -默认会去除相同行,如果需要保留相同行,使用 UNION ALL 。 - -只能包含一个 ORDER BY 子句,并且必须位于语句的最后。 - -```sql -SELECT col -FROM mytable -WHERE col = 1 -UNION -SELECT col -FROM mytable -WHERE col =2; -``` - -# 插入 - -**普通插入** - -```sql -INSERT INTO mytable(col1, col2) -VALUES(val1, val2); -``` - -**插入检索出来的数据** - -```sql -INSERT INTO mytable1(col1, col2) -SELECT col1, col2 -FROM mytable2; -``` - -**将一个表的内容复制到一个新表** - -```sql -CREATE TABLE newtable AS -SELECT * FROM mytable; -``` - -# 更新 - -```sql -UPDATE mytable -SET col = val -WHERE id = 1; -``` - -# 删除 - -```sql -DELETE FROM mytable -WHERE id = 1; -``` - -**TRUNCATE TABLE** 可以清空表,也就是删除所有行。 - -使用更新和删除操作时一定要用 WHERE 子句,不然会把整张表的数据都破坏。可以先用 SELECT 语句进行测试,防止错误删除。 - -# 创建表 - -```sql -CREATE TABLE mytable ( -  id INT NOT NULL AUTO_INCREMENT, -  col1 INT NOT NULL DEFAULT 1, -  col2 VARCHAR(45) NULL, -  col3 DATE NULL, -  PRIMARY KEY (`id`)); -``` - -# 修改表 - -**添加列** - -```sql -ALTER TABLE mytable -ADD col CHAR(20); -``` - -**删除列** - -```sql -ALTER TABLE mytable -DROP COLUMN col; -``` - -**删除表** - -```sql -DROP TABLE mytable; -``` - -# 视图 - -视图是虚拟的表,本身不包含数据,也就不能对其进行索引操作。对视图的操作和对普通表的操作一样。 - -视图具有如下好处: - -1. 简化复杂的 SQL 操作,比如复杂的联结; -2. 只使用实际表的一部分数据; -3. 通过只给用户访问视图的权限,保证数据的安全性; -4. 更改数据格式和表示。 - -```sql -CREATE VIEW myview AS -SELECT Concat(col1, col2) AS concat_col, col3*col4 AS count_col -FROM mytable -WHERE col5 = val; -``` - -# 存储过程 - -存储过程可以看成是对一系列 SQL 操作的批处理; - -**使用存储过程的好处** - -1. 把实现封装在了存储过程中,不仅简单,也保证了安全性; -2. 可以复用代码; -3. 由于是预先编译,因此具有很高的性能。 - -**创建存储过程** - -命令行中创建存储过程需要自定义分隔符,因为命令行是以 ; 为结束符,而存储过程中也包含了分号,因此会错误把这部分分号当成是结束符,造成语法错误。 - -包含 in、out 和 inout 三种参数。 - -给变量赋值都需要用 select into 语句。 - -每次只能给一个变量赋值,不支持集合的操作。 - -```sql -delimiter // - -create procedure myprocedure( out ret int ) -    begin -        declare y int; -        select sum(col1) -        from mytable -        into y; -        select y*y into ret; -    end // -delimiter ; -``` - -```sql -call myprocedure(@ret); -select @ret; -``` - -# 游标 - -在存储过程中使用游标可以对一个结果集进行移动遍历。 - -游标主要用于交互式应用,其中用户需要对数据集中的任意行进行浏览和修改。 - -**使用游标的四个步骤:** - -1. 声明游标,这个过程没有实际检索出数据; -2. 打开游标; -3. 取出数据; -4. 关闭游标; - -```sql -delimiter // -create procedure myprocedure(out ret int) -    begin -        declare done boolean default 0; - -        declare mycursor cursor for -        select col1 from mytable; -        # 定义了一个continue handler,当 sqlstate '02000' 这个条件出现时,会执行 set done = 1 -        declare continue handler for sqlstate '02000' set done = 1; - -        open mycursor; - -        repeat -            fetch mycursor into ret; -            select ret; -        until done end repeat; - -        close mycursor; -    end // - delimiter ; -``` - -# 触发器 - -触发器会在某个表执行以下语句时而自动执行:DELETE、INSERT、UPDATE - -触发器必须指定在语句执行之前还是之后自动执行,之前执行使用 BEFORE 关键字,之后执行使用 AFTER 关键字。BEFORE 用于数据验证和净化。 - -INSERT 触发器包含一个名为 NEW 的虚拟表。 - -```sql -CREATE TRIGGER mytrigger AFTER INSERT ON mytable -FOR EACH ROW SELECT NEW.col; -``` - -DELETE 触发器包含一个名为 OLD 的虚拟表,并且是只读的。 - -UPDATE 触发器包含一个名为 NEW 和一个名为 OLD 的虚拟表,其中 NEW 是可以被修改地,而 OLD 是只读的。 - -可以使用触发器来进行审计跟踪,把修改记录到另外一张表中。 - -MySQL 不允许在触发器中使用 CALL 语句 ,也就是不能调用存储过程。 - -# 事务处理 - -**基本术语** - -1. 事务(transaction)指一组 SQL 语句; -2. 回退(rollback)指撤销指定 SQL 语句的过程; -3. 提交(commit)指将未存储的 SQL 语句结果写入数据库表; -4. 保留点(savepoint)指事务处理中设置的临时占位符(placeholder),你可以对它发布回退(与回退整个事务处理不同)。 - -不能回退 SELECT 语句,回退 SELECT 语句也没意义;也不能回退 CRETE 和 DROP 语句。 - -MySQL 的事务提交默认是隐式提交,也就是每执行一条语句就会提交一次。当出现 START TRANSACTION 语句时,会关闭隐式提交;当 COMMIT 或 ROLLBACK 语句执行后,事务会自动关闭,重新恢复隐式提交。 - -通过设置 autocommit 为 0 可以取消自动提交,直到 autocommit 被设置为 1 才会提交;autocommit 标记是针对每个连接而不是针对服务器的。 - -如果没有设置保留点,ROLLBACK 会回退到 START TRANSACTION 语句处;如果设置了保留点,并且在 ROLLBACK 中指定该保留点,则会回退到该保留点。 - -```sql -START TRANSACTION -// ... -SAVEPOINT delete1 -// ... -ROLLBACK TO delete1 -// ... -COMMIT -``` - -# 字符集 - -**基本术语** - -1. 字符集为字母和符号的集合; -2. 编码为某个字符集成员的内部表示; -3. 校对字符指定如何比较,主要用于排序和分组。 - -除了给表指定字符集和校对外,也可以给列指定: - -```sql -CREATE TABLE mytable -(col VARCHAR(10) CHARACTER SET latin COLLATE latin1_general_ci ) -DEFAULT CHARACTER SET hebrew COLLATE hebrew_general_ci; -``` - -可以在排序、分组时指定校对: - -```sql -SELECT * -FROM mytable -ORDER BY col COLLATE latin1_general_ci; -``` - -# 权限管理 - -MySQL 的账户信息保存在 mysql 这个数据库中。 - -```sql -USE mysql; -SELECT user FROM user; -``` - -**创建账户** - -```sql -CREATE USER myuser IDENTIFIED BY 'mypassword'; -``` - -新创建的账户没有任何权限。 - -**修改账户名** - -```sql -RENAME myuser TO newuser; -``` - -**删除账户** - -```sql -DROP USER myuser; -``` - -**查看权限** - -```sql -SHOW GRANTS FOR myuser; -``` -![](index_files/c73aa08e-a987-43c9-92be-adea4a884c25.png) - -账户用 username@host 的形式定义,username@% 使用的是默认主机名。 - -**授予权限** - -```sql -GRANT SELECT, INSERT ON mydatabase.* TO myuser; -``` - -**删除权限** - -```sql -REVOKE SELECT, INSERT ON mydatabase.* FROM myuser; -``` - -GRANT 和 REVOKE 可在几个层次上控制访问权限: - -- 整个服务器,使用 GRANT ALL和 REVOKE ALL; -- 整个数据库,使用 ON database.\*; -- 特定的表,使用 ON database.table; -- 特定的列; -- 特定的存储过程。 - -**更改密码** - -必须使用 Password() 函数 - -```sql -SET PASSWROD FOR myuser = Password('newpassword'); -``` - diff --git a/notes/笔记/一致性原理.md.txt b/notes/笔记/一致性原理.md.txt deleted file mode 100644 index 00360a0b..00000000 --- a/notes/笔记/一致性原理.md.txt +++ /dev/null @@ -1,219 +0,0 @@ -[TOC] -# 两阶段提交协议(2PC) - -保证多个节点操作的原子性,实现事务操作。 - -包含两类节点:协调者(coordinator)和参与者(participants),协调者只有一个,参与者可以有多个。 - -**Phase1:**请求阶段,协调者通知事务参与者准备提交或者取消事务,然后进入表决过程。在表决过程中,参与者告知协调者自己的决策:如果事务在本地执行成功,就告知同意,否者告知取消。 - -**Phase2:**提交阶段,协调者将基于表决阶段的投票结果进行决策,当且仅当所有参与者同意提交事务,决策结果才为同意,否者为取消。协调者把决策结果通知所有的参与者,参与者接收到协调者发来的消息后执行相应的操作。 - -# Paxos协议 - -Paxos用于确保多个节点对某个投票达成一致。常用来选举主节点,当主节点出现故障时,使用Paxos协议就可以从备节点中选举出新的主节点。也用来构建高可用的全局服务,例如分布式锁服务,全局命名和配置服务等,Apache Zookeeper就实现了Paxos。 - -Paxos协议涉及两类节点:提议者(proposer)和接受者(acceptor)。 - -在只有一个proposer的情况下,Paxos协议执行步骤如下: - -1. 批准:proposer发送提议给acceptor,acceptor决定接受或者拒绝这个提议; -2. 确认:如果超过一半的acceptor接受,则提议生效,proposer发送acknowledge消息通知所有的acceptor提议生效。 - -如果存在网络分区的情况下,可能会存在多个proposer,用提议号来控制每个提议,只有提议号最大的才会被接受。 - -![](index_files/f257d633-48e9-431e-9206-625ec36bfab5.jpg) - -# Raft协议 - -Raft和Poxas同为一致性协议,但是更容易理解,也更容易实现。 - -[Raft可视化](http://thesecretlivesofdata.com/raft/) - -有三种节点:Follower、Candidate和Leader。 - -Leader会周期性的发送消息给Follower。每个Follower都设置了一个随机的竞选超时时间,一般为150ms~300ms,如果在这个时间内没有收到Leader的消息,则节点就会变成Candidate,进入竞选阶段。最开始系统只有Follower。 - -![](index_files/5.gif) - -Candidate会请求其它所有节点的投票,如果一个Candidate获得多数票,则它成为Leader。 - -所有来自客户端的修改都会被传到Leader,每个修改被作为一个entry添加到节点的日志。Leader先不会修改其节点值,而是把entry都赋值到所有的Follower,只有所有的Follower都写入这个entry,Leader才会修改节点值,并且通知所有的Follower也修改节点值。 - -![](index_files/4.gif) - -该协议也可以用来处理网络分区的问题。 - -![](index_files/3.gif) - -# 拜占庭将军问题 - -该问题主要用于保证分布式系统的一致性和可用性。 - -## 问题场景 - -拜占庭帝国想要进攻一个强大的敌人,为此派出了10支军队去包围这个敌人。这个敌人虽不比拜占庭帝国,但也足以抵御5支常规拜占庭军队的同时袭击。基于一些原因,这10支军队不能集合在一起单点突破,必须在分开的包围状态下同时攻击。他们任一支军队单独进攻都毫无胜算,除非有至少6支军队同时袭击才能攻下敌国。他们分散在敌国的四周,依靠通信兵相互通信来协商进攻意向及进攻时间。困扰这些将军的问题是,他们不确定他们中是否有叛徒,叛徒可能擅自变更进攻意向或者进攻时间。在这种状态下,拜占庭将军们能否找到一种分布式的协议来让他们能够远程协商,从而赢取战斗?这就是著名的拜占庭将军问题。 - -## 相关问题:两军问题 - -拜占庭将军问题中并不去考虑通信兵是否会被截获或无法传达信息等问题,即消息传递的信道绝无问题。如果需要考虑信道是有问题的,这涉及到了另一个相关问题:两军问题。 - -![](index_files/a8ec729f-cb9b-4daa-93a4-c6851db6e111.jpg) - -白军驻扎在沟渠里,蓝军则分散在沟渠两边。白军比任何一支蓝军都更为强大,但是蓝军若能同时合力进攻则能够打败白军。他们不能够远程的沟通,只能派遣通信兵穿过沟渠去通知对方蓝军协商进攻时间。是否存在一个能使蓝军必胜的通信协议,这就是两军问题。通信兵得经过敌人的沟渠,在这过程中他可能被捕,也就是说,两军问题中信道是不可靠的,并且其中没有叛徒之说,这就是两军问题和拜占庭将军问题的根本性不同。 - -倘若1号蓝军(简称1)向2号蓝军(简称2)派出了通信兵,若1要知道2是否收到了自己的信息,1必须要求2给自己传输一个回执,说“你的信息我已经收到了,我同意你提议的明天早上10点9分准时进攻”。然而,就算2已经送出了这条信息,2也不能确定1就一定会在这个时间进攻,因为2发出的回执1并不一定能够收到。所以,1必须再给2发出一个回执说“我收到了”,但是1也不会知道2是否收到了这样一个回执,所以1还会期待一个2的回执。但在这个系统中永远需要存在一个回执,这对于两方来说都并不一定能够达成十足的确信。更要命的是,我们还没有考虑,通信兵的信息还有可能被篡改。由此可见,经典情形下两军问题是不可解的,并不存在一个能使蓝军一定胜利的通信协议。 - -不幸的是,两军问题作为现代通信系统中必须解决的问题,我们尚不能将之完全解决,这意味着你我传输信息时仍然可能出现丢失、监听或篡改的情况。但我们能不能通过一种相对可靠的方式来解决大部分情形呢?这需要谈到TCP协议。 - -![](index_files/10a84b72-c2fe-470b-b10c-07720c7380d1.jpg) - -TCP协议中,A先向B发出一个随机数x,B收到x了以后,发给A另一个随机数y以及x+1作为答复,这样A就知道B已经收到了,因为要破解随机数x可能性并不大;然后A再发回y+1给B,这样B就知道A已经收到了。这样,A和B之间就建立一个可靠的连接,彼此相信对方已经收到并确认了信息。而事实上,A并不会知道B是否收到了y+1;并且,由于信道的不可靠性,x或者y都是可能被截获的,这些问题说明了即使是三次握手,也并不能够彻底解决两军问题,只是在现实成本可控的条件下,我们把TCP协议当作了两军问题的现实可解方法。 - -## 问题形式化 - -只要忠诚的将军能够让别的将军接受到自己真实意图,并且所有忠诚将军能够达成一致的决定,就能解决该问题。 - -定义一个变量vi,作为其他将军收到的第i个将军的命令值;之后,定义一个函数来处理向量(v1,v2,…,vn),各将军用这个函数的结果作为自己最终采用的命令。 - -**一致性条件**:每一个忠诚的将军必须得到相同的(v1,v2,…,vn)。这意味着,忠诚的将军并不一定使用i将军送来的信息作为vi,i将军也可能是叛徒。 - -**正确性条件**:若i将军是忠诚的,其他忠诚的将军必须以他送出的值作为vi。 - -改写一致性条件如下:无论i将军是忠诚或是叛徒,任何两个忠诚的将军都使用相同的vi。这是很巧妙的一步转换,如此一致性条件)和正确性条件都只涉及一个将军i如何帮别的将军接受自己送出的值vi,所以可以将问题改为司令-副官模式来简化问题,即一个司令把自己的命令传递给n-1个副官。 - -**IC1:**所有忠诚的副官遵守一个命令,即一致性。 - -**IC2:**若司令是忠诚的,每一个忠诚的副官遵守他发出的命令,即正确性。 - -司令-副官模式只要将司令遍历各个将军,就可以变成完整问题,而他们采用的算法可以是完全一致的。在这种模式下,司令副官的形式下达成的一致意味着司令的命令得到了有效传达,若出现了异议,有异议的将军会作为司令发起新的司令副官模式寻求自己的观点表达,以协商达成一致。 - -有两种解决方法:口头协议和书面协议。 - -**4.4 口头协议** - -口头协议满足以下条件: - -**A1:**每个被发送的消息都能够被正确的投递 - -**A2:**信息接收者知道是谁发送的消息 - -**A3:**能够知道缺少的消息 - -**4.4.1 OM(m)** - -采用口头协议,若叛徒数少于1/3,则拜占庭将军问题可解。这个结论说明了,对于拜占庭将军问题,由于叛徒可以做出各种各样的判断,就必须四模冗余系统才足够容错。 - -**OM(0)算法** - -**(1)**司令将他的命令发送给每个副官。 - -**(2)**每个副官采用从司令发来的命令;如果没有收到命令,则默认为撤退命令。 - -**OM(m)算法** - -**(1)**司令将他的命令发送给每个副官。 - -**(2)**对于每个i,vi是每个副官i从司令收到的命令,如果没有收到命令,则默认为撤退命令。副官i在OM(m-1) 中作为发令者将之发送给另外n-2 个副官。 - -**(3)**对于每个i,和每个j ≠ i,vj 是副官i 从第2步中的副官j (使用OM(m-1)算法)发送过来的命令,如果没有收到第2步中副官j 的命令,则默认为撤退命令。最后副官i 使用majority(v1,…,vn-1)得到命令。 - -这个算法是一个递归算法,在OM(m)中需要采用OM(m-1)得到相关结果。m代表的是叛徒数量,从m到0,意味着对于每个将军,需要m+1轮的算法才能完成。 - -该算法是关于m的,意味着使用该算法必须知道有多少个叛徒。 - -对于任意kn,将该哈希空间看成一个哈希环,将每个节点都配置到哈希环上。每个数据对象通过哈希取模得到哈希值之后,存放到哈希环中顺时针方向第一个大于等于该哈希值的节点上。 - -![](index_files/d2d34239-e7c1-482b-b33e-3170c5943556.jpg) - -一致性哈希的优点是在加入或者删除节点时只会影响到哈希环中相邻的节点,例如下图中新增节点 X,只需要将数据对象 C 重新存放到节点 X 上即可,而对于节点 A、B、D 都没有影响。 - -![](index_files/91ef04e4-923a-4277-99c0-6be4ce81e5ac.jpg) - -## 顺序分布 - -顺序分布将有序的数据划分为多个连续的部分,按一定策略分布到不同节点上。 - -## 负载均衡 - -分布式存储系统中每个集群都有一个总控节点,其它节点为工作节点,由总控节点根据全局负载信息进行整体调度。系统运行过程中需要不断地执行数据迁移工作,将数据从负载较高的工作节点迁移到负载较低的工作节点。 - -工作节点通过心跳包(Heartbeat,定时发送)将节点负载相关的信息,如 CPU、内存、磁盘、网络等资源使用情况发送给总控节点。 - -考虑到一个新上线的节点由于其负载较低,如果不加控制,总控节点会将大量数据同时迁移到该节点上,造成该节点一段时间内无法工作。因此负载均衡操作需要平滑进行,新加入的节点需要较长的一段时间来达到比较均衡的状态。 - -# 复制 - -## 主从复制 - -分布式存储系统采用冗余机制,将一份数据保存多份副本。多个副本通常有一个为主副本,其它为备副本。主副本用来处理写请求,并将写操作日志同步到备副本,备副本通过回放操作日志来保证数据一致性。主副本和备副本都可以处理读请求。当主副本出现故障时,可以将一个备副本选举为主副本。 - -有两种同步方式:强同步复制和异步复制。强同步复制要求备副本对写操作日志回放成功之后才算完成,而异步复制只需要主副本在本地上的修改完成就算完成。强同步复制一致性好,但是可用性差,异步复制方式相反。 - -## CAP - -分布式存储系统不可能同时确保一致性(Consistency)、可用性(Availablity)和分区容忍性(Partition),设计中往往需要弱化对某个特性的保证。 - -- 一致性:读操作总是能读取到之前完成的写操作结果,满足这个条件的系统称为强一致性系统; -- 可用性:读写操作在单台机器发生故障的情况下仍然能够正常执行; -- 分区容忍性:允许发生网络分区。 - -# 容错 - -大规模集群每天都有故障发生,分布式存储系统需要实现自动化容错,才能实现高可用以及减少人工运维成本。 - -## 故障检测 - -通过**租约机制**来对故障进行检测。假设节点 A 为主控节点,节点 A 向节点 B 发送租约,节点 B 在租约规定的期限内才能提供服务。当节点 B 拥有的租约规定的期限块到达时,节点 B 向 A 重新申请租约,否则主动停止服务。 - -## 故障恢复 - -当某个节点故障时,就将它上面的服务迁移到其它节点。 - -# CDN 架构 - -CND 通过将网络内容发布到靠近用户的边缘节点,使不同地域的用户在访问相同网页时可以就近获取。这样既可以减轻服务器的负担,也可以提高用户的访问速度。 - -从下图可以看出,DNS 在对域名解析时不再向用户返回源服务器的 IP 地址,而是返回边缘节点的 IP 地址,所以用户最终访问的是边缘节点。边缘会先从源服务器中获取用户所需的数据,如果请求成功,边缘节点会将页面缓存下来,下次用户访问时可以直接读取。 - -![](index_files/dbd60b1f-b700-4da6-a993-62578e892333.jpg) - - -# 参考资料 - -- 杨传辉. 大规模分布式存储系统: 原理解析与架构实战[M]. 机械工业出版社, 2013. \ No newline at end of file diff --git a/notes/笔记/剑指 offer 题解.md.txt b/notes/笔记/剑指 offer 题解.md.txt deleted file mode 100644 index 44193c47..00000000 --- a/notes/笔记/剑指 offer 题解.md.txt +++ /dev/null @@ -1,1728 +0,0 @@ -[TOC] - -# 第二章 面试需要的基础知识 - -## 2. 实现 Singleton - -**经典实现** - -以下实现中,私有静态变量被延迟化实例化,这样做的好处是,如果没有用到该类,那么就不会创建该私有静态变量,从而节约资源。这个实现在多线程环境下是不安全的,因为多个线程能够同时进入 if(uniqueInstance == null) 内的语句块,那么就会多次实例化 uniqueInstance 私有静态变量。 - -```java -public class Singleton { - private static Singleton uniqueInstance; - private Singleton() { - } - public static Singleton getUniqueInstance() { - if (uniqueInstance == null) { - uniqueInstance = new Singleton(); - } - return uniqueInstance; - } -} -``` - -**线程不安全问题的解决方案一** - -只需要对 getUniqueInstance() 方法加锁,就能让该方法一次只能一个线程访问,从而避免了对 uniqueInstance 变量进行多次实例化的问题。但是这样有一个问题是一次只能一个线程进入,性能上会有一定的浪费。 - -```java -public static synchronized Singleton getUniqueInstance() { - if (uniqueInstance == null) { - uniqueInstance = new Singleton(); - } - return uniqueInstance; -} -``` -**线程不安全问题的解决方案二** - -不用延迟实例化,采用直接实例化。 - -```java -private static Singleton uniqueInstance = new Singleton(); -``` - -**线程不安全问题的解决方案三** - -考虑第一个解决方案,它是直接对 getUniqueInstance() 方法进行加锁,而实际上只需要对 uniqueInstance = new Singleton(); 这条语句加锁即可。使用两个条件语句来判断 uniqueInstance 是否已经实例化,如果没有实例化才需要加锁。 - -```java -public class Singleton { - private volatile static Singleton uniqueInstance; - private Singleton() { - } - public static synchronized Singleton getUniqueInstance() { - if (uniqueInstance == null) { - synchronized (Singleton.class) { - if (uniqueInstance == null) { - uniqueInstance = new Singleton(); - } - } - } - return uniqueInstance; - } -} -``` - -## 3. 数组中重复的数字 - -**题目描述** - -在一个长度为 n 的数组里的所有数字都在 0 到 n-1 的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为 7 的数组 {2, 3, 1, 0, 2, 5, 3},那么对应的输出是第一个重复的数字 2。 - -**解题思路** - -这种数组元素在 [0, n-1] 范围内的问题,可以将值为 i 的元素放到第 i 个位置上。 - -```java -public boolean duplicate(int numbers[], int length, int[] duplication) { - for (int i = 0; i < length; i++) { - while (numbers[i] != i && numbers[i] != numbers[numbers[i]]) { - swap(numbers, i, numbers[i]); - } - if (numbers[i] != i && numbers[i] == numbers[numbers[i]]) { - duplication[0] = numbers[i]; - return true; - } - } - return false; -} - -private void swap(int[] numbers, int i, int j) { - int t = numbers[i]; - numbers[i] = numbers[j]; - numbers[j] = t; -} -``` - -## 4. 二维数组中的查找 - -**题目描述** - -在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。 - -```java -public boolean Find(int target, int [][] array) { - if (array == null || array.length == 0 || array[0].length == 0) return false; - int m = array.length, n = array[0].length; - int row = 0, col = n - 1; - while (row < m && col >= 0) { - if (target == array[row][col]) return true; - else if (target < array[row][col]) col--; - else row++; - } - return false; -} -``` - -## 5. 替换空格 - -**题目描述** - -请实现一个函数,将一个字符串中的空格替换成“%20”。例如,当字符串为 We Are Happy. 则经过替换之后的字符串为 We%20Are%20Happy。 - -**题目要求** - -以 O(1) 的空间复杂度来求解。 - -```java -public String replaceSpace(StringBuffer str) { - int n = str.length(); - for (int i = 0; i < n; i++) { - if (str.charAt(i) == ' ') str.append(" "); // 尾部填充两个 - } - - int idxOfOriginal = n - 1; - int idxOfNew = str.length() - 1; - while (idxOfOriginal >= 0 && idxOfNew > idxOfOriginal) { - if (str.charAt(idxOfOriginal) == ' ') { - str.setCharAt(idxOfNew--, '0'); - str.setCharAt(idxOfNew--, '2'); - str.setCharAt(idxOfNew--, '%'); - } else { - str.setCharAt(idxOfNew--, str.charAt(idxOfOriginal)); - } - idxOfOriginal--; - } - return str.toString(); -} -``` - -## 6. 从尾到头打印链表 - -正向遍历然后调用 Collections.reverse(). - -```java -public ArrayList printListFromTailToHead(ListNode listNode) { - ArrayList ret = new ArrayList<>(); - while (listNode != null) { - ret.add(listNode.val); - listNode = listNode.next; - } - Collections.reverse(ret); - return ret; -} -``` - -递归 - -```java -public ArrayList printListFromTailToHead(ListNode listNode) { - ArrayList ret = new ArrayList<>(); - if(listNode != null) { - ret.addAll(printListFromTailToHead(listNode.next)); - ret.add(listNode.val); - } - return ret; -} -``` - -不使用库函数,并且不使用递归的迭代实现,利用链表的头插法为逆序的特性。 - -```java -public ArrayList printListFromTailToHead(ListNode listNode) { - ListNode head = new ListNode(-1); // 头结点 - ListNode cur = listNode; - while (cur != null) { - ListNode next = cur.next; - cur.next = head.next; - head.next = cur; - cur = next; - } - ArrayList ret = new ArrayList<>(); - head = head.next; - while (head != null) { - ret.add(head.val); - head = head.next; - } - return ret; -} -``` - -## 7. 重建二叉树 - -**题目描述** - -根据二叉树的前序遍历和中序遍历的结果,重建出该二叉树。 - -```java -public TreeNode reConstructBinaryTree(int[] pre, int[] in) { - return reConstructBinaryTree(pre, 0, pre.length - 1, in, 0, in.length - 1); -} -private TreeNode reConstructBinaryTree(int[] pre, int preL, int preR, int[] in, int inL, int inR) { - if(preL > preR || inL > inR) return null; - TreeNode root = new TreeNode(pre[preL]); - if (preL != preR) { - int idx = inL; - while (idx <= inR && in[idx] != root.val) idx++; - int leftTreeLen = idx - inL; - root.left = reConstructBinaryTree(pre, preL + 1, preL + leftTreeLen, in, inL, inL + leftTreeLen - 1); - root.right = reConstructBinaryTree(pre, preL + leftTreeLen + 1, preR, in, inL + leftTreeLen + 1, inR); - } - return root; -} -``` - -## 8. 二叉树的下一个结点 - -**题目描述** - -给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。 - -```java -public TreeLinkNode GetNext(TreeLinkNode pNode) { - if (pNode == null) return null; - if (pNode.right != null) { - pNode = pNode.right; - while (pNode.left != null) pNode = pNode.left; - return pNode; - } else { - TreeLinkNode parent = pNode.next; - while (parent != null) { - if (parent.left == pNode) return parent; - pNode = pNode.next; - parent = pNode.next; - } - } - return null; -} -``` - -## 9. 用两个栈实现队列 - -```java -Stack stack1 = new Stack(); -Stack stack2 = new Stack(); - -public void push(int node) { - stack1.push(node); -} - -public int pop() { - if (stack2.isEmpty()) { - while (!stack1.isEmpty()) { - stack2.push(stack1.pop()); - } - } - return stack2.pop(); -} -``` - -## 10.1 斐波那契数列 - -```java -private int[] fib = new int[40]; - -public Solution() { - fib[1] = 1; - fib[2] = 2; - for (int i = 2; i < fib.length; i++) { - fib[i] = fib[i - 1] + fib[i - 2]; - } -} - -public int Fibonacci(int n) { - return fib[n]; -} -``` - -## 10.2 跳台阶 - -```java -public int JumpFloor(int target) { - if (target == 1) return 1; - int[] dp = new int[target]; - dp[0] = 1; - dp[1] = 2; - for (int i = 2; i < dp.length; i++) { - dp[i] = dp[i - 1] + dp[i - 2]; - } - return dp[target - 1]; -} -``` - -## 10.3 变态跳台阶 - -```java -public int JumpFloorII(int target) { - int[] dp = new int[target]; - Arrays.fill(dp, 1); - for (int i = 1; i < target; i++) { - for (int j = 0; j < i; j++) { - dp[i] += dp[j]; - } - } - return dp[target - 1]; -} -``` - -## 10.4 矩形覆盖 - -**题目描述** - -我们可以用 2\*1 的小矩形横着或者竖着去覆盖更大的矩形。请问用 n 个 2\*1 的小矩形无重叠地覆盖一个 2\*n 的大矩形,总共有多少种方法? - -```java -public int RectCover(int target) { - if (target <= 2) return target; - return RectCover(target - 1) + RectCover(target - 2); -} -``` - - -## 11. 旋转数组的最小数字 - -**题目描述** - -把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。 例如数组 {3, 4, 5, 1, 2} 为 {1, 2, 3, 4, 5} 的一个旋转,该数组的最小值为 1。 NOTE:给出的所有元素都大于 0,若数组大小为 0,请返回 0。 - -```java -public int minNumberInRotateArray(int[] array) { - if (array.length == 0) return 0; - for (int i = 0; i < array.length - 1; i++) { - if (array[i] > array[i + 1]) return array[i + 1]; - } - return 0; -} -``` - -## 12. 矩阵中的路径 - -**题目描述** - -请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。 例如 a b c e s f c s a d e e 矩阵中包含一条字符串 "bcced" 的路径,但是矩阵中不包含 "abcb" 路径,因为字符串的第一个字符 b 占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。 - -```java -private int[][] next = {{0, -1}, {0, 1}, {-1, 0}, {1, 0}}; - -public boolean hasPath(char[] matrix, int rows, int cols, char[] str) { - if (rows == 0 || cols == 0) return false; - char[][] m = new char[rows][cols]; - for (int i = 0, idx = 0; i < rows; i++) { - for (int j = 0; j < cols; j++) { - m[i][j] = matrix[idx++]; - } - } - - for (int i = 0; i < rows; i++) { - for (int j = 0; j < cols; j++) { - if (backtracking(m, rows, cols, str, new boolean[rows][cols], 0, i, j)) return true; - } - } - return false; -} - -private boolean backtracking(char[][] m, int rows, int cols, char[] str, boolean[][] used, int path, int r, int c) { - if (path == str.length) return true; - if (r < 0 || r >= rows || c < 0 || c >= cols) return false; - if (m[r][c] != str[path]) return false; - if (used[r][c]) return false; - used[r][c] = true; - for (int i = 0; i < next.length; i++) { - if (backtracking(m, rows, cols, str, used, path + 1, r + next[i][0], c + next[i][1])) return true; - } - used[r][c] = false; - return false; -} -``` - - -## 13. 机器人的运动范围 - -**题目描述** - -地上有一个 m 行和 n 列的方格。一个机器人从坐标 0, 0 的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于 k 的格子。 例如,当 k 为 18 时,机器人能够进入方格(35, 37),因为 3+5+3+7 = 18。但是,它不能进入方格(35, 38),因为 3+5+3+8 = 19。请问该机器人能够达到多少个格子? - -```java -private int cnt = 0; -private int[][] next = {{0, -1}, {0, 1}, {-1, 0}, {1, 0}}; -private int[][] digitSum; - -public int movingCount(int threshold, int rows, int cols) { - initDigitSum(rows, cols); - dfs(new boolean[rows][cols], threshold, rows, cols, 0, 0); - return cnt; -} - -private void dfs(boolean[][] visited, int threshold, int rows, int cols, int r, int c) { - if (r < 0 || r >= rows || c < 0 || c >= cols) return; - if (visited[r][c]) return; - visited[r][c] = true; - if (this.digitSum[r][c] > threshold) return; - this.cnt++; - for (int i = 0; i < this.next.length; i++) { - dfs(visited, threshold, rows, cols, r + next[i][0], c + next[i][1]); - } -} - -private void initDigitSum(int rows, int cols) { - int[] digitSumOne = new int[Math.max(rows, cols)]; - for (int i = 0; i < digitSumOne.length; i++) { - int n = i; - while (n > 0) { - digitSumOne[i] += n % 10; - n /= 10; - } - } - this.digitSum = new int[rows][cols]; - for (int i = 0; i < rows; i++) { - for (int j = 0; j < cols; j++) { - this.digitSum[i][j] = digitSumOne[i] + digitSumOne[j]; - } - } -} -``` - -## 14. 剪绳子 - -**题目描述** - -把一根绳子剪成多段,并且使得每段的长度乘积最大。 - -**解题思路** - -尽可能多得剪长度为 3 的绳子,并且不允许有长度为 1 的绳子出现,如果出现了,就从已经切好长度为 3 的绳子中拿出一段与长度为 1 的绳子重新组合,把它们切成两段长度为 2 的绳子。 - -```java -int maxProductAfterCuttin(int length) { - if (length < 2) return 0; - if (length == 2) return 1; - if (length == 3) return 2; - int timesOf3 = length / 3; - if (length - timesOf3 * 3 == 1) timesOf3--; - int timesOf2 = (length - timesOf3 * 3) / 2; - return (int) (Math.pow(3, timesOf3)) * (int) (Math.pow(2, timesOf2)); -} -``` - -## 15. 二进制中 1 的个数 - -```java -public int NumberOf1(int n) { - return Integer.bitCount(n); -} -``` - -n&(n-1) 该位运算是去除 n 的位级表示中最低的那一位。例如对于二进制表示 10110100,减去 1 得到 10110011,这两个数相与得到 10110000。 - -```java -public int NumberOf1(int n) { - int cnt = 0; - while (n != 0) { - cnt++; - n &= (n - 1); - } - return cnt; -} - -``` - -# 第三章 高质量的代码 - -## 16. 数值的整数次方 - -```java -public double Power(double base, int exponent) { - if (exponent == 0) return 1; - if (exponent == 1) return base; - boolean isNegative = false; - if (exponent < 0) { - exponent = -exponent; - isNegative = true; - } - double pow = Power(base * base, exponent / 2); - if (exponent % 2 != 0) pow = pow * base; - return isNegative ? 1 / pow : pow; -} -``` - -## 18. 删除链表中重复的结点 - -```java -public ListNode deleteDuplication(ListNode pHead) { - if (pHead == null) return null; - if (pHead.next == null) return pHead; - if (pHead.val == pHead.next.val) { - ListNode next = pHead.next; - while (next != null && pHead.val == next.val) { - next = next.next; - } - return deleteDuplication(next); - } else { - pHead.next = deleteDuplication(pHead.next); - return pHead; - } -} -``` - -## 19. 正则表达式匹配 - -**题目描述** - -请实现一个函数用来匹配包括 '.' 和 '\*' 的正则表达式。模式中的字符 '.' 表示任意一个字符,而 '\*' 表示它前面的字符可以出现任意次(包含 0 次)。 在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串 "aaa" 与模式 "a.a" 和 "ab\*ac\*a" 匹配,但是与 "aa.a" 和 "ab\*a" 均不匹配 - -```java -public boolean match(char[] str, char[] pattern) { - int n = str.length, m = pattern.length; - boolean[][] dp = new boolean[n + 1][m + 1]; - dp[0][0] = true; - for (int i = 1; i <= m; i++) { - if (pattern[i - 1] == '*') dp[0][i] = dp[0][i - 2]; - } - for (int i = 1; i <= n; i++) { - for (int j = 1; j <= m; j++) { - if (str[i - 1] == pattern[j - 1] || pattern[j - 1] == '.') dp[i][j] = dp[i - 1][j - 1]; - else if (pattern[j - 1] == '*') { - if (pattern[j - 2] != str[i - 1] && pattern[j - 2] != '.') dp[i][j] = dp[i][j - 2]; - else dp[i][j] = dp[i][j - 1] || dp[i][j - 2] || dp[i - 1][j]; - } - } - } - return dp[n][m]; -} -``` - -## 20. 表示数值的字符串 - -**题目描述** - -请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串 "+100","5e2","-123","3.1416" 和 "-1E-16" 都表示数值。 但是 "12e","1a3.14","1.2.3","+-5" 和 "12e+4.3" 都不是。 - -```java -public boolean isNumeric(char[] str) { - String string = String.valueOf(str); - return string.matches("[\\+-]?[0-9]*(\\.[0-9]*)?([eE][\\+-]?[0-9]+)?"); -} -``` - -## 21. 调整数组顺序使奇数位于偶数前面 - -时间复杂度 : O(n2) -空间复杂度 : O(1) - -```java -public void reOrderArray(int[] array) { - int n = array.length; - for (int i = 0; i < n; i++) { - if (array[i] % 2 == 0) { - int nextOddIdx = i + 1; - while (nextOddIdx < n && array[nextOddIdx] % 2 == 0) nextOddIdx++; - if (nextOddIdx == n) break; - int nextOddVal = array[nextOddIdx]; - for (int j = nextOddIdx; j > i; j--) { - array[j] = array[j - 1]; - } - array[i] = nextOddVal; - } - } -} -``` - -时间复杂度 : O(n) -空间复杂度 : O(n) - -```java -public void reOrderArray(int[] array) { - int oddCnt = 0; - for (int num : array) if (num % 2 == 1) oddCnt++; - int[] copy = array.clone(); - int i = 0, j = oddCnt; - for (int num : copy) { - if (num % 2 == 1) array[i++] = num; - else array[j++] = num; - } -} -``` - -## 22. 链表中倒数第 k 个结点 - -```java -public ListNode FindKthToTail(ListNode head, int k) { - if (head == null) return null; - ListNode fast, slow; - fast = slow = head; - while (fast != null && k-- > 0) fast = fast.next; - if (k > 0) return null; - while (fast != null) { - fast = fast.next; - slow = slow.next; - } - return slow; -} -``` - - - -## 23. 链表中环的入口结点 - -```java -public ListNode EntryNodeOfLoop(ListNode pHead) { - if (pHead == null) return null; - ListNode slow = pHead, fast = pHead; - while (fast != null && fast.next != null) { - fast = fast.next.next; - slow = slow.next; - if (slow == fast) { - fast = pHead; - while (slow != fast) { - slow = slow.next; - fast = fast.next; - } - return slow; - } - } - return null; -} -``` - -## 24. 反转链表 - -```java -public ListNode ReverseList(ListNode head) { - ListNode newList = new ListNode(-1); - while (head != null) { - ListNode next = head.next; - head.next = newList.next; - newList.next = head; - head = next; - } - return newList.next; -} -``` - -## 25. 合并两个排序的链表 - -```java -public ListNode Merge(ListNode list1, ListNode list2) { - ListNode head = new ListNode(-1); - ListNode cur = head; - while (list1 != null && list2 != null) { - if (list1.val < list2.val) { - cur.next = list1; - list1 = list1.next; - } else { - cur.next = list2; - list2 = list2.next; - } - cur = cur.next; - } - if (list1 != null) cur.next = list1; - if (list2 != null) cur.next = list2; - return head.next; -} -``` - -## 26. 树的子结构 - -```java -public boolean HasSubtree(TreeNode root1, TreeNode root2) { - if (root1 == null || root2 == null) return false; - return isSubtree(root1, root2) || HasSubtree(root1.left, root2) || HasSubtree(root1.right, root2); -} - -private boolean isSubtree(TreeNode root1, TreeNode root2) { - if (root1 == null && root2 == null) return true; - if (root1 == null) return false; - if (root2 == null) return true; - if (root1.val != root2.val) return false; - return isSubtree(root1.left, root2.left) && isSubtree(root1.right, root2.right); -} -``` - -# 第四章 解决面试题的思路 - -## 27. 二叉树的镜像 - -```java -public void Mirror(TreeNode root) { - if (root == null) return; - TreeNode t = root.left; - root.left = root.right; - root.right = t; - Mirror(root.left); - Mirror(root.right); -} -``` - -## 28.1 对称的二叉树 - -```java -boolean isSymmetrical(TreeNode pRoot) { - if (pRoot == null) return true; - return isSymmetrical(pRoot.left, pRoot.right); -} - -boolean isSymmetrical(TreeNode t1, TreeNode t2) { - if (t1 == null && t2 == null) return true; - if (t1 == null || t2 == null) return false; - if (t1.val != t2.val) return false; - return isSymmetrical(t1.left, t2.right) && isSymmetrical(t1.right, t2.left); -} -``` - -## 28.2 平衡二叉树 - -```java -private boolean isBalanced = true; - -public boolean IsBalanced_Solution(TreeNode root) { - height(root); - return isBalanced; -} - -private int height(TreeNode root) { - if (root == null) return 0; - int left = height(root.left); - int right = height(root.right); - if (Math.abs(left - right) > 1) isBalanced = false; - return 1 + Math.max(left, right); -} -``` - -## 29. 顺时针打印矩阵 - -```java -public ArrayList printMatrix(int[][] matrix) { - ArrayList ret = new ArrayList<>(); - int r1 = 0, r2 = matrix.length - 1, c1 = 0, c2 = matrix[0].length - 1; - while (r1 <= r2 && c1 <= c2) { - for (int i = c1; i <= c2; i++) ret.add(matrix[r1][i]); - for (int i = r1 + 1; i <= r2; i++) ret.add(matrix[i][c2]); - if (r1 != r2) for (int i = c2 - 1; i >= c1; i--) ret.add(matrix[r2][i]); - if (c1 != c2) for (int i = r2 - 1; i > r1; i--) ret.add(matrix[i][c1]); - r1++; r2--; c1++; c2--; - } - return ret; -} -``` - -## 30. 包含 min 函数的栈 - -```java -private Stack stack = new Stack<>(); -private Stack minStack = new Stack<>(); -private int min = Integer.MAX_VALUE; - -public void push(int node) { - stack.push(node); - if (min > node) min = node; - minStack.push(min); -} - -public void pop() { - stack.pop(); - minStack.pop(); - min = minStack.peek(); -} - -public int top() { - return stack.peek(); -} - -public int min() { - return minStack.peek(); -} -``` - -## 31. 栈的压入、弹出序列 - -```java -public boolean IsPopOrder(int[] pushA, int[] popA) { - int n = pushA.length; - Stack stack = new Stack<>(); - for (int i = 0, j = 0; i < n; i++) { - stack.push(pushA[i]); - while (j < n && stack.peek() == popA[j]) { - stack.pop(); - j++; - } - } - return stack.isEmpty(); -} -``` - -## 32.1 从上往下打印二叉树 - -```java -public ArrayList PrintFromTopToBottom(TreeNode root) { - Queue queue = new LinkedList<>(); - ArrayList ret = new ArrayList<>(); - if (root == null) return ret; - queue.add(root); - while (!queue.isEmpty()) { - int cnt = queue.size(); - for (int i = 0; i < cnt; i++) { - TreeNode t = queue.poll(); - if (t.left != null) queue.add(t.left); - if (t.right != null) queue.add(t.right); - ret.add(t.val); - } - } - return ret; -} -``` - -## 32.3 把二叉树打印成多行 - -```java -ArrayList> Print(TreeNode pRoot) { - ArrayList> ret = new ArrayList<>(); - if (pRoot == null) return ret; - Queue queue = new LinkedList<>(); - queue.add(pRoot); - while (!queue.isEmpty()) { - int cnt = queue.size(); - ArrayList list = new ArrayList<>(); - for (int i = 0; i < cnt; i++) { - TreeNode node = queue.poll(); - list.add(node.val); - if (node.left != null) queue.add(node.left); - if (node.right != null) queue.add(node.right); - } - ret.add(list); - } - return ret; -} -``` - -## 32.3 按之字形顺序打印二叉树 - -```java -public ArrayList> Print(TreeNode pRoot) { - ArrayList> ret = new ArrayList<>(); - if (pRoot == null) return ret; - Queue queue = new LinkedList<>(); - queue.add(pRoot); - boolean reverse = false; - while (!queue.isEmpty()) { - int cnt = queue.size(); - ArrayList list = new ArrayList<>(); - for (int i = 0; i < cnt; i++) { - TreeNode node = queue.poll(); - list.add(node.val); - if (node.left != null) queue.add(node.left); - if (node.right != null) queue.add(node.right); - } - if (reverse) { - Collections.reverse(list); - reverse = false; - } else { - reverse = true; - } - ret.add(list); - } - return ret; -} -``` - - -## 33. 二叉搜索树的后序遍历序列 - -```java -public boolean VerifySquenceOfBST(int[] sequence) { - if (sequence.length == 0) return false; - return verify(sequence, 0, sequence.length - 1); -} - -private boolean verify(int[] sequence, int start, int end) { - if (end - start <= 1) return true; - int rootVal = sequence[end]; - int cutIdx = start; - while (cutIdx < end) { - if (sequence[cutIdx] > rootVal) break; - cutIdx++; - } - for (int i = cutIdx + 1; i < end; i++) { - if (sequence[i] < rootVal) return false; - } - return verify(sequence, start, cutIdx - 1) && verify(sequence, cutIdx, end - 1); -} -``` - -## 34. 二叉树中和为某一值的路径 - -```java -private ArrayList> ret = new ArrayList<>(); - -public ArrayList> FindPath(TreeNode root, int target) { - dfs(root, target, 0, new ArrayList<>()); - return ret; -} - -private void dfs(TreeNode node, int target, int curSum, ArrayList path) { - if (node == null) return; - curSum += node.val; - path.add(node.val); - if (curSum == target && node.left == null && node.right == null) { - ret.add(new ArrayList(path)); - } else { - dfs(node.left, target, curSum, path); - dfs(node.right, target, curSum, path); - } - path.remove(path.size() - 1); -} -``` - -## 35. 复杂链表的复制 - -**题目描述** - -输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的 head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空) - -第一步,在每个节点的后面插入复制的节点。 - -![](index_files/f8b12555-967b-423d-a84e-bc9eff104b8b.jpg) - -第二步,对复制节点的 random 链接进行赋值。 - -![](index_files/7b877a2a-8fd1-40d8-a34c-c445827300b8.jpg) - -第三步,拆分。 - -![](index_files/b2b6253c-c701-4b30-aff4-bc3c713542a7.jpg) - - -```java -public RandomListNode Clone(RandomListNode pHead) { - if (pHead == null) return null; - // 插入新节点 - RandomListNode cur = pHead; - while (cur != null) { - RandomListNode node = new RandomListNode(cur.label); - node.next = cur.next; - cur.next = node; - cur = node.next; - } - // 建立 random 链接 - cur = pHead; - while (cur != null) { - RandomListNode clone = cur.next; - if (cur.random != null) { - clone.random = cur.random.next; - } - cur = clone.next; - } - // 拆分 - RandomListNode pCloneHead = pHead.next; - cur = pHead; - while (cur.next != null) { - RandomListNode t = cur.next; - cur.next = t.next; - cur = t; - } - return pCloneHead; -} -``` - -## 36. 二叉搜索树与双向链表 - -**题目描述** - -输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。 - -```java -private TreeNode pre = null; -public TreeNode Convert(TreeNode pRootOfTree) { - if(pRootOfTree == null) return null; - inOrder(pRootOfTree); - while(pRootOfTree.left != null) pRootOfTree = pRootOfTree.left; - return pRootOfTree; -} - -private void inOrder(TreeNode node) { - if(node == null) return; - inOrder(node.left); - node.left = pre; - if(pre != null) pre.right = node; - pre = node; - inOrder(node.right); -} -``` - -## 37. 序列化二叉树 - -```java -private String serizeString = ""; - -String Serialize(TreeNode root) { - if (root == null) return "#"; - return root.val + " " + Serialize(root.left) + " " - + Serialize(root.right); -} - -TreeNode Deserialize(String str) { - this.serizeString = str; - return Deserialize(); -} - -private TreeNode Deserialize() { - if (this.serizeString.length() == 0) return null; - int idx = this.serizeString.indexOf(" "); - if (idx == -1) return null; - String sub = this.serizeString.substring(0, idx); - this.serizeString = this.serizeString.substring(idx + 1); - if (sub.equals("#")) { - return null; - } - int val = Integer.valueOf(sub); - TreeNode t = new TreeNode(val); - t.left = Deserialize(); - t.right = Deserialize(); - return t; -} -``` - -## 38. 字符串的排列 - -**题目描述** - -输入一个字符串 , 按字典序打印出该字符串中字符的所有排列。例如输入字符串 abc, 则打印出由字符 a, b, c 所能排列出来的所有字符串 abc, acb, bac, bca, cab 和 cba。 - -```java -private ArrayList ret = new ArrayList<>(); - -public ArrayList Permutation(String str) { - if (str.length() == 0) return new ArrayList<>(); - char[] chars = str.toCharArray(); - Arrays.sort(chars); - backtracking(chars, new boolean[chars.length], ""); - return ret; -} - -private void backtracking(char[] chars, boolean[] used, String s) { - if (s.length() == chars.length) { - ret.add(s); - return; - } - for (int i = 0; i < chars.length; i++) { - if (used[i]) continue; - if (i != 0 && chars[i] == chars[i - 1] && !used[i - 1]) continue; // 保证不重复 - used[i] = true; - backtracking(chars, used, s + chars[i]); - used[i] = false; - } -} -``` - -# 第五章 优化时间和空间效率 - -## 39. 数组中出现次数超过一半的数字 - -```java -public int MoreThanHalfNum_Solution(int[] array) { - int cnt = 1, num = array[0]; - for (int i = 1; i < array.length; i++) { - if (array[i] == num) cnt++; - else cnt--; - if (cnt == 0) { - num = array[i]; - cnt = 1; - } - } - cnt = 0; - for (int i = 0; i < array.length; i++) { - if (num == array[i]) cnt++; - } - return cnt > array.length / 2 ? num : 0; -} -``` - - -## 40. 最小的 K 个数 - -构建大小为 k 的小顶堆。 - -时间复杂度:O(nlgk) -空间复杂度:O(k) - -```java -public ArrayList GetLeastNumbers_Solution(int[] input, int k) { - if (k > input.length || k <= 0) return new ArrayList<>(); - PriorityQueue pq = new PriorityQueue<>((o1, o2) -> o2 - o1); - for (int num : input) { - pq.add(num); - if (pq.size() > k) { - pq.poll(); - } - } - ArrayList ret = new ArrayList<>(pq); - return ret; -} -``` - -利用快速选择 - -时间复杂度:O(n) -空间复杂度:O(1) - -```java -public ArrayList GetLeastNumbers_Solution(int[] input, int k) { - if (k > input.length || k <= 0) return new ArrayList<>(); - int kthSmallest = findKthSmallest(input, k - 1); - ArrayList ret = new ArrayList<>(); - for (int num : input) { - if(num <= kthSmallest && ret.size() < k) ret.add(num); - } - return ret; -} - -public int findKthSmallest(int[] nums, int k) { - int lo = 0; - int hi = nums.length - 1; - while (lo < hi) { - int j = partition(nums, lo, hi); - if (j < k) { - lo = j + 1; - } else if (j > k) { - hi = j - 1; - } else { - break; - } - } - return nums[k]; -} - -private int partition(int[] a, int lo, int hi) { - int i = lo; - int j = hi + 1; - while (true) { - while (i < hi && less(a[++i], a[lo])) ; - while (j > lo && less(a[lo], a[--j])) ; - if (i >= j) { - break; - } - exch(a, i, j); - } - exch(a, lo, j); - return j; -} - -private void exch(int[] a, int i, int j) { - final int tmp = a[i]; - a[i] = a[j]; - a[j] = tmp; -} - -private boolean less(int v, int w) { - return v < w; -} -``` - -## 41.1 数据流中的中位数 - - -**题目描述** - -如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。 - -```java -private PriorityQueue maxHeap = new PriorityQueue<>((o1, o2) -> o2-o1); // 实现左边部分 -private PriorityQueue minHeep = new PriorityQueue<>(); // 实现右边部分,右边部分所有元素大于左边部分 -private int cnt = 0; - -public void Insert(Integer num) { - // 插入要保证两个堆存于平衡状态 - if(cnt % 2 == 0) { - // 为偶数的情况下插入到最小堆,先经过最大堆筛选,这样就能保证最大堆中的元素都小于最小堆中的元素 - maxHeap.add(num); - minHeep.add(maxHeap.poll()); - } else { - minHeep.add(num); - maxHeap.add(minHeep.poll()); - } - cnt++; -} - -public Double GetMedian() { - if(cnt % 2 == 0) { - return (maxHeap.peek() + minHeep.peek()) / 2.0; - } else { - return (double) minHeep.peek(); - } -} -``` - -## 14.2 字符流中第一个不重复的字符 - -**题目描述** - -请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符 "go" 时,第一个只出现一次的字符是 "g"。当从该字符流中读出前六个字符“google" 时,第一个只出现一次的字符是 "l"。 - -```java -//Insert one char from stringstream -private int[] cnts = new int[256]; -private Queue queue = new LinkedList<>(); - -public void Insert(char ch) { - cnts[ch]++; - queue.add(ch); - while (!queue.isEmpty() && cnts[queue.peek()] > 1) { - queue.poll(); - } -} - -//return the first appearence once char in current stringstream -public char FirstAppearingOnce() { - if (queue.isEmpty()) return '#'; - return queue.peek(); -} -``` - - -## 42. 连续子数组的最大和 - -```java -public int FindGreatestSumOfSubArray(int[] array) { - if(array.length == 0) return 0; - int ret = Integer.MIN_VALUE; - int sum = 0; - for(int num : array) { - if(sum <= 0) sum = num; - else sum += num; - ret = Math.max(ret, sum); - } - return ret; -} -``` - -## 43. 从 1 到 n 整数中 1 出现的次数 - -解题参考:[Leetcode : 233. Number of Digit One](https://leetcode.com/problems/number-of-digit-one/discuss/64381/4+-lines-O(log-n)-C++JavaPython) - -```java -public int NumberOf1Between1AndN_Solution(int n) { - int cnt = 0; - for (int m = 1; m <= n; m *= 10) { - int a = n / m, b = n % m; - cnt += (a + 8) / 10 * m + (a % 10 == 1 ? b + 1 : 0); - } - return cnt; -} -``` - -## 45. 把数组排成最小的数 - -**题目描述** - -输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组 {3,32,321},则打印出这三个数字能排成的最小数字为 321323。 - -```java -public String PrintMinNumber(int[] numbers) { - int n = numbers.length; - String[] nums = new String[n]; - for (int i = 0; i < n; i++) nums[i] = numbers[i] + ""; - Arrays.sort(nums, (s1, s2) -> (s1 + s2).compareTo(s2 + s1)); - String ret = ""; - for (String str : nums) ret += str; - return ret; -} -``` - -## 49. 丑数 - -**题目描述** - -把只包含因子 2、3 和 5 的数称作丑数(Ugly Number)。例如 6、8 都是丑数,但 14 不是,因为它包含因子 7。 习惯上我们把 1 当做是第一个丑数。求按从小到大的顺序的第 N 个丑数。 - -```java -public int GetUglyNumber_Solution(int index) { - if (index <= 6) return index; - int i2 = 0, i3 = 0, i5 = 0; - int cnt = 1; - int[] dp = new int[index]; - dp[0] = 1; - while (cnt < index) { - int n2 = dp[i2] * 2, n3 = dp[i3] * 3, n5 = dp[i5] * 5; - int tmp = Math.min(n2, Math.min(n3, n5)); - dp[cnt++] = tmp; - if (tmp == n2) i2++; - if (tmp == n3) i3++; - if (tmp == n5) i5++; - } - return dp[index - 1]; -} -``` - -## 50. 第一个只出现一次的字符位置 - -```java -public int FirstNotRepeatingChar(String str) { - int[] cnts = new int[256]; - for (int i = 0; i < str.length(); i++) cnts[str.charAt(i)]++; - for (int i = 0; i < str.length(); i++) if (cnts[str.charAt(i)] == 1) return i; - return -1; -} -``` - -## 51. 数组中的逆序对 - -```java -private long cnt = 0; - -public int InversePairs(int[] array) { - mergeSortUp2Down(array, 0, array.length - 1); - return (int) (cnt % 1000000007); -} - -private void mergeSortUp2Down(int[] a, int start, int end) { - if (end - start < 1) return; - int mid = start + (end - start) / 2; - mergeSortUp2Down(a, start, mid); - mergeSortUp2Down(a, mid + 1, end); - merge(a, start, mid, end); -} - -private void merge(int[] a, int start, int mid, int end) { - int[] tmp = new int[end - start + 1]; - int i = start, j = mid + 1, k = 0; - while (i <= mid || j <= end) { - if (i > mid) tmp[k] = a[j++]; - else if (j > end) tmp[k] = a[i++]; - else if (a[i] < a[j]) tmp[k] = a[i++]; - else { - tmp[k] = a[j++]; - this.cnt += mid - i + 1; // a[i] > a[j] ,说明 a[i...mid] 都大于 a[j] - } - k++; - } - - for (k = 0; k < tmp.length; k++) { - a[start + k] = tmp[k]; - } -} -``` - -## 52. 两个链表的第一个公共结点 - -```java -public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) { - ListNode l1 = pHead1, l2 = pHead2; - while (l1 != l2) { - if (l1 == null) l1 = pHead2; - else l1 = l1.next; - if (l2 == null) l2 = pHead1; - else l2 = l2.next; - } - return l1; -} -``` - -# 第六章 面试中的各项能力 - -## 53 数字在排序数组中出现的次数 - - - -```java -public int GetNumberOfK(int[] array, int k) { - int l = 0, h = array.length - 1; - while (l <= h) { - int m = l + (h - l) / 2; - if (array[m] >= k) h = m - 1; - else l = m + 1; - } - int cnt = 0; - while (l < array.length && array[l++] == k) cnt++; - return cnt; -} -``` - -## 54. 二叉搜索树的第 k 个结点 - -```java -TreeNode ret; -int cnt = 0; - -TreeNode KthNode(TreeNode pRoot, int k) { - inorder(pRoot, k); - return ret; -} - -private void inorder(TreeNode root, int k) { - if (root == null) return; - if (cnt > k) return; - inorder(root.left, k); - cnt++; - if (cnt == k) ret = root; - inorder(root.right, k); -} -``` - -## 55 二叉树的深度 - -```java -public int TreeDepth(TreeNode root) { - if (root == null) return 0; - return 1 + Math.max(TreeDepth(root.left), TreeDepth(root.right)); -} -``` - -## 56. 数组中只出现一次的数字 - -**题目描述** - -一个整型数组里除了两个数字之外,其他的数字都出现了两次,找出这两个数。 - -**解题思路** - -两个不相等的元素在位级表示上必定会有一位存在不同。 - -将数组的所有元素异或得到的结果为不存在重复的两个元素异或的结果。 - -diff &= -diff 得到出 diff 最右侧不为 0 的位,也就是不存在重复的两个元素在位级表示上最右侧不同的那一位,利用这一位就可以将两个元素区分开来。 - -```java -public void FindNumsAppearOnce(int[] array, int num1[], int num2[]) { - int diff = 0; - for (int num : array) diff ^= num; - // 得到最右一位 - diff &= -diff; - for (int num : array) { - if ((num & diff) == 0) num1[0] ^= num; - else num2[0] ^= num; - } -} -``` - -## 57.1 和为 S 的两个数字 - -**题目描述** - -输入一个递增排序的数组和一个数字 S,在数组中查找两个数,是的他们的和正好是 S,如果有多对数字的和等于 S,输出两个数的乘积最小的。 - -```java -public ArrayList FindNumbersWithSum(int[] array, int sum) { - int i = 0, j = array.length - 1; - while (i < j) { - int cur = array[i] + array[j]; - if (cur == sum) return new ArrayList(Arrays.asList(array[i], array[j])); - else if (cur < sum) i++; - else j--; - } - return new ArrayList(); -} -``` - -## 57.2 和为 S 的连续正数序列 - -**题目描述** - -和为 100 的连续序列有 18, 19, 20, 21, 22 - -```java -public ArrayList> FindContinuousSequence(int sum) { - ArrayList> ret = new ArrayList<>(); - int start = 1, end = 2; - int mid = sum / 2; - int curSum = 3; - while (start <= mid && end < sum) { - if (curSum > sum) { - curSum -= start; - start++; - } else if (curSum < sum) { - end++; - curSum += end; - } else { - ArrayList list = new ArrayList<>(); - for (int i = start; i <= end; i++) { - list.add(i); - } - ret.add(list); - curSum -= start; - start++; - end++; - curSum += end; - } - } - return ret; -} -``` - -## 58.1 翻转单词顺序列 - -**题目描述** - -输入:"I am a student." - -输出:"student. a am I" - -```java -public String ReverseSentence(String str) { - if (str.length() == 0) return str; - int n = str.length(); - char[] chars = str.toCharArray(); - int start = 0, end = 0; - while (end <= n) { - if (end == n || chars[end] == ' ') { - reverse(chars, start, end - 1); - start = end + 1; - } - end++; - } - reverse(chars, 0, n - 1); - return new String(chars); -} - -private void reverse(char[] c, int start, int end) { - while (start < end) { - char t = c[start]; - c[start] = c[end]; - c[end] = t; - start++; - end--; - } -} -``` - -## 58.2 左旋转字符串 - -**题目描述** - -对于一个给定的字符序列 S,请你把其循环左移 K 位后的序列输出。例如,字符序列 S=”abcXYZdef”, 要求输出循环左移 3 位后的结果,即“XYZdefabc”。 - -```java -public String LeftRotateString(String str, int n) { - if (str.length() == 0) return ""; - char[] c = str.toCharArray(); - reverse(c, 0, n - 1); - reverse(c, n, c.length - 1); - reverse(c, 0, c.length - 1); - return new String(c); -} - -private void reverse(char[] c, int i, int j) { - while (i < j) { - char t = c[i]; - c[i] = c[j]; - c[j] = t; - i++; - j--; - } -} -``` - -## 59. 滑动窗口的最大值 - -**题目描述** - -给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组 {2, 3, 4, 2, 6, 2, 5, 1} 及滑动窗口的大小 3,那么一共存在 6 个滑动窗口,他们的最大值分别为 {4, 4, 6, 6, 6, 5}; - -```java -public ArrayList maxInWindows(int[] num, int size) { - ArrayList ret = new ArrayList<>(); - if (size > num.length || size < 1) return ret; - PriorityQueue heap = new PriorityQueue((o1, o2) -> o2 - o1); - for (int i = 0; i < size; i++) heap.add(num[i]); - ret.add(heap.peek()); - for (int i = 1; i + size - 1 < num.length; i++) { - heap.remove(num[i - 1]); - heap.add(num[i + size - 1]); - ret.add(heap.peek()); - } - return ret; -} -``` - -## 61. 扑克牌顺子 - -**题目描述** - -五张牌,其中大小鬼为癞子,牌面大小为 0。判断是否能组成顺子。 - -```java -public boolean isContinuous(int[] numbers) { - if (numbers.length < 5) return false; - Arrays.sort(numbers); - int cnt = 0; - for (int num : numbers) if (num == 0) cnt++; - for (int i = cnt; i < numbers.length - 1; i++) { - if (numbers[i + 1] == numbers[i]) return false; - int cut = numbers[i + 1] - numbers[i] - 1; - if (cut > cnt) return false; - cnt -= cut; - } - return true; -} -``` - -## 62. 圆圈中最后剩下的数 - -**题目描述** - -让小朋友们围成一个大圈。然后 , 他随机指定一个数 m, 让编号为 0 的小朋友开始报数。每次喊到 m-1 的那个小朋友要出列唱首歌 , 然后可以在礼品箱中任意的挑选礼物 , 并且不再回到圈中 , 从他的下一个小朋友开始 , 继续 0...m-1 报数 .... 这样下去 .... 直到剩下最后一个小朋友 , 可以不用表演。 - -**解题思路** - -约瑟夫环 - -```java -public int LastRemaining_Solution(int n, int m) { - if (n == 0) return -1; - if (n == 1) return 0; - return (LastRemaining_Solution(n - 1, m) + m) % n; -} -``` - -## 63. 股票的最大利润 - -**题目描述** - -可以有一次买入和一次卖出,买入必须在前。求最大收益。 - -```java -public int maxProfit(int[] prices) { - int n = prices.length; - if(n == 0) return 0; - int soFarMin = prices[0]; - int max = 0; - for(int i = 1; i < n; i++) { - if(soFarMin > prices[i]) soFarMin = prices[i]; - else max = Math.max(max, prices[i] - soFarMin); - } - return max; -} -``` - -## 64. 求 1+2+3+...+n - -**题目描述** - -求 1+2+3+...+n,要求不能使用乘除法、for、while、if、else、switch、case 等关键字及条件判断语句(A?B:C) - -```java -public int Sum_Solution(int n) { - if(n == 0) return 0; - return n + Sum_Solution(n - 1); -} -``` - -## 65. 不用加减乘除做加法 - -a ^ b 表示没有考虑进位的情况下两数的和,(a & b) << 1 就是进位。递归会终止的原因是 (a & b) << 1 最右边会多一个 0,那么继续递归,进位最右边的 0 会慢慢增多,最后进位会变为 0,递归终止。 - -```java -public int Add(int num1, int num2) { - if(num2 == 0) return num1; - return Add(num1 ^ num2, (num1 & num2) << 1); -} -``` - -## 66. 构建乘积数组 - -**题目描述** - -给定一个数组 A[0, 1,..., n-1], 请构建一个数组 B[0, 1,..., n-1], 其中 B 中的元素 B[i]=A[0]\*A[1]\*...\*A[i-1]\*A[i+1]\*...\*A[n-1]。不能使用除法。 - -```java -public int[] multiply(int[] A) { - int n = A.length; - int[][] dp = new int[n][n]; - for (int i = 0; i < n; i++) { - dp[i][i] = A[i]; - } - for (int i = 0; i < n; i++) { - for (int j = i + 1; j < n; j++) { - dp[i][j] = dp[i][j - 1] * A[j]; - } - } - - int[] B = new int[n]; - Arrays.fill(B, 1); - for (int i = 0; i < n; i++) { - if (i != 0) B[i] *= dp[0][i - 1]; - if (i != n - 1) B[i] *= dp[i + 1][n - 1]; - } - return B; -} -``` - -# 第七章 两个面试案例 - -## 67. 把字符串转换成整数 - -```java -public int StrToInt(String str) { - if (str.length() == 0) return 0; - char[] chars = str.toCharArray(); - boolean isNegative = chars[0] == '-'; - int ret = 0; - for (int i = 0; i < chars.length; i++) { - if (i == 0 && (chars[i] == '+' || chars[i] == '-')) continue; - if (chars[i] < '0' || chars[i] > '9') return 0; - ret = ret * 10 + (chars[i] - '0'); - } - return isNegative ? -ret : ret; -} -``` - -## 68. 树中两个节点的最低公共祖先 - -树是二叉查找树的最低公共祖先问题: - -```java -public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { - if(root.val > p.val && root.val > q.val) return lowestCommonAncestor(root.left, p, q); - if(root.val < p.val && root.val < q.val) return lowestCommonAncestor(root.right, p, q); - return root; -} -``` diff --git a/notes/笔记/扩展性与空间限制.md.txt b/notes/笔记/扩展性与空间限制.md.txt deleted file mode 100644 index 52e22a4f..00000000 --- a/notes/笔记/扩展性与空间限制.md.txt +++ /dev/null @@ -1,163 +0,0 @@ -[TOC] - -# 正在完成... - -# 解题思路 - -空间限制问题基本思路是拆分。如果一个数据很大,无法放在一台机器上,就用分布式的方法放到多台机器上;在程序运行时无法直接加载一个大文件,就分多次加载,并且每次运行结果都保存到一个小文件中。 - - - -# 布隆过滤器 - -**题目** - -不安全网页的黑名单包含 100 亿个黑名单网页,每个网页的 URL 最多占用 64B。现在想要实现一种网页过滤系统,可以根据网页的 URL 判断该网页是否在黑名单上,请设计该系统。 - -**要求** - -1. 该系统允许有万分之一以下的判断失误率。 -2. 使用的额外空间不要超过 30GB。 - -![](index_files/82fec0f5-2fdf-4e27-aea9-2e1b2a3cf9eb.jpg) - -[程序员代码面试指南 P303]() - -# 查找所有包含某一组词的文件 - -> 给定数百万份文件,设计一个程序找出所有包含某一组词的文件,该程序会被多次调用。 - -## 1. 预处理 - -预处理每个文件,并创建一个散列表的索引。这个散列表会将词映射到含有这个 -词的一组文件。 - -```html -“books” -> {doc2, doc3, doc6, doc8} -“many” -> {doc1, doc3, doc7, doc8, doc9} -``` - -若要查找“many books”,只需对“books”和“many”的值进行交集运算,于是得到结果{doc3, doc8}。 - -## 2. 拆分 - -若有数百万份文件,那么一台机器无法放下这么多文件,需要存放到多台机器上。并且散列表可能也会很大,一台机器可能无法放下整个散列表,因此需要将散列表拆分到多台机器上。 - -拆分的方法有很多,例如根据键的散列值,或者根据键的范围。 - -## 3. 查找 - -例如查找“after builds boat amaze banana”,先划分成单词序列,然后每个单词都到存放该单词散列表的机器上去查找,最后将每个单词的查找结果做交集运算。 - -# 大型社交网站计算两个人的好友关系 - -> 你会如何设计诸如Facebook或LinkedIn的超大型社交网站?请设计一种算法,展示两个人之间的“连接关系”或“社交路径”(比如,我 → 鲍勃 → 苏珊 → 杰森 → 你) - -## 1. 好友关系的实现 - -为每个用户保存好友 id 的列表,利用这个列表,可以构建一个图,每个用户看作一个结点,两个结点之间若有连线,则表示这两个用户为朋友。 - -要找到两个人之间的连接,可以从其中一个人开始,直接进行广度优先搜索。为什么深度优先搜索效果不彰呢?因为它非常低效。两个用户可能只有一度之隔,却可能要在他们的“子树”中搜索几百万个结点后,才能找到这条非常简单而直接的连接。 - -```java -public class Person { -    private ArrayList friendIDs; -} -``` - -## 2. 进行查找 - -处理LinkedIn或Facebook这种规模的服务时,不可能将所有数据存放在一台机器上。这就意味着前面定义的简单数据结构Person并不管用,朋友的资料和我们的资料不一定在同一台机器上。 - -1. 针对每个朋友ID,找出所在机器的位置:int machine_index = getMachineIDForUser(personID); -2. 转到编号为#machine_index的机器。 -3. 在那台机器上,执行:Person friend = getPersonWithID(person_id);。 - -下面的代码描绘了这一过程。我们定义了一个Server类,包含一份所有机器的列表,还有一个Machine类,代表一台单独的机器。这两个类都用了散列表,从而有效地查找数据。 - -```java -public class Server { -    HashMap machines = new HashMap(); -    HashMap personToMachineMap = new HashMap(); -     -    public Machine getMachineWithId(int machineID) { -        return machines.get(machineID); -    } -     -    public int getMachineIDForUser(int personID) { -        Integer machineID = personToMachineMap.get(personID); -        return machineID == null ? -1 : machineID; -    } -     -    public Person getPersonWithID(int personID) { -        Integer machineID = personToMachineMap.get(personID); -        if (machineID == null) { -            return null; -        } -        Machine machine = getMachineWithId(machineID); -        if (machine == null) { -            return null; -        } -        return machine.getPersonWithID(personID); -    } -} -``` - -```java -public class Person { -    private ArrayList friends; -    private int personID; -    private String info; -     -    public String getInfo() { return info; } -    public void setInfo(String info) { -        this.info = info; -    } - -    public int[] getFriends() { -        int[] temp = new int[friends.size()]; -        for (int i = 0; i < temp.length; i++) { -            temp[i] = friends.get(i); -        } -        return temp; -    } -    public int getID() { return personID; } -    public void addFriend(int id) { friends.add(id); } -     -    public Person(int id) { -        this.personID = id; -    } -} -``` - -```java -public class Machine { -    public HashMap persons = new HashMap(); -    public int machineID; -     -    public Person getPersonWithID(int personID) { -        return persons.get(personID); -    }     -} -``` - -## 3. 优化 - -**减少机器间跳转的次数** - -从一台机器跳转到另一台机器的开销很昂贵。不要为了找到某个朋友就在机器之间任意跳转,而是试着批处理这些跳转动作。举例来说,如果有五个朋友都在同一台机器上,那就应该一次性找出来。 - -**智能划分用户和机器** - -人们跟生活在同一国家的人成为朋友的可能性比较大。因此,不要随意将用户划分到不同机器上,而应该尽量按国家、城市、州等进行划分。这样一来,就可以减少跳转的次数。 - -**广度优先搜索通常要求“标记”访问过的结点。在这种情况下你会怎么做?** - -在广度优先搜索中,通常我们会设定结点类的visited标志,以标记访问过的结点。但针对此题,我们并不想这么做。同一时间可能会执行很多搜索操作,因此直接编辑数据的做法并不妥当。反之,我们可以利用散列表模仿结点的标记动作,以查询结点id,看它是否访问过。 - -## 4. 其它扩展问题 - -1. 在真实世界中,服务器会出故障。这会对你造成什么影响? -2. 你会如何利用缓存? -3. 你会一直搜索,直到图的终点(无限)吗?该如何判断何时放弃? -4. 在现实生活中,有些人比其他人拥有更多朋友的朋友,因此更容易在你和其他人之间构建一条路径。该如何利用该数据选择从哪里开始遍历? diff --git a/notes/笔记/数据库系统原理.md.txt b/notes/笔记/数据库系统原理.md.txt deleted file mode 100644 index 52dd2e76..00000000 --- a/notes/笔记/数据库系统原理.md.txt +++ /dev/null @@ -1,301 +0,0 @@ -[TOC] - -# 事务四大特性 - -## 原子性 - -要么都执行,要么都不执行。 - -## 一致性 - -事务执行前后都保持一致性状态。在一致性状态下,所有事务对一个数据的读取结果都是相同的。 - -## 隔离性 - -多个事务单独执行,互不影响。 - -## 持久性 - -即使系统发生故障,事务执行的结果也不能丢失。持久性通过数据库备份和恢复来保证。 - -# 数据不一致 - -## 丢失修改 - -T1 和 T2 两个事务同时对一个数据进行修改,T1 先修改,T2 随后修改,T2 的修改覆盖了 T1 的修改。 - -## 读脏数据 - -T1 做修改后写入数据库,T2 读取这个修改后的数据,但是如果 T1 撤销了这次修改,使得 T2 读取的数据是脏数据。 - -## 不可重复读 - -T1 读入某个数据,T2 对该数据做了修改,如果 T1 再读这个数据,该数据已经改变,和最开始读入的是不一样的。 - -# 隔离级别 - -数据库管理系统需要防止数据出现不一致,并且有多种级别可以实现,这些级别称为隔离级别。 - -## 未提交读(READ UNCOMMITTED) - -一个事务可以读取自己的未提交数据,也被称为脏读。 - -## 提交读(READ COMMITTED) - -一个事务可以读取自己的已提交数据,但是该数据可能过后就会被其它事务改变,因此也称为不可重复读。 - -## 可重复读(REPEATABLE READ) - -保证在同一个事务中多次读取同样的记录结果是一致的。但是会出现幻读的问题,所谓幻读,指的是某个事务在读取某个范围内的记录时,其它事务会在范围内插入数据,产生幻行。 - -## 可串行化(SERIALIXABLE) - -强制事务串行执行,避免幻行的出现。 - -# 可串行化调度 - -如果并行的事务的执行结果和某一个串行的方式执行的结果一样,那么可以认为结果是正确的。 - -# 封锁类型 - -排它锁 (X 锁 ),共享锁 (S 锁 ) - -一个事务 T 对数据对象 A 加了 X 锁,T 就可以对 A 进行读取和更新。加锁期间其它事务不能对数据对象 A 加任何其它锁; - -一个事务 T 对数据对象加了 S 锁,T 可以对 A 进行读取操作,但是不能进行更新操作。加锁期间其它事务能对数据对象 A 加 S 锁,但是不能加 X 锁。 - -# 封锁粒度 - -粒度可以是整个数据库,也可以是表,行,或者分量。 - -粒度越小,开销越大。 - -# 封锁协议 - -## 三级封锁协议 - -(1) 1 级封锁协议 - -事务 T 要修改数据 A 时必须加 X 锁,直到事务结束才释放锁。 - -可以解决丢失修改问题; - -(2) 2 级封锁协议 - -在 1 级的基础上,要求读取数据 A 时必须加 S 锁,读取完马上释放 S 锁。 - -可以解决读脏数据问题,因为如果一个事务在对数据 A 进行修改,根据 1 级封锁协议,会加 X 锁,那么就不能再加 S 锁了,也就是不会读入数据。 - -(3) 3 级封锁协议 - -在 2 级的基础上,要求读取数据 A 时必须加 S 锁,直到事务结束了才能释放 S 锁。 - -可以解决不可重复读的问题,因为读 A 时,其它事务不能对 A 加 X 锁,从而避免了在读的期间数据发生改变。 - -![](index_files/785806ed-c46b-4dca-b756-cebe7bf8ac3a.jpg) - -## 两段锁协议 - -加锁和解锁分为两个阶段进行。两段锁是并行事务可串行化的充分条件,但不是必要条件。 - -```html -lock-x(A)...lock-s(B)...lock-s(c)...unlock(A)...unlock(C)...unlock(B) -``` - -# 乐观锁和悲观锁 - -## 悲观锁 - -假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。 - -Java synchronized 就属于悲观锁的一种实现,每次线程要修改数据时都先获得锁,保证同一时刻只有一个线程能操作数据,其他线程则会被阻塞。 - -## 乐观锁 - -假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。 - -Java JUC 中的 atomic 包就是乐观锁的一种实现,AtomicInteger 通过 CAS(Compare And Set)操作实现线程安全的自增。 - -乐观锁有两种实现方式,数据版本和时间戳。它们都需要在数据库表中增加一个字段,使用这个字段来判断数据是否过期。例如,数据版本实现方式中,需要在数据库表中增加一个数字类型的 version 字段,当读取数据时,将 version 字段的值一同读出。随后数据每更新一次,对此 version 值加 1。当提交更新的时候,判断读出的 version 和数据库表中的 version 是否一致,如果一致,则予以更新;否则认为是过期数据。 - -## MySQL 隐式和显示锁定 - -MySQL InnoDB 采用的是两阶段锁协议。在事务执行过程中,随时都可以执行锁定,锁只有在执行 COMMIT 或者 ROLLBACK 的时候才会释放,并且所有的锁是在同一时刻被释放。前面描述的锁定都是隐式锁定,InnoDB 会根据事务隔离级别在需要的时候自动加锁。 - -另外,InnoDB 也支持通过特定的语句进行显示锁定,这些语句不属于 SQL 规范: - -- ELECT ... LOCK IN SHARE MODE -- SELECT ... FOR UPDATE - -# 范式 - -记 A->B 表示 A 函数决定于 B,也就是 B 函数依赖于 A。 - -如果 {A1,A2,... ,An} 是关系的一个或多个属性的集合,该集合决定了关系的其它所有属性并且是最小的,那么该集合就称为键码。 - -对于函数依赖 W->A,如果能找到 W 的真子集使得 A 依赖于这个真子集,那么就是部分依赖,否则就是完全依赖; - -以下关系中,Sno 表示学号,Cname 表示课程名,Sname 表示学生姓名,Sdept 表示学院,Mname 表示院长姓名。函数依赖为 (Sno, Cname) -> (Sname, Sdept, Mname)。注:实际开发过程中,不会出现这种表,而是每个实体都放在单独一张表中,然后实体之间的联系表用实体 id 来表示。 - -![](index_files/b6a678c0-c875-4038-afba-301846620786.jpg) - -不符合范式的关系,会产生很多异常。主要有以下四种异常: - -1. 冗余数据 -2. 修改异常 -3. 删除异常 -4. 插入异常,比如如果新插入一个学生的信息,而这个学生还没选课,那么就无法插入该学生。 - -关系数据库的范式理论就是是为了解决这四种异常。 - -高级别范式的依赖基于低级别的范式。 - -## 第一范式 (1NF) - -属性不可分。 - -## 第二范式 (2NF) - -每个非主属性完全函数依赖于键码。 - -可以通过分解来满足。 - -**分解前** - -S(Sno, Cname, Sname, Sdept, Mname) - -(Sno, Cname) -> (Sname, Sdept, Mname) - -**分解后** - -S1(Sno, Sname, Sdept, Mname) - -(Sno) -> (Sname, Sdept, Mname) - -(Sdept) ->  (Mname) - -![](index_files/8ef22836-8800-4765-b4b8-ade80096b323.jpg) - -S2(Sno, Cname, Grade) - -(Sno, Cname) ->  (Grade) - -![](index_files/b0748916-1acd-4138-b24c-69326cb452fe.jpg) - - -## 第三范式 (3NF) - -非主属性不传递依赖于键码。 - -上述 S1 存在传递依赖,Mname 依赖于 Sdept,而 Sdept 又依赖于 Sno,可以继续分解。 - -![](index_files/4995b547-5620-45af-89d7-10f35c9621a1.jpg) - -## BC 范式(BCNF) - -所有属性不传递依赖于键码。 - -关系模式 STC(Sname, Tname, Cname, Grade),其中四个属性分别为学生姓名、教师姓名、课程名和成绩。有以下函数依赖: - -(Sname, Cname) -> (Tname) - -(Sname, Cname) -> (Grade) - -(Sname, Tname) -> (Cname) - -(Sname, Tname) -> (Grade) - -(Tname) -> (Cname) - -分解成 SC(Sname, Cname, Grade) 和 ST(Sname, Tname),对于 ST,属性之间是多对多关系,无函数依赖。 - -# 约束 - -## 键码 - -用于唯一表示一个实体。键码可以由多个属性构成,每个构成键码的属性成为码。 - -## 单值约束 - -某个属性的值是唯一的。 - -## 引用完整性约束 - -一个实体的属性引用的值在另一个实体的某个属性中存在。 - -## 域约束 - -某个属性的值在特定范围之内。 - -## 一般约束 - -一般性约束,比如大小约束,数量约束。 - -# 数据库的三层模式和两层映像 - -外模式:局部逻辑结构;模式:全局逻辑结构;内模式:物理结构。 - -## 外模式 - -又称用户模式,是用户和数据库系统的接口,特定的用户只能访问数据库系统提供给他的外模式中的数据。例如不同的用户创建了不同数据库,那么一个用户只能访问他有权限访问的数据库。一个数据库可以有多个外模式,一个用户只能有一个外模式,但是一个外模式可以给多个用户使用。 - -## 模式 - -可以分为概念模式和逻辑模式,概念模式可以用概念 - 关系来描述;逻辑模式使用特定的数据模式(比如关系模型)来描述数据的逻辑结构,这种逻辑结构包括数据的组成、数据项的名称、类型、取值范围。不仅如此,逻辑模式还要描述数据之间的关系,数据的完整性与安全性要求。 - -## 内模式 - -又称为存储模式,描述记录的存储方式,例如索引的组织方式、数据是否压缩以及是否加密等等。 - -## 外模式/模式映像 - -把外模式的局部逻辑结构和模式的全局逻辑结构联系起来。该映像可以保证数据和应用程序的逻辑独立性。 - -## 模式/内模式映像 - -把模式的全局逻辑结构和内模式的物理结构联系起来,该映像可以保证数据和应用程序的物理独立性。 - -# ER 图 - -Entity-Relationship,包含三个部分:实体、属性、联系。 - -## 实体的三种联系 - -联系包含 1 对 1,1 对多,多对多三种。 - -如果 A 到 B 是 1 对多关系,那么画个带箭头的线段指向 B;如果是 1 对 1,画两个带箭头的线段;如果是多对多,画两个不带箭头的线段。 - -![](index_files/292b4a35-4507-4256-84ff-c218f108ee31.jpg) - -## 表示出现多次的关系 - -一个实体在联系出现几次,就要用几条线连接。如下表示一个课程的先修关系,先修关系中,应当出现两个 Course 实体,第一个是先修课程,后一个是后修课程,因此需要用两条线来表示这种关系。 - -![](index_files/8b798007-e0fb-420c-b981-ead215692417.jpg) - -## 联系的多向性 - -下图中一个联系表示三个实体的关系。虽然老师可以开设多门课,并且可以教授多名学生,但是对于特定的学生和课程,只有一个老师教授,这就构成了一个三元联系。 - -![](index_files/423f2a40-bee1-488e-b460-8e76c48ee560.png) - -一般只使用二元联系,可以把多元关系转换为二元关系。 - -![](index_files/de9b9ea0-1327-4865-93e5-6f805c48bc9e.png) - -## 表示子类 - -用 is-a 联系来表示子类,具体做法是用一个三角形和两条线来连接类和子类。与子类有关的属性和联系都连到子类上,而与父类和子类都有关的连到父类上。 - -![](index_files/7ec9d619-fa60-4a2b-95aa-bf1a62aad408.jpg) - -# 一些概念 - -**数据模型** 由数据结构、数据操作和完整性三个要素组成。 - -**数据库系统** 包括了数据库,数据库管理系统,应用程序以及数据库管理员和用户,还包括相关的硬件和软件。也就是说数据库系统包含所有与数据库相关的内容。 - -# 参考资料 - -- 史嘉权. 数据库系统概论[M]. 清华大学出版社有限公司, 2006. -- [MySQL 乐观锁与悲观锁 ](https://www.jianshu.com/p/f5ff017db62a) \ No newline at end of file diff --git a/notes/笔记/文档-Google Java 编程风格指南.md.txt b/notes/笔记/文档-Google Java 编程风格指南.md.txt deleted file mode 100644 index 6505ac48..00000000 --- a/notes/笔记/文档-Google Java 编程风格指南.md.txt +++ /dev/null @@ -1,115 +0,0 @@ -[TOC] - -# 地址 - -[Google Java编程风格指南](http://www.hawstein.com/posts/google-java-style.html) - -# 命名约定 - -在 Google 其它编程语言风格中使用的特殊前缀或后缀,如 name_ , mName , s_name 和 kName ,在 Java 编程风格中都不再使用。 - -**1. 包名** - -包名全部小写,连续的单词只是简单地连接起来,不使用下划线。 - -**2. 类名** - -类名都以 UpperCamelCase 风格编写。 - -类名通常是名词或名词短语,接口名称有时可能是形容词或形容词短语。 - -测试类的命名以它要测试的类的名称开始,以 Test 结束。 - -**3. 方法名** - -方法名都以 lowerCamelCase 风格编写。 - -方法名通常是动词或动词短语。 - -下划线可能出现在JUnit测试方法名称中用以分隔名称的逻辑组件。一个典型的模式是: test<MethodUnderTest>\_<state> ,例如 testPop_emptyStack 。 并不存在唯一正确的方式来命名测试方法。 - -**4. 常量名** - -常量名命名模式为 CONSTANT_CASE ,全部字母大写,用下划线分隔单词。 - -每个常量都是一个静态final字段,但不是所有静态final字段都是常量。 - -**5. 非常量字段名** - -非常量字段名以 lowerCamelCase 风格编写。 - -**6. 参数名** - -参数名以 lowerCamelCase 风格编写。 - -**7. 局部变量名** - -局部变量名以 lowerCamelCase 风格编写 - -**8. 类型变量名** - -类型变量可用以下两种风格之一进行命名: - -- 单个的大写字母,后面可以跟一个数字(如:E, T, X, T2)。 -- 以类命名方式,后面加个大写的 T (如:RequestT, FooBarT)。 - -**9. 驼峰式命名法** - -1. 把短语转换为纯 ASCII 码,并且移除任何单引号。例如:"Müller’s algorithm" 将变成 "Muellers algorithm"。 -2. 把这个结果切分成单词,在空格或其它标点符号(通常是连字符)处分割开。 -3. 现在将所有字母都小写(包括缩写),然后将单词的第一个字母大写。 -4. 最后将所有的单词连接起来得到一个标识符。 - -```html -Prose form Correct Incorrect ------------------------------------------------------------------- -"XML HTTP request" XmlHttpRequest XMLHTTPRequest -"new customer ID" newCustomerId newCustomerID -"inner stopwatch" innerStopwatch innerStopWatch -"supports IPv6 on iOS?" supportsIpv6OnIos supportsIPv6OnIOS -"YouTube importer" YouTubeImporter - YoutubeImporter* -``` - -# 编程实践 - -- 覆盖的方法加上 @Override 注释。 - -- 异常不能忽视,否则需要在 catch 中注释说明。 - -``` -try { - int i = Integer.parseInt(response); - return handleNumericResponse(i); -} catch (NumberFormatException ok) { - // it's not numeric; that's fine, just continue -} -return handleTextResponse(response); -``` - -- 使用类调用静态成员 - -```java -Foo.aStaticMethod(); -``` - -- 禁止使用 finalize() - -# Javadoc - -基本格式如下: - -```java -/** - * Multiple lines of Javadoc text are written here, - * wrapped normally... - */ -``` - -或者单行形式: -``` -/** An especially short bit of Javadoc. */ -``` - -标准的 Javadoc 标记按以下顺序出现:@param, @return, @throws, @deprecated,前面这4种标记如果出现,描述都不能为空。当描述无法在一行中容纳,连续行需要至少再缩进4个空格。 - diff --git a/notes/笔记/智力题.md.txt b/notes/笔记/智力题.md.txt deleted file mode 100644 index 02a631e5..00000000 --- a/notes/笔记/智力题.md.txt +++ /dev/null @@ -1,193 +0,0 @@ -[TOC] - -# 智力题 - -**1. 有 20 瓶药丸,其中 19 瓶装有 1 克/粒的药丸,余下一瓶装有 1.1 克/粒的药丸。给你一台称重精准的天平,怎么找出比较重的那瓶药丸?天平只能用一次。** - -可以从药丸的数量上来制造差异:从第 i 瓶药丸中取出 i 个药丸,然后一起称重。可以知道,如果第 i 瓶药丸重 1.1 克/粒,那么称重结果就会比正常情况下重 0.1 * i 克。 - -**2. 有个 8×8 棋盘,其中对角的角落上,两个方格被切掉了。给定 31 块多米诺骨牌,一块骨牌恰好可以覆盖两个方格。用这 31 块骨牌能否盖住整个棋盘?** - -![](index_files/0588c158-1dff-434c-b590-9cd2fe5fc7e2.jpg) - -将棋盘划分成黑白两种颜色,每种颜色不相邻,那么一块骨牌需要同时覆盖黑色和白色两个方格。假设切掉的两个对角的方格是黑色的,那么只剩下 32 块白色方格和 30 块黑色方格,因此不能用 31 块骨牌覆盖。 - -**3. 有两个水壶,容量分别为 5 夸脱和 3 夸脱,若水的供应不限量,怎么用这两个水壶得到刚好 4 夸脱的水?** - -![](index_files/9ba3b88b-eace-4a3a-9095-1e9c5c62a6cf.png) - -**4. 有个岛上住着一群人,有一天来了个游客,定了一条奇怪的规矩:所有蓝眼睛的人都必须尽快离开这个岛。 每晚 8 点会有一个航班离岛。每个人都看得见别人眼睛的颜色,但不知道自己的(别人也不可以告知)。此外,他们不知道岛上到底有多少人是蓝眼睛的,只知道至少有一个人的眼睛是蓝色的。所有蓝眼睛的人要花几天才能离开这个岛?** - -假定这个岛上一共有 n 人,其中 c 人有蓝眼睛。 - -1\. 情况 c = 1 -假设岛上所有人都是聪明的,蓝眼睛的人四处观察之后,发现没有人是蓝眼睛的。但他知道至少有一人是蓝眼睛的,于是就能推导出自己一定是蓝眼睛的。因此,他会搭乘当晚的飞机离开。 - -2\. 情况 c = 2 -两个蓝眼睛的人看到对方,并不确定 c 是 1 还是 2,但是由上一种情况,他们知道,如果 c = 1,那个蓝眼睛的人第一晚就会离岛。因此,发现另一个蓝眼睛的人仍在岛上,他一定能推断出 c = 2,也就意味着他自己也是蓝眼睛的。于是,两个蓝眼睛的人都会在第二晚离岛。 - -3\. 情况 c > 2:一般情况 -逐步提高 c 时,我们可以看出上述逻辑仍旧适用。如果 c = 3,那么,这三个人会立即意识到有 2 到 3 人是蓝眼睛的。如果有两人是蓝眼睛的,那么这两人会在第二晚离岛。因此,如果过了第二晚另外两人还在岛上,每个蓝眼睛的人都能推断出 c = 3,因此这三人都有蓝眼睛。他们会在第三晚离岛。不论 c 为什么值,都可以套用这个模式。所以,如果有 c 人是蓝眼睛的,则 **所有蓝眼睛的人要用 c 晚才能离岛,且都在同一晚离开**。 - -**5. 有栋建筑物高 100 层。若从第 N 层或更高的楼层扔下来,鸡蛋就会破掉。若从第 N 层以下的楼层扔下来则不会破掉。给你 2 个鸡蛋,请找出 N,并要求最差情况下扔鸡蛋的次数为最少。** - -首先,让我们试着从 10 层开始扔鸡蛋,然后是 20 层,等等。 -1\. 如果鸡蛋1第一次扔下楼(10层)就破掉了,那么,最多需要扔 10 次。 -2\. 如果鸡蛋1最后一次扔下楼(100层)才破掉,那么,最多要扔 19 次(10、 20、…、 90、100 层,然后是 91 到 99 层)。 - -这么做也挺不错,但我们只考虑了绝对最差情况。我们应该进行“负载均衡”,让这两种情况下扔鸡蛋的次数更均匀。 - -我们的目标是设计一种扔鸡蛋的方法,使得扔鸡蛋1时,不论是在第一次还是最后一次扔下楼才破掉,次数越稳定越好。 - -完美负载均衡要求每次鸡蛋1多扔一次,鸡蛋2就应该少扔一次,这样每种情况下扔鸡蛋的次数都相同。由此可知,鸡蛋1必须从 X 层开始往下扔,然后再往上增加 X - 1 层……直至到达 100 层。 - -求解方程式 X + (X-1) + (X-2) + … + 1 = 100,得到X (X + 1) / 2 = 100 → X = 14。 - -我们先从 14 层开始,然后是 27 层,接着是 39 层,依此类推,最差情况下鸡蛋要扔 14 次。 - -正如解决其他许多最大化/最小化的问题一样,这类问题的关键在于“平衡最差情况”。 - -**6. 走廊上有 100 个关上的储物柜。有个人先是将 100 个柜子全都打开。接着,每数两个柜子关上一个。然后,在第三轮时,再每隔两个就切换第三个柜子的开关状态。照此规律反复操作 100 次,在第 i 轮,这个人会每数 i 个就切换第 i 个柜子的状态。当第 100 轮经过走廊时,只切换第 100 个柜子的开关状态,此时有几个柜子是开着的?** - -1\. 问题:柜子会在哪几轮切换状态? -柜子n会在n的每个因子(包括 1 和 n 本身)对应的那一轮切换状态。也就是说,柜子15会在第 1、 3、 5 和 15 轮开或关一次。 - -2\. 问题:柜子什么时候还是开着的? -如果因子个数(记作 x )为奇数,则这个柜子是开着的。你可以把一对因子比作开和关,若还剩一个因子,则柜子就是开着的。 - -3\. 问题: x 什么时候为奇数? -若 n 为完全平方数,则 x 的值为奇数。理由如下:将 n 的两个互补因子配对。例如,如 n 为 36,则因子配对情况为: (1, 36)、 (2, 18)、 (3, 12)、 (4, 9)、 (6, 6)。注意, (6, 6) 其实只有一个因子,因此 n 的因子个数为奇数。 -4\. 问题:有多少个完全平方数? -一共有 10 个完全平方数:1\*1, 2\*2, 3\*3, ..., 10\*10 - -因此,**最后共有 10 个柜子是开着的**。 - -**7. 给定两条绳子,每条绳子燃烧殆尽正好要用一个小时。怎样用这两条绳子准确计量 15 分钟?注意这些绳子密度不均匀,因此烧掉半截绳子不一定正好用半个小时。** - -1\. 点燃绳子 1 两头的同时,点燃绳子 2 的一头。 -2\. 当绳子 1 烧完,正好过去 30 分钟。而绳子 2 还可以再烧 30 分钟。 -3\. 点燃绳子 2 的另一头。 -4\. 15 分钟后,绳子 2 将全部烧完。 - -**8. 给定 9 个球,其中 8 个球的重量相同,只有一个比较重。然后给定一个天平,可以称出左右两边哪边更重。最多用两次天平,找出这个重球。** - -将这些球均分成 3 个一组共 3 组,称量一次就能知道哪一组球更重。我们甚至可以总结出一条规律:给定 N 个球,其中 N 能被 3 整除,称量一次便能找到包含重球的那一组球。 - -找到这一组 3 个球之后,我们只是简单地重复此前的模式:先把一个球放到一边,称量剩下的两个球。从中挑出那个重球;或者,如果这两个球重量相同,那第 3 个球便是重球。 - -**9. 考虑一个双人游戏。游戏在一个圆桌上进行。每个游戏者都有足够多的硬币。他们需要在桌子上轮流放置硬币,每次必需且只能放置一枚硬币,要求硬币完全置于桌面内(不能有一部分悬在桌子外面),并且不能与原来放过的硬币重叠。谁没有地方放置新的硬币,谁就输了。游戏的先行者还是后行者有必胜策略?** - -先行者在桌子中心放置一枚硬币,以后的硬币总是放在与后行者刚才放的地方相对称的位置。这样,只要后行者能放,先行者一定也有地方放。先行者必胜。 - -**10. 一个矩形蛋糕,蛋糕内部有一块矩形的空洞。只用一刀,如何将蛋糕切成大小相等的两块?** - -注意到平分矩形面积的线都经过矩形的中心。过大矩形和空心矩形各自的中心画一条线,这条线显然把两个矩形都分成了一半,它们的差当然也是相等的。 - -**11. 一块矩形的巧克力,初始时由 N x M 个小块组成。每一次你只能把一块巧克力掰成两个小矩形。最少需要几次才能把它们掰成 N x M 块 1x1 的小巧克力?** - -因为每掰一次后当前巧克力的块数只能增加 1,把巧克力分成 N x M 块当然需要至少掰 N x M - 1 次。 - -**12. 地球上有多少个点,使得从该点出发向南走一英里,向东走一英里,再向北走一英里之后恰好回到了起点?** - -“北极点”是一个传统的答案,其实这个问题还有其它的答案。事实上,满足要求的点有无穷多个。所有距离南极点 1 + 1/(2π) 英里的地方都是满足要求的,向南走一英里后到达距离南极点 1/(2π) 的地方,向东走一英里后正好绕行纬度圈一周,再向北走原路返回到起点。事实上,这仍然不是满足要求的全部点。距离南极点 1 + 1/(2kπ) 的地方都是可以的,其中 k 可以是任意一个正整数。 - -**13. A、B 两人分别在两座岛上。B 生病了,A 有 B 所需要的药。C 有一艘小船和一个可以上锁的箱子。C 愿意在 A 和 B 之间运东西,但东西只能放在箱子里。只要箱子没被上锁,C 都会偷走箱子里的东西,不管箱子里有什么。如果 A 和 B 各自有一把锁和只能开自己那把锁的钥匙,A 应该如何把东西安全递交给 B ?** - -A 把药放进箱子,用自己的锁把箱子锁上。B 拿到箱子后,再在箱子上加一把自己的锁。箱子运回 A 后,A 取下自己的锁。箱子再运到 B 手中时,B 取下自己的锁,获得药物。 - -**14. 一对夫妇邀请 N-1 对夫妇参加聚会(因此聚会上总共有 2N 人)。每个人都和所有自己不认识的人握了一次手。然后,男主人问其余所有人(共 2N-1 个人)各自都握了几次手,得到的答案全部都不一样。假设每个人都认识自己的配偶,那么女主人握了几次手?** - -握手次数只可能是从 0 到 2N-2 这 2N-1 个数。除去男主人外,一共有 2N-1 个人,因此每个数恰好出现了一次。其中有一个人(0)没有握手,有一个人(2N-2)和所有其它的夫妇都握了手。这两个人肯定是一对夫妻,否则后者将和前者握手(从而前者的握手次数不再是 0)。除去这对夫妻外,有一个人(1)只与(2N-2)握过手,有一个人(2N-3)和除了(0)以外的其它夫妇都握了手。这两个人肯定是一对夫妻,否则后者将和前者握手(从而前者的握手次数不再是 1)。以此类推,直到握过 N-2 次手的人和握过 N 次手的人配成一对。此时,除了男主人及其配偶以外,其余所有人都已经配对。根据排除法,最后剩下来的那个握手次数为 N-1 的人就是女主人了。 - -**15. 你在一幢 100 层大楼下,有 21 根电线线头标有数字 1..21。这些电线一直延伸到大楼楼顶,楼顶的线头处标有字母 A..U。你不知道下面的数字和上面的字母的对应关系。你有一个电池,一个灯泡,和许多很短的电线。如何只上下楼一次就能确定电线线头的对应关系?** - -在下面把 2,3 连在一起,把 4 到 6 全连在一起,把 7 到 10 全连在一起,等等,这样你就把电线分成了 6 个“等价类”,大小分别为 1, 2, 3, 4, 5, 6。然后到楼顶,测出哪根线和其它所有电线都不相连,哪些线和另外一根相连,哪些线和另外两根相连,等等,从而确定出字母 A..U 各属于哪个等价类。现在,把每个等价类中的第一个字母连在一起,形成一个大小为 6 的新等价类;再把后 5 个等价类中的第二个字母连在一起,形成一个大小为 5 的新等价类;以此类推。回到楼下,把新的等价类区别出来。这样,你就知道了每个数字对应了哪一个原等价类的第几个字母,从而解决问题。 - -**16. 某种药方要求非常严格,你每天需要同时服用 A、B 两种药片各一颗,不能多也不能少。这种药非常贵,你不希望有任何一点的浪费。一天,你打开装药片 A 的药瓶,倒出一粒药片放在手心;然后打开另一个药瓶,但不小心倒出了两粒药片。现在,你手心上有一颗药片 A,两颗药片 B,并且你无法区别哪个是 A,哪个是 B。你如何才能严格遵循药方服用药片,并且不能有任何的浪费?** - -把手上的三片药各自切成两半,分成两堆摆放。再取出一粒药片 A,也把它切成两半,然后在每一堆里加上半片的 A。现在,每一堆药片恰好包含两个半片的 A 和两个半片的 B。一天服用其中一堆即可。 - -**17. 你在一个飞船上,飞船上的计算机有 n 个处理器。突然,飞船受到外星激光武器的攻击,一些处理器被损坏了。你知道有超过一半的处理器仍然是好的。你可以向一个处理器询问另一个处理器是好的还是坏的。一个好的处理器总是说真话,一个坏的处理器总是说假话。用 n-2 次询问找出一个好的处理器。** - -给处理器从 1 到 n 标号,并用符号 a->b 表示向标号为 a 的处理器询问处理器 b 是不是好的。 - -首先 1->2,如果 1 说不是,就把他们俩都去掉(去掉了一个好的和一个坏的,则剩下的处理器中好的仍然过半),然后从 3->4 开始继续发问;如果 1 说 2 是好的,就继续问 2->3,3->4,…… 直到某一次 j 说 j+1 是坏的,把 j 和 j+1 去掉,然后问 j-1 -> j+2,如果前面已经没有 j-1 了,从 j+2 -> j+3 开始发问。 - -注意到你始终维护着这样一个“链”,前面的每一个处理器都说后面那个是好的。这条链里的所有处理器要么都是好的,要么都是坏的。当这条链越来越长,剩下的处理器越来越少时,总有一个时候这条链超过了剩下的处理器的一半,此时可以肯定这条链里的所有处理器都是好的。或者,越来越多的处理器都被去掉了,链的长度依旧为 0,而最后只剩下一个或两个处理器没被问过,那他们一定就是好的了。另外注意到,第一个处理器的好坏从来没被问过,仔细想想你会发现最后一个处理器的好坏也不可能被问到(一旦链长超过剩余处理器的一半,或者最后没被去掉的就只剩这一个了时,你就不问了),因此询问次数不会超过 n-2。 - -**18. 有 25 匹马,速度都不同,但每匹马的速度都是定值。现在只有 5 条赛道,无法计时,即每赛一场最多只能知道 5 匹马的相对快慢。问最少赛几场可以找出 25 匹马中速度最快的前 3 名?(百度2008年面试题)** - -每匹马都至少要有一次参赛的机会,所以 25 匹马分成 5 组,一开始的这 5 场比赛是免不了的。接下来要找冠军也很容易,每一组的冠军在一起赛一场就行了(第 6 场)。最后就是要找第 2 和第 3 名。我们按照第 6 场比赛中得到的名次依次把它们在前 5 场比赛中所在的组命名为 A、B、C、D、E。即:A 组的冠军是第 6 场的第 1 名,B 组的冠军是第 6 场的第 2 名……每一组的 5 匹马按照他们已经赛出的成绩从快到慢编号: - -A组:1,**2,3**,4,5 -B组:**1,2**,3,4,5 -C组:**1,**2,3,4,5 -D组:1,2,3,4,5 -E组:1,2,3,4,5 - -从现在所得到的信息,我们可以知道哪些马已经被排除在 3 名以外。只要已经能确定有 3 匹或 3 匹以上的马比这匹马快,那么它就已经被淘汰了。可以看到,只有上表中粗体的那 5 匹马才有可能为 2、3 名的。即:A 组的 2、3 名;B 组的 1、2 名,C 组的第 1 名。取这 5 匹马进行第 7 场比赛,第 7 场比赛的前两名就是 25 匹马中的 2、3 名。故一共最少要赛 7 场。 - -**19. 有 7 克、2 克砝码各一个,天平一只,如何只用这些物品三次将140 克的盐分成 50、90 克各一份?** - -第一步:把 140 克盐分成两等份,每份 70 克。 -第二步:把天平一边放上 2+7 克砝码,另一边放盐,这样就得到 9 克和 61 克分开的盐。 -第三步:将 9 克盐和 2 克砝码放在天平一边,另一边放盐,这样就得到 11 克和 50 克。于是 50 和 90 就分开了。 - -**20. 有三筐水果,一筐装的全是苹果,第二筐装的全是橘子,第三筐是橘子与苹果混在一起。筐上的标签都是骗人的,(比如,如果标签写的是橘子,那么可以肯定筐里不会只有橘子,可能还有苹果)你的任务是拿出其中一筐,从里面只拿一只水果,然后正确写出三筐水果的标签。** - -从贴有苹果和橘子标签的筐中拿出一个水果,如果是苹果,说明这个筐中全是苹果,那么贴苹果标签的筐里装的全是桔子,则贴有桔子标签的筐中装的苹果和桔子;如果拿出的一个水果是桔子,说明这个筐中全是桔子,那么贴桔子标签的筐里装的全是苹果,贴苹果标签的筐里装的是苹果和桔子。 - -# 数字推理 - -数列常见的有等差等比关系,等差关系中数之间的 **间隔** 相同,等比关系中数之间的 **比值** 相同。不规则的数列,间隔和比值都不会相同,但是间隔和比值又可以当做一个数列,并且这个数列具有等差等比关系。例如 3,4,6,9,13,18,间隔数列为 1, 2, 3, 4, 5 为等差数列,8,8,12,24,60,180,比值数列为 1, 1.5, 2, 2.5 为等比数列。 - -数列也可能和 **平方、立方、次方** 相关。例如 66,83,102,123,146 为 8, 9, 10, 11, 12 的平方再加 2 所得,0,6,24,60,120,210 为 n3 - 1。 - -当相邻的数都没规律时,考虑 **错位** 的情况。例如 5,4,10,8,15,16,20,32 ,奇数列和偶数列分别为一个等差数列和一个等比数列。 - -数列中一个数不一定只和前一个数有关,有可能 **和前两个数有关**,例如 2,5,10,50,500,第三个数为前两个数的乘积。 - -# 图形推理 - -**1. 结构类** - -- 对称性(水平对称、垂直对称、中心对称) -- 直曲性(直线、曲线) -- 封闭与开放 - -**2. 位置类** - -- 相对位置(相交、相离、相切;相对与相邻;独立于覆盖) -- 移动、旋转、翻转 - -**3. 叠加类** - -- 直接叠加 -- 去同存异 -- 去异存同 -- 自定义叠加(阴影变化) - -**4. 组合类** - -**5. 数量类** - -- 点(十字交叉点、T 字交叉点、切点、接触点数量) -- 线(线条数:直线数、曲线数、线条总数;笔画数;一笔画与多笔画) -- 角(锐角、直角、钝角的数量) -- 面(封闭区域数、面积大小、面的个数) - -数量关系有: - -- 相同 -- 等差 -- 等比 -- 和相等 - -![](index_files/FireShot_20Capture_209_20-_20_u.png) - -# 参考资料 - -1. 程序员面试金典 -2. [数字推理题的各种规律范例题型](http://www.linquan.info/archives/62.html) -3. [程序员有趣的面试智力题](http://blog.csdn.net/hackbuteer1/article/details/6726419) -4. [图形推理50题 答案及解析](http://blog.sina.com.cn/s/blog_7815fa370100tdgm.html) \ No newline at end of file diff --git a/notes/笔记/正则表达式.md.txt b/notes/笔记/正则表达式.md.txt deleted file mode 100644 index 13eab5b9..00000000 --- a/notes/笔记/正则表达式.md.txt +++ /dev/null @@ -1,391 +0,0 @@ -[TOC] - -# 第 1 章 概述 - -正则表达式用于文本内容的查找和替换。 - -正则表达式内置于其它语言或者软件产品中,它本身不是一种语言或者软件。 - -一个问题往往可以用多种正则表达式方案来解决。 - -[ 正则表达式在线工具 ](http://tool.chinaz.com/regex) - -# 第 2 章 匹配单个字符 - -正则表达式一般是区分大小写的,但是也有些实现是不区分。 - -**.** 可以用来匹配任何的单个字符,但是在绝大多数实现里面,不能匹配换行符; - -**\\** 是元字符,表示它有特殊的含义,而不是字符本身的含义。如果需要匹配 . ,那么要用 \ 进行转义,即在 . 前面加上 \ 。 - - -**正则表达式** - -``` -nam. -``` - -**匹配结果** - -My **name** is Zheng. - - -# 第 3 章 匹配一组字符 - -**[ ]** 定义一个字符集合; - -0-9、a-z 定义了一个字符区间,区间使用 ASCII 码来确定。字符区间只能用在 [ ] 之间,因此 **-** 元字符只有在 [ ] 之间才是元字符,在 [ ] 之外就是一个普通字符; - -**^** 是取非操作,必须在 [ ] 字符集合中使用; - -**应用** - -匹配以 abc 为开头,并且最后一个字母不为数字的字符串: - -**正则表达式** - -``` -abc[^0-9] -``` - -**匹配结果** - -**abcd** -abc1 -abc2 - -# 第 4 章 使用元字符 - -## 4.1 匹配空白字符 - -|  元字符 | 说明  | -| ------------ | ------------ | -|  [\b] | 回退(删除)一个字符   | -|  \f |  换页符 | -|  \n |  换行符 | -|  \r |  回车符 | -|  \t |  制表符 | -|  \v |  垂直制表符 | - -\r\n 是 Windows 中的文本行结束标签,在 Unix/Linux 则是 \n ;\r\n\r\n 可以匹配 Windows 下的空白行,因为它将匹配两个连续的行尾标签,而这正是两条记录之间的空白行; - -. 是元字符,前提是没有对它们进行转义; f 和 n 也是元字符,但是前提是对他们进行了转义。 - -## 4.2 匹配特定的字符类别 - -**1. 数字元字符** - -|  元字符 | 说明  | -| ------------ | ------------ | -| \d  | 数字字符,等价于 [0-9]  | -| \D  | 非数字字符,等价于 [^0-9]   | - -**2. 字母数字元字符** - -|  元字符 | 说明  | -| ------------ | ------------ | -| \w  |  大小写字母,下划线和数字,等价于 [a-zA-Z0-9\_] | -|  \W |  对 \w 取非 | - -**3. 空白字符元字符** - -| 元字符  | 说明  | -| ------------ | ------------ | -|  \s | 任何一个空白字符,等价于 [\f\n\r\t\v]  | -| \S  |  对 \s 取非  | - - -\x 匹配十六进制字符,\0 匹配八进制,例如 \x0A 对应 ASCII 字符 10 ,等价于 \n,也就是它会匹配 \n 。 - -## 4.3 使用 POSIX 字符类 - -| 字符类 | 说明 | -| --- | --- | -| [:alnum:] | 字母数字字符 | -| [:alpha:] | 字母字符 | -| [:cntrl:] | 控制字符 | -| [:digit:] | 数字字符 | -| [:graph:] | 非空白字符 ( 非空格、控制字符等 ) | -| [:lower:] | 小写字母 | -| [:print:] | 与 [:graph:] 相似,但是包含空格字符 | -| [:punct:] | 标点字符 | -| [:space:] | 所有的空白字符 ( 换行符、空格、制表符 ) | -| [:upper:] | 大写字母 | -| [:xdigit:] | 允许十六进制的数字 (0-9a-fA-F) | - -并不是所有正则表达式实现都支持 POSIX 字符类,也不一定使用它。 - -使用时需要用两对方括号,例如 [[:alpha:]]。 - -# 第 5 章 重复匹配 - -**\+** 匹配 1 个或者多个字符, **\*** 匹配 0 个或者多个,**?** 匹配 0 个或者 1 个。 - -**应用** - -匹配邮箱地址。 - -**正则表达式** - -``` -[\w.]+@\w+.\w+ -``` - -[\w.] 匹配的是字母数字或者 . ,在其后面加上 + ,表示匹配多次。在字符集合 [ ] 里,. 不是元字符; - -**匹配结果** - -**abc.def@qq.com** - -为了可读性,常常把转义的字符放到字符集合 [ ] 中,但是含义确实相同的。 - -``` -\w+@\w+.\w+ -[\w]+@[\w]+.[\w]+ -``` - -**{n}** 匹配 n 个字符,**{m, n}** 匹配 m~n 个字符,**{m,}** 至少匹配 m 个字符; - -\* 和 + 都是贪婪型元字符,会匹配最多的内容,在元字符后面加 ? 可以转换为懒惰型元字符,例如 \*?、+? 和 {m, n}? 。 - -**正则表达式** - -``` -a.+c -``` - -由于 + 是贪婪型的,因此 .+ 会匹配更可能多的内容,所以会把整个 abcabcabc 文本都匹配,而不是只匹配前面的 abc 文本。用懒惰型可以实现匹配前面的。 - -**匹配结果** - -**abcabcabc** - -# 第 6 章 位置匹配 - -## 6.1 单词边界 - -**\b** 可以匹配一个单词的边界,边界是指位于 \w 和 \W 之间的位置;**\B** 匹配一个不是单词边界的位置。 - -\b 只匹配位置,不匹配字符,因此 \babc\b 匹配出来的结果为 3 个字符。 - -## 6.2 字符串边界 - -**^** 匹配整个字符串的开头,**$** 匹配结尾。 - -^ 元字符在字符集合中用作求非,在字符集合外用作匹配字符串的开头。 - -使用 (?m) 来打开分行匹配模式,在该模式下,换行被当做字符串的边界。 - -**应用** - -匹配代码中以 // 开始的注释行 - -**正则表达式** - -``` -(?m)^\s*//.*$ -``` - -如果没用 (?m),则只会匹配 // 注释 1 以及之后的所有内容,因为 * 是贪婪型的。用了分行匹配模式之后,换行符被当成是字符串分隔符,因此能正确匹配出两个注释内容。 - -**匹配结果** - -public void fun() { -        **// 注释 1** -        int a = 1; -        int b = 2; -        **// 注释 2** -        int c = a + b; -} - -# 第 7 章 使用子表达式 - -使用 **( )** 定义一个子表达式。子表达式的内容可以当成一个独立元素,即可以将它看成一个字符,并且使用 * 等元字符。 - -子表达式可以嵌套,但是嵌套层次过深会变得很难理解。 - -**正则表达式** - -``` -(ab) {2,} -``` - -**匹配结果** - -**ababab** - -**|** 是或元字符,它把左边和右边所有的部分都看成单独的两个部分,两个部分只要有一个匹配就行。 - -``` -(19|20)\d{2} -``` - -**匹配结果** - -**1900 -2010** -1020 - -**应用** - -匹配 IP 地址。IP 地址中每部分都是 0-255 的数字,用正则表达式匹配时以下情况是合法的: - -① 一位或者两位的数字 -② 1 开头的三位数 -③ 2 开头,第 2 位是 0-4 的三位数 -④ 25 开头,第 3 位是 0-5 的三位数 - -**正则表达式** - -``` -(((\d{1, 2})|(1\d{2})|(2[0-4]\d)|(25[0-5]))\.) {3}(((\d{1, 2})|(1\d{2})|(2[0-4]\d)|(25[0-5]))) -``` - -**匹配结果** - -**192.168.0.1** -555.555.555.555 - -# 第 8 章 回溯引用 - -回溯引用使用 **\n** 来引用某个子表达式,其中 n 代表的是子表达式的序号,从 1 开始。它和子表达式匹配的内容一致,比如子表达式匹配到 abc ,那么回溯引用部分也需要匹配 abc 。 - -**应用** - -匹配 HTML 中合法的标题元素。 - -**正则表达式** - -\1 将回溯引用 子表达式 (h[1-6]) 匹配的内容,也就是说必须和子表达式匹配的内容一致。 - -``` -<(h[1-6])>\w*? -``` - -**匹配结果** - -**<h1>x</h1> -<h2>x</h2>** -<h3>x</h1> - -## 替换 - -需要用到两个正则表达式。 - -**应用** - -修改电话号码格式。 - -**文本** - -313-555-1234 - -**查找正则表达式** - -``` -(\d{3})(-)(\d{3})(-)(\d{4}) -``` - -**替换正则表达式** - -在第一个子表达式查找的结果加上 () ,然后加一个空格,在第三个和第五个字表达式查找的结果中间加上 - 进行分隔。 - -``` -($1) $3-$5 -``` - -**结果** - -(313) 555-1234 - -## 大小写转换 - -|  元字符 | 说明  | -| ---| ---| -|  \l | 把下个字符转换为小写  | -|   \u| 把下个字符转换为大写  | -|  \L | 把\L 和\E 之间的字符全部转换为小写  | -|  \U | 把\U 和\E 之间的字符全部转换为大写  | -|  \E | 结束\L 或者\U  | - -**应用** - -把文本的第二个和第三个字符转换为大写。 - -**文本** - -abcd - -**查找** - -``` -(\w)(\w{2})(\w) -``` - -**替换** - -``` -$1\U$2\E$3 -``` - -**结果** - -aBCd - -# 第 9 章 前后查找 - -前后查找规定了匹配的内容首尾应该匹配的内容,但是又不包含首尾匹配的内容。向前查找用 **?=** 来定义,它规定了尾部匹配的内容,这个匹配的内容在 ?= 之后定义。所谓向前查找,就是规定了一个匹配的内容,然后以这个内容为尾部向前面查找需要匹配的内容。向后匹配用 ?<= 定义。 - -**应用** - -查找出邮件地址 @ 字符前面的部分。 - -**正则表达式** - -``` -\w+(?=@) -``` - -**结果** - -**abc**@qq.com - -对向前和向后查找取非,只要把 = 替换成 ! 即可,比如 (?=) 替换成 (?!) 。取非操作使得匹配那些首尾不符合要求的内容。 - -# 第 10 章 嵌入条件 - -## 10.1 回溯引用条件 - -条件判断为某个子表达式是否匹配,如果匹配则需要继续匹配条件表达式后面的内容。 - -**正则表达式** - -子表达式 (\\() 匹配一个左括号,其后的 ? 表示匹配 0 个或者 1 个。 ?(1) 为条件,当子表达式 1 匹配时条件成立,需要执行 \) 匹配。 - -``` -(\()?abc(?(1)\)) -``` - -**结果** - -**(abc)** -**abc** -(abc - -## 10.2 前后查找条件 - -条件为定义的首尾是否匹配,如果匹配,则继续执行后面的匹配。注意,首尾不包含在匹配的内容中。 - -**正则表达式** - - ?(?=-) 为前向查找条件,只有在以 - 为前向查找的结尾能匹配 \d{5} ,才继续匹配 -\d{4} 。 - -``` -\d{5}(?(?=-)-\d{4}) -``` - -**结果** - -**11111** -22222- -**33333-4444** diff --git a/notes/笔记/程序员的职业素养.md.txt b/notes/笔记/程序员的职业素养.md.txt deleted file mode 100644 index 173aa834..00000000 --- a/notes/笔记/程序员的职业素养.md.txt +++ /dev/null @@ -1,120 +0,0 @@ -[TOC] - -# 译者序 - -作者 Bob 大叔,著有《敏捷软件开发:原则、模式与实践》《代码整洁之道》《程序员的职业素养》 - -对于解决问题,重要的不是问题本身,而是解决问题的方式、步骤以及反思的深度,这就是职业素养。 - -# 前言 - -由于管理者面临很大的财务和政治压力,他们无视技术人员发出的危险警告,抱着侥幸心理发射了“挑战者”航天飞机,最终导致航天飞机在高空爆炸。虽说技术人员已经做很多事情去阻止这次发射,但是并不是说做了所有他们能做的,例如他们就没有打电话去新闻台揭露此次发射的危险性。 - -![](index_files/bccb799f-56e2-4356-95f0-a9ea05b0de2a.jpg) - - -# 专业主义 - -专业主义不仅意味着荣誉和骄傲,也意味着责任。 - -作者在自己开发的一个电话线路故障检测系统时,因为赶着在到期日交付新功能,没有对整个系统进行测试,导致了一个 bug 的出现。作者认为这是一种不负责任的表现,也就是不专业的表现。 - -代码中难免有 bug,但是不意味着你不用为 bug 负责。 - -有人把 QA 当成查 bug 的机器,等着 QA 发现 bug,而自己却不主动去发现 bug,这是不对的。 - -为了保证代码的正确性,需要写一些随时可以运行的单元测试,并且不断地去运行它们。 - -软件项目的根本原则 - 易于修改。为了让自己的软件易于修改,需要经常去修改它! - -单元测试可以让程序员更有自信去修改代码,因为它会让人确信修改地是否正确。 - -专业素养包括以下内容: - -1. 坚持学习 -2. 每天练习一些简单的编程题目 -3. 与他们合作,从彼此身上学到更多东西 -4. 交流。通过交流表达自己的思想,发现自己的不足以及与别人思想上的差异。 -5. 了解业务领域,找几本相关领域的书看。 -6. 与雇主 / 客户保持一致 -7. 谦逊,因为他们知道自己也可能犯错。 - -# 说不 - -对一些不合理的要求,不能只是说“试试看”,这会被当成是接受了这个要求。如果自己深知这种不合理的要求会导致严重的后果,就不能任由它发展下去,对这一要求说“不”。 - -说“不”时要用最详细的细节来说明,而不是烦躁地反驳。 - -当要求明显不合理时,可以协商一些双方都能接受的方案。 - -"有可能写出好代码吗" 这篇博文,讲述了一次开发一家零售商为了在"黑色星期五"能够让顾客看到商品信息商店信息以及发布优惠码等功能的 ipone 应用,最开始零售商的经理承诺说只要写个硬编码的应用就行了,但是客户所要的任何一项功能,总比它最开始时所说的要复杂许多。面对时间的紧急,客户方的不配合,以及需求的变更,让博文作者写了一堆很烂的代码,而他自己确实对那些设计模式等高级开发技术是非常热衷的。所以他认为在实际的开发中因为客户的需求等因素,很难写作自己想写的代码。Bob 认为,上面那篇博文的作者才是此次开发过程碰到的那些问题的负责人,因为他没有对那些不合理的要求说不。 - -# 说是 - -做出承诺的是三个步骤:口头上答应,放在心里,付诸行动。 - -缺乏承诺的词语: - -1. 需要:我需要把这事做完; -2. 希望:希望今天我能完成任务; -3. 让我们:让我们把这件事做完。 - -缺乏承诺的人不会把重点放在自己身上,也不会明确说明一件事情的截止日期。真正的承诺是这样的“我会在...之前...” - -对自己无法完全掌握的事,不要轻易承诺。 - -发现自己无法做到承诺,立马去调整别人对你的预期。 - -“试试看”说明自己对承诺的内容有点顾忌,因此不要用试试看来作为一种承诺。如果不能确信自己能达到承诺的内容而随便承诺,那么最后往往会产生严重的后果。 - -# 编程 - -要精通一项技术,要对该技术具备“信心”和“感知能力”。 - -状态不好的时候最好别写代码。状态不好分为:疲劳,比如熬夜;焦虑,因为外界的事情让自己不能安心下来,这个时候应该下调整好再进行编程。 - -进入流状态的感觉很好,觉得可以做很多事,但是流状态其实是一种浅层冥想,思维能力会下降,因此要避免进入流状态。 - -![](index_files/f0321ed1-fa93-460e-951b-4239fef819f3.jpg) - -流状态的特点是隔绝沟通,而结对编程能够打破这种隔绝,因此结对编程能够防止进入流状态。 - -当自己没法继续编程时,结对编程是个好选择,结对编程时,大脑和身体会有化学变化,也就是说思维方式会改变,有助于突破当前的思维阻塞状态。 - -听音乐时音乐会占用一部分脑力资源,因此会导致效率下降,但是这不是绝对。 - -多输入一些创造性的内容,比如科幻小说等,自己的创造力也会得到提升。 - -调试时间也很宝贵,测试驱动开发能够降低调试的时间。 - -编码和跑马拉松一样,无法全程以最快的速度冲刺,只能通过保持体力和维持节奏来取得胜利。因此要保持好自己的节奏,在疲劳的时候适当休息。 - -定期做进度衡量,用乐观预估、标准预估和悲观预估这三个时间点。 - -如果自己预估的进度赶不上截止时间,不要答应在截止时间去完成,因为你知道根据自己的预估这是不可能的。 - -任务完成的标准通常用一个自动化的验收测试来定义。 - -帮助他人也会给自己带来好处,在帮助别人的时候不要让自己看起来很仓促,就像是在随便应付。一般帮助别人不需要特别多的时间。也要学会请求别人的帮助,因为自己不可能是万能的,通过别人的帮助能更好的解决问题。 - -# 测试驱动开发 - -TDD 的运行周期很短,可能一两分钟就可以运行一次程序,短周期可以快速得到反馈,反馈在开发中特别重要,一方面是提高程序员编码的激情,另一方面是能够及早发现 bug,而 bug 越早发现修改地代价也越低。 - -TDD 三法则: - -1. 先写测试; -2. 在一个单元测试失败的时候,不要再写测试代码; -3. 产品代码能恰好通过当前失败的单元测试即可,不要多写。 - -TDD 优点 - -1. 确定性:确定当前系统是否正确; -2. 信心:程序员更有信息去修改混乱的代码,因为通过单元测试可以知道是否修改地正确; -3. 文档:单元测试是底层设计细节的文档,通过阅读单元测试能够知道程序的运行方式; - -# 练习 - -卡塔在武术里面是一套设计好的招式,该招式模拟真实搏斗场景,习武者不断训练来让身体熟悉该招式,从而做到纯熟。编程卡塔是一套编程过程敲击鼠标和键盘的动作,练习者不是为了解决真正的问题,因为已经知道了解决方案,而是练习解决这个问题所需要的动作和决策。 - -瓦萨即使两人对练的卡塔。在编程领域,可以是一个人写单元测试,另一个人写程序通过单元测试。比如,一个人实现排序算法,写测试的人可以很容易地限制速度和内存,给同伴施压。 diff --git a/notes/笔记/算法.md.txt b/notes/笔记/算法.md.txt deleted file mode 100644 index 5692f709..00000000 --- a/notes/笔记/算法.md.txt +++ /dev/null @@ -1,1539 +0,0 @@ -[TOC] - -# 基础 - -## 栈 - -### 1. 数组实现 - - -```java -public class ResizeArrayStack implements Iterable { -    private Item[] a = (Item[]) new Object[1]; -    private int N = 0; - -    public void push(Item item) { -        if (N >= a.length) { -            resize(2 * a.length); -        } -        a[N++] = item; -    } - -    public Item pop() { -        Item item = a[--N]; -        if (N <= a.length / 4) { -            resize(a.length / 2); -        } -        return item; -    } - -    // 调整数组大小,使得栈具有伸缩性 -    private void resize(int size) { -        Item[] tmp = (Item[]) new Object[size]; -        for (int i = 0; i < N; i++) { -            tmp[i] = a[i]; -        } -        a = tmp; -    } - -    public boolean isEmpty() { -        return N == 0; -    } - -    public int size() { -        return N; -    } - -    @Override -    public Iterator iterator() { -        // 需要返回逆序遍历的迭代器 -        return new ReverseArrayIterator(); -    } - -    private class ReverseArrayIterator implements Iterator { -        private int i = N; - -        @Override -        public boolean hasNext() { -            return i > 0; -        } - -        @Override -        public Item next() { -            return a[--i]; -        } -    } -} -``` - -上面实现使用了泛型,Java 不能直接创建泛型数组,只能使用转型来创建。 - -```java -Item[] arr = (Item[]) new Object[N]; -``` - -### 2. 链表实现 - -需要使用链表的头插法来实现,因为头插法中最后压入栈的元素在链表的开头,它的 next 指针指向前一个压入栈的元素,在弹出元素使就可以让前一个压入栈的元素称为栈顶元素。 - -```java -public class Stack { -     -    private Node top = null; -    private int N = 0; - -    private class Node { -        Item item; -        Node next; -    } - -    public boolean isEmpty() { -        return N == 0; -    } - -    public int size() { -        return N; -    } - -    public void push(Item item) { -        Node newTop = new Node(); -        newTop.item = item; -        newTop.next = top; -        top = newTop; -        N++; -    } - -    public Item pop() { -        Item item = top.item; -        top = top.next; -        N--; -        return item; -    } -} -``` - -## 队列 - -下面是队列的链表实现,需要维护 first 和 last 节点指针,分别指向队首和队尾。 - -这里需要考虑让哪个指针指针链表头部节点,哪个指针指向链表尾部节点。因为出队列操作需要让队首元素的下一个元素成为队首,就需要容易获取下一个元素,而链表的头部节点的 next 指针指向下一个元素,因此让队首指针 first 指针链表的开头。 - -```java -public class Queue { -    private Node first; -    private Node last; -    int N = 0; -    private class Node{ -        Item item; -        Node next; -    } - -    public boolean isEmpty(){ -        return N == 0; -    } - -    public int size(){ -        return N; -    } - -    // 入队列 -    public void enqueue(Item item){ -        Node newNode = new Node(); -        newNode.item = item; -        newNode.next = null; -        if(isEmpty()){ -            last = newNode; -            first = newNode; -        } else{ -            last.next = newNode; -            last = newNode; -        } -        N++; -    } - -    // 出队列 -    public Item dequeue(){ -        Node node = first; -        first = first.next; -        N--; -        return node.item; -    } -} -``` - -## 算法分析 - -### 1. 函数转换 - -指数函数可以转换为线性函数,从而在函数图像上显示的更直观。 - -T(N)=aN3 转换为 lg(T(N))=3lgN+lga - -![](index_files/5510045a-8f32-487f-a756-463e51a6dab0.png) - -### 2. 数学模型 - -**近似** - -使用 ~f(N) 来表示所有随着 N 的增大除以 f(N) 的结果趋近于 1 的函数 , 例如 N3/6-N2/2+N/3 ~ N3/6。 - -![](index_files/ca3a793e-06e5-4ff3-b28e-a9c20540d164.png) - -**增长数量级** - -增长数量级将算法与它的实现隔离开来,一个算法的增长数量级为 N3 与它是否用 Java 实现,是否运行与特定计算机上无关。 - -![](index_files/1ea4dc9a-c4dd-46b5-bb11-49f98d57ded1.png) - -**内循环** - -执行最频繁的指令决定了程序执行的总时间,把这些指令称为程序的内循环。 - -**成本模型** - -使用成本模型来评估算法,例如数组的访问次数就是一种成本模型。 - -### 3. ThreeSum - -ThreeSum 程序用于统计一个数组中三元组的和为 0 的数量。 - -```java -public class ThreeSum { -    public static int count(int[] a) { -        int N = a.length; -        int cnt = 0; -        for (int i = 0; i < N; i++) { -            for (int j = i + 1; j < N; j++) { -                for (int k = j + 1; k < N; k++) { -                    if (a[i] + a[j] + a[k] == 0) { -                        cnt++; -                    } -                } -            } -        } -        return cnt; -    } -} -``` - -该程序的内循环为 if (a[i] + a[j] + a[k] == 0) 语句,总共执行的次数为 N3/6-N2/2+N/3,因此它的近似执行次数为 ~N3/6,增长数量级为 N3。 - -**改进** - -通过将数组先排序,对两个元素求和,并用二分查找方法查找是否存在该和的相反数,如果存在,就说明存在三元组的和为 0。 - -该方法可以将 ThreeSum 算法增长数量级降低为 N2logN。 - -```java -public class ThreeSumFast { -    public static int count(int[] a) { -        Arrays.sort(a); -        int N = a.length; -        int cnt = 0; -        for (int i = 0; i < N; i++) { -            for (int j = i + 1; j < N; j++) { -                for (int k = j + 1; k < N; k++) { -                    // rank() 方法返回元素在数组中的下标,如果元素不存在,这里会返回 -1。应该注意这里的下标必须大于 j,这样就不会重复统计了。 -                    if (BinarySearch.rank(-a[i] - a[j], a) > j) { -                        cnt++; -                    } -                } -            } -        } -        return cnt; -    } -} -``` - -### 4. 倍率实验 - -如果 T(N) ~ aNblgN,那么 T(2N)/T(N) ~ 2b,例如对于暴力方法的 ThreeSum 算法,近似时间为 ~N3/6,对它进行倍率实验得到如下结果: - -![](index_files/6f5ed46f-86d7-4852-a34f-c1cf1b6343a0.png) - -可见 T(2N)/T(N)~23,也就是 b 为 3。 - -### 5. 注意事项 - -**大常数** - -在求近似时,如果低级项的常数系数很大,那么近似的结果就是错误的。 - -**缓存** - -计算机系统会使用缓存技术来组织内存,访问数组相邻的元素会比访问不相邻的元素快很多。 - -**对最坏情况下的性能的保证** - -在核反应堆、心脏起搏器或者刹车控制器中的软件,最坏情况下的性能是十分重要的。 - -**随机化算法** - -通过打乱输入,去除算法对输入的依赖。 - -**均摊分析** - -将所有操作的总成本所以操作总数来将成本均摊。例如对一个空栈进行 N 次连续的 push() 调用需要访问数组的元素为 N+4+8+16+...+2N=5N-4(N 是向数组写入元素,其余的都是调整数组大小时进行复制需要的访问数组操作),均摊后每次操作访问数组的平均次数为常数。 - -## union-find - -**概览** - -用于解决动态连通性问题,能动态连接两个点,并且判断两个点是否连接。 - -![](index_files/5d387d02-6f96-44d6-b5d0-4538349f868e.png) - -**API** - -![](index_files/a9b91b7d-65d7-4aa3-8ef6-21876b05ad16.png) - -**基本数据结构** - -```java -public class UF { -    // 使用 id 数组来保存点的连通信息 -    private int[] id; - -    public UF(int N) { -        id = new int[N]; -        for (int i = 0; i < N; i++) { -            id[i] = i; -        } -    } - -    public boolean connected(int p, int q) { -        return find(p) == find(q); -    } -} -``` - -### 1. quick-find 算法 - -保证在同一连通分量的所有触点的 id 值相等。 - -这种方法可以快速取得一个触点的 id 值,并且判断两个触点是否连通,但是 union 的操作代价却很高,需要将其中一个连通分量中的所有节点 id 值都修改为另一个节点的 id 值。 - -```java -    public int find(int p) { -        return id[p]; -    } -    public void union(int p, int q) { -        int pID = find(p); -        int qID = find(q); - -        if (pID == qID) return; -        for (int i = 0; i < id.length; i++) { -            if (id[i] == pID) id[i] = qID; -        } -    } -``` - -### 2. quick-union 算法 - -在 union 时只将触点的 id 值指向另一个触点 id 值,不直接用 id 来存储所属的连通分量。这样就构成一个倒置的树形结构,根节点需要指向自己。在进行查找一个节点所属的连通分量时,要一直向上查找直到根节点,并使用根节点的 id 值作为本连通分量的 id值。 - -![](index_files/9192dc0a-a7cd-4030-8df6-e388600644cf.jpg) - -```java -    public int find(int p) { -        while (p != id[p]) p = id[p]; -        return p; -    } - -    public void union(int p, int q) { -        int pRoot = find(p); -        int qRoot = find(q); -        if (pRoot == qRoot) return; -        id[pRoot] = qRoot; -    } -``` - -这种方法可以快速进行 union 操作,但是 find 操作和树高成正比,最坏的情况下树的高度为触点的数目。 - -![](index_files/d206d090-d911-4263-a1fe-d6f63f5d1776.png) - -### 3. 加权 quick-union 算法 - -为了解决 quick-union 的树通常会很高的问题,加权 quick-union 在 union 操作时会让较小的树连接较大的树上面。 - -理论研究证明,加权 quick-union 算法构造的树深度最多不超过 lgN。 - -![](index_files/8d6af5ac-74eb-4e07-99aa-654b9f21f1d3.jpg) - -```java -public class WeightedQuickUnionUF { -    private int[] id; -    // 保存节点的数量信息 -    private int[] sz; - -    public WeightedQuickUnionUF(int N) { -        id = new int[N]; -        sz = new int[N]; -        for (int i = 0; i < N; i++) { -            id[i] = i; -            sz[i] = 1; -        } -    } - -    public boolean connected(int p, int q) { -        return find(p) == find(q); -    } - -    public int find(int p) { -        while (p != id[p]) p = id[p]; -        return p; -    } - -    public void union(int p, int q) { -        int i = find(p); -        int j = find(q); -        if (i == j) return; -        if (sz[i] < sz[j]) { -            id[i] = j; -            sz[j] += sz[i]; -        } else { -            id[j] = i; -            sz[i] += sz[j]; -        } -    } -} -``` - -### 4. 路径压缩的加权 quick-union 算法 - -在检查节点的同时将它们直接链接到根节点,只需要在 find 中添加一个循环即可。 - -### 5. 各种 union-find 算法的比较 - -![](index_files/e5baeb38-0ec9-4ad7-8374-1cdb0dba74a6.jpg) - -# 第二章 排序 - -## 初级排序算法 - -### 1. 约定 - -待排序的元素需要实现 Java 的 Comparable 接口,该接口有 compareTo() 方法。 - -研究排序算法的成本模型时,计算的是比较和交换的次数。 - -使用辅助函数 less() 和 exch() 来进行比较和交换的操作,使得代码的可读性和可移植性更好。 - -```java -private static boolean less(Comparable v, Comparable w){ -    return v.compareTo(w) < 0; -} - -private void exch(Comparable[] a, int i, int j){ -    Comparable t = a[i]; -    a[i] = a[j]; -    a[j] = t; -} -``` - -### 2. 选择排序 - -找到数组中的最小元素,然后将它与数组的第一个元素交换位置。然后再从剩下的元素中找到最小的元素,将它与数组的第二个元素交换位置。不断进行这样的操作,直到将整个数组排序。 - -![](index_files/222768a7-914f-4d64-b874-d98f3b926fb6.jpg) - -```java -public class Selection { -    public static void sort(Comparable[] a) { -        int N = a.length; -        for (int i = 0; i < N; i++) { -            int min = i; -            for (int j = i + 1; j < N; j++) { -                if (less(a[j], a[min])) min = j; -            } -            exch(a, i, min); -        } -    } -} -``` - -选择排序需要 ~N2/2 次比较和 ~N 次交换,它的运行时间与输入无关,这个特点使得它对一个已经排序的数组也需要这么多的比较和交换操作。 - -### 3. 插入排序 - -将一个元素插入到已排序的数组中,使得插入之后的数组也是有序的。插入排序从左到右插入每个元素,每次插入之后左部的子数组是有序的。 - -![](index_files/065c3bbb-3ea0-4dbf-8f26-01d0e0ba7db7.png) - -```java -public class Insertion { -    public static void sort(Comparable[] a) { -        int N = a.length; -        for (int i = 1; i < N; i++) { -            for (int j = i; j > 0 && less(a[j], a[j - 1]); j--) { -                exch(a, j, j - 1); -            } -        } -    } -} -``` - -插入排序的复杂度取决于数组的初始顺序,如果数组已经部分有序了,那么插入排序会很快。平均情况下插入排序需要 ~N2/4 比较以及 ~N2/4 次交换,最坏的情况下需要 ~N2/2 比较以及 ~N2/2 次交换,最坏的情况是数组是逆序的;而最好的情况下需要 N-1 次比较和 0 次交换,最好的情况就是数组已经有序了。 - -插入排序对于部分有序数组和小规模数组特别高效。 - -### 4. 选择排序和插入排序的比较 - -对于随机排序的无重复主键的数组,插入排序和选择排序的运行时间是平方级别的,两者之比是一个较小的常数。 - -### 5. 希尔排序 - -对于大规模的数组,插入排序很慢,因为它只能交换相邻的元素,如果要把元素从一端移到另一端,就需要很多次操作。 - -希尔排序的出现就是为了改进插入排序的这种局限性,它通过交换不相邻的元素,使得元素更快的移到正确的位置上。 - -希尔排序使用插入排序对间隔 h 的序列进行排序,如果 h 很大,那么元素就能很快的移到很远的地方。通过不断减小 h,最后令 h=1,就可以使得整个数组是有序的。 - -![](index_files/8320bad6-3f91-4a15-8e3d-68e8f39649b5.png) - -```java -public class Shell { -    public static void sort(Comparable[] a) { -        int N = a.length; -        int h = 1; -        while (h < N / 3) { -            h = 3 * h + 1;// 1, 4, 13, 40, ... -        } -        while (h >= 1) { -            for (int i = h; i < N; i++) { -                for (int j = i; j >= h && less(a[j], a[j - h]); j -= h) { -                    exch(a, j, j - h); -                } -            } -            h = h / 3; -        } -    } -} -``` - -希尔排序的运行时间达不到平方级别,使用递增序列 1, 4, 13, 40, ... 的希尔排序所需要的比较次数不会超过 N 的若干倍乘于递增序列的长度。后面介绍的高级排序算法只会比希尔排序快两倍左右。 - -## 归并排序 - -归并排序的思想是将数组分成两部分,分别进行排序,然后归并起来。 - -![](index_files/dcf265ad-fe35-424d-b4b7-d149cdf239f4.png) - -### 1. 归并方法 - -```java -public class MergeSort { -    private static Comparable[] aux; - -    private static void merge(Comparable[] a, int lo, int mid, int hi) { -        int i = lo, j = mid + 1; - -        for (int k = lo; k <= hi; k++) { -            aux[k] = a[k]; // 将数据复制到辅助数组 -        } - -        for (int k = lo; k <= hi; k++) { -            if (i > mid) a[k] = aux[j++]; -            else if (j > hi) a[k] = aux[i++]; -            else if (aux[i].compareTo(a[j]) < 0) a[k] = aux[i++]; // 先进行这一步,保证稳定性 -            else a[k] = aux[j++]; -        } -    } -} -``` - -### 2. 自顶向下归并排序 - -```java -    public static void sort(Comparable[] a) { -        aux = new Comparable[a.length]; -        sort(a, 0, a.length - 1); -    } - -    private static void sort(Comparable[] a, int lo, int hi) { -        if (hi <= lo) return; -        int mid = lo + (hi - lo) / 2; -        sort(a, lo, mid); -        sort(a, mid + 1, hi); -        merge(a, lo, mid, hi); -    } -``` - -![](index_files/6468a541-3a9a-4008-82b6-03a0fe941d2a.png) - -![](index_files/c7665f73-c52f-4ce4-aed3-592bbd76265b.png) - -很容易看出该排序算法的时间复杂度为 O(NlgN)。 - -因为小数组的递归操作会过于频繁,因此使用插入排序来处理小数组将会获得更高的性能。 - -### 3. 自底向上归并排序 - -先归并那些微型数组,然后成对归并得到的子数组。 - -![](index_files/c7b9b4c8-83d1-4eb0-8408-ea6576a9ed90.png) - -```java -    public static void busort(Comparable[] a) { -        int N = a.length; -        aux = new Comparable[N]; -        for (int sz = 1; sz < N; sz += sz) { -            for (int lo = 0; lo < N - sz; lo += sz + sz) { -                merge(a, lo, lo + sz - 1, Math.min(lo + sz + sz - 1, N - 1)); -            } -        } -    } -``` - -## 快速排序 - -### 1. 基本算法 - -归并排序将数组分为两个子数组分别排序,并将有序的子数组归并使得整个数组排序;快速排序通过一个切分元素将数组分为两个子数组,左子数组小于等于切分元素,右子数组大于等于切分元素,将这两个子数组排序也就将整个数组排序了。 - -![](index_files/61b4832d-71f3-413c-84b6-237e219b9fdc.png) - -```java -public class QuickSort { -    public static void sort(Comparable[] a) { -        shuffle(a); -        sort(a, 0, a.length - 1); -    } - -    private static void sort(Comparable[] a, int lo, int hi) { -        if (hi <= lo) return; -        int j = partition(a, lo, hi); -        sort(a, lo, j - 1); -        sort(a, j + 1, hi); -    } -} -``` - -### 2. 切分 - -取 a[lo] 作为切分元素,然后从数组的左端向右扫描直到找到第一个大于等于它的元素,再从数组的右端向左扫描找到第一个小于等于它的元素,交换这两个元素,并不断继续这个过程,就可以保证左指针的左侧元素都不大于切分元素,右指针 j 的右侧元素都不小于切分元素。当两个指针相遇时,将切分元素 a[lo] 和左子数组最右侧的元素 a[j] 交换然后返回 j 即可。 - -![](index_files/e198c201-f386-4491-8ad6-f7e433bf992d.png) - -```java -    private static int partition(Comparable[] a, int lo, int hi) { -        int i = lo, j = hi + 1; -        Comparable v = a[lo]; -        while (true) { -            while (less(a[++i], v)) if (i == hi) break; -            while (less(v, a[--j])) if (j == lo) break; -            if (i >= j) break; -            exch(a, i, j); -        } -        exch(a, lo, j); -        return j; -    } -``` - -### 3. 性能分析 - -快速排序是原地排序,不需要辅助数组,但是递归调用需要辅助栈。 - -快速排序最好的情况下是每次都正好能将数组对半分,这样递归调用次数才是最少的。这种情况下比较次数为 CN=2CN/2+N,也就是复杂度为 O(NlgN)。 - -最坏的情况下,第一次从最小的元素切分,第二次从第二小的元素切分,如此这般。因此最坏的情况下需要比较 N2/2。为了防止数组最开始就是有序的,在进行快速排序时需要随机打乱数组。 - -### 4. 算法改进 - -#### 4.1 切换到插入排序 - -因为快速排序在小数组中也会调用自己,对于小数组,插入排序比快速排序的性能更好,因此在小数组中可以切换到插入排序。 - -#### 4.2 三取样 - -最好的情况下是每次都能取数组的中位数作为切分元素,但是计算中位数的代价很高。人们发现取 3 个元素并将大小居中的元素作为切分元素的效果最好。 - -#### 4.3 三向切分 - -对于有大量重复元素的数组,可以将数组切分为三部分,分别对应小于、等于和大于切分元素。 - -三向切分快速排序对于只有若干不同主键的随机数组可以在线性时间内完成排序。 - -![](index_files/9d2226dc-c4a3-40ec-9b3e-a46bf86af499.png) - -```java -public class Quick3Way { -    public static void sort(Comparable[] a, int lo, int hi) { -        if (hi <= lo) return; -        int lt = lo, i = lo + 1, gt = hi; -        Comparable v = a[lo]; -        while (i <= gt) { -            int cmp = a[i].compareTo(v); -            if (cmp < 0) exch(a, lt++, i++); -            else if (cmp > 0) exch(a, i, gt--); -            else i++; -        } -        sort(a, lo, lt - 1); -        sort(a, gt + 1, hi); -    } -} -``` - -## 优先队列 - -优先队列主要用于处理最大元素。 - -### 1. 堆 - -定义:一颗二叉树的每个节点都大于等于它的两个子节点。 - -堆可以用数组来表示,因为堆是一种完全二叉树,而完全二叉树很容易就存储在数组中。位置 k 的节点的父节点位置为 k/2,而它的两个子节点的位置分别为 2k 和 2k+1。这里我们不使用数组索引为 0 的位置,是为了更清晰地理解节点的关系。 - -![](index_files/a9b6c1db-0f4a-4e91-8ac8-6b19bd106b51.png) - -```java -public class MaxPQ { -    private Key[] pq; -    private int N = 0; - -    public MaxPQ(int maxN) { -        pq = (Key[]) new Comparable[maxN + 1]; -    } - -    public boolean isEmpty() { -        return N == 0; -    } - -    public int size() { -        return N; -    } - -    private boolean less(int i, int j) { -        return pq[i].compareTo(pq[j]) < 0; -    } - -    private void exch(int i, int j) { -        Key t = pq[i]; -        pq[i] = pq[j]; -        pq[j] = t; -    } -} -``` - -### 2. 上浮和下沉 - -在堆中,当一个节点比父节点大,那么需要交换这个两个节点。交换后还可能比它新的父节点大,因此需要不断地进行比较和交换操作。把这种操作称为上浮。 - -```java -private void swim(int k) { -    while (k > 1 && less(k / 2, k)) { -        exch(k / 2, k); -        k = k / 2; -    } -} -``` - -类似地,当一个节点比子节点来得小,也需要不断的向下比较和交换操作,把这种操作称为下沉。一个节点有两个子节点,应当与两个子节点中最大那么节点进行交换。 - -```java -private void sink(int k) { -    while (2 * k <= N) { -        int j = 2 * k; -        if (j < N && less(j, j + 1)) j++; -        if (!less(k, j)) break; -        exch(k, j); -        k = j; -    } -} -``` - -### 3. 插入元素 - -将新元素放到数组末尾,然后上浮到合适的位置。 - -```java -public void insert(Key v) { -    pq[++N] = v; -    swim(N); -} -``` - -### 4. 删除最大元素 - -从数组顶端删除最大的元素,并将数组的最后一个元素放到顶端,并让这个元素下沉到合适的位置。 - -```java -public Key delMax() { -    Key max = pq[1]; -    exch(1, N--); -    pq[N + 1] = null; -    sink(1); -    return max; -} -``` - -### 5. 堆排序 - -由于堆可以很容易得到最大的元素并删除它,不断地进行这种操作可以得到一个递减序列。如果把最大元素和当前堆中数组的最后一个元素交换位置,并且不删除它,那么就可以得到一个从尾到头的递减序列,从正向来看就是一个递增序列。因此很容易使用堆来进行排序,并且堆排序是原地排序,不占用额外空间。 - -堆排序要分两个阶段,第一个阶段是把无序数组建立一个堆;第二个阶段是交换最大元素和当前堆的数组最后一个元素,并且进行下沉操作维持堆的有序状态。 - -无序数组建立堆最直接的方法是从左到右遍历数组,然后进行上浮操作。一个更高效的方法是从右至左进行下沉操作,如果一个节点的两个节点都已经是堆有序,那么进行下沉操作可以使得这个节点为根节点的堆有序。叶子节点不需要进行下沉操作,因此可以忽略叶子节点的元素,因此只需要遍历一半的元素即可。 - -![](index_files/a2670745-a7b1-497b-90a4-dbddc4e2006d.jpg) - -```java -public static void sort(Comparable[] a){ -    int N = a.length; -    for(int k = N/2; k >= 1; k--){ -        sink(a, k, N); -    } -    while(N > 1){ -        exch(a, 1, N--); -        sink(a, 1, N); -    } -} -``` - -### 6. 分析 - -一个堆的高度为 lgN,因此在堆中插入元素和删除最大元素的复杂度都为 lgN。 - -对于堆排序,由于要对 N 个节点进行下沉操作,因此复杂度为 NlgN。 - -堆排序时一种原地排序,没有利用额外的空间。 - -现代操作系统很少使用堆排序,因为它无法利用缓存,也就是数组元素很少和相邻的元素进行比较。 - -## 应用 - -### 1. 排序算法的比较 - -![](index_files/be53c00b-2534-4dc6-ad03-c55995c47db9.jpg) - -快速排序时最快的通用排序算法,它的内循环的指令很少,而且它还能利用缓存,因为它总是顺序地访问数据。它的运行时间增长数量级为 ~cNlgN,这里的 c 比其他线性对数级别的排序算法都要小。使用三向切分之后,实际应用中可能出现的某些分布的输入能够达到线性级别,而其它排序算法仍然需要线性对数时间。 - -### 2. Java 的排序算法实现 - -Java 系统库中的主要排序方法为 java.util.Arrays.sort(),对于原始数据类型使用三向切分的快速排序,对于引用类型使用归并排序。 - -### 3. 基于切分的快速选择算法 - -快速排序的 partition() 方法,会将数组的 a[lo] 至 a[hi] 重新排序并返回一个整数 j 使得 a[lo..j-1] 小于等于 a[j],且 a[j+1..hi] 大于等于 a[j]。那么如果 j=k,a[j] 就是第 k 个数。 - -该算法是线性级别的,因为每次正好将数组二分,那么比较的总次数为 (N+N/2+N/4+..),直到找到第 k 个元素,这个和显然小于 2N。 - -```java -    public static Comparable select(Comparable[] a, int k) { -        int lo = 0, hi = a.length - 1; -        while (hi > lo) { -            int j = partion(a, lo, hi); -            if (j == k) return a[k]; -            else if (j > k) hi = j - 1; -            else lo = j + 1; -        } -        return a[k]; -    } -``` - -# 第三章 查找 - -本章使用三种经典的数据类型来实现高效的符号表:二叉查找树、红黑树和散列表。 - -## 符号表 - -### 1. 无序符号表 - -![](index_files/b69d7184-ab62-4957-ba29-fb4fa25f9b65.jpg) - -### 2. 有序符号表 - -![](index_files/ba6ae411-82da-4d86-a434-6776d1731e8e.jpg) - -有序符号表的键需要实现 Comparable 接口。 - -查找的成本模型:键的比较次数,在不进行比较时使用数组的访问次数。 - -### 3. 二分查找实现有序符号表 - -使用一对平行数组,一个存储键一个存储值。 - -需要创建一个 Key 类型的 Comparable 对象数组和一个 Value 类型的 Object 对象数组。 - -rank() 方法至关重要,当键在表中时,它能够知道该键的位置;当键不在表中时,它也能知道在何处插入新键。 - -复杂度:二分查找最多需要 lgN+1 次比较,使用二分查找实现的符号表的查找操作所需要的时间最多是对数级别的。但是插入操作需要移动数组元素,是线性级别的。 - -```java -public class BinarySearchST, Value> { -    private Key[] keys; -    private Value[] values; -    private int N; - -    public BinarySearchST(int capacity) { -        keys = (Key[]) new Comparable[capacity]; -        values = (Value[]) new Object[capacity]; -    } - -    public int size() { -        return N; -    } - -    public Value get(Key key) { -        int i = rank(key); -        if (i < N && keys[i].compareTo(key) == 0) { -            return values[i]; -        } -        return null; -    } - -    public int rank(Key key) { -        int lo = 0, hi = N - 1; -        while (lo <= hi) { -            int mid = lo + (hi - lo) / 2; -            int cmp = key.compareTo(keys[mid]); -            if (cmp == 0) return mid; -            else if (cmp < 0) hi = mid - 1; -            else lo = mid + 1; -        } -        return lo; -    } - -    public void put(Key key, Value value) { -        int i = rank(key); -        if (i < N && keys[i].compareTo(key) == 0) { -            values[i] = value; -            return; -        } -        for (int j = N; j > i; j--) { -            keys[j] = keys[j - 1]; -            values[j] = values[j - 1]; -        } -        keys[i] = key; -        values[i] = value; -        N++; -    } - -    public Key ceiling(Key key){ -        int i = rank(key); -        return keys[i]; -    } -} -``` - - -## 二叉查找树 - -定义:二叉树定义为一个空链接,或者是一个有左右两个链接的节点,每个链接都指向一颗子二叉树。二叉查找树(BST)是一颗二叉树,并且每个节点的键都大于其左子树中的任意节点的键而小于右子树的任意节点的键。 - -![](index_files/25226bb2-92cc-40cb-9e7f-c44e79fbb64a.jpg) - -二叉查找树的查找操作每次迭代都会让区间减少一半,和二分查找类似。 - -```java -public class BST, Value> { -    private Node root; - -    private class Node { -        private Key key; -        private Value val; -        private Node left, right; -        // 以该节点为根的子树中节点总数 -        private int N; - -        public Node(Key key, Value val, int N) { -            this.key = key; -            this.val = val; -            this.N = N; -        } -    } - -    public int size() { -        return size(root); -    } - -    private int size(Node x) { -        if (x == null) return 0; -        return x.N; -    } -} -``` - -### 1. get() - -如果树是空的,则查找未命中;如果被查找的键和根节点的键相等,查找命中,否则递归地在子树中查找:如果被查找的键较小就在左子树中查找,较大就在右子树中查找。 - -```java -public Value get(Key key) { -    return get(root, key); -} -private Value get(Node x, Key key) { -    if (x == null) return null; -    int cmp = key.compareTo(x.key); -    if (cmp == 0) return x.val; -    else if (cmp < 0) return get(x.left, key); -    else return get(x.right, key); -} -``` - -### 2. put() - -当插入的键不存在于树中,需要创建一个新节点,并且更新上层节点的链接使得该节点正确链接到树中。 - -```java -public void put(Key key, Value val) { -    root = put(root, key, val); -} -private Node put(Node x, Key key, Value val) { -    if (x == null) return new Node(key, val, 1); -    int cmp = key.compareTo(x.key); -    if (cmp == 0) x.val = val; -    else if (cmp < 0) x.left = put(x.left, key, val); -    else x.right = put(x.right, key, val); -    x.N = size(x.left) + size(x.right) + 1; -    return x; -} -``` - -### 3. 分析 - -二叉查找树的算法运行时间取决于树的形状,而树的形状又取决于键被插入的先后顺序。最好的情况下树是完全平衡的,每条空链接和根节点的距离都为 lgN。在最坏的情况下,树的高度为 N。 - -![](index_files/73a3983d-dd18-4373-897e-64b706a7e370.jpg) - -复杂度:查找和插入操作都为对数级别。 - -### 4. floor() - -如果 key 小于根节点的 key,那么小于等于 key 的最大键节点一定在左子树中;如果 key 大于根节点的 key,只有当根节点右子树中存在小于等于 key 的节点,小于等于 key 的最大键节点才在右子树中,否则根节点就是小于等于 key 的最大键节点。 - -```java -public Key floor(Key key) { -    Node x = floor(root, key); -    if (x == null) return null; -    return x.key; -} -private Node floor(Node x, Key key) { -    if (x == null) return null; -    int cmp = key.compareTo(x.key); -    if (cmp == 0) return x; -    if (cmp < 0) return floor(x.left, key); -    Node t = floor(x.right, key); -    if (t != null) { -        return t; -    } else { -        return x; -    } -} -``` - -### 5. rank() - -```java -public int rank(Key key) { -    return rank(key, root); -} -private int rank(Key key, Node x) { -    if (x == null) return 0; -    int cmp = key.compareTo(x.key); -    if (cmp == 0) return size(x.left); -    else if (cmp < 0) return rank(key, x.left); -    else return 1 + size(x.left) + rank(key, x.right); -} -``` - -### 6. min() - -```java -private Node min(Node x) { -    if (x.left == null) return x; -    return min(x.left); -} -``` - -### 7. deleteMin() - -令指向最小节点的链接指向最小节点的右子树。 - -![](index_files/6e2cb20a-8d2a-46fe-9ac7-68a2126b7bd5.jpg) - -```java -public void deleteMin() { -    root = deleteMin(root); -} -public Node deleteMin(Node x) { -    if (x.left == null) return x.right; -    x.left = deleteMin(x.left); -    x.N = size(x.left) + size(x.right) + 1; -    return x; -} -``` - -### 8. delete() - -如果待删除的节点只有子树,那么只需要让指向节点的链接指向唯一的子树即可;否则,让右子树的最小节点替换该节点。 - -![](index_files/b488282d-bfe0-464f-9e91-1f5b83a975bd.jpg) - -```java -public void delete(Key key) { -    root = delete(root, key); -} -private Node delete(Node x, Key key) { -    if (x == null) return null; -    int cmp = key.compareTo(x.key); -    if (cmp < 0) x.left = delete(x.left, key); -    else if (cmp > 0) x.right = delete(x.right, key); -    else { -        if (x.right == null) return x.left; -        if (x.left == null) return x.right; -        Node t = x; -        x = min(t.right); -        x.right = deleteMin(t.right); -        x.left = t.left; -    } -    x.N = size(x.left) + size(x.right) + 1; -    return x; -} -``` - -### 9. keys() - -利用二叉查找树中序遍历的结果为有序序列的特点。 - -```java -public Iterable keys(Key lo, Key hi) { -    Queue queue = new LinkedList<>(); -    keys(root, queue, lo, hi); -    return queue; -} -private void keys(Node x, Queue queue, Key lo, Key hi) { -    if (x == null) return; -    int cmpLo = lo.compareTo(x.key); -    int cmpHi = hi.compareTo(x.key); -    if (cmpLo < 0) keys(x.left, queue, lo, hi); -    if (cmpLo <= 0 && cmpHi >= 0) queue.add(x.key); -    if (cmpHi > 0) keys(x.right, queue, lo, hi); -} -``` - -### 10. 性能分析 - -复杂度:二叉查找树所有操作在最坏的情况下所需要的时间都和树的高度成正比。 - -## 平衡查找树 - -### 2-3 查找树 - -![](index_files/2548f2ec-7b00-4ec7-b286-20fc3022e084.jpg) - -一颗完美平衡的 2-3 查找树的所有空链接到根节点的距离应该是相同的。 - -#### 1. 插入操作 - -当插入之后产生一个临时 4- 节点时,需要将 4- 节点分裂成 3 个 2- 节点,并将中间的 2- 节点移到上层节点中,如果上移操作继续产生临时 4- 节点则一直进行分裂上移,直到不存在临时 4- 节点。 - -![](index_files/912174d8-0786-4222-b7ef-a611d36e5db9.jpg) - -#### 2. 性质 - -2-3 查找树插入操作的变换都是局部的,除了相关的节点和链接之外不必修改或者检查树的其它部分,而这些局部变换不会影响树的全局有序性和平衡性。 - -2-3 查找树的查找和插入操作复杂度和插入顺序无关,在最坏的情况下查找和插入操作访问的节点必然不超过 logN 个。含有 10 亿个节点的 2-3 查找树最多只需要访问 30 个节点就能进行任意的查找和插入操作。 - -### 红黑二叉查找树 - -2-3 查找树需要用到 2- 节点和 3- 节点,红黑树使用红链接来实现 3- 节点。指向一个节点的链接颜色如果为红色,那么这个节点和上层节点表示的是一个 3- 节点,而黑色则是普通链接。 - -![](index_files/7080a928-06ba-4e10-9792-b8dd190dc8e2.jpg) - -红黑树具有以下性质: - -1. 红链接都为左链接; -2. 完美黑色平衡,即任意空链接到根节点的路径上的黑链接数量相同。 - -画红黑树时可以将红链接画平。 - -![](index_files/62077f5d-a06d-4129-9b43-78715b82cb03.png) - -```java -public class RedBlackBST, Value> { -    private Node root; -    private static final boolean RED = true; -    private static final boolean BLACK = false; - -    private class Node { -        Key key; -        Value val; -        Node left, right; -        int N; -        boolean color; - -        Node(Key key, Value val, int n, boolean color) { -            this.key = key; -            this.val = val; -            N = n; -            this.color = color; -        } -    } - -    private boolean isRed(Node x) { -        if (x == null) return false; -        return x.color == RED; -    } -} -``` - -#### 1. 左旋转 - -因为合法的红链接都为左链接,如果出现右链接为红链接,那么就需要进行左旋转操作。 - -![](index_files/33a4e822-2dd0-481e-ac89-7f6161034402.jpg) - -![](index_files/5e0cef33-4087-4f21-a428-16d5fddda671.jpg) - -```java -public Node rotateLeft(Node h) { -    Node x = h.right; -    h.right = x.left; -    x.left = h; -    x.color = h.color; -    h.color = RED; -    x.N = h.N; -    h.N = 1 + size(h.left) + size(h.right); -    return x; -} -``` - -#### 2. 右旋转 - -进行右旋转是为了转换两个连续的左红链接,这会在之后的插入过程中探讨。 - -![](index_files/dfd078b2-aa4f-4c50-8319-232922d822b8.jpg) - -![](index_files/3f8d8c9d-a9a9-4d7a-813c-2de05ee5a97e.jpg) - -```java -public Node rotateRight(Node h) { -    Node x = h.left; -    h.left = x.right; -    x.color = h.color; -    h.color = RED; -    x.N = h.N; -    h.N = 1 + size(h.left) + size(h.right); -    return x; -} -``` - -#### 3. 颜色转换 - -一个 4- 节点在红黑树中表现为一个节点的左右子节点都是红色的。分裂 4- 节点除了需要将子节点的颜色由红变黑之外,同时需要将父节点的颜色由黑变红,从 2-3 树的角度看就是将中间节点移到上层节点。 - -![](index_files/de7c5a31-55f5-4e9d-92ec-4ed5b2ec3828.jpg) - -![](index_files/e5ad625e-729d-4a8d-923a-7c3df5773e1c.jpg) - -```java -void flipColors(Node h){ -    h.color = RED; -    h.left.color = BLACK; -    h.right.color = BLACK; -} -``` - -#### 4. 插入 - -插入算法: - -- 如果右子节点是红色的而左子节点是黑色的,进行左旋转; -- 如果左子节点是红色的且它的左子节点也是红色的,进行右旋转; -- 如果左右子节点均为红色的,进行颜色转换。 - -![](index_files/40639782-5df2-4e96-a4f3-f9dd664d0ca1.jpg) - -```java -public void put(Key key, Value val) { -    root = put(root, key, val); -    root.color = BLACK; -} - -private Node put(Node x, Key key, Value val) { -    if (x == null) return new Node(key, val, 1, RED); -    int cmp = key.compareTo(x.key); -    if (cmp == 0) x.val = val; -    else if (cmp < 0) x.left = put(x.left, key, val); -    else x.right = put(x.right, key, val); - -    if (isRed(x.right) && !isRed(x.left)) x = rotateLeft(x); -    if (isRed(x.left) && isRed(x.left.left)) x = rotateRight(x); -    if (isRed(x.left) && isRed(x.right)) flipColors(x); - -    x.N = size(x.left) + size(x.right) + 1; -    return x; -} -``` - -可以看到该插入操作和 BST 的插入操作类似,只是在最后加入了旋转和颜色变换操作即可。 - -根节点一定为黑色,因为根节点没有上层节点,也就没有上层节点的左链接指向根节点。flipColors() 有可能会使得根节点的颜色变为红色,每当根节点由红色变成黑色时树的黑链接高度加 1. - -#### 5. 删除最小键 - -如果最小键在一个 2- 节点中,那么删除该键会留下一个空链接,就破坏了平衡性,因此要确保最小键不在 2- 节点中。将 2- 节点转换成 3- 节点或者 4- 节点有两种方法,一种是向上层节点拿一个 key,或者向兄弟节点拿一个 key。如果上层节点是 2- 节点,那么就没办法从上层节点拿 key 了,因此要保证删除路径上的所有节点都不是 2- 节点。在向下删除的过程中,保证以下情况之一发生: - -1. 如果当前节点的左子节点不是 2- 节点,完成; -2. 如果当前节点的左子节点是 2- 节点而它的兄弟节点不是 2- 节点,向兄弟节点拿一个 key 过来; -3. 如果当前节点的左子节点和它的兄弟节点都是 2- 节点,将左子节点、父节点中的最小键和最近的兄弟节点合并为一个 4- 节点。 - -![](index_files/b001fa64-307c-49af-b4b2-2043fc26154e.png) - -最后得到一个含有最小键的 3- 节点或者 4- 节点,直接从中删除。然后再从头分解所有临时的 4- 节点。 - -![](index_files/70b66757-755c-4e17-a7b7-5ce808023643.png) - -#### 6. 分析 - -一颗大小为 N 的红黑树的高度不会超过 2lgN。最坏的情况下是它所对应的 2-3 树中构成最左边的路径节点全部都是 3- 节点而其余都是 2- 节点。 - -红黑树大多数的操作所需要的时间都是对数级别的。 - -## 散列表 - -散列表类似于数组,可以把散列表的散列值看成数组的索引值。访问散列表和访问数组元素一样快速,它可以在常数时间内实现查找和插入的符号表。 - -由于无法通过散列值知道键的大小关系,因此散列表无法实现有序性操作。 - -### 散列函数 - -对于一个大小为 M 的散列表,散列函数能够把任意键转换为 [0, M-1] 内的正整数,该正整数即为 hash 值。 - -散列表有冲突的存在,也就是两个不同的键可能有相同的 hash 值。 - -散列函数应该满足以下三个条件: - -1. 一致性:相等的键应当有相等的 hash 值。 -2. 高效性:计算应当简便,有必要的话可以把 hash 值缓存起来,在调用 hash 函数时直接返回。 -3. 均匀性:所有键的 hash 值应当均匀地分布到 [0, M-1] 之间,这个条件至关重要,直接影响到散列表的性能。 - -除留余数法可以将整数散列到 [0, M-1] 之间,例如一个正整数 k,计算 k%M 既可得到一个 [0, M-1] 之间的 hash 值。注意 M 必须是一个素数,否则无法利用键包含的所有信息。例如 M 为 10k,那么只能利用键的后 k 位。 - -对于其它数,可以将其转换成整数的形式,然后利用除留余数法。例如对于浮点数,可以将其表示成二进制形式,然后使用二进制形式的整数值进行除留余数法。 - -对于有多部分组合的键,每部分都需要计算 hash 值,并且最后合并时需要让每部分 hash 值都具有同等重要的地位。可以将该键看成 R 进制的整数,键中每部分都具有不同的权值。 - -例如,字符串的散列函数实现如下 - -```java -int hash = 0; -for(int i = 0; i < s.length(); i++) -    hash = (R * hash + s.charAt(i)) % M; -``` - -再比如,拥有多个成员的自定义类的哈希函数如下 - -```java -int hash = (((day * R + month) % M) * R + year) % M; -``` - -R 的值不是很重要,通常取 31。 - -Java 中的 hashCode() 实现了 hash 函数,但是默认使用对象的内存地址值。在使用 hashCode() 函数时,应当结合除留余数法来使用。因为内存地址是 32 位整数,我们只需要 31 位的非负整数,因此应当屏蔽符号位之后再使用除留余数法。 - -```java -int hash = (x.hashCode() & 0x7fffffff) % M; -``` - -使用 Java 自带的 HashMap 等自带的哈希表实现时,只需要去实现 Key 类型的 hashCode() 函数即可,因此也就不需要考虑 M 的大小等。Java 规定 hashCode() 能够将键均匀分布于所有的 32 位整数,Java 中的 String、Integer 等对象的 hashCode() 都能实现这一点。以下展示了自定义类型如何实现 hashCode()。 - -```java -public class Transaction{ -    private final String who; -    private final Date when; -    private final double amount; - -    public int hashCode(){ -        int hash = 17; -        hash = 31 * hash + who.hashCode(); -        hash = 31 * hash + when.hashCode(); -        hash = 31 * hash + ((Double) amount).hashCode(); -        return hash; -    } -} -``` - -### 基于拉链法的散列表 - -拉链法使用链表来存储 hash 值相同的键,从而解决冲突。此时查找需要分两步,首先查找 Key 所在的链表,然后在链表中顺序查找。 - -![](index_files/540133af-aaaf-4208-8f7f-33cb89ac9621.png) - -对于 N 个键,M 条链表 (N>M),如果哈希函数能够满足均匀性的条件,每条链表的大小趋向于 N/M,因此未命中的查找和插入操作所需要的比较次数为 ~N/M。 - -### 基于线性探测法的散列表 - -线性探测法使用空位来解决冲突,当冲突发生时,向前探测一个空位来存储冲突的键。使用线程探测法,数组的大小 M 应当大于键的个数 N(M>N)。 - -![](index_files/2b3410f1-9559-4dd1-bc3d-e3e572247be2.png) - -```java -public class LinearProbingHashST { -    private int N; -    private int M = 16; -    private Key[] keys; -    private Value[] vals; - -    public LinearProbingHashST() { -        init(); -    } - -    public LinearProbingHashST(int M) { -        this.M = M; -        init(); -    } - -    private void init() { -        keys = (Key[]) new Object[M]; -        vals = (Value[]) new Object[M]; -    } - -    private int hash(Key key) { -        return (key.hashCode() & 0x7fffffff) % M; -    } -} -``` - -#### 查找 - -```java -public Value get(Key key) { -    for (int i = hash(key); keys[i] != null; i = (i + 1) % M) { -        if (keys[i].equals(key)) { -            return vals[i]; -        } -    } -    return null; -} -``` - -#### 插入 - -```java -public void put(Key key, Value val) { -    int i; -    for (i = hash(key); keys[i] != null; i = (i + 1) % M) { -        if (keys[i].equals(key)) { -            vals[i] = val; -            return; -        } -    } -    keys[i] = key; -    vals[i] = val; -    N++; -    resize(); -} -``` - -#### 删除 - -删除操作应当将右侧所有相邻的键值重新插入散列表中。 - -```java -public void delete(Key key) { -    if (!contains(key)) return; -    int i = hash(key); -    while (!key.equals(keys[i])) { -        i = (i + 1) % M; -    } -    keys[i] = null; -    vals[i] = null; -    i = (i + 1) % M; -    while (keys[i] != null) { -        Key keyToRedo = keys[i]; -        Value valToRedo = vals[i]; -        keys[i] = null; -        vals[i] = null; -        N--; -        put(keyToRedo, valToRedo); -        i = (i + 1) % M; -    } -    N--; -    resize(); -} -``` - -#### 调整数组大小 - -线性探测法的成本取决于连续条目的长度,连续条目也叫聚簇。当聚簇很长时,在查找和插入时也需要进行很多次探测。 - -α = N/M,把 α 称为利用率。理论证明,当 α 小于 1/2 时探测的预计次数只在 1.5 到 2.5 之间。 - -![](index_files/0ddebc5c-7c24-46b1-98db-4fa5e54db16b.png) - -为了保证散列表的性能,应当调整数组的大小,使得 α 在 [1/4, 1/2] 之间。 - -```java -private void resize() { -    if (N >= M / 2) resize(2 * M); -    else if (N <= M / 8) resize(M / 2); -} - -private void resize(int cap) { -    LinearProbingHashST t = new LinearProbingHashST<>(cap); -    for (int i = 0; i < M; i++) { -        if (keys[i] != null) { -            t.put(keys[i], vals[i]); -        } -    } -    keys = t.keys; -    vals = t.vals; -    M = t.M; -} -``` - -虽然每次重新调整数组都需要重新把每个键值对插入到散列表,但是从摊还分析的角度来看,所需要的代价却是很小的。从下图可以看出,每次数组长度加倍后,累计平均值都会增加 1,因为表中每个键都需要重新计算散列值,但是随后平均值会下降。 - -![](index_files/01658047-0d86-4a7a-a8ca-7ea20fa1fdde.png) - -## 应用 - -### 各种符号表实现的比较 - -![](index_files/9ee83c8c-1165-476c-85a6-e6e434e5307a.jpg) - -应当优先考虑散列表,当需要有序性操作时使用红黑树。 - -### Java 的符号表实现 - -Java 的 java.util.TreeMap 和 java.util.HashMap 分别是基于红黑树和拉链法的散列表的符号表实现。 - -### 集合类型 - -除了符号表,集合类型也经常使用,它只有键没有值,可以用集合类型来存储一系列的键然后判断一个键是否在集合中。 - -### 稀疏向量乘法 - -向量运算涉及到 N 次乘法,当向量为稀疏向量时,可以使用符号表来存储向量中的非 0 索引和值,使得乘法运算只需要对那些非 0 元素进行即可。 - -```java -import java.util.HashMap; - -public class SparseVector { -    private HashMap hashMap; - -    public SparseVector(double[] vector) { -        hashMap = new HashMap<>(); -        for (int i = 0; i < vector.length; i++) { -            if (vector[i] != 0) { -                hashMap.put(i, vector[i]); -            } -        } -    } - -    public double get(int i) { -        return hashMap.getOrDefault(i, 0.0); -    } - -    public double dot(SparseVector other) { -        double sum = 0; -        for (int i : hashMap.keySet()) { -            sum += this.get(i) * other.get(i); -        } -        return sum; -    } -} -``` \ No newline at end of file diff --git a/notes/笔记/编写可读代码的艺术.md.txt b/notes/笔记/编写可读代码的艺术.md.txt deleted file mode 100644 index aeddb05d..00000000 --- a/notes/笔记/编写可读代码的艺术.md.txt +++ /dev/null @@ -1,328 +0,0 @@ -[TOC] - -# 第 1 章 可读性的重要性 - -编程有很大一部分时间是在阅读代码,不仅要阅读自己的代码,而且要阅读别人的代码。因此,可读性良好的代码能够大大提高编程效率。 - -可读性良好的代码往往会让代码架构更好,因为程序员更愿意去修改这部分代码,而且也更容易修改。 - -只有在核心领域为了效率才可以放弃可读性,否则可读性是第一位。 - -# 第 2 章 用名字表达代码含义 - -一些比较有表达力的单词: - -|  单词 |  可替代单词 | -| --- | --- | -|  send | deliver、dispatch、announce、distribute、route  | -| find  |  search、extract、locate、recover | -| start| launch、create、begin、open| -|make|create、set up、build、generate、compose、add、new| - -使用 i、j、k 作为循环迭代器不总是好的,为迭代器添加更有表达力的名字会更好,比如 user_i、member_i。因为循环层次越多,代码越难理解,有表达力的迭代器名字可读性会更高 - -为名字添加形容词等信息能让名字更具有表达力,但是名字也会变长。名字长短的准则是:作用域越大,名字越长。因此只有在短作用域才能使用一些简单名字。 - -# 第 3 章 名字不能带来歧义 - -起完名字要思考一下别人会对这个名字有何解读,会不会误解了原本想表达的含义。 - -用 min、max 表示数量范围; - -用 first、last 表示访问空间的包含范围,begin、end 表示访问空间的排除范围,即 end 不包含尾部。 - -![](index_files/26772ecc-a3e3-4ab7-a46f-8b4656990c27.jpg) - -布尔相关的命名加上 is、can、should、has 等前缀。 - -# 第 4 章 良好的代码风格 - -适当的空行和缩进 - -排列整齐的注释: - -``` -int a = 1;   // 注释 -int b = 11;  // 注释 -int c = 111; // 注释 -``` - -语句顺序不能随意,比如与 html 表单相关联的变量的赋值应该和表单在 html 中的顺序一致; - -把相关的代码按块组织放在一起。 - -# 第 5 章 编写注释 - -阅读代码首先会注意到注释,如果注释没太大作用,那么就会浪费代码阅读的时间。那些能直接看出含义的代码不需要写注释,特别是并不需要为每个方法都加上注释,比如那些简单的 getter 和 setter 方法,为这些方法写注释反而让代码可读性更差。 - -不能因为有注释就随便起个名字,而是争取起个好名字而不写注释。 - -可以用注释来记录采用当前解决办法的思考过程,从而让读者更容易理解代码。 - -注释用来提醒一些特殊情况。 - -用 TODO 等做标记: - -| 标记 | 用法 | -|---|---| -|TODO| 待做 | -|FIXME| 待修复 | -|HACH| 粗糙的解决方案 | -|XXX| 危险!这里有重要的问题 | - -# 第 6 章 如何编写注释 - -尽量简洁明了: - -``` -// The first String is student's name -// The Second Integer is student's score -Map scoreMap = new HashMap<>(); -``` - -``` -// Student' name -> Student's score -Map scoreMap = new HashMap<>(); -``` - -添加测试用例来说明: - -``` -//... -// Example: add(1, 2), return 3 -int add(int x, int y) { -    return x + y; -} -``` - -在很复杂的函数调用中对每个参数标上名字: - -``` -int a = 1; -int b = 2; -int num = add(\* x = *\ a, \* y = *\ b); -``` - -使用专业名词来缩短概念上的解释,比如用设计模式名来说明代码。 - -# 第 7 章 提高控制流的可读性 - -条件表达式中,左侧是变量,右侧是常数。比如下面第一个语句正确: - -``` -if(len < 10) -if(10 > len) -``` - -if / else 条件语句,先处理以下逻辑:① 正逻辑;② 关键逻辑;③:简单逻辑 -``` -if(a == b) { -    // 正逻辑 -} else{ -    // 反逻辑 -} -``` - -只有在逻辑简单的情况下使用 ? : 三目运算符来使代码更紧凑,否则应该拆分成 if / else; - -do / while 的条件放在后面,不够简单明了,并且会有一些迷惑的地方,最好使用 while 来代替。 - -如果只有一个 goto 目标,那么 goto 尚且还能接受,但是过于复杂的 goto 会让代码可读性特别差,应该避免使用 goto。 - -在嵌套的循环中,用一些 return 语句往往能减少嵌套的层数。 - -# 第 8 章 拆分长表达式 - -长表达式的可读性很差,可以引入一些解释变量从而拆分表达式: - -``` -if line.split(':')[0].strip() == "root": -    ... -``` -``` -username = line.split(':')[0].strip() -if username == "root": -    ... -``` - -使用摩根定理简化一些逻辑表达式: - -``` -if(!a && !b) { -    ... -} -``` -``` -if(a || b) { -    ... -} -``` - -# 第 9 章 变量与可读性 - -去除控制流变量。在循环中通过 break 或者 return 可以减少控制流变量的使用。 - -``` -boolean done = false; -while(/* condition */ && !done) { -    ... -    if(...) { -        done = true; -        continue; -    } -} -``` -``` -while(/* condition */) { -    ... -    if(...) { -        break; -    } -} -``` - -减小变量作用域。作用域越小,越容易定位到变量所有使用的地方。 - -JavaScript 可以用闭包减小作用域。以下代码中 submit_form 是函数变量,submitted 变量控制函数不会被提交两次。第一个实现中 submitted 是全局变量,第二个实现把 submitted 放到匿名函数中,从而限制了起作用域范围。 - -``` -submitted = false; -var submit_form = function(form_name) { -    if(submitted) { -        return; -    } -    submitted = true; -}; -``` - -``` -var submit_form = (function() { -    var submitted = false; -    return function(form_name) { -        if(submitted) { -            return; -        } -        submitted = true; -    } -}());  // () 使得外层匿名函数立即执行 -``` - -JavaScript 中没有用 var 声明的变量都是全局变量,而全局变量很容易造成迷惑,因此应当总是用 var 来声明变量。 - -变量定义的位置应当离它使用的位置最近。 - -**实例解析** - -在一个网页中有以下文本输入字段: - -``` - - - - -``` - -现在要接受一个字符串并把它放到第一个空的 input 字段中,初始实现如下: - -``` -var setFirstEmptyInput = function(new_alue) { -    var found = false; -    var i = 1; -    var elem = document.getElementById('input' + i); -    while(elem != null) { -        if(elem.value === '') { -            found = true; -            break; -        } -        i++; -        elem = document.getElementById('input' + i); -    } -    if(found) elem.value = new_value; -    return elem; -} -``` - -以上实现有以下问题: - -- found 可以去除; -- elem 作用域过大; -- 可以用 for 循环代替 while 循环; - -``` -var setFirstEmptyInput = function(new_value) { -    for(var i = 1; true; i++) { -        var elem = document.getElementById('input' + i); -        if(elem === null) { -            return null; -        } -        if(elem.value === '') { -            elem.value = new_value; -            return elem; -        } -    } -}; -``` - -# 第 10 章 抽取函数 - -工程学就是把大问题拆分成小问题再把这些问题的解决方案放回一起。 - -首先应该明确一个函数的高层次目标,然后对于不是直接为了这个目标工作的代码,抽取出来放到独立的函数中。 - -介绍性的代码: - -``` -int findClostElement(int[] arr) { -    int clostIdx; -    int clostDist = Interger.MAX_VALUE; -    for(int i = 0; i < arr.length; i++) { -        int x = ...; -        int y = ...; -        int z = ...; -        int value = x * y * z; -        int dist = Math.sqrt(Math.pow(value, 2), Math.pow(arr[i], 2)); -        if(dist < clostDist) { -            clostIdx = i; -            clostDist = value; -        } -    } -    return clostIdx; -} -``` - -以上代码中循环部分主要计算距离,这部分不属于代码高层次目标,高层次目标是寻找最小距离的值,因此可以把这部分代替提取到独立的函数中。这样做也带来一个额外的好处有:可以单独进行测试、可以快速找到程序错误并修改。 - -``` -public int findClostElement(int[] arr) { -    int clostIdx; -    int clostDist = Interger.MAX_VALUE; -    for(int i = 0; i < arr.length; i++) { -        int dist = computDist(arr, i); -        if(dist < clostDist) { -            clostIdx = i; -            clostDist = value; -        } -    } -    return clostIdx; -} -``` - -并不是函数抽取的越多越好,如果抽取过多,在阅读代码的时候可能需要不断跳来跳去。只有在当前函数不需要去了解某一块代码细节而能够表达其内容时,把这块代码抽取成子函数才是好的。 - -函数抽取也用于减小代码的冗余。 - -# 第 11 章 一次只做一件事 - -只做一件事的代码很容易让人知道其要做的事; - -基本流程:列出代码所做的所有任务;把每个任务拆分到不同的函数,或者不同的段落。 - -# 第 12 章 用自然语言表述代码 - -先用自然语言书写代码逻辑,也就是伪代码,然后再写代码,这样代码逻辑会更清晰。 - -# 第 13 章 减少代码量 - -不要过度设计,编码过程会有很多变化,过度设计的内容到最后往往是无用的。 - -多用标准库实现。 \ No newline at end of file diff --git a/notes/笔记/计算机操作系统+.md.txt b/notes/笔记/计算机操作系统+.md.txt deleted file mode 100644 index 6133ed8c..00000000 --- a/notes/笔记/计算机操作系统+.md.txt +++ /dev/null @@ -1,10 +0,0 @@ -[TOC] - -# 链接 - -# IO 多路复用 - -http://www.hollischuang.com/archives/1830 - -http://www.cnblogs.com/Anker/p/3265058.html - diff --git a/notes/笔记/计算机操作系统.md.txt b/notes/笔记/计算机操作系统.md.txt deleted file mode 100644 index 0a3cc697..00000000 --- a/notes/笔记/计算机操作系统.md.txt +++ /dev/null @@ -1,657 +0,0 @@ -[TOC] - -# 第一章 概述 - -## 操作系统基本特征 - -### 1. 并发 - -并发性是指宏观上在一段时间内能同时运行多个程序,而并行性则指同一时刻能运行多个指令。 - -并行需要硬件支持,如多流水线或者多处理器。 - -操作系统通过引入进程和线程,使得程序能够并发运行。 - -### 2. 共享 - -共享是指系统中的资源可以供多个并发的进程共同使用。 - -有两种共享方式:互斥共享和同时共享。 - -互斥共享的资源称为临界资源,例如打印机等,在同一时间只允许一个进程访问,否则会出现错误,需要用同步机制来实现对临界资源的访问。 - -### 3. 虚拟 - -虚拟技术把一个物理实体转换为多个逻辑实体。主要有两种虚拟技术:时分复用技术和空分复用技术,例如多个进程能在同一个处理器上并发执行使用了时分复用技术,让每个进程轮流占有处理器,每次只执行一小个时间片并快速切换,这样就好像有多个处理器进行处理。 - -### 4. 异步 - -异步是指进程不是一次性执行完毕,而是走走停停,以不可知的速度向前推进。 - -## 系统调用 - -如果一个进程在用户态需要用到操作系统的一些功能,就需要使用系统调用从而陷入内核,由操作系统代为完成。 - -可以由系统调用请求的功能有设备管理、文件管理、进程管理、进程通信、存储器管理等。 - -## 中断分类 - -### 1. 外中断 - -由 CPU 执行指令以外的事件引起,如 I/O 结束中断,表示设备输入/输出处理已经完成,处理器能够发送下一个输入/输出请求。此外还有时钟中断、控制台中断等。 - -### 2. 异常 - -由 CPU 执行指令的内部事件引起,如非法操作码、地址越界、算术溢出等。 - -### 3. 陷入 - -在用户程序中使用系统调用。 - -## 大内核和微内核 - -### 1. 大内核 - -大内核是将操作系统功能作为一个紧密结合的整体放到内核,由于各模块共享信息,因此有很高的性能。 - -### 2. 微内核 - -由于操作系统不断复杂,因此将一部分操作系统功能移出内核,从而降低内核的复杂性。移出的部分根据分层的原则划分成若干服务,相互独立。但是需要频繁地在用户态和核心态之间进行切换,会有一定的性能损失。 - -# 第二章 进程管理 - -## 进程与线程 - -### 1. 进程 - -进程是操作系统进行资源分配的基本单位。 - -进程控制块 (Process Control Block, PCB) 描述进程的基本信息和运行状态,所谓的创建进程和撤销进程,都是指对 PCB 的操作。 - -### 2. 线程 - -一个线程中可以有多个线程,是独立调度的基本单位。同一个进程中的多个线程之间可以并发执行,它们共享进程资源。 - -### 3. 区别 - -① 拥有资源:进程是资源分配的基本单位,但是线程不拥有资源,线程可以访问率属进程的资源。 - -② 调度:线程是独立调度的基本单位,在同一进程中,线程的切换不会引起进程切换,从一个进程内的线程切换到另一个进程中的线程时,会引起进程切换。 - -③ 系统开销:由于创建或撤销进程时,系统都要为之分配或回收资源,如内存空间、I/O 设备等,因此操作系统所付出的开销远大于创建或撤销线程时的开销。类似地,在进行进程切换时,涉及当前执行进程 CPU 环境的保存及新调度进程 CPU 环境的设置。而线程切换时只需保存和设置少量寄存器内容,开销很小。此外,由于同一进程内的多个线程共享进程的地址空间,因此,这些线程之间的同步与通信非常容易实现,甚至无需操作系统的干预。 - -④ 通信方面:进程间通信 (IPC) 需要进程同步和互斥手段的辅助,以保证数据的一致性,而线程间可以通过直接读/写进程数据段(如全局变量)来进行通信。 - -举例:QQ 和 浏览器是两个进程,浏览器进程里面有很多线程,例如 http 请求线程、事件响应线程、渲染线程等等,线程的并发执行使得在浏览器中点击一个新链接从而发起 http 请求时,浏览器还可以响应用户的其它事件。 - -## 进程状态的切换 - -![](index_files/1706ce58-a081-4fed-9b36-c3c0d7e22b3a.jpg) - -阻塞状态是缺少需要的资源从而由运行状态转换而来,但是该资源不包括 CPU,缺少 CPU 会让进程从运行态转换为就绪态。 - -只有就绪态和运行态可以相互转换,其它的都是单向转换。就绪状态的进程通过调度算法从而获得 CPU 时间,转为运行状态;而运行状态的进程,在分配给它的 CPU 时间片用完之后就会转为就绪状态,等待下一次调度。 - -## 调度算法 - -需要针对不同环境来讨论调度算法。 - -### 1. 批处理系统中的调度 - -#### 1.1 先来先服务(FCFS) - -first-come first-serverd。 - -调度最先进入就绪队列的作业。 - -有利于长作业,但不利于短作业,因为短作业必须一直等待前面的长作业执行完毕才能执行,而长作业又需要执行很长时间,造成了短作业等待时间过长。 - -#### 1.2 短作业优先(SJF) - -shortest job first。 - -调度估计运行时间最短的作业。 - -长作业有可能会饿死,处于一直等待短作业执行完毕的状态。如果一直有短作业到来,那么长作业永远得不到调度。 - -#### 1.3 最短剩余时间优先(SRTN) - -shortest remaining time next。 - -### 2. 交互式系统中的调度 - -#### 2.1 优先权优先 - -除了可以手动赋予优先权之外,还可以把响应比作为优先权,这种调度方式叫做高响应比优先调度算法。 - -响应比 = (等待时间 + 要求服务时间) / 要求服务时间 = 响应时间 / 要求服务时间 - -这种调度算法主要是为了解决 SJF 中长作业可能会饿死的问题,因为随着等待时间的增长,响应比也会越来越高。 - -#### 2.2 时间片轮转 - -将所有就绪进程按 FCFS 的原则排成一个队列,每次调度时,把 CPU 分配给队首进程,该进程可以执行一个时间片。当时间片用完时,由计时器发出时钟中断,调度程序便停止该进程的执行,并将它送往就绪队列的末尾,同时继续把 CPU 分配给队首的进程。 - -时间片轮转算法的效率和时间片有很大关系。因为每次进程切换都要保存进程的信息并且载入新进程的信息,如果时间片太短,进程切换太频繁,在进程切换上就会花过多时间。 - -#### 2.3 多级反馈队列 - -![](index_files/042cf928-3c8e-4815-ae9c-f2780202c68f.png) - -① 设置多个就绪队列,并为各个队列赋予不同的优先级。第一个队列的优先级最高,第二个队列次之,其余各队列的优先权逐个降低。该算法赋予各个队列中进程执行时间片的大小也各不相同,在优先权越高的队列中,为每个进程所规定的执行时间片就越小。 - -② 当一个新进程进入内存后,首先将它放入第一队列的末尾,按 FCFS 原则排队等待调度。当轮到该进程执行时,如它能在该时间片内完成,便可准备撤离系统;如果它在一个时间片结束时尚未完成,调度程序便将该进程转入下一个队列的队尾。 - -③ 仅当前 i -1 个队列均空时,才会调度第 i 队列中的进程运行。 - -优点:实时性好,也适合运行短作业和长作业。 - -#### 2.4 短进程优先 - -### 3. 实时系统中的调度 - -实时系统要一个服务请求在一个确定时间内得到响应。 - -分为硬实时和软实时,前者必须满足绝对的截止时间,后者可以容忍一定的超时。 - -## 进程同步 - -### 1. 临界区 - -对临界资源进行访问的那段代码称为临界区。 - -为了互斥访问临界资源,每个进程在进入临界区之前,需要先进行检查。 - -```html -// entry section -// critical section; -// exit section -``` - -### 2. 同步与互斥 - -同步指多个进程按一定顺序执行;互斥指多个进程在同一时刻只有一个进程能进入临界区。 - -同步是在对临界区互斥访问的基础上,通过其它机制来实现有序访问的。 - -### 3. 信号量 - -**信号量(Samaphore)**是一个整型变量,可以对其执行 down 和 up 操作,也就是常见的 P 和 V 操作。 - -- **down** : 如果信号量大于 0 ,执行 - 1 操作;如果信号量等于 0,将进程睡眠,等待信号量大于 0; -- **up**:对信号量执行 + 1 操作,并且唤醒睡眠的进程,让进程完成 down 操作。 - -down 和 up 操作需要被设计成原语,不可分割,通常的做法是在执行这些操作的时候屏蔽中断。 - -如果信号量的取值只能为 0 或者 1,那么就成为了**互斥量(Mutex)**,0 表示临界区已经加锁,1 表示临界区解锁。 - -```c -typedef int samaphore; -samaphore mutex = 1; -void P1() { -    down(mutex); -    // 临界区 -    up(mutex); -} - -void P2() { -    down(mutex); -    // 临界区 -    up(mutex); -} -``` - -**使用信号量实现生产者-消费者问题** - -使用一个互斥量 mutex 来对临界资源进行访问;empty 记录空缓冲区的数量,full 记录满缓冲区的数量。 - -注意,必须先执行 down 操作再用互斥量对临界区加锁,否则会出现死锁。如果都先对临界区加锁,然后再执行 down 操作,考虑这种情况:生产者对临界区加锁后,执行 down(empty) 操作,发现 empty = 0,此时生成者睡眠。消费者此时不能进入临界区,因为生产者对临界区加锁了,也就无法对执行 up(empty) 操作,那么生产者和消费者就会一直等待下去。 - -```c -#define N 100 -typedef int samaphore; -samaphore mutex = 1; -samaphore empty = N; -samaphore full = 0; - -void producer() { -    while(TRUE){ -        int item = produce_item; -        down(empty); -        down(mutex); -        insert_item(item); -        up(mutex); -        up(full); -    } -} - -void consumer() { -    while(TRUE){ -        down(full); -        down(mutex); -        int item = remove_item(item); -        up(mutex); -        up(empty); -        consume_item(item); -    } -} -``` - -### 4. 管程 - -使用信号量机制实现的生产者消费者问题需要客户端代码做很多控制,而管程把控制的代码独立出来,不仅不容易出错,也使得客户端代码调用更容易。 - -c 语言不支持管程,下面的示例代码使用了类 Pascal 语言来描述管程。示例代码中的管程提供了 insert() 和 remove() 方法,客户端代码通过调用这两个方法来解决生产者-消费者问题。 - -```html -monitor ProducerConsumer -    integer i; -    condition c; -     -    procedure insert(); -    begin -     -    end; -     -    procedure remove(); -    begin -     -    end; -end monitor; -``` - -管程有一个重要特性:在一个时刻只能有一个进程使用管程。进程在无法继续执行的时候不能一直占用管程,必须将进程阻塞,否者其它进程永远不能使用管程。 - -管程引入了 **条件变量** 以及相关的操作:**wait()** 和 **signal()** 来实现同步操作。对条件变量执行 wait() 操作会导致调用进程阻塞,把管程让出来让另一个进程持有。signal() 操作用于唤醒被阻塞的进程。 - -**使用管程实现生成者-消费者问题** - -```html -monitor ProducerConsumer -    condition full, empty; -    integer count := 0; -    condition c; - -    procedure insert(item: integer); -    begin -        if count = N then wait(full); -        insert_item(item); -        count := count + 1; -        if count = 1 ten signal(empty); -    end; - -    function remove: integer; -    begin -        if count = 0 then wait(empty); -        remove = remove_item; -        count := count - 1; -        if count = N -1 then signal(full); -    end; -end monitor; - -procedure producer -begin -    while true do -    begin -        item = produce_item; -        ProducerConsumer.insert(item); -    end -end; - -procedure consumer -begin -    while true do -    begin -        item = ProducerConsumer.remove; -        consume_item(item); -    end -end; -``` - -## 进程通信 - -进程通信可以看成是不同进程间的线程通信,对于同一个进程内线程的通信方式,主要使用信号量、条件变量等同步机制。 - -### 1. 管道 - -管道是单向的、先进先出的、无结构的、固定大小的字节流,它把一个进程的标准输出和另一个进程的标准输入连接在一起。写进程在管道的尾端写入数据,读进程在管道的首端读出数据。数据读出后将从管道中移走,其它读进程都不能再读到这些数据。 - -管道提供了简单的流控制机制,进程试图读空管道时,在有数据写入管道前,进程将一直阻塞。同样地,管道已经满时,进程再试图写管道,在其它进程从管道中移走数据之前,写进程将一直阻塞。 - -Linux 中管道是通过空文件来实现。 - -管道有三种: - -① 普通管道:有两个限制:一是只支持半双工通信方式,即只能单向传输;二是只能在父子进程之间使用; - -② 流管道:去除第一个限制,支持双向传输; - -③ 命名管道:去除第二个限制,可以在不相关进程之间进行通信。 - -### 2. 信号量 - -信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其它进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。 - -### 3. 消息队列 - -消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。 - -### 4. 信号 - -信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。 - -### 5. 共享内存 - -共享内存就是映射一段能被其它进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其它进程间通信方式运行效率低而专门设计的。它往往与其它通信机制(如信号量)配合使用,来实现进程间的同步和通信。 - -### 6. 套接字 - -套接字也是一种进程间通信机制,与其它通信机制不同的是,它可用于不同机器间的进程通信。 - -## 经典同步问题 - -生产者和消费者问题前面已经讨论过。 - -### 1. 读者-写者问题 - -允许多个进程同时对数据进行读操作,但是不允许读和写以及写和写操作同时发生。 - -一个整型变量 count 记录在对数据进行读操作的进程数量,一个互斥量 count_mutex 用于对 count 加锁,一个互斥量 data_mutex 用于对读写的数据加锁。 - -```c -typedef int semaphore; -semaphore count_mutex = 1; -semaphore data_mutex = 1; -int count = 0; - -void reader() { -    while(TRUE) { -        down(count_mutex); -        count++; -        if(count == 1) down(data_mutex); // 第一个读者需要对数据进行加锁,防止写进程访问 -        up(count_mutex); -        read(); -        down(count_mutex); -        count--; -        if(count == 0) up(data_mutex); -        up(count_mutex); -    } -} - -void writer() { -    while(TRUE) { -        down(data_mutex); -        write(); -        up(data_mutex); -    } -} -``` - -### 2. 哲学家进餐问题 - -![](index_files/a9077f06-7584-4f2b-8c20-3a8e46928820.jpg) - -五个哲学家围着一张圆周,每个哲学家面前放着饭。哲学家的生活有两种交替活动:吃饭以及思考。当一个哲学家吃饭时,需要先一根一根拿起左右两边的筷子。 - -下面是一种错误的解法,考虑到如果每个哲学家同时拿起左手边的筷子,那么就无法拿起右手边的筷子,造成死锁。 - -```c -#define N 5 -#define LEFT (i + N - 1) % N -#define RIGHT (i + N) % N -typedef int semaphore; -semaphore chopstick[N]; - -void philosopher(int i) { -    while(TURE){ -        think(); -        down(chopstick[LEFT[i]]); -        down(chopstick[RIGHT[i]]); -        eat(); -        up(chopstick[RIGHT[i]]); -        up(chopstick[LEFT[i]]); -    } -} -``` - -为了防止死锁的发生,可以加一点限制,只允许同时拿起左右两边的筷子,方法是引入一个互斥量,对拿起两个筷子的那段代码加锁。 - -```c -semaphore mutex = 1; - -void philosopher(int i) { -    while(TURE){ -        think(); -        down(mutex); -        down(chopstick[LEFT[i]]); -        down(chopstick[RIGHT[i]]); -        up(mutex); -        eat(); -        down(mutex); -        up(chopstick[RIGHT[i]]); -        up(chopstick[LEFT[i]]); -        up(mutex); -    } -} -``` - -# 第三章 死锁 - -## 死锁的条件 - -![](index_files/c037c901-7eae-4e31-a1e4-9d41329e5c3e.png) - -1. 互斥 -2. 请求与保持:一个进程因请求资源而阻塞时,对已获得的资源保持不放。 -3. 不可抢占 -4. 环路等待 - -## 死锁的处理方法 - -### 1. 鸵鸟策略 - -把头埋在沙子里,假装根本没发生问题。 - -这种策略不可取。 - -### 2. 死锁预防 - -在程序运行之前预防发生死锁。 - -#### 2.1 破坏互斥条件 - -例如假脱机打印机技术允许若干个进程同时输出,唯一真正请求物理打印机的进程是打印机守护进程。 - -#### 2.2 破坏请求与保持条件 - -一种实现方式是规定所有进程在开始执行前请求所需要的全部资源。 - -#### 2.3 破坏不可抢占条件 - -#### 2.4 破坏环路等待 - -给资源统一编号,进程只能按编号顺序来请求资源。 - -### 3. 死锁避免 - -在程序运行时避免发生死锁。 - -#### 3.1 安全状态 - -![](index_files/ed523051-608f-4c3f-b343-383e2d194470.png) - -图 a 的第二列 has 表示已拥有的资源数,第三列 max 表示总共需要的资源数,free 表示还有可以使用的资源数。从图 a 开始出发,先让 B 拥有所需的所有资源,运行结束后释放 B,此时 free 变为 4;接着以同样的方式运行 C 和 A,使得所有进程都能成功运行,因此可以称图 a 所示的状态时安全的。 - -定义:如果没有死锁发生,并且即使所有进程突然请求对资源的最大需求,也仍然存在某种调度次序能够使得每一个进程运行完毕,则称该状态是安全的。 - -#### 3.2 单个资源的银行家算法 - -一个小城镇的银行家,他向一群客户分别承诺了一定的贷款额度,算法要做的是判断对请求的满足是否会进入不安全状态,如果是,就拒绝请求;否则予以分配。 - -![](index_files/d160ec2e-cfe2-4640-bda7-62f53e58b8c0.png) - -上图 c 为不安全状态,因此算法会拒绝之前的请求,从而避免进入图 c 中的状态。 - -#### 3.3 多个资源的银行家算法 - -![](index_files/62e0dd4f-44c3-43ee-bb6e-fedb9e068519.png) - -上图中有五个进程,四个资源。左边的图表示已经分配的资源,右边的图表示还需要分配的资源。最右边的 E、P 以及 A 分别表示:总资源、已分配资源以及可用资源,注意这三个为向量,而不是具体数值,例如 A=(1020),表示 4 个资源分别还剩下 1/0/2/0。 - -检查一个状态是否安全的算法如下: - -① 查找右边的矩阵是否存在一行小于等于向量 A。如果不存在这样的行,那么系统将会发生死锁,状态是不安全的。 - -② 假若找到这样一行,将该进程标记为终止,并将其已分配资源加到 A 中。 - -③ 重复以上两步,直到所有进程都标记为终止,则状态时安全的。 - -### 4. 死锁检测与死锁恢复 - -不试图组织死锁,而是当检测到死锁发生时,采取措施进行恢复。 - -#### 4.1 死锁检测算法 - -死锁检测的基本思想是,如果一个进程所请求的资源能够被满足,那么就让它执行,释放它拥有的所有资源,然后让其它能满足条件的进程执行。 - -![](index_files/e1eda3d5-5ec8-4708-8e25-1a04c5e11f48.png) - -上图中,有三个进程四个资源,每个数据代表的含义如下: - -E 向量:资源总量 -A 向量:资源剩余量 -C 矩阵:每个进程所拥有的资源数量,每一行都代表一个进程拥有资源的数量 -R 矩阵:每个进程请求的资源数量 - -进程 P1 和 P2 所请求的资源都得不到满足,只有进程 P3 可以,让 P3 执行,之后释放 P3 拥有的资源,此时 A = (2 2 2 0)。P1 可以执行,执行后释放 P1 拥有的资源, A = (4 2 2 2) ,P2 也可以执行。所有进程都可以顺利执行,没有死锁。 - -算法总结如下: - -每个进程最开始时都不被标记,执行过程有可能被标记。当算法结束时,任何没有被标记的进程都是死锁进程。 - -① 寻找一个没有标记的进程 Pi,它所请求的资源小于等于 A。 -② 如果找到了这样一个进程,那么将 C 矩阵的第 i 行向量加到 A 中,标记该进程,并转回 ①。 -③ 如果有没有这样一个进程,算法终止。 - -#### 4.2 死锁恢复 - -① 利用抢占恢复 -② 杀死进程 - -# 第四章 存储器管理 - -## 虚拟内存 - -每个程序拥有自己的地址空间,这个地址空间被分割成多个块,每一块称为一 页。这些页被映射到物理内存,但不需要映射到连续的物理内存,也不需要所有页都必须在物理内存中。 - -当程序引用到一部分在物理内存中的地址空间时,由硬件立即执行必要的映射。当程序引用到一部分不在物理内存中的地址空间时,由操作系统负责将缺失的部分装入物理内存并重新执行失败的指令。 - -## 分页与分段 - -### 1. 分页 - -用户程序的地址空间被划分为若干固定大小的区域,称为“页”。相应地,内存空间分成若干个物理块,页和块的大小相等。可将用户程序的任一页放在内存的任一块中,实现了离散分配,由一个页表来维护它们之间的映射关系。 - -### 2. 分段 - -![](index_files/22de0538-7c6e-4365-bd3b-8ce3c5900216.png) - -上图为一个编译器在编译过程中建立的多个表,有 4 个表是动态增长的,如果使用分页系统的一维地址空间,动态递增的特点会导致覆盖问题的出现。 - -![](index_files/e0900bb2-220a-43b7-9aa9-1d5cd55ff56e.png) - -分段的做法是把每个表分成段,一个段构成一个独立的地址空间。每个段的长度可以不同,可以动态改变。 - -每个段都需要程序员来划分。 - -### 3. 段页式 - -用分段方法来分配和管理虚拟存储器。程序的地址空间按逻辑单位分成基本独立的段,而每一段有自己的段名,再把每段分成固定大小的若干页。 - -用分页方法来分配和管理实存。即把整个主存分成与上述页大小相等的存储块,可装入作业的任何一页。程序对内存的调入或调出是按页进行的。但它又可按段实现共享和保护。 - -### 4. 分页与分段区别 - -① 对程序员的透明性:分页透明,但是分段需要程序员显示划分每个段。 - -② 地址空间的维度:分页是一维地址空间,分段是二维的。 - -③ 大小是否可以改变:页的大小不可变,段的大小可以动态改变。 - -④ 出现的原因:分页主要用于实现虚拟内存,从而获得更大的地址空间;分段主要是为了使程序和数据可以被划分为逻辑上独立的地址空间并且有助于共享和保护。 - -## 页面置换算法 - -在程序运行过程中,若其所要访问的页面不在内存而需要把它们调入内存,但是内存已无空闲空间时,系统必须从内存中调出一个页面到磁盘对换区中,并且将程序所需要的页面调入内存中。页面置换算法的主要目标是使页面置换频率最低(也可以说缺页率最低)。 - -### 1. 最佳(Optimal) - -所选择的被换出的页面将是最长时间内不再被访问,通常可以保证获得最低的缺页率。 - -是一种理论上的算法,因为无法知道一个页面多长时间会被再访问到。 - -举例:一个系统为某进程分配了三个物理块,并有如下页面引用序列: - -7,0,1,2,0,3,0,4,2,3,0,3,2,1,2,0,1,7,0,1 - -进程运行时,先将 7,0,1 三个页面装入内存。当进程要访问页面 2 时,产生缺页中断,会将页面 7 换出,因为页面 7 再次被访问的时间最长。 - -### 2. 先进先出(FIFO) - -所选择换出的页面是最先进入的页面。 - -该算法会将那些经常被访问的页面也被换出,从而使缺页率升高。 - -### 3. 最近最久未使用(LRU,Least Recently Used) - -虽然无法知道将来要使用的页面情况,但是可以知道过去使用页面的情况。LRU 将最近最久未使用的页面换出。 - -可以用栈来实现该算法,栈中存储页面的页面号。当进程访问一个页面时,将该页面的页面号从栈移除,并将它压入栈顶,这样,最近被访问的页面的页面号总是在栈顶,而最近最久未使用的页面的页面号总是在栈底。 - -4,7,0,7,1,0,1,2,1,2,6 - -![](index_files/eb859228-c0f2-4bce-910d-d9f76929352b.png) - -### 4. 时钟(Clock) - -Clock 页面置换算法需要用到一个访问位,当一个页面被访问时,将访问为置为 1。 - -首先,将内存中的所有页面链接成一个循环队列,当缺页中断发生时,检查当前指针所指向页面的访问位,如果访问位为 0,就将该页面换出;否则将该页的访问位设置为 0,给该页面第二次的机会,移动指针继续检查。 - -# 第五章 设备管理 - -## 磁盘调度算法 - -当多个进程同时请求访问磁盘时,需要进行磁盘调度来控制对磁盘的访问。磁盘调度的主要目标是使磁盘的平均寻道时间最少。 - -### 1. 先来先服务(FCFS,First Come First Serverd) - -根据进程请求访问磁盘的先后次序来进行调度。优点是公平和简单,缺点也很明显,因为未对寻道做任何优化,使平均寻道时间可能较长。 - -### 2. 最短寻道时间优先(SSTF,Shortest Seek Time First) - -要求访问的磁道与当前磁头所在磁道距离最近的优先进行调度。这种算法并不能保证平均寻道时间最短,但是比 FCFS 好很多。 - -### 3. 扫描算法(SCAN) - -SSTF 会出现进行饥饿现象。考虑以下情况,新进程请求访问的磁道与磁头所在磁道的距离总是比一个在等待的进程来的近,那么等待的进程会一直等待下去。 - -SCAN 算法在 SSTF 算法之上考虑了磁头的移动方向,要求所请求访问的磁道在磁头当前移动方向上才能够得到调度。因为考虑了移动方向,那么一个进程请求访问的磁道一定会得到调度。 - -当一个磁头自里向外移动时,移到最外侧会改变移动方向为自外向里,这种移动的规律类似于电梯的运行,因此又常称 SCAN 算法为电梯调度算法。 - -### 4. 循环扫描算法(CSCAN) - -CSCAN 对 SCAN 进行了改动,要求磁头始终沿着一个方向移动。 - -# 参考资料 - -- Tanenbaum A S, Bos H. Modern operating systems[M]. Prentice Hall Press, 2014. - -- 汤子瀛, 哲凤屏, 汤小丹. 计算机操作系统[M]. 西安电子科技大学出版社, 2001. - -- Bryant, R. E., & O’Hallaron, D. R. (2004). 深入理解计算机系统. - -- [小土刀的面试刷题笔记](http://wdxtub.com/interview/index.html) - -- [进程间的几种通信方式](http://blog.csdn.net/yufaw/article/details/7409596) \ No newline at end of file diff --git a/notes/笔记/计算机网络+.md.txt b/notes/笔记/计算机网络+.md.txt deleted file mode 100644 index 6f5b71df..00000000 --- a/notes/笔记/计算机网络+.md.txt +++ /dev/null @@ -1,21 +0,0 @@ -## Web 页面请求过程 - -1. 向 DNS 服务器发送 DNS 查询报文来解析域名。 - -2. 在运输层,DNS 查询报文会放入端口号为 53 的 UDP 数据报中。 - -3. 在网络层,UDP 数据报封装进 IP 分组中,路由器会执行路由选择算法来转发分组。 - -4. 在链路层,IP 分组会封装进链路层帧中,此时需要使用 ARP 来查询 MAC 地址,通过 ARP 得到 MAC 地址之后就可以进行链路层传输了。 - -5. 在 DNS 服务器返回域名的 IP 地址后,就可以开始进行 HTTP 会话了。 - -6. 要进行 HTTP 会话,需要建立 TCP 连接。TCP 连接的建立需要进行进行 TCP 的三次握手。TCP 连接建立后会保持持久连接。 - -7. 客户端发送 HTTP 请求报文,请求获取页面。 - -8. 如果存在缓存服务器,并且缓存服务器上有该页面,那么缓存服务器通过 HTTP 响应报文向客户端发送缓存的页面。否则,服务器发送该页面。 - -9. 浏览器得到页面内容之后,解析并渲染,向用户展示页面。 - -10. 当客户端不再需要连接时,发送的 HTTP 请求报文中包含 Connection: close 字段,从而释放 TCP 连接,TCP 连接的释放需要进行四次握手过程。 diff --git a/notes/笔记/计算机网络.md.txt b/notes/笔记/计算机网络.md.txt deleted file mode 100644 index cf5e74cd..00000000 --- a/notes/笔记/计算机网络.md.txt +++ /dev/null @@ -1,760 +0,0 @@ -[TOC] - -# 第一章 概述 - -## 网络的网络 - -网络把主机连接起来,而互联网是把多种不同的网络连接起来,因此互联网是网络的网络。 - -![](index_files/04ff7ae6-7bee-4cf8-82f8-dfe2ba1f3616.jpg) - -## ISP - -互联网服务提供商 ISP 可以从互联网管理机构获得许多 IP 地址,同时拥有通信线路以及路由器等联网设备,个人或机构向 ISP 缴纳一定的费用就可以接入互联网。 - -目前的互联网是一种多层次 ISP 结构,ISP 根据覆盖面积的大小分为主干 ISP、地区 ISP 和本地 ISP。 - -互联网交换点 IXP 允许两个网络直接相连而不用经过第三个来转发分组。 - -![](index_files/17d807ef-03bf-4824-a97c-ea5fb58ec61d.jpg) - -## 互联网的组成 - -1. 边缘部分:所有连接在互联网上的主机。用户可以直接使用; -2. 核心部分:由大量的网络和连接这些网络的路由器组成。为边缘部分的主机提供服务。 - -![](index_files/005d83c2-e64a-41f0-bbdd-51c71d494a18.jpg) - -## 主机之间的通信方式 - -**1. 客户 - 服务器(C/S)** - -客户即是服务请求方,服务器是服务提供方。 - -**2. 对等(P2P)** - -不区分客户和服务器。 - -## 电路交换与分组交换 - -![](index_files/b97958dd-3e43-45f7-97f5-3ec20f3f8b88.jpg) - -### 1. 电路交换 - -电路交换用于电话通信系统,两个用户要通信之前需要建立一条专用的物理链路,并且在整个通信过程中始终占用该链路。由于通信的过程中不可能一直在使用传输线路,因此电路交换对线路的利用率很低,往往不到 10%。 - -### 2. 报文交换 - -报文交换用于邮局通信系统,邮局接收到一份报文之后,先存储下来,然后根据目的地选择性地把报文转发到下一个目的地,这个过程就是存储转发过程。 - -### 3. 分组交换 - -分组交换也使用了存储转发,但是转发的是分组而不是报文。把整块数据称为一个报文,由于一个报文可能很长,需要先进行切分,来满足分组能处理的大小。在每个切分的数据前面加上首部之后就成为了分组,首部包含了目的地址和源地址等控制信息。 - -![](index_files/6f4af159-8b03-4246-8d0e-222db65bb83c.jpg) - -存储转发允许在一条传输线路上传送多个主机的分组,也就是分组交换不需要占用端到端的线路资源。 - -相比于报文交换,由于分组比报文更小,存储转发的速度也就更快。 - -## 时延 - -总时延 = 发送时延 + 传播时延 + 处理时延 + 排队时延 - -![](index_files/ceee91c2-da26-4169-94c3-e4608b46b9ac.png) - - -### 1. 发送时延 - -主机或路由器发送数据帧所需要的时间。 - -$$ delay = \frac{l(bit)}{v(bit/s)} $$ - -其中 l 表示数据帧的长度,v 表示发送速率。 - -### 2. 传播时延 - -电磁波在信道中传播一定的距离需要花费的时间,电磁波传播速度接近光速。 - -$$ delay = \frac{l(m)}{v(m/s)} $$ - -其中 l 表示信道长度,v 表示电磁波在信道上的传播速率。 - -### 3. 处理时延 - -主机或路由器收到分组时进行处理所需要的时间,例如分析首部,从分组中提取数据部分等。 - -### 4. 排队时延 - -分组在路由器的输入队列和输出队列中排队等待的时间,取决于网络当前的通信量。 - -## 计算机网络体系结构* - -![](index_files/1005dc9d-9049-4b06-9524-6171e56ebd8c.png) - -### 1. 七层协议 - -如图 a 所示,其中表示层和会话层用途如下: - -1. 表示层:信息的语法语义以及它们的关联,如加密解密、转换翻译、压缩解压缩; -2. 会话层:不同机器上的用户之间建立及管理会话。 - -### 2. 五层协议 - -1. 应用层:为特定应用程序提供数据传输服务,例如 HTTP、DNS 等。数据单位为报文。 - -2. 运输层:提供的是进程间的通用数据传输服务。由于应用层协议很多,定义通用的运输层协议就可以支持不断增多的应用层协议。运输层包括两种协议:传输控制协议 TCP,提供面向连接、可靠的数据传输服务,数据单位为报文段;用户数据报协议 UDP,提供无连接、尽最大努力的数据传输服务,数据单位为用户数据报。 - -3. 网络层:为主机之间提供服务,而不是像运输层协议那样是为主机中的进程提供服务。网络层把运输层产生的报文段或者用户数据报封装成分组来进行传输。 - -4. 数据链路层:网络层针对的还是主机之间,而主机之间可以有很多链路,链路层协议就是为相邻结点之间提供服务。数据链路层把网络层传来的分组封装成帧。 - -5. 物理层:考虑的是怎样在传输媒体上传输数据比特流,而不是指具体的传输媒体。物理层的作用是尽可能屏蔽传输媒体和通信手段的差异,使物理层上的数据链路层感觉不到这些差异。 - -### 3. 数据在各层之间的传递过程 - -在向下的过程中,需要添加下层协议所需要的首部或者尾部,而在向上的过程中不断拆开首部和尾部。 - -路由器只有下面三层协议,因为路由器位于网络核心中,不需要为进程或者应用程序提供服务。 - -![](index_files/f7d5da89-2d75-4d8f-85e7-6b608865dc00.jpg) - -### 4. TCP/IP 体系结构 - -它只有四层,相当于五层协议中数据链路层和物理层合并为网络接口层。 - -现在的 TCP/IP 体系结构不严格遵循 OSI 分层概念,应用层可能会直接使用 IP 层或者网络接口层。 - -![](index_files/3e2200b3-1c18-4853-ae42-7788e8e1f939.png) - -TCP/IP 协议族是一种沙漏形状,中间小两边大,IP 协议在其中占用举足轻重的地位。 - -![](index_files/9ecaebee-670e-4cb2-9cdb-3029c00f33bd.png) - -# 第二章 物理层 - -## 通信方式 - -1. 单向通信,又称为单工通信; -2. 双向交替通信,又称为半双工通信; -3. 双向同时通信,又称为全双工通信。 - -## 带通调制 - -模拟信号是连续的信号,数字信号是离散的信号。带通调制把数字信号转换为模拟信号。 - -![](index_files/d2c55c84-aa1f-43c1-bd97-457bcb7816b3.png) - -## 信道复用技术 - -### 1. 频分复用、时分复用 - -频分复用的所有用户在相同的时间占用不同的频率带宽资源;时分复用的所有用户在不同的时间占用相同的频率带宽资源。 - -使用这两种方式进行通信,在通信的过程中用户会一直占用一部分信道资源。但是由于计算机数据的突发性质,没必要一直占用信道资源而不让出给其它用户使用,因此这两种方式对信道的利用率都不高。 - -![](index_files/543d47a1-f0dd-414f-b23c-0c142c814854.png) - -### 2. 统计时分复用 - -是对时分复用的一种改进,不固定每个用户在时分复用帧中的位置,只要有数据就集中起来组成时分复用帧然后发送。 - -![](index_files/29058e09-bb72-4040-a73d-4c497895e9ce.jpg) - -### 3. 波分复用 - -光的频分复用。由于光的频率很高,因此习惯上用波长而不是频率来表示所使用的光载波。 - -![](index_files/78534153-88d1-4f83-a6e0-59064dbdc43a.png) - -### 4. 码分复用 - -为每个用户分配 m bit 的码片,并且所有的码片正交,对于任意两个码片 $\vec{S}$ 和 $\vec{T}$ 有 - -$$ \vec{S} \cdot \vec{T} = 0 $$ - -为了方便,取 m=8,设码片 $\vec{S}$ 为 00011011。在拥有该码片的用户发送比特 1 时就发送该码片,发送比特 0 时就发送该码片的反码 11100100。 - -在计算时将 00011011 记作 (-1 -1 -1 +1 +1 -1 +1 +1),可以得到 - -$$ \frac{1}{m} \vec{S} \cdot \vec{S} = 1 $$ - -$$ \frac{1}{m} \vec{S} \cdot \vec{S'} = -1 $$ - -其中 $\vec{S'}$ 为 $\vec{S}$ 的反码。 - -利用上面的式子我们知道,当接收端使用码片 $\vec{S}$ 对接收到的数据进行内积运算时,结果为 0 的是其它用户发送的数据,结果为 1 的是用户发送的比特 1,结果为 -1 的是用户发送的比特 0。 - -码分复用发送的数据量为原先的 m 倍。 - -![](index_files/0042edad-8e3b-4279-bd93-6906fcd1b640.jpg) - -# 第三章 数据链路层 - -## 三个基本问题 - -### 1. 封装成帧 - -将网络层传下来的分组添加首部和尾部,用于标记帧的开始和结束。 - -![](index_files/ea5ed9b2-6d9f-48fb-b890-0288caf9088a.jpg) - -### 2. 透明传输 - -透明表示一个实际存在的事物看起来好像不存在一样。 - -帧中有首部和尾部,如果帧的数据部分含有和首部尾部相同的内容,那么帧的开始和结束位置就会被错误的判定。需要在数据中出现首部尾部相同的内容前面插入转义字符,如果需要传输的内容正好就是转义字符,那么就在转义字符前面再加个转义字符,在接收端进行处理之后可以还原出原始数据。这个过程透明传输的内容是转义字符,用户很难察觉到转义字符的存在。 - -![](index_files/44e1d90e-3fe6-4dd6-8dce-6daab12e7663.jpg) - -### 3. 差错检测 - -目前数据链路层广泛使用了循环冗余检验(CRC)来检查比特差错。 - -## 点对点信道 -PPP 协议 - -互联网用户通常需要连接到某个 ISP 之后才能接入到互联网,PPP 协议就是用户计算机和 ISP 进行通信时所使用的数据链路层协议。 - -![](index_files/8b5bd2c8-8425-4a8b-89db-235c95800de9.jpg) - -在 PPP 的帧中,F 字段为帧的定界符,A 和 C 暂时没有意义。FCS 是使用 CRC 的检验序列。信息字段的长度不超过 1500。 - -![](index_files/a5fa89e7-54b9-4e2f-8c48-a35712d7b2f5.jpg) - -## 局域网的拓扑 - -![](index_files/8b15e36f-69b4-46b6-a07c-7234ac7c7927.jpg) - -## 广播信道 - CSMA/CD 协议* - -在广播信道上,同一时间只能允许一台计算机发送数据。 - -CSMA/CD 表示载波监听多点接入 / 碰撞检测。 - -**多点接入**:说明这是总线型网络,许多计算机以多点的方式连接到总线上。 -**载波监听**:每个站都必须不停地检听信道。在发送前,如果检听信道正在使用,就必须等待。 -**碰撞检测**:在发送中,如果检听信道已有其它站正在发送数据,就表示发生了碰撞。虽然每一个站在发送数据之前都已经检听信道为空闲,但是由于电磁波的传播时延的存在,还是有可能会发生碰撞。 - -![](index_files/f9ed4da5-0032-41e6-991a-36d995ec28fd.png) - -记端到端的传播时延为 τ,最先发送的站点最多经过 2τ 就可以知道是否发生了碰撞,称 2τ 为 **争用期**。只有经过争用期之后还没有检测到碰撞,才能肯定这次发送不会发生碰撞。 - -当发生碰撞时,站点要停止发送,等待一段时间再发送。这个时间采用 **截断二进制指数退避算法** 来确定,从离散的整数集合 {0, 1, .., (2k-1)} 中随机取出一个数,记作 r,然后取 r 倍的争用期作为重传等待时间。 - -## 集线器 - -从表面上看,使用集线器的局域网在物理上是一个星型网。但是集线器使用电子器件来模拟实际缆线的工作,逻辑上仍是一个总线网,整个系统仍像一个传统以太网那样运行。 - -![](index_files/897a4f4e-2683-44e1-a26a-c0d0234dc576.jpg) - -![](index_files/40c3f8e5-3a20-45b6-a60c-77b9b952e104.jpg) - -## MAC 层 - -MAC 地址是 6 字节(48 位)的地址,用于唯一表示适配器,一台主机拥有多个适配器就有多个 MAC 地址,例如笔记本电脑普遍存在无线网络适配器和有线网络适配器。 - -MAC 帧用类型字段来标记上层使用什么协议;数据字段长度在 46-1500 之间,如果太小则需要填充;FCS 为帧检验序列,使用的是 CRC 检验方法;前面插入的前同步码只是为了计算 FCS 临时加入的,计算结束之后会丢弃。 - -![](index_files/50d38e84-238f-4081-8876-14ef6d7938b5.jpg) - -## 虚拟局域网 - -虚拟局域网可以建立与物理位置无关的逻辑组,只有在同一个虚拟局域网中的成员才会收到广播信息,例如下图中 (A1, A2, A3, A4) 属于一个虚拟局域网,A1 发送的广播会被 A2、A3、A4 收到,而其它站点收不到。 - -![](index_files/a74b70ac-323a-4b31-b4d5-90569b8a944b.png) - -# 第四章 网络层* - -## 网际协议 IP 概述 - -因为网络层是整个互联网的核心,因此应当让网络层尽可能简单。网络层向上只提供简单灵活的、无连接的、尽最大努力交互的数据报服务。 - -使用 IP 协议,可以把异构的物理网络连接起来,使得在网络层看起来好像是一个统一的网络。 - -![](index_files/fe3d224c-8ffd-40f9-85b1-86ffe1393f6c.jpg) - -与 IP 协议配套使用的还有三个协议: - -1. 地址解析协议 ARP(Address Resolution Protocol) -2. 网际控制报文协议 ICMP(Internet Control Message Protocol) -3. 网际组管理协议 IGMP(Internet Group Management Protocol) - -![](index_files/163cf8b4-5f30-46c9-af00-316a71b3c890.jpg) - -## IP 数据报格式 - -![](index_files/8681db55-0873-434b-aa98-83d07e8392ae.jpg) - -**版本** : 有 4(IPv4)和 6(IPv6)两个值; - -**首部长度** : 占 4 位,因此最大值为 15。值为 1 表示的是 1 个 32 位字的长度,也就是 4 字节。因为首部固定长度为 20 字节,因此该值最小为 5。如果可选部分的长度不是 4 字节的整数倍,就用尾部的填充部分来填充。 - -**区分服务** : 用来获得更好的服务,一般情况下不适用。 - -**总长度** : 包括首部长度和数据部分长度。 - -**标识** : 在数据报长度过长从而发生分片的情况下,相同数据报的不同分片具有相同的标识符。 - -**片偏移** : 和标识符一起,用于发生分片的情况。片偏移的单位为 8 字节。 - -![](index_files/45c86855-9b18-4cf4-a9a7-f8b6eb78d133.png) - -**生存时间** :TTL,它的存在为了防止无法交付的数据报在互联网中不断兜圈子。以路由器跳数为单位,当 TTL 为 0 时就丢弃数据报。 - -**协议**:指出携带的数据应该上交给哪个协议进行处理,例如 ICMP、TCP、UDP 等。 - -**首部检验和**:因为数据报每经过一个路由器,都要重新计算检验和,因此检验和不包含数据部分可以减少计算的工作量。 - -## IP 地址编址 - -IP 地址的编址方式经历了三个历史阶段: - -1. 分类的 IP 地址; -2. 子网的划分; -3. 构成超网。 - -### 1. 分类的 IP 地址 - -由两部分组成,网络号和主机号,其中不同类别具有不同的网络号长度,并且是固定的。 - -IP 地址 ::= {< 网络号 >, < 主机号 >} - -![](index_files/2ddd6132-60be-4a72-9daa-3d9756191f4a.png) - -### 2. 划分子网 - -通过在网络号字段中拿一部分作为子网号,把两级 IP 地址划分为三级 IP 地址。注意,外部网络看不到子网的存在。 - -IP 地址 ::= {< 网络号 >, < 子网号 >, < 主机号 >} - -要使用子网,必须配置子网掩码。一个 B 类地址的默认子网掩码为 255.255.0.0,如果 B 类地址的子网占两个比特,那么子网掩码为 11111111 11111111 11000000 000000,也就是 255.255.192.0。 - -### 3. 无分类编址 CIDR(构成超网) - -CIDR 消除了传统 A 类、B 类和 C 类地址以及划分子网的概念,使用网络前缀和主机号来对 IP 地址进行编码,网络前缀的长度可以根据需要变化。 - -IP 地址 ::= {< 网络前缀号 >, < 主机号 >} - -CIDR 的记法上采用在 IP 地址后面加上网络前缀长度的方法,例如 128.14.35.7/20 表示前 20 位为网络前缀。 - -CIDR 的地址掩码可以继续称为子网掩码,子网掩码首 1 长度为网络前缀的长度。 - -一个 CIDR 地址块中有很多地址,一个 CIDR 表示的网络就可以表示原来的很多个网络,并且在路由表中只需要一个路由就可以代替原来的多个路由,减少了路由表项的数量。把这种通过使用网络前缀来减少路由表项的方式称为路由聚合,也称为构成超网。 - -在路由表中每个项目由“网络前缀”和“下一跳地址”组成,在查找时可能会得到不止一个匹配结果,应当采用最长前缀匹配。 - -## IP 地址和 MAC 地址 - -网络层实现主机之间的通信,而链路层实现具体每段链路之间的通信。因此在通信过程中,IP 数据报的源地址和目的地址始终不变,而 MAC 地址随着链路的改变而改变。 - -![](index_files/86b71296-0d1e-4a63-bcd9-54955b6b781b.jpg) - - -## 地址解析协议 ARP - -实现由 IP 地址得到 MAC 地址。 - -每个主机都有一个 ARP 高速缓存,存放映射表。如果一个 IP 地址 到 MAC 地址的映射不在该表中,主机通过广播的方式发送 ARP 请求分组,匹配 IP 地址的主机会发送 ARP 响应分组告知 MAC 地址。 - -![](index_files/8bc6fc2c-d198-4759-b06c-18d94d851e97.png) - -## 路由器的结构 - -路由器可以划分为两大部分:路由选择和分组转发。 - -分组转发部分由三部分组成:交换结构、一组输入端口和一组输出端口。 - -![](index_files/3a676c54-b559-4466-9b21-eb10f1e25879.jpg) - -交换结构的交换网络有以下三种实现方式: - -![](index_files/7f82fd18-7f16-4125-ada6-bb6b795b4fda.png) - -## 交换机与路由器的区别 - -- 交换机工作于数据链路层,能识别 MAC 地址,根据 MAC 地址转发链路层数据帧。具有自学机制来维护 IP 地址与 MAC 地址的映射。 - -- 路由器位于网络层,能识别 IP 地址并根据 IP 地址转发分组。维护着路由表,根据路由表选择最佳路线。 - -## 路由器分组转发流程 - -1. 从数据报的首部提取目的主机的 IP 地址 D,得到目的网络地址 N。(路由表项是网络号而不是 IP 地址,这样做大大减少了路由表条目数量) -2. 若 N 就是与此路由器直接相连的某个网络地址,则进行直接交付; -3. 若路由表中有目的地址为 D 的特定主机路由,则把数据报传送给表中所指明的下一跳路由器; -4. 若路由表中有到达网络 N 的路由,则把数据报传送给路由表中所指明的下一跳路由器; -5. 若路由表中有一个默认路由,则把数据报传送给路由表中所指明的默认路由器; -6. 报告转发分组出错。 - -![](index_files/8d211911-0e62-4190-ab00-d8610adec4a0.jpg) - -## 路由选择协议 - -互联网使用的路由选择协议都是自适应的,能随着网络通信量和拓扑变化而自适应地进行调整。 - -互联网可以划分为许多较小的自治系统 AS,一个 AS 可以使用一种和别的 AS 不同的路由选择协议。 - -可以把路由选择协议划分为两大类: - -1. 内部网关协议 IGP(Interior Gateway Protocol) 在自治系统内部使用,如 RIP 和 OSPF。 -2. 外部网关协议 EGP(External Gateway Protocol) 在自治系统之间使用,如 BGP。 - -![](index_files/e0be6970-5b0e-44a2-bc71-df4d61c42b8f.jpg) - -### 1. 内部网关协议 RIP - -RIP 是一种分布式的基于距离向量的路由选择协议。距离是指跳数,直接相连的路由器跳数为 1,跳数最多为 15,超过 15 表示不可达。 - -RIP 按固定的时间间隔仅和相邻路由器交换自己的路由表,经过若干次交换之后,所有路由器最终会知道到达本自治系统中任何一个网络的最短距离和下一跳路由器地址。 - -距离向量算法: - -1. 对地址为 X 的相邻路由器发来的 RIP 报文,先修改报文中的所有项目,把下一跳字段中的地址改为 X,并把所有的距离字段加 1; -2. 对修改后的 RIP 报文中的每一个项目,进行以下步骤: - - 若原来的路由表中没有目的网络 N,则把该项目添加到路由表中; - - 否则:若下一跳路由器地址是 X,则把收到的项目替换原来路由表中的项目;否则:若收到的项目中的距离 d 小于路由表中的距离,则进行更新(例如原始路由表项为 Net2, 5, P,新表项为 Net2, 4, X,则更新);否则什么也不做。 -3. 若 3 分钟还没有收到相邻路由器的更新路由表,则把该相邻路由器标为不可达,即把距离置为 16。 - -RIP 协议实现简单,开销小,但是 RIP 能使用的最大距离为 15,限制了网络的规模。并且当网络出现故障时,要经过比较长的时间才能将此消息传送到所有路由器。 - -### 2. 内部网关协议 OSPF - -开放最短路径优先 OSPF,是为了克服 RIP 的缺点而开发出来的。 - -开放表示 OSPF 不受某一家厂商控制,而是公开发表的;最短路径优先是因为使用了 Dijkstra 提出的最短路径算法 SPF。 - -OSPF 具有以下特点: - -1. 向本自治系统中的所有路由器发送信息,这种方法是洪泛法。 -2. 发送的信息就是与相邻路由器的链路状态,链路状态包括与哪些路由器相连以及链路的度量,度量用费用、距离、时延、带宽等来表示。 -3. 只有当链路状态发生变化时,路由器才会发送信息。 - -所有路由器都具有全网的拓扑结构图,并且是一致的。相比于 RIP,OSPF 的更新过程收敛的很快。 - -### 3. 外部网关协议 BGP - -AS 之间的路由选择很困难,主要是互联网规模很大。并且各个 AS 内部使用不同的路由选择协议,就无法准确定义路径的度量。并且 AS 之间的路由选择必须考虑有关的策略,比如有些 AS 不愿意让其它 AS 经过。 - -BGP 只能寻找一条比较好的路由,而不是最佳路由。它采用路径向量路由选择协议。 - -每个 AS 都必须配置 BGP 发言人,通过在两个相邻 BGP 发言人之间建立 TCP 连接来交换路由信息。 - -![](index_files/eb6271de-22c9-4f4b-8b31-eab1f560efac.png) - -## 网际控制报文协议 ICMP - -ICMP 是为了更有效地转发 IP 数据报和提高交付成功的机会。它封装在 IP 数据报中,但是不属于高层协议。 - -![](index_files/9b5e0fa0-9274-4219-a3a9-84fbb509c735.jpg) - -ICMP 报文分为差错报告报文和询问报文。 - -![](index_files/6e11b122-95ce-4869-bf7d-3b0d7591707e.jpg) - -## 分组网间探测 PING - -PING 是 ICMP 的一个重要应用,主要用来测试两台主机之间的连通性。 - -PING 的过程: - -1. PING 同一个网段的主机,查找目的主机的 MAC 地址,然后直接交付。如果无法查找到 MAC 地址,就要进行一次 ARP 请求。 -2. PING 不同网段的主机,就发送给网关让其进行转发。同样要发送给网关也需要通过查找网关的 MAC 地址,根据 MAC 地址进行转发。 - -## IP 多播 - -在一对多的通信中,多播不需要将分组复制多份,从而大大节约网络资源。 - -![](index_files/c77b6a18-dfac-42a2-ac89-7e99481275dc.jpg) - -## 虚拟专用网 VPN - -由于 IP 地址的紧缺,一个机构能申请到的 IP 地址数往往远小于本机构所拥有的主机数。并且一个机构并不需要把所有的主机接入到外部的互联网中,机构内的计算机可以使用仅在本机构有效的 IP 地址(专用地址)。 - -有三个专用地址块: - -1. 10.0.0.0 ~ 10.255.255.255 -2. 172.16.0.0 ~ 172.31.255.255 -3. 192.168.0.0 ~ 192.168.255.255 - -VPN 使用公用的互联网作为本机构各专用网之间的通信载体。专用指机构内的主机只与本机构内的其它主机通信;虚拟指“好像是”,而实际上并不是,它有经过公用的互联网。 - -下图中,场所 A 和 B 的通信部经过互联网,如果场所 A 的主机 X 要和另一个场所 B 的主机 Y 通信,IP 数据报的源地址是 10.1.0.1,目的地址是 10.2.0.3。数据报先发送到与互联网相连的路由器 R1,R1 对内部数据进行加密,然后重新加上数据报的首部,源地址是路由器 R1 的全球地址 125.1.2.3,目的地址是路由器 R2 的全球地址 194.4.5.6。路由器 R2 收到数据报后将数据部分进行解密,恢复原来的数据报,此时目的地址为 10.2.0.3,就交付给 Y。 - -![](index_files/bf4ed077-d481-4db7-9e7a-85d841a5a8c3.jpg) - -## 网络地址转换 NAT - -专用网内部的主机使用本地 IP 地址又想和互联网上的主机通信时,可以使用 NAT 来将本地 IP 转换为全球 IP。 - -在以前,NAT 将本地 IP 和全球 IP 一一对应,这种方式下拥有 n 个全球 IP 地址的专用网内最多只可以同时有 n 台主机接入互联网。为了更有效地利用全球 IP 地址,现在常用的 NAT 转换表把运输层的端口号也用上了,这样可以使得多个专用网内部的主机共用一个全球 IP 地址。使用端口号的 NAT 也叫做网络地址与端口转换 NAPT。 - -![](index_files/0f31bc7a-d60b-48a6-8e3f-597708369e52.png) - -# 第五章 运输层* - -网络层只把分组发送到目的主机,但是真正通信的并不是主机而是主机中的进程。 - -运输层提供了应用进程间的逻辑通信。运输层向高层用户屏蔽了下面网络层的核心细节,使应用程序看见的好像在两个运输层实体之间有一条端到端的逻辑通信信道。 - -## UDP 和 TCP 的特点 - -用户数据包协议 UDP(User Datagram Protocol) -传输控制协议 TCP(Transmission Control Protocol) - -UDP 是无连接的,尽最大可能交付,没有拥塞控制,面向报文(对于应用程序传下来的报文不合并也不拆分,只是添加 UDP 首部)。 - -TCP 是面向连接的,提供可靠交付,有流量控制,拥塞控制,提供全双工通信,面向字节流(把应用层传下来的报文看成字节流,把字节流组织成大小不等的数据块) - -## UDP 首部格式 - -![](index_files/bd6c05f3-02ee-4c8a-b374-40c87154a898.jpg) - -首部字段只有 8 个字节,包括源端口、目的端口、长度、检验和。12 字节的伪首部是为了计算检验和而临时添加的。 - -## TCP 首部格式 - -![](index_files/21a00b02-c0a6-4bcd-9af0-5ec6bb66e34c.jpg) - -**序号** :用于对字节流进行编号,例如序号为 301,表示第一个字节的编号为 301,如果携带的数据长度为 100 字节,那么下一个报文段的序号应为 401。 - -**确认号** :期望收到的下一个报文段的序号。例如 B 正确收到 A 发送来的一个报文段,序号为 501,携带的数据长度为 200 字节,因此 B 期望下一个报文段的序号为 701,B 发送给 A 的确认报文段中确认号就为 701。 - -**数据偏移** :指的是数据部分距离报文段起始处的偏移量,实际上指的是首部的长度。 - -**确认 ACK** :当 ACK=1 时确认号字段有效,否则无效。TCP 规定,在连接建立后所有传送的报文段都必须把 ACK 置 1。 - -**同步 SYN** :在连接建立时用来同步序号。当 SYN=1,ACK=0 时表示这是一个连接请求报文段。若对方同意建立连接,则响应报文中 SYN=1,ACK=1。 - -**终止 FIN** :用来释放一个连接,当 FIN=1 时,表示此报文段的发送方的数据已发送完毕,并要求释放运输连接。 - -**窗口** :窗口值作为接收方让发送方设置其发送窗口的依据。之所以要有这个限制,是因为接收方的数据缓存空间是有限的。 - -## TCP 的三次握手 - -![](index_files/086871db-5871-460f-97b7-126cd738bb0e.jpg) - -假设 A 为客户端,B 为服务器端。 - -1. 首先 B 处于 LISTEN(监听)状态,等待客户的连接请求。 -2. A 向 B 发送连接请求报文段,SYN=1,ACK=0,选择一个初始的序号 x。 -3. B 收到连接请求报文段,如果同意建立连接,则向 A 发送连接确认报文段,SYN=1,ACK=1,确认号为 x+1,同时也选择一个初始的序号 y。 -4. A 收到 B 的连接确认报文段后,还要向 B 发出确认,确认号为 y+1,序号为 x+1。 -5. B 收到 A 的确认后,连接建立。 - - -## TCP 的四次挥手 - -![](index_files/78f65456-666b-4044-b4ee-f7692dbbc0d3.jpg) - -以下描述不讨论序号和确认号,因为序号和确认号的规则比较简单。并且不讨论 ACK,因为 ACK 在连接建立之后都为 1。 - -1. A 发送连接释放报文段,FIN=1; -2. B 收到之后发出确认,此时 TCP 属于半关闭状态,B 能向 A 发送数据但是 A 不能向 B 发送数据; -3. 当 B 要不再需要连接时,发送连接释放请求报文段,FIN=1; -4. A 收到后发出确认,此时连接释放。 - -**TIME_WAIT** - -客户端接收到服务器端的 FIN 报文后进入此状态,此时并不是直接进入 CLOSED 状态,还需要等待一个时间计时器设置的时间。这么做有两个理由: - -1. 确保最后一个确认报文段能够到达。如果 B 没收到 A 发送来的确认报文段,那么就会重新发送连接释放请求报文段,A 等待一段时间就是为了处理这种情况的发生。 -2. 连接过程可能“已失效的连接请求报文段”,为了防止这种报文段出现在本次连接之外,需要等待一段时间。 - -## TCP 滑动窗口 - -![](index_files/223fc26e-2fd6-484c-bcb7-443cac134f15.jpg) - -窗口是缓存的一部分,用来暂时存放字节流。发送方和接收方各有一个窗口,接收方通过 TCP 报文段中的窗口字段告诉发送方自己的窗口大小,发送方根据这个值和其它信息设置自己的窗口大小。 - -发送窗口内的字节都允许被发送,接收窗口内的字节都允许被接收。如果发送窗口左部的字节已经发送并且收到了确认,那么就将发送窗口向右滑动一定距离,直到左部第一个字节不是已发送并且已确认的状态;接收窗口的滑动类似,接收窗口左部字节已经发送确认并交付主机,就向右滑动接收窗口。 - -接收窗口只会对窗口内最后一个按序到达的字节进行确认,例如接收窗口已经收到的字节为 {31, 32, 34, 35},其中 {31, 32} 按序到达,而 {34, 35} 就不是,因此只对字节 32 进行确认。发送方得到一个字节的确认之后,就知道这个字节之前的所有字节都已经被接收。 - -## TCP 可靠传输 - -TCP 使用超时重传来实现可靠传输:如果一个已经发送的报文段在超时时间内没有收到确认,那么就重传这个报文段。 - -一个报文段从发送到接收到确认所经过的时间称为往返时间 RTT,加权平均往返时间 RTTs 计算如下: - -$$ RTTs = (1-a)*(RTTs) + a*RTT $$ - -可以知道,超时时间 RTO 应该略大于 RRTs,TCP 使用的超时时间计算如下: - -$$ RTO = RTTs + 4*RTT_d $$ - -其中 RTTd 为偏差,它与新的 RRT 和 RRTs 有关。 - -## TCP 流量控制 - -流量控制是为了控制发送方发送速率,保证接收方来得及接收。 - -接收方发送的确认报文中的窗口字段可以用来控制发送方窗口大小,从而影响发送方的发送速率。例如将窗口字段设置为 0,则发送方不能发送数据。 - -## TCP 拥塞控制 - -如果网络出现拥塞,分组将会丢失,此时发送方会继续重传,从而导致网络拥塞程度更高。因此当出现拥塞时,应当控制发送方的速率。这一点和流量控制很像,但是出发点不同。流量控制是为了让接收方能来得及接受,而拥塞控制是为了降低整个网络的拥塞程度。 - -![](index_files/a69af9bb-b5ad-4896-862d-697e5ee4feb1.png) - -TCP 主要通过四种算法来进行拥塞控制:慢开始、拥塞避免、快重传、快恢复。发送方需要维护有一个叫做拥塞窗口(cwnd)的状态变量。注意拥塞窗口与发送方窗口的区别,拥塞窗口只是一个状态变量,实际决定发送方能发送多少数据的是发送方窗口。 - -为了便于讨论,做如下假设: - -1. 接收方有足够大的接收缓存,因此不会发生流量控制; -2. 虽然 TCP 的窗口基于字节,但是这里设窗口的大小单位为报文段。 - -![](index_files/346244ff-98c1-4f12-9a87-d0832e8c04cf.jpg) - -### 慢开始与拥塞避免 - -发送的最初执行慢开始,令 cwnd=1,发送方只能发送 1 个报文段;当收到确认后,将 cwnd 加倍,因此之后发送方能够发送的报文段为:2、4、8 ... - -注意到慢开始每个轮次都将 cwnd 加倍,这样会让 cwnd 增长速度非常快,从而使得发送方发送的速度增长速度过快,网络拥塞的可能也就更高。设置一个慢开始门限 ssthresh,当 cwnd >= ssthresh 时,进入拥塞避免,每个轮次只将 cwnd 加 1。 - -如果出现了超时,则令 ssthresh = cwnd / 2,然后重新执行慢开始。 - -### 快重传与快恢复 - -在接收方,要求每次接收到报文段都应该发送对已收到有序报文段的确认,例如已经接收到 M1 和 M2,此时收到 M4,应当发送对 M2 的确认。 - -在发送方,如果收到三个重复确认,那么可以确认下一个报文段丢失,例如收到三个 M2 ,则 M3 丢失。此时执行快重传,立即重传下一个报文段。 - -在这种情况下,只是丢失个别报文段,而不是网络拥塞,因此执行快恢复,令 ssthresh = cwnd / 2 ,cwnd = ssthresh,注意到此时直接进入拥塞避免。 - -![](index_files/b18d679b-c8e2-4564-88ee-7600090e46da.jpg) - -# 第六章 应用层* - -## 域名系统 DNS - -把主机名解析为 IP 地址。 - -被设计成分布式系统。 - -### 1. 层次结构 - -一个域名由多个层次构成,从上层到下层分别为顶级域名、二级域名、三级域名以及四级域名。所有域名可以画成一颗域名树。 - -![](index_files/c2117f61-1177-4768-bf33-cf4f950d911c.png) - -![](index_files/a4b162e5-db2a-4a27-b213-1fe481c5a06a.png) - -域名服务器可以分为以下四类: - -**(1) 根域名服务器**:解析顶级域名; - -**(2) 顶级域名服务器**:解析二级域名; - -**(3) 权限域名服务器**:解析区内的域名; - -区和域的概念不同,可以在一个域中划分多个区。图 b 在域 abc.com 中划分了两个区:abc.com 和 y.abc.com - -![](index_files/fc0c6b2d-68c7-4de8-aaaa-97355a4f0472.jpg) - -因此就需要两个权限域名服务器: - -![](index_files/8b335d94-c1ca-42e1-ad48-bb179d28a4f1.jpg) - -**(4) 本地域名服务器**:也称为默认域名服务器。可以在其中配置高速缓存。 - -### 2. 解析过程 - -主机向本地域名服务器解析的过程采用递归,而本地域名服务器向其它域名服务器解析可以使用递归和迭代两种方式。 - -迭代的方式下,本地域名服务器向一个域名服务器解析请求解析之后,结果返回到本地域名服务器,然后本地域名服务器继续向其它域名服务器请求解析;而递归地方式下,结果不是直接返回的,而是继续向前请求解析,最后的结果才会返回。 - -![](index_files/6bc61bb8-3b1c-4dc8-ac25-cef925ace0eb.jpg) - -## 文件传输协议 FTP - -FTP 在运输层使用 TCP,并且需要建立两个并行的 TCP 连接:控制连接和数据连接。控制连接在整个会话期间一直保持打开,而数据连接在数据传送完毕之后就关闭。控制连接使用端口号 21,数据连接使用端口号 20。 - -![](index_files/58633775-8584-4a01-ad3f-eee4d9a466e1.jpg) - -## 远程终端协议 TELNET - -TELNET 用于登录到远程主机上,并且远程主机上的输出也会返回。 - -TELNET 可以适应许多计算机和操作系统的差异,例如不同操作系统系统的换行符定义。 - -## 万维网 WWW - -见 HTTP 笔记。 - -## 电子邮件协议 - -一个电子邮件系统由三部分组成:用户代理、邮件服务器以及邮件发送协议和读取协议。其中发送协议常用 SMTP,读取协议常用 POP3 和 IMAP。 - -![](index_files/de1e46d2-748f-4da3-a29e-7de7bc840366.jpg) - -### POP3 - -POP3 的特点是只要用户从服务器上读取了邮件,就把该邮件删除。 - -### IMAP - -IMAP 协议中客户端和服务器上的邮件保持同步,如果不去手动删除邮件,那么服务器上的邮件也不会被删除。IMAP 这种做法可以让用户随时随地去访问服务器上的邮件。IMAP 协议也支持创建自定义的文件夹。 - -### SMTP - -SMTP 只能发送 ASCII 码,而互联网邮件扩充 MIME 可以发送二进制文件。MIME 并没有改动或者取代 SMTP,而是增加邮件主题的结构,定义了非 ASCII 码的编码规则。 - -![](index_files/ed5522bb-3a60-481c-8654-43e7195a48fe.png) - -## 动态主机配置协议 DHCP - -DHCP 提供了即插即用的连网方式,用户不再需要去手动配置 IP 地址等信息。 - -DHCP 配置的内容不仅是 IP 地址,还包括子网掩码、默认路由器 IP 地址、域名服务器的 IP 地址。 - -工作方式如下:需要 IP 地址的主机广播发送 DHCP 发现报文(将目的地址置为全 1,即 255.255.255.255:67,源地址设置为全 0,即 0.0.0.0:68),DHCP 服务器收到发现报文之后,则在 IP 地址池中取一个地址,发送 DHCP 提供报文给该主机。 - -## 点对点传输 P2P - -把某个文件分发的所有对等集合称为一个洪流。文件的数据单元称为文件块,它的大小是固定的。一个新的对等方加入某个洪流,一开始并没有文件块,但是能够从其它对等方中逐渐地下载到一些文件块,与此同时,它也为别的对等方上传一些文件块。 - -每个洪流都有一个基础设施,称为追踪器。当一个对等方加入洪流时,必须向追踪器登记,并周期性地通知追踪器它仍在洪流中。可以在任何时间加入和退出某个洪流。 - -一个新的对等方加入洪流时,追踪器会随机从洪流中选择若干个对等方,并让新对等方与这些对等方建立连接,把这些对等方称为相邻对等方。接收和发送文件块都是在相邻对等方中进行。 - -当一个对等方需要很多文件块时,通过使用最稀有优先的策略来取得文件块,也就是一个文件块在相邻对等方中副本最少,那么就优先请求这个文件块。 - -当很多对等方向同一个对等方请求文件块时,该对等方优先选择以最高速率向其发送文件块的对等方。 - -P2P 是一个分布式系统,任何时候都有对等方加入或者退出。使用分布式散列表 DHT,可以查找洪流中的资源和 IP 地址映射。 - -## Web 页面请求过程 - -1.  向 DNS 服务器发送 DNS 查询报文来解析域名。 - -2. 开始进行 HTTP 会话,需要先建立 TCP 连接。 - -3. 在运输层的传输过程中,HTTP 报文被封装进 TCP 中。HTTP 请求报文使用端口号 80,因为服务器监听的是 80 端口。连接建立之后,服务器会随机分配一个端口号给特定的客户端,之后的 TCP 传输都是用这个分配的端口号。 - -4. 在网络层的传输过程中,TCP 报文段会被封装进 IP 分组中,IP 分组经过路由选择,最后到达目的地。 - -5. 在链路层,IP 分组会被封装进 MAC 帧中,IP 地址解析成 MAC 地址需要使用 ARP。 - -6. 客户端发送 HTTP 请求报文,请求获取页面。 - -7. 服务器发送 HTTP 相应报文,客户端从而获取该页面。 - -8. 浏览器得到页面内容之后,解析并渲染,向用户展示页面。 - - -## 常用端口 - -| 应用层协议 | 端口号 | 运输层协议 | -| -- | -- | -- | -| DNS | 53 | UDP | -| FTP | 控制连接 21,数据连接 20 | TCP | -| TELNET | 23 | TCP | -| DHCP | 67 68 | UDP | -| HTTP | 80 | TCP | -| SMTP | 25 | TCP | -| POP3 | 110 | TCP | -| IMAP | 143 | TCP | - -# 参考资料 - -- 计算机网络 第七版 -- 自顶向下计算机网络 \ No newline at end of file diff --git a/notes/笔记/设计模式.md.txt b/notes/笔记/设计模式.md.txt deleted file mode 100644 index 0e9602fc..00000000 --- a/notes/笔记/设计模式.md.txt +++ /dev/null @@ -1,1703 +0,0 @@ -[TOC] - -# 第 1 章 设计模式入门 - -**1. 设计模式概念** - -设计模式不是代码,而是解决问题的方案,学习现有的设计模式可以做到经验复用。 - -拥有设计模式词汇,在沟通时就能用更少的词汇来讨论,并且不需要了解底层细节。 - -**2. 问题描述** - -设计不同种类的鸭子拥有不同的叫声和飞行方式。 - -**3. 简单实现方案** - -使用继承的解决方案如下,这种方案代码无法复用,如果两个鸭子类拥有同样的飞行方式,就有两份重复的代码。 - -![](index_files/144d28a0-1dc5-4aba-8961-ced5bc88428a.jpg) - -**4. 设计原则** - -**封装变化**在这里变化的是鸭子叫和飞行的行为方式。 - -**针对接口编程,而不是针对实现编程** 变量声明的类型为父类,而不是具体的某个子类。父类中的方法实现不在父类,而是在各个子类。程序在运行时可以动态改变变量所指向的子类类型。 - -运用这一原则,将叫和飞行的行为抽象出来,实现多种不同的叫和飞行的子类,让子类去实现具体的叫和飞行方式。 - -![](index_files/1c8ccf5c-7ecd-4b8a-b160-3f72a510ce26.png) - -**多用组合,少用继承** 组合也就是 has-a 关系,通过组合,可以在运行时动态改变实现,只要通过改变父类对象具体指向哪个子类即可。而继承就不能做到这些,继承体系在创建类时就已经确定。 - -运用这一原则,在 Duck 类中组合 FlyBehavior 和 QuackBehavior 类,performQuack() 和 performFly() 方法委托给这两个类去处理。通过这种方式,一个 Duck 子类可以根据需要去实例化 FlyBehavior 和 QuackBehavior 的子类对象,并且也可以动态地进行改变。 - -![](index_files/29574e6f-295c-444e-83c7-b162e8a73a83.jpg) - -**5. 整体设计图** - -![](index_files/e13833c8-e215-462e-855c-1d362bb8d4a0.jpg) - -**6. 模式定义** - -**策略模式** :定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。 - -**7. 实现代码** - -```java -public abstract class Duck { -    FlyBehavior flyBehavior; -    QuackBehavior quackBehavior; - -    public Duck(){ -    } - -    public void performFly(){ -        flyBehavior.fly(); -    } - -    public void setFlyBehavior(FlyBehavior fb){ -        flyBehavior = fb; -    } - -    public void performQuack(){ -        quackBehavior.quack(); -    } - -    public void setQuackBehavior(QuackBehavior qb){ -        quackBehavior = qb; -    } -} -``` -```java -public class MallarDuck extends Duck{ -    public MallarDuck(){ -        flyBehavior = new FlyWithWings(); -        quackBehavior = new Quack(); -    } -} -``` -```java -public interface FlyBehavior { -    void fly(); -} -``` -```java -public class FlyNoWay implements FlyBehavior{ -    @Override -    public void fly() { -        System.out.println("FlyBehavior.FlyNoWay"); -    } -} -``` -```java -public class FlyWithWings implements FlyBehavior{ -    @Override -    public void fly() { -        System.out.println("FlyBehavior.FlyWithWings"); -    } -} -``` -```java -public interface QuackBehavior { -    void quack(); -} -``` -```java -public class Quack implements QuackBehavior{ -    @Override -    public void quack() { -        System.out.println("QuackBehavior.Quack"); -    } -} -``` -```java -public class MuteQuack implements QuackBehavior{ -    @Override -    public void quack() { -        System.out.println("QuackBehavior.MuteQuack"); -    } -} -``` -```java -public class Squeak implements QuackBehavior{ -    @Override -    public void quack() { -        System.out.println("QuackBehavior.Squeak"); -    } -} -``` -```java -public class MiniDuckSimulator { -    public static void main(String[] args) { -        Duck mallarDuck = new MallarDuck(); -        mallarDuck.performQuack(); -        mallarDuck.performFly(); -        mallarDuck.setFlyBehavior(new FlyNoWay()); -        mallarDuck.performFly(); -    } -} -``` -执行结果 -```html -QuackBehavior.Quack -FlyBehavior.FlyWithWings -FlyBehavior.FlyNoWay -``` - -# 第 2 章 观察者模式 - -**1. 模式定义** - -定义了对象之间的一对多依赖,当一个对象改变状态时,它的所有依赖者都会受到通知并自动更新。主题(Subject)是被观察的对象,而其所有依赖者(Observer)成为观察者。 - -![](index_files/26cb5e7e-6fa3-44ad-854e-fe24d1a5278c.jpg) - -**2. 模式类图** - -主题中具有注册和移除观察者,并通知所有注册者的功能,主题是通过维护一张观察者列表来实现这些操作的。 - -观察者拥有一个主题对象的引用,因为注册、移除还有数据都在主题当中,必须通过操作主题才能完成相应功能。 - -![](index_files/5c558190-fccd-4b5e-98ed-1896653fc97f.jpg) - -**3. 问题描述** - -天气数据布告板会在天气信息发生改变时更新其内容,布告板有多个,并且在将来会继续增加。 - -**4. 解决方案类图** - -![](index_files/760a5d63-d96d-4dd9-bf9a-c3d126b2f401.jpg) - -**5. 设计原则** - -**为交互对象之间的松耦合设计而努力** 当两个对象之间松耦合,它们依然可以交互,但是不太清楚彼此的细节。由于松耦合的两个对象之间互相依赖程度很低,因此系统具有弹性,能够应对变化。 - -**6. 实现代码** - -```java -public interface Subject { -    public void resisterObserver(Observer o); -    public void removeObserver(Observer o); -    public void notifyObserver(); -} -``` -```java -import java.util.ArrayList; -import java.util.List; - -public class WeatherData implements Subject { -    private List observers; -    private float temperature; -    private float humidity; -    private float pressure; - -    public WeatherData() { -        observers = new ArrayList<>(); -    } - -    @Override -    public void resisterObserver(Observer o) { -        observers.add(o); -    } - -    @Override -    public void removeObserver(Observer o) { -        int i = observers.indexOf(o); -        if (i >= 0) { -            observers.remove(i); -        } -    } - -    @Override -    public void notifyObserver() { -        for (Observer o : observers) { -            o.update(temperature, humidity, pressure); -        } -    } - -    public void setMeasurements(float temperature, float humidity, float pressure) { -        this.temperature = temperature; -        this.humidity = humidity; -        this.pressure = pressure; -        notifyObserver(); -    } -} -``` -```java -public interface Observer { -    public void update(float temp, float humidity, float pressure); -} -``` -```java -public class CurrentConditionsDisplay implements Observer { -    private Subject weatherData; - -    public CurrentConditionsDisplay(Subject weatherData) { -        this.weatherData = weatherData; -        weatherData.resisterObserver(this); -    } - -    @Override -    public void update(float temp, float humidity, float pressure) { -        System.out.println("CurrentConditionsDisplay.update:" + temp + " " + humidity + " " + pressure); -    } -} -``` -```java -public class StatisticsDisplay implements Observer { -    private Subject weatherData; - -    public StatisticsDisplay(Subject weatherData) { -        this.weatherData = weatherData; -        weatherData.resisterObserver(this); -    } - -    @Override -    public void update(float temp, float humidity, float pressure) { -        System.out.println("StatisticsDisplay.update:" + temp + " " + humidity + " " + pressure); -    } -} -``` -```java -public class WeatherStation { -    public static void main(String[] args) { -        WeatherData weatherData = new WeatherData(); -        CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay(weatherData); -        StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData); - -        weatherData.setMeasurements(0, 0, 0); -        weatherData.setMeasurements(1, 1, 1); -    } -} -``` -执行结果 -```html -CurrentConditionsDisplay.update:0.0 0.0 0.0 -StatisticsDisplay.update:0.0 0.0 0.0 -CurrentConditionsDisplay.update:1.0 1.0 1.0 -StatisticsDisplay.update:1.0 1.0 1.0 -``` - -# 第 3 章 装饰模式 - -**1. 问题描述** - -设计不同种类的饮料,并且每种饮料可以动态添加新的材料,比如可以添加牛奶。计算一种饮料的价格。 - -**2. 模式定义** - -动态地将责任附加到对象上。在扩展功能上,装饰者提供了比继承更有弹性的替代方案。 - -下图中 DarkRoast 对象被 Mocha 包裹,Mocha 对象又被 Whip 包裹,并且他们都继承自相同父类,都有 cost() 方法,但是外层对象的 cost() 方法实现调用了内层对象的 cost() 方法。因此,如果要在 DarkRoast 上添加 Mocha,那么只需要用 Mocha 包裹 DarkRoast,如果还需要 Whip ,就用 Whip 包裹 Mocha,最后调用 cost() 方法能把三种对象的价格都包含进去。 - -![](index_files/41a4cb30-f393-4b3b-abe4-9941ccf8fa1f.jpg) - -**3. 模式类图** - -装饰者和具体组件都继承自组件类型,其中具体组件的方法实现不需要依赖于其它对象,而装饰者拥有一个组件类型对象,这样它可以装饰其它装饰者或者具体组件。所谓装饰,就是把这个装饰者套在被装饰的对象之外,从而动态扩展被装饰者的功能。装饰者的方法有一部分是自己的,这属于它的功能,然后调用被装饰者的方法实现,从而也保留了被装饰者的功能。可以看到,具体组件应当是装饰层次的最低层,因为只有具体组件有直接实现而不需要委托给其它对象去处理。 - -![](index_files/3dc454fb-efd4-4eb8-afde-785b2182caeb.jpg) - -**4. 问题解决方案的类图** - -![](index_files/9c997ac5-c8a7-44fe-bf45-2c10eb773e53.jpg) - -**5. 设计原则** - -**类应该对扩展开放,对修改关闭。**也就是添加新功能时不需要修改代码。在本章问题中该原则体现在,在饮料中添加新的材料,而不需要去修改饮料的代码。观察则模式也符合这个原则。不可能所有类都能实现这个原则,应当把该原则应用于设计中最有可能改变的地方。 - -**6. Java I/O 中的装饰者模式** - -![](index_files/2a40042a-03c8-4556-ad1f-72d89f8c555c.jpg) - -**7. 代码实现** - -```java -public interface Beverage { -    public double cost(); -} -``` -```java -public class HouseBlend implements Beverage{ -    @Override -    public double cost() { -        return 1; -    } -} -``` -```java -public class DarkRoast implements Beverage{ -    @Override -    public double cost() { -        return 1; -    } -} -``` -```java -public abstract class CondimentDecorator implements Beverage{ -    protected Beverage beverage; -} -``` -```java -public class Mocha extends CondimentDecorator { - -    public Mocha(Beverage beverage) { -        this.beverage = beverage; -    } - -    @Override -    public double cost() { -        return 1 + beverage.cost(); -    } -} -``` -```java -public class Milk extends CondimentDecorator { - -    public Milk(Beverage beverage) { -        this.beverage = beverage; -    } - -    @Override -    public double cost() { -        return 1 + beverage.cost(); -    } -} -``` -```java -public class StartbuzzCoffee { -    public static void main(String[] args) { -        Beverage beverage = new HouseBlend(); -        beverage = new Mocha(beverage); -        beverage = new Milk(beverage); -        System.out.println(beverage.cost()); -    } -} -``` - -输出 - -```html -3.0 -``` - -# 第 4 章 工厂模式 - -## 4.1 简单工厂 - -**1. 问题描述** - -有不同的 Pizza,根据不同的情况用不同的子类实例化一个 Pizza 对象。 - -**2. 定义** - -简单工厂不是设计模式,更像是一种编程习惯。在实例化一个超类的对象时,可以用它的所有子类来进行实例化,要根据具体需求来决定使用哪个子类。在这种情况下,把实例化的操作放到工厂来中,让工厂类来决定应该用哪个子类来实例化。这样做把客户对象和具体子类的实现解耦,客户对象不再需要知道有哪些子类以及实例化哪个子类。因为客户类往往有多个,如果不使用简单工厂,那么所有的客户类都要知道所有子类的细节,一旦子类发生改变,例如增加子类,那么所有的客户类都要发生改变。 - -![](index_files/c470eb9b-fb05-45c5-8bb7-1057dc3c16de.jpg) - -**3. 解决方案类图** - -![](index_files/dc3e704c-7c57-42b8-93ea-ddd068665964.jpg) - - -**4. 代码实现** - -```java -public interface Pizza { -    public void make(); -} -``` -```java -public class CheesePizza implements Pizza{ -    @Override -    public void make() { -        System.out.println("CheesePizza"); -    } -} -``` -```java -public class GreekPizza implements Pizza{ -    @Override -    public void make() { -        System.out.println("GreekPizza"); -    } -} -``` -```java -public class SimplePizzaFactory { -    public Pizza createPizza(String type) { -        if (type.equals("cheese")) { -            return new CheesePizza(); -        } else if (type.equals("greek")) { -            return new GreekPizza(); -        } else { -            throw new UnsupportedOperationException(); -        } -    } -} -``` -```java -public class PizzaStore { -    public static void main(String[] args) { -        SimplePizzaFactory simplePizzaFactory = new SimplePizzaFactory(); -        Pizza pizza = simplePizzaFactory.createPizza("cheese"); -        pizza.make(); -    } -} -``` - -运行结果 - -```java -CheesePizza -``` - -## 4.2 工厂方法模式 - -**1. 问题描述** - -每个地区的 Pizza 店虽然种类相同,但是都有自己的风味,需要单独区分。例如,一个客户点了纽约的 cheese 种类的 Pizza 和在芝加哥点的相同种类的 Pizza 是不同的。 - -**2. 模式定义** - -定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。 - -**3. 模式类图** - -在简单工厂中,创建对象的是另一个类,而在工厂方法中,是由子类来创建对象。工厂方法常用在在每个子类都有自己的一组产品类。可以为每个子类创建单独的简单工厂,但是把简单工厂中创建对象的代码放到子类中来可以减少类的数目,因为子类不算是产品类,因此完全可以这么做。 - -![](index_files/903093ec-acc8-4f9b-bf2c-b990b9a5390c.jpg) - -**4. 解决方案类图** - -![](index_files/664f8901-5dc7-4644-a072-dad88cc5133a.jpg) - -**5. 代码实现** - -```java -public interface Pizza { -    public void make(); -} -``` -```java -public interface PizzaStore { -    public Pizza orderPizza(String item); -} -``` -```java -public class NYStyleCheesePizza implements Pizza{ -    @Override -    public void make() { -        System.out.println("NYStyleCheesePizza is making.."); -    } -} -``` -```java -public class NYStyleVeggiePizza implements Pizza { -    @Override -    public void make() { -        System.out.println("NYStyleVeggiePizza is making.."); -    } -} -``` -```java -public class ChicagoStyleCheesePizza implements Pizza{ -    @Override -    public void make() { -        System.out.println("ChicagoStyleCheesePizza is making.."); -    } -} -``` -```java -public class ChicagoStyleVeggiePizza implements Pizza{ -    @Override -    public void make() { -        System.out.println("ChicagoStyleVeggiePizza is making.."); -    } -} -``` -```java -public class NYPizzaStore implements PizzaStore { -    @Override -    public Pizza orderPizza(String item) { -        Pizza pizza = null; -        if (item.equals("cheese")) { -            pizza = new NYStyleCheesePizza(); -        } else if (item.equals("veggie")) { -            pizza = new NYStyleVeggiePizza(); -        } else { -            throw new UnsupportedOperationException(); -        } -        pizza.make(); -        return pizza; -    } -} -``` -```java -public class ChicagoPizzaStore implements PizzaStore { -    @Override -    public Pizza orderPizza(String item) { -        Pizza pizza = null; -        if (item.equals("cheese")) { -            pizza = new ChicagoStyleCheesePizza(); -        } else if (item.equals("veggie")) { -            pizza = new ChicagoStyleVeggiePizza(); -        } else { -            throw new UnsupportedOperationException(); -        } -        pizza.make(); -        return pizza; -    } -} -``` -```java -public class PizzaTestDrive { -    public static void main(String[] args) { -        PizzaStore nyStore = new NYPizzaStore(); -        nyStore.orderPizza("cheese"); -        PizzaStore chicagoStore = new ChicagoPizzaStore(); -        chicagoStore.orderPizza("cheese"); -    } -} -``` - -运行结果 - -```html -NYStyleCheesePizza is making.. -ChicagoStyleCheesePizza is making.. -``` - -## 4.3 抽象工厂模式 - -**1. 设计原则** - -**依赖倒置原则**:要依赖抽象,不要依赖具体类。听起来像是针对接口编程,不针对实现编程,但是这个原则说明了:不能让高层组件依赖底层组件,而且,不管高层或底层组件,两者都应该依赖于抽象。例如,下图中 PizzaStore 属于高层组件,但是它依赖于底层组件 Pizza 的具体类。如果依赖的是 Pizza 的抽象类,那么就可以不用关心 Pizza 的具体实现细节。 - -![](index_files/ddf72ca9-c0be-49d7-ab81-57a99a974c8e.jpg) - -**2. 模式定义** - -提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。 - -**3. 模式类图** - -抽象工厂模式创建的是对象家族,也就是很多对象而不是一个对象,并且这些对象是相关的,也就是说必须一起创建出来。而工厂模式只是用于创建一个对象,这和抽象工厂模式有很大不同。并且,抽象工厂模式也用到了工厂模式来创建单一对象,在类图左部,AbstractFactory 中的 CreateProductA 和 CreateProductB 方法都是让子类来实现,这两个方法单独来看就是在创建一个对象,这符合工厂模式的定义。至于创建对象的家族这一概念是在 Client 体现,Client 要通过 AbstractFactory 同时调用两个方法来创建出两个对象,在这里这两个对象就有很大的相关性,Client 需要这两个对象的协作才能完成任务。从高层次来看,抽象工厂使用了组合,即 Cilent 组合了 AbstractFactory ,而工厂模式使用了继承。 - -![](index_files/d301774f-e0d2-41f3-95f4-bfe39859b52e.jpg) - -**4. 解决方案类图** - -![](index_files/8785dabd-1285-4bd0-b3aa-b05cc060a24a.jpg) - -**5. 代码实现** - -```java -public interface Dough { -    public String doughType(); -} -``` -```java -public class ThickCrustDough implements Dough{ - -    @Override -    public String doughType() { -        return "ThickCrustDough"; -    } -} -``` -```java -public class ThinCrustDough implements Dough { -    @Override -    public String doughType() { -        return "ThinCrustDough"; -    } -} -``` -```java -public interface Sauce { -    public String sauceType(); -} -``` -```java -public class MarinaraSauce implements Sauce { -    @Override -    public String sauceType() { -        return "MarinaraSauce"; -    } -} -``` -```java -public class PlumTomatoSauce implements Sauce { -    @Override -    public String sauceType() { -        return "PlumTomatoSauce"; -    } -} -``` -```java -public interface PizzaIngredientFactory { -    public Dough createDough(); -    public Sauce createSauce(); -} -``` -```java -public class NYPizzaIngredientFactory implements PizzaIngredientFactory{ -    @Override -    public Dough createDough() { -        return new ThickCrustDough(); -    } - -    @Override -    public Sauce createSauce() { -        return new MarinaraSauce(); -    } -} -``` -```java -public class ChicagoPizzaIngredientFactory implements PizzaIngredientFactory{ -    @Override -    public Dough createDough() { -        return new ThinCrustDough(); -    } - -    @Override -    public Sauce createSauce() { -        return new PlumTomatoSauce(); -    } -} -``` -```java -public class NYPizzaStore { -    private PizzaIngredientFactory ingredientFactory; - -    public NYPizzaStore() { -        ingredientFactory = new NYPizzaIngredientFactory(); -    } - -    public void makePizza() { -        Dough dough = ingredientFactory.createDough(); -        Sauce sauce = ingredientFactory.createSauce(); -        System.out.println(dough.doughType()); -        System.out.println(sauce.sauceType()); -    } -} -``` -```java -public class NYPizzaStoreTestDrive { -    public static void main(String[] args) { -        NYPizzaStore nyPizzaStore = new NYPizzaStore(); -        nyPizzaStore.makePizza(); -    } -} -``` - -运行结果 - -```html -ThickCrustDough -MarinaraSauce -``` - -# 第 5 章 单件模式 - -**1. 模式定义** - -确保一个类只有一个实例,并提供了一个全局访问点。 - -**2. 模式类图** - -单件模式的 Java 实现用一个私有构造器、一个私有静态变量以及一个公有静态函数,该函数返回私有变量,使得所有通过该函数获取的对象都指向这个唯一的私有静态变量。 - -![](index_files/59aff6c1-8bc5-48e4-9e9c-082baeb2f274.jpg) - -**3. 经典实现** - -以下实现中,私有静态变量被延迟化实例化,这样做的好处是,如果没有用到该类,那么就不会创建该私有静态变量,从而节约资源。这个实现在多线程环境下是不安全的,因为多个线程能够同时进入 if(uniqueInstance == null) 内的语句块,那么就会多次实例化 uniqueInstance 私有静态变量。 - -```java -public class Singleton { - -    private static Singleton uniqueInstance; - -    private Singleton() { -    } - -    public static Singleton getUniqueInstance() { -        if (uniqueInstance == null) { -            uniqueInstance = new Singleton(); -        } -        return uniqueInstance; -    } -} -``` - -**4. 线程不安全问题的解决方案一** - -只需要对 getUniqueInstance() 方法加锁,就能让该方法一次只能一个线程访问,从而避免了对 uniqueInstance 变量进行多次实例化的问题。但是这样有一个问题是一次只能一个线程进入,性能上会有一定的浪费。 - -```java -    public static synchronized Singleton getUniqueInstance() { -        if (uniqueInstance == null) { -            uniqueInstance = new Singleton(); -        } -        return uniqueInstance; -    } -``` - -**5. 线程不安全问题的解决方案二** - -不用延迟实例化,采用直接实例化。 - -```java -private static Singleton uniqueInstance = new Singleton(); -``` - -**6. 线程不安全问题的解决方案三** - -考虑第一个解决方案,它是直接对 getUniqueInstance() 方法进行加锁,而实际上只需要对 uniqueInstance = new Singleton(); 这条语句加锁即可。使用两个条件语句来判断 uniqueInstance 是否已经实例化,如果没有实例化才需要加锁。 - -```java -public class Singleton { - -    private volatile static Singleton uniqueInstance; - -    private Singleton() { -    } - -    public static synchronized Singleton getUniqueInstance() { -        if (uniqueInstance == null) { -            synchronized (Singleton.class) { -                if (uniqueInstance == null) { -                    uniqueInstance = new Singleton(); -                } -            } -        } -        return uniqueInstance; -    } -} -``` - -# 第 6 章 命令模式 - -**1. 问题描述** - -设计一个遥控器,它有很多按钮,每个按钮可以发起一个命令,让一个家电完成相应操作。有非常多的家电,并且也会增加家电。 - -![](index_files/7b8f0d8e-a4fa-4c9d-b9a0-3e6a11cb3e33.jpg) - -![](index_files/c3ca36b2-8459-4cf1-98b0-cc95a0e94f20.jpg) - -**2. 模式定义** - -将命令封装成对象,以便使用不同的命令来参数化其它对象。 - -**3. 模式类图** - -![](index_files/1e09d75f-6268-4425-acf8-8ecd1b4a0ef3.jpg) - -**4. 解决方案类图** - -Invoker 是遥控器,它可以设置命令,并且调用命令对象的 execute() 方法。Receiver 是电灯,是命令真正的执行者。ConcreteCommand 类组合了一个 Receiver 对象,命令的执行委托给 Receiver 对象来处理,也就是 LightOnCommand 命令的 excute 方法委托给 Light 对象来处理,Light 对象通过调用 on() 方法来完成操作。Invoker 不是 Client 对象,是因为命令的创建不是在 Invoker 中完成的,因此需要额外的 Client 对象来处理这些操作。 - -![](index_files/5ef94f62-98ce-464d-a646-842d9c72c8b8.jpg) - -**5. 代码实现** - -```java -public interface Command { -    public void execute(); -} -``` -```java -public class Light { - -    public void on() { -        System.out.println("Light is on!"); -    } - -    public void off() { -        System.out.println("Light is off!"); -    } -} -``` -```java -public class LightOnCommand implements Command{ -    Light light; - -    public LightOnCommand(Light light) { -        this.light = light; -    } - -    @Override -    public void execute() { -        light.on(); -    } -} -``` -```java -/** - * 遥控器类 - */ -public class SimpleRemoteControl { -    Command slot; - -    public SimpleRemoteControl() { - -    } - -    public void setCommand(Command command) { -        this.slot = command; -    } - -    public void buttonWasPressed() { -        slot.execute(); -    } - -} -``` -```java -public class RemoteLoader { -    public static void main(String[] args) { -        SimpleRemoteControl remote = new SimpleRemoteControl(); -        Light light = new Light(); -        LightOnCommand lightOnCommand = new LightOnCommand(light); -        remote.setCommand(lightOnCommand); -        remote.buttonWasPressed(); -    } -} -``` - -输出 - -```html -Light is on! -``` - -# 第 7 章 适配器模式与外观模式 - -## 7.1 适配器模式 - -**1. 模式定义** - -将一个类的接口,转换为客户期望的另一个接口。适配器让原本不兼容的类可以合作无间。 - -![](index_files/8e8ba824-7a9e-4934-a212-e6a41dcc1602.jpg) - -**2. 模式类图** - -有两种适配器模式的实现,一种是对象方式,一种是类方式。对象方式是通过组合的方法,让适配器类(Adapter)拥有一个待适配的对象(Adaptee),从而把相应的处理委托给待适配的对象。类方式用到多重继承,Adapter 可以看成 Target 和 Adaptee 类型,先把它当成 Adaptee 类型然后实例化一个 Adapter 对象,再把它当成 Target 类型的,这样 Client 就可以把这个对象当成 Target 的对象来处理。 - -![](index_files/253bd869-ea48-4092-9aed-6906ccb2f3b0.jpg) - -![](index_files/a797959a-0ed5-475b-8d97-df157c672019.jpg) - -**3. 问题描述** - -让鸭子(Duck)适配火鸡(Turkey),Duck 有 quack() 方法,而 Turkey 只有 gobble() 方法。也就是要让 Turkey 也有 Duck 的 quack() 方法。 - -**4. 解决方案类图** - -![](index_files/1a511c76-bb6b-40ab-b8aa-39eeb619d673.jpg) - -**5. 代码实现** - -```java -public interface Duck { -    public void quack(); -    public void fly(); -} -``` -```java -public interface Turkey { -    public void gobble(); -    public void fly(); -} -``` -```java -public class WildTurkey implements Turkey{ -    @Override -    public void gobble() { -        System.out.println("gobble!"); -    } - -    @Override -    public void fly() { -        System.out.println("fly!"); -    } -} -``` -```java -public class TurkeyAdapter implements Duck{ -    Turkey turkey; - -    public TurkeyAdapter(Turkey turkey) { -        this.turkey = turkey; -    } - -    @Override -    public void quack() { -        turkey.gobble(); -    } - -    @Override -    public void fly() { -        turkey.fly(); -    } -} -``` -```java -public class DuckTestDrive { -    public static void main(String[] args) { -        Turkey turkey = new WildTurkey(); -        Duck duck = new TurkeyAdapter(turkey); -        duck.quack(); -        duck.fly(); -    } -} -``` - -运行结果 -```html -gobble! -fly! -``` - -## 7.2 外观模式 - -**1. 模式定义** - -提供了一个统一的接口,用来访问子系统中的一群接口。外观定义了一个高层接口,让子系统更容易使用。 - -**2. 模式类图** - -![](index_files/78f2314e-2643-41df-8f3d-b7e28294094b.jpg) - -**3. 问题描述** - -家庭影院中有众多电器,当要进行观看电影时需要对很多电器进行操作。要求简化这些操作,使得家庭影院类只提供一个简化的接口,例如提供一个看电影的接口而不用具体操作众多电器。 - -![](index_files/106f5585-b2e7-4718-be5d-3b322d1ef42a.jpg) - -**4. 解决方案类图** - -![](index_files/25387681-89f8-4365-a2fa-83b86449ee84.jpg) - -**5. 设计原则** - -**最少知识原则**:只和你的密友谈话。也就是应当使得客户对象所需要交互的对象应当尽可能少。 - -# 第 8 章 模板方法模式 - -**1. 模式定义** - -在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。 - -模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。 - -**2. 模式类图** - -模板方法 templateMethod() 定义了算法的骨架,确定了 primitiveOperation1() 和 primitiveOperation2() 方法执行的顺序,而 primitiveOperation1() 和 primitiveOperation2() 让子类去具体实现。 - -![](index_files/ed62f400-192c-4185-899b-187958201f0c.jpg) - -**3. 问题描述** - -冲咖啡和冲茶都有类似的流程,但是某些步骤会有点不一样。 - -![](index_files/d8f873fc-00bc-41ee-a87c-c1b4c0172844.png) - -**4. 解决方案类图** - -![](index_files/aa20c123-b6b5-432a-83d3-45dc39172192.jpg) - -**5. 设计原则** - -**好莱坞原则**:别调用(打电话给)我们,我们会调用(打电话给)你。这一原则可以防止依赖腐败,即防止高层组件依赖底层组件,底层组件又依赖高层组件。该原则在模板方法的体现为,只有父类会调用子类,子类不会调用父类。 - -**6. 钩子** - -钩子(hock):某些步骤在不同实现中可有可无,可以先定义一个什么都不做的方法,把它加到模板方法 templteMethod() 中,如果子类需要它就覆盖默认实现并加上自己的实现。 - -**7. 代码实现** - -```java -public abstract class CaffeineBeverage { - -    final void prepareRecipe(){ -        boilWater(); -        brew(); -        pourInCup(); -        addCondiments(); -    } - -    abstract void brew(); - -    abstract void addCondiments(); - -    void boilWater(){ -        System.out.println("boilWater"); -    } - -    void pourInCup(){ -        System.out.println("pourInCup"); -    } -} -``` -```java -public class Coffee extends CaffeineBeverage{ -    @Override -    void brew() { -        System.out.println("Coffee.brew"); -    } - -    @Override -    void addCondiments() { -        System.out.println("Coffee.addCondiments"); -    } -} -``` -```java -public class Tea extends CaffeineBeverage{ -    @Override -    void brew() { -        System.out.println("Tea.brew"); -    } - -    @Override -    void addCondiments() { -        System.out.println("Tea.addCondiments"); -    } -} -``` -```java -public class CaffeineBeverageTestDrive { -    public static void main(String[] args) { -        CaffeineBeverage caffeineBeverage = new Coffee(); -        caffeineBeverage.prepareRecipe(); -        System.out.println("-----------"); -        caffeineBeverage = new Tea(); -        caffeineBeverage.prepareRecipe(); -    } -} -``` - -运行结果 - -```html -boilWater -Coffee.brew -pourInCup -Coffee.addCondiments ------------ -boilWater -Tea.brew -pourInCup -Tea.addCondiments -``` - -# 第 9 章 迭代器和组合模式 - -## 9.1 迭代器模式 - -**1. 模式定义** - -提供一种顺序访问一个聚合对象中的各个元素的方法,而又不暴露其内部的表示。 - -**2. 模式类图** - -客户类拥有一个聚合对象和迭代器对象,迭代器对象是聚合对象生成的。只需要迭代器定义好移动的操作,就可以让聚合对象能够顺序遍历。 - -![](index_files/439deca7-fed0-4c89-87e5-7088d10f1fdb.jpg) - -**3. 代码实现** - -```java -public class Aggregate { - -    private int[] items; - -    public Aggregate() { -        items = new int[10]; -        for (int i = 0; i < items.length; i++) { -            items[i] = i; -        } -    } - -    public Iterator createIterator() { -        return new ConcreteIterator(items); -    } - -} -``` -```java -public interface Iterator { -    boolean hasNext(); -    int next(); -} -``` -```java -public class ConcreteIterator implements Iterator { - -    private int[] items; -    private int position = 0; - -    public ConcreteIterator(int[] items) { -        this.items = items; -    } - -    @Override -    public boolean hasNext() { -        return position < items.length; -    } - -    @Override -    public int next() { -        return items[position++]; -    } -} -``` -```java -public class Client { -    public static void main(String[] args) { -        Aggregate aggregate = new Aggregate(); -        Iterator iterator = aggregate.createIterator(); -        while(iterator.hasNext()){ -            System.out.println(iterator.next()); -        } -    } -} -``` -运行结果 -```html -0 -1 -2 -3 -4 -5 -6 -7 -8 -9 -``` - -## 9.2 Java 内置的迭代器 - -**1. 实现接口** - -Java 中已经有了 Iterator 接口,在使用 Java 实现时,需要让聚合对象实现 Iterable 接口,该接口有一个 iterator() 方法会返回一个 Iterator 对象。使用 Java 内置的迭代器实现,客户对象可以使用 foreach 循环来遍历聚合对象中的每个元素。 - -**2. 代码实现** - -```java -import java.util.Iterator; - -public class Aggregate implements Iterable{ - -    private int[] items; - -    public Aggregate() { -        items = new int[10]; -        for (int i = 0; i < items.length; i++) { -            items[i] = i; -        } -    } - -    @Override -    public Iterator iterator() { -        return new ConcreteIterator(items); -    } -} -``` -```java -import java.util.Iterator; - -public class ConcreteIterator implements Iterator { - -    private int[] items; -    private int position = 0; - -    public ConcreteIterator(int[] items) { -        this.items = items; -    } - -    @Override -    public boolean hasNext() { -        return position < items.length; -    } - -    @Override -    public Integer next() { -        return items[position++]; -    } -} -``` -```java -public class Client { -    public static void main(String[] args) { -        Aggregate aggregate = new Aggregate(); -        for (int item : aggregate) { -            System.out.println(item); -        } -    } -} -``` - -## 9.3 组合模式 - -**1. 设计原则** - -一个类应该只有一个引起改变的原因。 - -**2. 模式定义** - -允许将对象组合成树形结构来表现“整体 / 部分”层次结构。 - -组合能让客户以一致的方式处理个别对象以及对象组合。 - -**3. 模式类图** - -由于组合(Composite)类拥有一个组件(Component)对象,因此组合对象位于树形结构的中间,它还可以继续操作这个组件对象,并忽略组件对象的具体类型。 - -![](index_files/f99c019e-7e91-4c2e-b94d-b031c402dcb5.jpg) - -**4. 代码实现** - -```java -public abstract class Component { -    protected String name; - -    public Component(String name) { -        this.name = name; -    } - -    abstract public void addChild(Component component); - -    public void print() { -        print(0); -    } - -    abstract protected void print(int level); -} -``` -```java -public class Leaf extends Component { -    public Leaf(String name) { -        super(name); -    } - -    @Override -    public void addChild(Component component) { -        throw new UnsupportedOperationException(); // 牺牲透明性换取单一职责原则 , 这样就不用考虑是叶子节点还是组合节点 -    } - -    @Override -    protected void print(int level) { -        for (int i = 0; i < level; i++) { -            System.out.print("--"); -        } -        System.out.println("left:" + name); -    } -} -``` -```java -import java.util.ArrayList; -import java.util.List; - -public class Composite extends Component { - -    private List childs; - -    public Composite(String name) { -        super(name); -        childs = new ArrayList<>(); -    } - -    @Override -    public void addChild(Component component) { -        childs.add(component); -    } - -    @Override -    protected void print(int level) { -        for (int i = 0; i < level; i++) { -            System.out.print("--"); -        } -        System.out.println("Composite:" + name); -        for (Component component : childs) { -            component.print(level + 1); -        } -    } -} -``` -```java -public class Client { -    public static void main(String[] args) { -        Composite root = new Composite("root"); -        Component node1 = new Leaf("1"); -        Component node2 = new Composite("2"); -        Component node3 = new Leaf("3"); -        root.addChild(node1); -        root.addChild(node2); -        root.addChild(node3); -        Component node21 = new Leaf("21"); -        Component node22 = new Composite("22"); -        node2.addChild(node21); -        node2.addChild(node22); -        Component node221 = new Leaf("221"); -        node22.addChild(node221); -        root.print(); -    } -} -``` -运行结果 - -```html -Composite:root ---left:1 ---Composite:2 -----left:21 -----Composite:22 -------left:221 ---left:3 -``` - -# 第 10 章 状态模式 - -**1. 模式定义** - -允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。 - -状态模式的类图和策略模式一样,并且都是能够动态改变对象的行为。但是状态模式是通过状态对象的状态转移来改变客户对象组合的状态对象,而策略模式是通过客户对象本身的决策来改变组合的策略对象。例如,状态模式下,客户对象委托状态对象进行一个处理操作,那么状态对象有可能发生状态转移,使得客户对象拥有的状态对象发生改变。状态对象组合了客户对象,状态转移是状态对象通过改变客户对象所组合的状态对象实现的。 - -**2. 模式类图** - -![](index_files/c28fd93a-0d55-4a19-810f-72652feee00d.jpg) - -**3. 问题描述** - -糖果销售机有多种状态,每种状态下销售机有不同的行为,状态可以发生转移,使得销售机的行为也发生改变。 - -![](index_files/f7d880c9-740a-4a16-ac6d-be502281b4b2.jpg) - -**4. 直接解决方案** - -在糖果机的每个操作函数里面,判断当前的状态,根据不同的状态进行不同的处理,并且发生不同的状态转移。这种解决方案把所有的实现细节都放到客户类,这样在新增状态的时候就要去修改客户类的代码。 - -![](index_files/62ebbb63-8fd7-4488-a866-76a9dc911662.png) - -**5. 使用状态模式的解决方案** - -状态的转移被移到状态类里面,客户类的每个操作只需要委托给状态类即可,而不需要知道当前是什么状态以及状态时如何进行转移的。 - - -**6. 代码实现** - -```java -public interface State { -    /** -     * 投入25 分钱 -     */ -    void insertQuarter(); - -    /** -     * 退回25 分钱 -     */ -    void ejectQuarter(); - -    /** -     * 转动曲柄 -     */ -    void turnCrank(); - -    /** -     * 发放糖果 -     */ -    void dispense(); -} -``` -```java -public class HasQuarterState implements State{ -    private GumballMachine gumballMachine; - -    public HasQuarterState(GumballMachine gumballMachine){ -        this.gumballMachine = gumballMachine; -    } - -    @Override -    public void insertQuarter() { -        System.out.println("You can't insert another quarter"); -    } - -    @Override -    public void ejectQuarter() { -        System.out.println("Quarter returned"); -        gumballMachine.setState(gumballMachine.getNoQuarterState()); -    } - -    @Override -    public void turnCrank() { -        System.out.println("You turned..."); -        gumballMachine.setState(gumballMachine.getSoldState()); -    } - -    @Override -    public void dispense() { -        System.out.println("No gumball dispensed"); -    } -} -``` -```java -public class NoQuarterState implements State { -    GumballMachine gumballMachine; - -    public NoQuarterState(GumballMachine gumballMachine) { -        this.gumballMachine = gumballMachine; -    } - -    @Override -    public void insertQuarter() { -        System.out.println("You insert a quarter"); -        gumballMachine.setState(gumballMachine.getHasQuarterState()); -    } - -    @Override -    public void ejectQuarter() { -        System.out.println("You haven't insert a quarter"); -    } - -    @Override -    public void turnCrank() { -        System.out.println("You turned, but there's no quarter"); -    } - -    @Override -    public void dispense() { -        System.out.println("You need to pay first"); -    } -} -``` -```java -public class SoldOutState implements State { - -    GumballMachine gumballMachine; - -    public SoldOutState(GumballMachine gumballMachine) { -        this.gumballMachine = gumballMachine; -    } - -    @Override -    public void insertQuarter() { -        System.out.println("You can't insert a quarter, the machine is sold out"); -    } - -    @Override -    public void ejectQuarter() { -        System.out.println("You can't eject, you haven't inserted a quarter yet"); -    } - -    @Override -    public void turnCrank() { -        System.out.println("You turned, but there are no gumballs"); -    } - -    @Override -    public void dispense() { -        System.out.println("No gumball dispensed"); -    } -} -``` -```java -public class SoldState implements State { -    GumballMachine gumballMachine; - -    public SoldState(GumballMachine gumballMachine) { -        this.gumballMachine = gumballMachine; -    } - -    @Override -    public void insertQuarter() { -        System.out.println("Please wait, we're already giving you a gumball"); -    } - -    @Override -    public void ejectQuarter() { -        System.out.println("Sorry, you already turned the crank"); -    } - -    @Override -    public void turnCrank() { -        System.out.println("Turning twice doesn't get you another gumball!"); -    } - -    @Override -    public void dispense() { -        gumballMachine.releaseBall(); -        if(gumballMachine.getCount()>0){ -            gumballMachine.setState(gumballMachine.getNoQuarterState()); -        } else{ -            System.out.println("Oops, out of gumballs"); -            gumballMachine.setState(gumballMachine.getSoldOutState()); -        } -    } -} -``` -```java -public class GumballMachine { -    private State soldOutState; -    private State noQuarterState; -    private State hasQuarterState; -    private State soldState; - -    private State state; -    private int count = 0; - -    public GumballMachine(int numberGumballs) { -        count = numberGumballs; -        soldOutState = new SoldOutState(this); -        noQuarterState = new NoQuarterState(this); -        hasQuarterState = new HasQuarterState(this); -        soldState = new SoldState(this); - -        if (numberGumballs > 0) { -            state = noQuarterState; -        } else { -            state = soldOutState; -        } -    } - -    public void insertQuarter() { -        state.insertQuarter(); -    } - -    public void ejectQuarter() { -        state.ejectQuarter(); -    } - -    public void turnCrank() { -        state.turnCrank(); -        state.dispense(); -    } - -    public void setState(State state) { -        this.state = state; -    } - -    public void releaseBall() { -        System.out.println("A gumball comes rolling out the slot..."); -        if (count != 0) { -            count -= 1; -        } -    } - -    public State getSoldOutState() { -        return soldOutState; -    } - -    public State getNoQuarterState() { -        return noQuarterState; -    } - -    public State getHasQuarterState() { -        return hasQuarterState; -    } - -    public State getSoldState() { -        return soldState; -    } - -    public int getCount() { -        return count; -    } -} -``` -```java -public class GumballMachineTestDrive { -    public static void main(String[] args) { -        GumballMachine gumballMachine = new GumballMachine(5); - -        gumballMachine.insertQuarter(); -        gumballMachine.turnCrank(); - -        gumballMachine.insertQuarter(); -        gumballMachine.ejectQuarter(); -        gumballMachine.turnCrank(); - -        gumballMachine.insertQuarter(); -        gumballMachine.turnCrank(); -        gumballMachine.insertQuarter(); -        gumballMachine.turnCrank(); -        gumballMachine.ejectQuarter(); - -        gumballMachine.insertQuarter(); -        gumballMachine.insertQuarter(); -        gumballMachine.turnCrank(); -        gumballMachine.insertQuarter(); -        gumballMachine.turnCrank(); -        gumballMachine.insertQuarter(); -        gumballMachine.turnCrank(); -    } -} -``` -运行结果 -```html -You insert a quarter -You turned... -A gumball comes rolling out the slot... -You insert a quarter -Quarter returned -You turned, but there's no quarter -You need to pay first -You insert a quarter -You turned... -A gumball comes rolling out the slot... -You insert a quarter -You turned... -A gumball comes rolling out the slot... -You haven't insert a quarter -You insert a quarter -You can't insert another quarter -You turned... -A gumball comes rolling out the slot... -You insert a quarter -You turned... -A gumball comes rolling out the slot... -Oops, out of gumballs -You can't insert a quarter, the machine is sold out -You turned, but there are no gumballs -No gumball dispensed -``` - -# 第 11 章 代理模式 // TODO - -# 第 12 章 复合模式 - -## 12.1 MVC - -### 12.1.1 传统 MVC - -视图使用组合模式,模型使用了观察者模式,控制器使用了策略模式。 - -![](index_files/4f67611d-492f-4958-9fa0-4948010e345f.jpg) - -### 12.1.2 Web 中的 MVC - -模式不再使用观察者模式。 - -![](index_files/1dd56e61-2970-4d27-97c2-6e81cee86978.jpg) - -# 第 13 章 与设计模式相处 - -定义:在某 **情境** 下,针对某 **问题** 的某种 **解决方案**。 - -过度使用设计模式可能导致代码被过度工程化,应该总是用最简单的解决方案完成工作,并在真正需要模式的地方才使用它。 - -反模式:不好的解决方案来解决一个问题。主要作用是为了警告不要使用这些解决方案。 - -模式分类: - -![](index_files/524a237c-ffd7-426f-99c2-929a6bf4c847.jpg) - -# 第 14 章 剩下的模式 // TODO \ No newline at end of file diff --git a/notes/笔记/重构.md.txt b/notes/笔记/重构.md.txt deleted file mode 100644 index 2b9ebafc..00000000 --- a/notes/笔记/重构.md.txt +++ /dev/null @@ -1,877 +0,0 @@ -[TOC] - -# 第一章 第一个案例 - -如果你发现自己需要为程序添加一个特性,而代码结构使你无法很方便地达成目的,那就先重构这个程序。 - -在重构前,需要先构建好可靠的测试环境,确保安全地重构。 - -重构是以微小的步伐修改程序,如果犯下错误,很容易便可以发现它。 - -**案例分析** - -影片出租店应用程序,包括三个类:Movie、Rental 和 Customer,Rental 包含租赁的 Movie 以及天数。 - -![](index_files/a758c8b2-0ac7-438f-90c2-3923ffad6328.png) - -最开始的实现是把所有的计费代码都放在 Customer 类中,在变化发生时,需要对这部分代码进行更改。本案例中可能发生的变化有:一种类别的计费方式发生改变;添加新的电影类别。考虑到计费代码可能存在于多处,一旦发生改变时,就需要对所有计费代码进行修改。 - -![](index_files/9e5e3cc6-3107-4051-b584-8ff077638fe6.png) - -以下是继承 Movie 的多态方案。但是由于一部 Movie 的类别会动态改变,因此这种方案不可行。 - -![](index_files/2a502516-5d34-4eef-8f39-916298a60035.png) - -引入 Price 来反应类别信息,通过组合的方式在 Movie 中加入 Price 对象,这样每种类别的计费方式都封装在不同的 Price 子类中,并且 Movie 对象也可以动态改变类别。这种方式可以很好地适应上述提到的变化。 - -![](index_files/c02a83b8-a6b9-4d00-a509-6f0516beaf5e.png) - -重构后的时序图和类图: - -![](index_files/95f4559c-3d2a-4176-b365-4fbc46c76cf1.png) - -![](index_files/293b9326-02fc-4ad8-8c79-b4a7b5ba60d3.png) - -# 第二章 重构原则 - -重构是对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。 - -重构的好处:改进软件设计;使软件更容易理解;帮助找到 bug;提高编程速度。 - -三次法则:第一次做某件事时只管去做;第二次做类似事情时可以去做;第三次再做类似的事,就应该重构。 - -间接层与重构:计算机科学中的很多问题可以通过增加一个间接层来解决,间接层具有以下价值:允许逻辑共享;分开解释意图和实现;隔离变化;封装条件逻辑。重构可以理解为在适当的位置插入间接层以及在不需要时移除间接层。 - -修改接口:可以保留旧接口,让旧接口去调用新接口,并且使用 Java 提供的 @deprecation 将旧接口标记为弃用。除非真有必要,不要发布接口,并且不要过早发布接口。 - -当现有代码过于混乱时,应当重写而不是重构。一个折中的办法是,将代码封装成一个个组件,然后对各个组件做重写或者重构的决定。 - -软件开发无法预先设计,因为开发过程有很多变化发生,在最开始不可能都把所有情况考虑进去。重构可以简化设计,重构在一个简单的设计上进行修修改改,当变化发生时,以一种灵活的方式去应对变化,进而带来更好的设计。 - -为了软代码更容易理解,重构可能会导致性能减低。在编写代码时,不用对性能过多关注,只有在最后性能优化阶段再考虑性能问题。应当只关注关键代码的性能,因为只有一小部分的代码是关键代码。 - -# 第三章 代码的坏味道 - -## 1. Duplicated Code(重复代码) - -同一个类的两个函数有相同表达式,则用 Extract Method 提取出重复代码; - -两个互为兄弟的子类含有相同的表达式,先使用 Extract Method,然后把提取出来的函数 Pull Up Method 推入超类。 - -如果只是部分相同,用 Extract Method 分离出相似部分和差异部分,然后使用 Form Template Method 这种模板方法设计模式。 - -如果两个毫不相关的类出现重复代码,则使用 Extract Class 方法将重复代码提取到一个独立类中。 - -## 2. Long Method(过长函数) - -间接层的价值:解释能力、共享能力、选择能力; - -分解函数的原则:当需要用注释来说明一段代码时,就需要把这部分代码写入一个独立的函数中。 - -Extract Method 会把很多参数和临时变量都当做参数,可以用 Replace Temp with Query 消除临时变量,Introduce Parameter Object 和 Preserve Whole Object 可以将过长的参数列变得更简洁。 - -条件和循环往往也需要提取到新的函数中。 - -## 3. Large Class(过大的类) - -过大的类做了过多事情,需要使用 Extract Class 或 Extract Subclass。 - -先确定客户端如何使用它们,然后运用 Extract Interface 为每一种使用方式提取出一个接口。 - -## 4. Long Parameter List(过长的参数列) - -## 5. Divergent Change(发散式变化) - -一个类受到多种变化的影响; - -针对某种原因的变化,使用 Extract Class 将它提炼到一个类中。 - -## 6. Shotgun Surgery(散弹式修改) - -一个变化引起多个类修改; - -使用 Move Method 和 Move Field 把所有需要修改地代码放到同一个类中。 - -## 7. Feature Envy(依恋情结) - -一个函数对某个类的兴趣高于对自己所处类的兴趣,通常是过多访问其它类的数据。 - -使用 Move Method 将它移到该去的地方,如果对多个类都有 Feature Envy,先用 Extract Method 提取出多个函数。 - -## 8. Data Clumps(数据泥团) - -有些数据经常一起出现,比如两个类具有相同的字段、许多函数有相同的参数。使用 Extract Class 将它们放在一起。 - -## 9. Primitive Obsession(基本类型偏执) - -使用类往往比使用基本类型更好,使用 Replace Data Value with Object 将数据值替换为对象。 - -## 10. Switch Statements(switch 惊悚现身) - -## 11. Parallel Inheritance Hierarchies(平行继承体系) - -每当为某个类增加一个子类,必须也为另一个类相应增加一个子类。 - -这种结果会带来一些重复性,消除重复性的一般策略:让一个继承体系的实例引用另一个继承体系的实例。 - -## 12. Lazy Class(冗余类) - -如果一个类没有做足够多的工作,就应该消失。 - -## 13. Speculative Generality(夸夸其谈未来性) - -有些内容是用来处理未来可能发生的变化,但是往往会造成系统难以理解和维护,并且预测未来可能发生的改变很可能和最开始的设想相反。因此,如果不是必要,就不要这么做。 - -## 14. Temporary Field(令人迷惑的暂时字段) - -某个字段仅为某种特定情况而设,这样的代码不易理解,因为通常认为对象在所有时候都需要它的所有字段。 - -把这种字段和特定情况的处理操作使用 Extract Class 提炼到一个独立类中。 - -## 15. Message Chains(过度耦合的消息链) - -一个对象请求另一个对象,然后再向后者请求另一个对象,然后...,这就是消息链。采用这种方式,意味着客户代码将与对象间的关系紧密耦合。 - -改用函数链,用函数委托另一个对象来处理。 - -## 16. Middle Man(中间人) - -中间人负责处理委托给它的操作,如果一个类中有过多的函数都委托给其它类,那就是过度运用委托,应当 Remove Middle Man,直接与负责的对象打交道。 - -## 17. Inappropriate Intimacy(狎昵关系) - -两个类多于亲密,花费太多时间去探讨彼此的 private 成分。 - -## 18. Alernative Classes with Different Interfaces(异曲同工的类) - -## 19. Incomplete Library Class(不完美的类库) - -类库的设计者不可能设计出完美的类库,当我们需要对类库进行一些修改时,可以使用以下两种方法:如果只是修改一两个函数,使用 Introduce Foreign Method;如果要添加一大堆额外行为,使用 Introduce Local Extension。 - -## 20. Data Class(幼稚的数据类) - -它只拥有一些数据字段。 - -找出字段使用的地方,然后把相应的操作移到 Data Class 中。 - -## 21. Refused Bequest(被拒绝的馈赠) - -子类继承超类的所有函数和数据,但是它只想要一部分。 - -为子类新建一个兄弟类,不需要的函数或数据使用 Push Down Method 和 Push Down Field 下推给那个兄弟。 - -## 22. Comments(过多的注释) - -使用 Extract Method 提炼出需要注释的部分,然后用函数名来解释函数的行为。 - -# 第四章 构筑测试体系 - -Java 可以使用 Junit 进行单元测试。 - -单元测试的对象是类的方法,而功能测以客户的角度保证软件正常运行。 - -应当集中测试可能出错的边界条件。 - -# 第五章 重构列表 - -小步前进,频繁测试。 - -# 第六章 重新组织函数 - -## 1. Extract Method(提炼函数) - -将这段代码放进一个独立函数中,并让函数名称解释该函数的用途。 - -## 2. Inline Method(内联函数) - -一个函数的本体与名称同样清楚易懂。 - -在函数调用点插入函数本体,然后移除该函数。 - -## 3. Inline Temp(内联临时变量) - -一个临时变量,只被简单表达式赋值一次,而它妨碍了其它重构手法。 - -将所有对该变量的引用替换为对它赋值的那个表达式自身。 - -```java -double basePrice = anOrder.basePrice(); -return basePrice > 1000; -``` - -```java -return anOrder.basePrice() > 1000; -``` - -## 4. Replace Temp with Query(以查询取代临时变量) - -以临时变量保存某一表达式的运算结果,将这个表达式提炼到一个独立函数中,将所有对临时变量的引用点替换为对新函数的调用。Replace Temp with Query 往往是 Extract Method 之前必不可少的一个步骤,因为局部变量会使代码难以提炼。 - -```java -double basePrice = quantity * itemPrice; -if(basePrice > 1000) -    return basePrice * 0.95; -else -    return basePrice * 0.98; -``` - -```java -if(basePrice() > 1000) -    return basePrice() * 0.95; -else -    return basePrice() * 0.98; - -// ... -double basePrice(){ -    return quantity * itemPrice; -} -``` - -## 5. Introduce Explaining Variable(引起解释变量) - -将复杂表达式(或其中一部分)的结果放进一个临时变量,以此变量名称来解释表达式用途。 - -```java -if((platform.toUpperCase().indexOf("MAC") > -1) && -  (browser.toUpperCase().indexOf("IE") > -1) && -  wasInitialized() && resize > 0) { -    // do something -} -``` - -```java -final boolean isMacOS = platform.toUpperCase().indexOf("MAC") > -1; -final boolean isIEBrower = browser.toUpperCase().indexOf("IE") > -1; -final boolean wasResized = resize > 0; - -if(isMacOS && isIEBrower && wasInitialized() && wasResized) { -    // do something -} -``` - -## 6. Split Temporary Variable(分解临时变量) - -某个临时变量被赋值超过一次,它既不是循环变量,也不是用于收集计算结果。 - -针对每次赋值,创造一个独立、对应的临时变量,每个临时变量只承担一个责任。 - -## 7. Remove Assigments to Parameters(移除对参数的赋值) - -以一个临时变量取代对该参数的赋值。 - -```java -int discount (int inputVal, int quentity, int yearToDate){ -    if (inputVal > 50) inputVal -= 2; -``` - -```java -int discount (int inputVal, int quentity, int yearToDate){ -    int result = inputVal; -    if (inputVal > 50) result -= 2; -``` - -## 8. Replace Method with Method Object(以函数对象取代函数) - -当对一个大型函数采用 Extract Method 时,由于包含了局部变量使得很难进行该操作。 - -将这个函数放进一个单独对象中,如此一来局部变量就成了对象内的字段。然后可以在同一个对象中将这个大型函数分解为多个小型函数。 - -## 9. Subsititute Algorithn(替换算法) - -# 第七章 在对象之间搬移特性 - -## 1. Move Method(搬移函数) - -类中的某个函数与另一个类进行更多交流:调用后者或者被后者调用。 - -将这个函数搬移到另一个类中。 - -## 2. Move Field(搬移字段) - -类中的某个字段被另一个类更多地用到,这里的用到是指调用取值设值函数,应当把该字段移到另一个类中。 - -## 3. Extract Class(提炼类) - -某个类做了应当由两个类做的事。 - -应当建立一个新类,将相关的字段和函数从旧类搬移到新类。 - -## 4. Inline Class(将类内联化) - -与 Extract Class 相反。 - -## 5. Hide Delegate(隐藏“委托关系”) - -建立所需的函数,隐藏委托关系。 - -```java -class Person{ -    Department department; - -    public Department getDepartment(){ -        return department; -    } -} - -class Department{ -    private Person manager; - -    public Person getManager(){ -        return manager; -    } -} -``` - -如果客户希望知道某人的经理是谁,必须获得 Department 对象,这样就对客户揭露了 Department 的工作原理。 - -```java -Person manager = john.getDepartment().getManager(); -``` - -通过为 Peron 建立一个函数来隐藏这种委托关系。 - -```java -public Person getManager(){ -    return department.getManager(); -} -``` - -## 6. Remove Middle Man(移除中间人) - -与 Hide Delegate 相反,本方法需要移除委托函数,让客户直接调用委托类。 - -Hide Delegate 有很大好处,但是它的代价是:每当客户要使用受托类的新特性时,就必须在服务器端添加一个简单的委托函数。随着受委托的特性越来越多,服务器类完全变成了一个“中间人”。 - -## 7. Introduce Foreign Method(引入外加函数) - -需要为提供服务的类添加一个函数,但是无法修改这个类。 - -可以在客户类中建立一个函数,并以第一参数形式传入一个服务类的实例,让客户类组合服务器实例。 - -## 8. Introduce Local Extension(引入本地扩展) - -和 Introduce Foreign Method 目的一样,但是 Introduce Local Extension 通过建立新的类来实现。有两种方式:子类或者包装类,子类就是通过继承实现,包装类就是通过组合实现。 - -# 第八章 重新组织数据 - -## 1. Self Encapsulate Field(自封装字段) - -为字段建立取值/设值函数,并用这些函数来访问字段。只有当子类想访问超类的一个字段,又想在子类中将对这个字段访问改为一个计算后的值,才使用这种方式,否则直接访问字段的方式简洁明了。 - -## 2. Replace Data Value with Object(以对象取代数据值) - -在开发初期,往往会用简单的数据项表示简单的情况,但是随着开发的进行,一些简单数据项会具有一些特殊行为。比如一开始会把电话号码存成字符串,但是随后发现电话号码需要“格式化”、“抽取区号”之类的特殊行为。 - -## 3. Change Value to Reference(将值对象改成引用对象) - -将彼此相等的实例替换为同一个对象。这就要用一个工厂来创建这种唯一对象,工厂类中需要保留一份已经创建对象的列表,当要创建一个对象时,先查找这份列表中是否已经存在该对象,如果存在,则返回列表中的这个对象;否则,新建一个对象,添加到列表中,并返回该对象。 - -## 4. Change Reference to value(将引用对象改为值对象) - -以 Change Value to Reference 相反。值对象有个非常重要的特性:它是不可变的,不可变表示如果要改变这个对象,必须用一个新的对象来替换旧对象,而不是修改旧对象。 - -需要为值对象实现 equals() 和 hashCode() 方法 - -## 5. Replace Array with Object(以对象取代数组) - -有一个数组,其中的元素各自代表不同的东西。 - -以对象替换数组,对于数组中的每个元素,以一个字段来表示,这样方便操作,也更容易理解。 - -## 6. Duplicate Observed Data(赋值“被监视数据”) - -一些领域数据置身于 GUI 控件中,而领域函数需要访问这些数据。 - -将该数据赋值到一个领域对象中,建立一个 Oberver 模式,用以同步领域对象和 GUI 对象内的重复数据。 - -![](index_files/e024bd7e-fb4e-4239-9451-9a6227f50b00.jpg) - -## 7. Change Unidirectional Association to Bidirectional(将单向关联改为双向关联) - -当两个类都需要对方的特性时,可以使用双向关联。 - -有两个类,分别为订单 Order 和客户 Customer,Order 引用了 Customer,Customer 也需要引用 Order 来查看其所有订单详情。 - -```java -class Order{ -    private Customer customer; -    public void setCustomer(Customer customer){ -        if(this.customer != null) -            this.customer.removeOrder(this); -        this.customer = customer; -        this.customer.add(this); -    } -} -``` -```java -class Curstomer{ -    private Set orders = new HashSet<>(); -    public void removeOrder(Order order){ -        orders.remove(order); -    } -    public void addOrder(Order order){ -        orders.add(order); -    } -} -``` - -注意到,这里让 Curstomer 类来控制关联关系。有以下原则来决定哪个类来控制关联关系:如果某个对象是组成另一个对象的部件,那么由后者负责控制关联关系;如果是一对多关系,则由单一引用那一方来控制关联关系。 - -## 8. Change Bidirectional Association to Unidirectional(将双向关联改为单向关联) - -和 Change Unidirectional Association to Bidirectiona 为反操作。 - -双向关联维护成本高,并且也不易于理解。大量的双向连接很容易造成“僵尸对象”:某个对象本身已经死亡了,却保留在系统中,因为它的引用还没有全部完全清除。 - -## 9. Replace Magic Number with Symbolic Constant(以字面常量取代魔法数) - -创建一个常量,根据其意义为它命名,并将字面常量换位这个常量。 - -## 10. Encapsulate Field(封装字段) - -public 字段应当改为 private,并提供相应的访问函数。 - -## 11. Encapsulate Collection(封装集合) - -函数返回集合的一个只读副本,并在这个类中提供添加/移除集合元素的函数。如果函数返回集合自身,会让用户得以修改集合内容而集合拥有者却一无所知。 - -## 12. Replace Record with Data Class(以数据类取代记录) - -## 13. Replace Type Code with Class(以类取代类型码) - -类中有一个数值类型码,但它并不影响类的行为,就用一个新类替换该数值类型码。如果类型码出现在 switch 语句中,需要使用 Replace Conditional with Polymorphism 去掉 switch,首先必须运用 Replace Type Code with Subcalss 或 Replace Type Code with State/Strategy 去掉类型码。 - -![](index_files/27c2e0b3-8f95-453d-bedc-6398a8566ce9.jpg) - -## 14. Replace Type Code with Subcalsses(以子类取代类型码) - -有一个不可变的类型码,它会影响类的行为,以子类取代这个类型码。 - -![](index_files/c41d3977-e0e7-4ee4-93e1-d84f1ae3e20e.jpg) - -## 15. Replace Type Code with State/Strategy (以 State/Strategy 取代类型码) - -有一个可变的类型码,它会影响类的行为,以状态对象取代类型码。 - -和 Replace Type Code with Subcalsses 的区别是 Replace Type Code with State/Strategy 的类型码是动态可变的,前者通过继承的方式来实现,后者通过组合的方式来实现。因为类型码可变,如果通过继承的方式,一旦一个对象的类型码改变,那么就要改变用新的对象来取代旧对象,而客户端难以改变新的对象。但是通过组合的方式,改变引用的状态类是很容易的。 - -![](index_files/81fd1d6f-a3b2-4160-9a0a-1f7cb50ba440.jpg) - -## 16. Replace Subclass with Fields(以字段取代子类) - -各个子类的唯一差别只在“返回常量数据”的函数上。 - -![](index_files/f2e0cee9-ecdc-4a96-853f-d9f6a1ad6ad1.jpg) - -# 第九章 简化条件表达式 - -## 1. Decompose Conditional(分解条件表达式) - -对于一个复杂的条件语句,可以从 if、then、else 三个段落中分别提炼出独立函数。 - -```java -if(data.befor(SUMMER_START) || data.after(SUMMER_END)) -    charge = quantity * winterRate + winterServiceCharge; -else charge = quantity * summerRate; -``` - -```java -if(notSummer(date)) -    charge = winterCharge(quantity); -else charge = summerCharge(quantity); -``` - -## 2. Consolidate Conditional Expression(合并条件表达式) - -有一系列条件测试,都得到相同结果。 - -将这些测试合并为一个条件表达式,并将这个条件表达式提炼成为一个独立函数。 - -```java -double disabilityAmount(){ -    if (seniority < 2) return 0; -    if (monthsDisabled > 12 ) return 0; -    if (isPartTime) return 0; -    // ... -} -``` -```java -double disabilityAmount(){ -    if (isNotEligibleForDisability()) return 0; -    // ... -} -``` - -## 3. Consolidate Duplicate Conditional Fragments (合并重复的条件片段) - -在条件表达式的每个分支上有着相同的一段代码。 - -将这段重复代码搬移到条件表达式之外。 - -```java -if (isSpecialDeal()){ -    total = price * 0.95; -    send(); -} else { -    total = price * 0.98; -    send(); -} -``` - -```java -if (isSpecialDeal()) { -    total = price * 0.95; -} else { -    total = price * 0.98; -} -send(); -``` - -## 4. Remove Control Flag(移除控制标记) - -在一系列布尔表达式中,某个变量带有“控制标记”的作用。 - -用 break语 句或 return 语句来取代控制标记。 - -## 5. Replace Nested Conditional with Guard Clauses (以卫语句取代嵌套条件表达式) - -如果某个条件极其罕见,就应该单独检查该条件,并在该条件为真时立刻从函数中返回,这样的单独检查常常被称为“卫语句”(guard clauses)。 - -条件表达式通常有两种表现形式。第一种形式是:所有分支都属于正常行为。第二种形式则是:条件表达式提供的答案中只有一种是正常行为,其他都是不常见的情况,可以使用卫语句表现所有特殊情况。 - -```java -double getPayAmount() { -    double result; -    if (isDead) result = deadAmount(); -    else { -        if (isSeparated) result = separatedAmount(); -        else { -            if (isRetired) result = retiredAmount(); -            else result = normalPayAmount(); -        }; -    } -    return result; -}; -``` - -```java -double getPayAmount() { -    if (isDead) return deadAmount(); -    if (isSeparated) return separatedAmount(); -    if (isRetired) return retiredAmount(); -    return normalPayAmount(); -}; -``` - -## 6. Replace Conditional with Polymorphism (以多态取代条件表达式) - -将这个条件表达式的每个分支放进一个子类内的覆写函数中,然后将原始函数声明为抽象函数。需要先使用 Replace Type Code with Subclass 或 Replace Type Code with State/Strategy 来建立继承结果。 - -```java -double getSpeed() { -    switch (type) { -        case EUROPEAN: -            return getBaseSpeed(); -        case AFRICAN: -            return getBaseSpeed()- getLoadFactor()* numberOfCoconuts; -        case NORWEGIAN_BLUE: -            return isNailed ? 0 : getBaseSpeed(voltage); -    } -    throw new RuntimeException("Should be unreachable"); -} -``` - -![](index_files/1c8432c8-2552-457f-b117-1da36c697221.jpg) - -## 7. Introduce Null Object(引入Null对象) - -将 null 值替换为 null 对象。这样做的好处在于,不需要询问对象是否为空,直接调用就行。 - -```java -if (customer == null) plan = BillingPlan.basic(); -else plan = customer.getPlan(); -``` - -## 8. Introduce Assertion(引入断言) - -以断言明确表现某种假设。断言只能用于开发过程中,产品代码中不会有断言。 - -```java -double getExpenseLimit() { -    // should have either expense limit or a primary project -    return (expenseLimit != NULL_EXPENSE) ? expenseLimit :  primaryProject.getMemberExpenseLimit(); -} -``` - -```java -double getExpenseLimit() { -    Assert.isTrue (expenseLimit != NULL_EXPENSE || primaryProject != null); -    return (expenseLimit != NULL_EXPENSE) ? expenseLimit :  primaryProject.getMemberExpenseLimit(); -} -``` - -# 第十章 简化函数调用 - -## 1. Rename Method(函数改名) - -使函数名能解释函数的用途。 - -## 2. Add Parameter(添加参数) - -使函数不需要通过调用获得某个信息。 - -## 3. Remove Parameter(移除参数) - -与 Add Parameter 相反,改用调用的方式来获得某个信息。 - -## 4. Separate Query from Modifier(将查询函数和修改函数分离) - -某个函数即返回对象状态值,又修改对象状态。 - -应当建立两个不同的函数,其中一个负责查询,另一个负责修改。任何有返回值的函数,都不应该有看得到的副作用。 - -```java -getTotalOutstandingAndSetReadyForSummaries(); -``` - -```java -getTotalOutstanding(); -setReadyForSummaries(); -``` - -## 5. Parameterize Method(令函数携带参数) - -若干函数做了类似的工作,但在函数本体中却包含了不同的值。 - -建立单一函数,以参数表达那些不同的值。 - -```java -fivePercentRaise(); -tenPercentRaise(); -``` -```java -raise(percentage); -``` - -## 6. Replace Parameter with Explicit Methods(以明确函数取代参数) - -有一个函数,完全取决于参数值而采取不同行为。 - -针对该参数的每一个可能值,建立一个独立函数。 - -```java -void setValue(String name, int value){ -    if (name.equals("height")){ -        height = value; -        return; -    } -    if (name.equals("width")){ -        width = value; -        return; -    } -    Assert.shouldNeverReachHere(); -} -``` - -```java -void setHeight(int arg){ -    height = arg; -} -void setWidth(int arg){ -    width = arg; -} -``` - -## 7. Preserve Whole Object(保持对象完整) - -从某个对象中取出若干值,将它们作为某一次函数调用时的参数。 - -改为传递整个对象。 - -```java -int low = daysTempRange().getLow(); -int high = daysTempRange().getHigh(); -withinPlan = plan.withinRange(low,high); -``` - -```java -withinPlan = plan.withinRange(daysTempRange()); -``` - -## 8. Replace Parameter with Methods(以函数取代参数) - -对象调用某个函数,并将所得结果作为参数,传递给另一个函数。而接受该参数的函数本身也能够调用前一个函数。 - -让参数接收者去除该项参数,而是直接调用前一个函数。 - -```java -int basePrice = _quantity * _itemPrice; -discountLevel = getDiscountLevel(); -double finalPrice = discountedPrice (basePrice, discountLevel); -``` - -```java -int basePrice = _quantity * _itemPrice; -double finalPrice = discountedPrice (basePrice); -``` - -## 9. Introduce Parameter Object(引入参数对象) - -某些参数总是很自然地同时出现,这些参数就是 Data Clumps。 - -以一个对象取代这些参数。 - -![](index_files/08738dd0-ae8e-404a-ba78-a6b1b7d225b3.jpg) - -## 10. Remove Setting Method(移除设值函数) - -类中的某个字段应该在对象创建时被设值,然后就不再改变。 - -去掉该字段的所有设值函数,并将该字段设为 final。 - -## 11. Hide Method(隐藏函数) - -有一个函数,从来没有被其他任何类用到。 - -将这个函数修改为 private。 - -## 12. Replace Constructor with Factory Method (以工厂函数取代构造函数) - -希望在创建对象时不仅仅是做简单的建构动作。 - -将构造函数替换为工厂函数。 - -## 13. Encapsulate Downcast(封装向下转型) - -某个函数返回的对象,需要由函数调用者执行向下转型(downcast)。 - -将向下转型动作移到函数中。 - -```java -Object lastReading(){ -    return readings.lastElement(); -} -``` -```java -Reading lastReading(){ -    return (Reading)readings.lastElement(); -} -``` - -## 14. Replace Error Code with Exception (以异常取代错误码) - -某个函数返回一个特定的代码,用以表示某种错误情况。 - -改用异常,异常将普通程序和错误处理分开,使代码更容易理解。 - -## 15. Replace Exception with Test(以测试取代异常) - -面对一个调用者可以预先检查的条件,你抛出了一个异常。 - -修改调用者,使它在调用函数之前先做检查。 - -```java -double getValueForPeriod(int periodNumber) { -    try { -        return values[periodNumber]; -    } catch (ArrayIndexOutOfBoundsException e) { -        return 0; -    } -} -``` -```java -double getValueForPeriod(int periodNumber) { -    if (periodNumber >= values.length) return 0; -    return values[periodNumber]; -``` - -# 第十一章 处理概括关系 - -## 1. Pull Up Field(字段上移) - -两个子类拥有相同的字段。 - -将该字段移至超类。 - -## 2. Pull Up Method(函数上移) - -有些函数,在各个子类中产生完全相同的结果。 - -将该函数移至超类。 - -## 3. Pull Up Constructor Body(构造函数本体上移) - -你在各个子类中拥有一些构造函数,它们的本体几乎完全一致。 - -在超类中新建一个构造函数,并在子类构造函数中调用它。 - -```java -class Manager extends Employee... - -public Manager(String name, String id, int grade) { -    this.name = name; -    this.id = id; -    this.grade = grade; -} -``` - -```java -public Manager(String name, String id, int grade) { -    super(name, id); -    this.grade = grade; -} -``` - -## 4. Push Down Method(函数下移) - -超类中的某个函数只与部分子类有关。 - -将这个函数移到相关的那些子类去。 - -## 5. Push Down Field(字段下移) - -超类中的某个字段只被部分子类用到。 - -将这个字段移到需要它的那些子类去。 - -## 6. Extract Subclass(提炼子类) - -类中的某些特性只被某些实例用到。 - -新建一个子类,将上面所说的那一部分特性移到子类中。 - -## 7. Extract Superclass(提炼超类) - -两个类有相似特性。 - -为这两个类建立一个超类,将相同特性移至超类。 - -## 8. Extract Interface(提炼接口) - -若干客户使用类接口中的同一子集,或者两个类的接口有部分相同。 - -将相同的子集提炼到一个独立接口中。 - -## 9. Collapse Hierarchy(折叠继承体系) - -超类和子类之间无太大区别。 - -将它们合为一体。 - -## 10. Form Template Method(塑造模板函数) - -你有一些子类,其中相应的某些函数以相同顺序执行类似的操作,但各个操作的细节上有所不同。 - -将这些操作分别放进独立函数中,并保持它们都有相同的签名,于是原函数也就变得相同了。然后将原函数上移至超类。(模板方法模式) - -## 11. Replace Inheritance with Delegation (以委托取代继承) - -某个子类只使用超类接口中的一部分,或是根本不需要继承而来的数据。 - -在子类中新建一个字段用以保存超类,调整子类函数,令它改而委托超类,然后去掉两者之间的继承关系。 - -## 12. Replace Delegation with Inheritance (以继承取代委托) - -你在两个类之间使用委托关系,并经常为整个接口编写许多极简单的委托函数。 - -让委托类继承受托类。 \ No newline at end of file diff --git a/notes/笔记/面向对象思想.md.txt b/notes/笔记/面向对象思想.md.txt deleted file mode 100644 index 058ef539..00000000 --- a/notes/笔记/面向对象思想.md.txt +++ /dev/null @@ -1,292 +0,0 @@ -[TOC] - -# S.O.L.I.D - -S.O.L.I.D是面向对象设计和编程(OOD&OOP)中几个重要编码原则(Programming Priciple)的首字母缩写。 - -|简写    |全拼    |中文翻译| -| -- | -- | -- | -|SRP|    The Single Responsibility Principle    |单一责任原则| -|OCP|    The Open Closed Principle            | 开放封闭原则| -|LSP|    The Liskov Substitution Principle    |里氏替换原则| -|ISP|    The Interface Segregation Principle    |接口分离原则| -|DIP|    The Dependency Inversion Principle    |依赖倒置原则| - - -## 1. 单一责任原则 - -当需要修改某个类的时候原因有且只有一个。换句话说就是让一个类只做一种类型责任,当这个类需要承当其他类型的责任的时候,就需要分解这个类。 - -## 2. 开放封闭原则 - -软件实体应该是可扩展,而不可修改的。也就是说,对扩展是开放的,而对修改是封闭的。 - -## 3. 里氏替换原则 - -当一个子类的实例应该能够替换任何其超类的实例时,它们之间才具有 is-a 关系。 - -## 4. 接口分离原则 - -不能强迫用户去依赖那些他们不使用的接口。换句话说,使用多个专门的接口比使用单一的总接口总要好。 - -## 5. 依赖倒置原则 - -1.  高层模块不应该依赖于低层模块,二者都应该依赖于抽象 -2.  抽象不应该依赖于细节,细节应该依赖于抽象 - -# 封装、继承、多态 - -封装、继承、多态是面向对象的三大特性。 - -## 1. 封装 - -利用抽象数据类型将数据和基于数据的操作封装在一起,使其构成一个不可分割的独立实体,数据被保护在抽象数据类型的内部,尽可能地隐藏内部的细节,只保留一些对外接口使之与外部发生联系。用户是无需知道对象内部的细节,但可以通过该对象对外的提供的接口来访问该对象。 - -封装有三大好处: - -1. 良好的封装能够减少耦合。 - -2. 类内部的结构可以自由修改。 - -3. 可以对成员进行更精确的控制。 - -4. 隐藏信息,实现细节。 - -以下 Person 类封装 name、gender、age 等属性,外界只能通过 get() 方法获取一个 Person 对象的 name 属性和 gender 属性,而无法获取 age 属性,但是 age 属性可以供 work() 方法使用。 - -注意到 gender 属性使用 int 数据类型进行存储,封装使得用户注意不到这种实现细节。并且在需要修改使用的数据类型时,也可以在不影响客户端代码的情况下进行。 - -```java -public class Person { -    private String name; -    private int gender; -    private int age; - -    public String getName() { -        return name; -    } - -    public String getGender() { -        return gender == 0 ? "man" : "woman"; -    } - -    public void work() { -        if(18 <= age && age <= 50) { -            System.out.println(name + " is working very hard!"); -        } else { -            System.out.println(name + " can't work!"); -        } -    } -} -``` - -## 2. 继承 - -继承实现了 **is-a** 关系,例如 Cat 和 Animal 就是一种 is-a 关系,因此可以将 Cat 继承自 Animal,从而获得 Animal 非 private 的属性和方法。 - -Cat 可以当做 Animal 来使用,也就是可以使用 Animal 引用 Cat 对象,这种子类转换为父类称为 **向上转型**。 - -继承应该遵循里氏替换原则:当一个子类的实例应该能够替换任何其超类的实例时,它们之间才具有 is-a 关系。 - -```java -Animal animal = new Cat(); -``` - -## 3. 多态 - -多态分为编译时多态和运行时多态。编译时多态主要指方法的重装,运行时多态指程序中定义的对象引用所指向的具体类型在运行期间才确定。 - -多态有三个条件:1. 继承;2. 覆盖父类方法;3. 向上转型。 - -下面的代码中,乐器类(Instrument)有两个子类:Wind 和 Percussion,它们都覆盖了 play() 方法,并且在 main() 方法中使用父类 Instrument 来引用 Wind 和 Percussion 对象。在 Instrument 引用调用 play() 方法时,会执行实际引用对象所在类的 play() 方法,而不是 Instrument 类的方法。 - -```java -public class Instrument { -    public void play() { -        System.out.println("Instument is playing..."); -    } -} - -public class Wind extends Instrument { -    public void play() { -        System.out.println("Wind is playing..."); -    } -} - -public class Percussion extends Instrument { -    public void play() { -        System.out.println("Percussion is playing..."); -    } -} - -public class Music { -    public static void main(String[] args){ -        List instruments = new ArrayList<>(); -        instruments.add(new Wind()); -        instruments.add(new Percussion()); -        for(Instrument instrument : instruments){ -            instrument.play(); -        } -    } -} - -``` - - - -# UML - -## 1. 类图 - -**1.1 继承相关** - -继承有两种形式: 泛化(generalize)和实现(realize),表现为 is-a 关系。 - -① 泛化关系(generalization) - -从具体类中继承 - -![](index_files/29badd92-109f-4f29-abb9-9857f5973928.png) - -② 实现关系(realize) - -从抽象类或者接口中继承 - -![](index_files/4b16e1d3-3a60-472c-9756-2f31b1c48abe.png) - -**1.2 整体和部分** - -① 聚合关系(aggregation) - -表示整体由部分组成,但是整体和部分不是强依赖的,整体不存在了部分还是会存在。以下表示 B 由 A 组成: - -![](index_files/34259bb8-ca3a-4872-8771-9e946782d9c3.png) - - -② 组合关系(composition) - -和聚合不同,组合中整体和部分是强依赖的,整体不存在了部分也不存在了。比如公司和部门,公司没了部门就不存在了。但是公司和员工就属于聚合关系了,因为公司没了员工还在。 - -![](index_files/7dda050d-ac35-4f47-9f51-18f18ed6fa9a.png) - -**1.3 相互联系** - -① 关联关系(association) - -表示不同类对象之间有关联,这是一种静态关系,与运行过程的状态无关,在最开始就可以确定。因此也可以用 1 对 1、多对 1、多对多这种关联关系来表示。比如学生和学校就是一种关联关系,一个学校可以有很多学生,但是一个学生只属于一个学校,因此这是一种多对一的关系,在运行开始之前就可以确定。 - -![](index_files/4ccd294c-d6b2-421b-839e-d88336ff5fb7.png) - -② 依赖关系(dependency) - -和关联关系不同的是, 依赖关系是在运行过程中起作用的。一般依赖作为类的构造器或者方法的参数传入。双向依赖时一种不好的设计。 - -![](index_files/47ca2614-509f-476e-98fc-50ec9f9d43c0.png) - - -## 2. 时序图 - -**2.1 定义** - -时序图描述了对象之间传递消息的时间顺序,它用来表示用例的行为顺序。它的主要作用是通过对象间的交互来描述用例(注意是对象),从而寻找类的操作。 - -**2.2 赤壁之战时序图** - -从虚线从上往下表示时间的推进。 - -![](index_files/80c5aff8-fc46-4810-aeaa-215b5c60a003.png) - -可见,通过时序图可以知道每个类具有以下操作: - -```java -publc class 刘备 { -   public void 应战(); -} - -publc class  孔明 { -  public void  拟定策略(); -  public void  联合孙权(); -  private void 借东风火攻(); -} - -public class 关羽 { -    public void  防守荊州(); -} - -public class 张飞 { -   public void  防守荆州前线(); -} - -public class 孙权 { -   public void  领兵相助(); -} -``` - -**2.3 活动图、时序图之间的关系** - -活动图示从用户的角度来描述用例; - -时序图是从计算机的角度(对象间的交互)描述用例。 - -**2.4 类图与时序图的关系** - -类图描述系统的静态结构,时序图描述系统的动态行为。 - -**2.5 时序图的组成** - -① 对象 - -有三种表现形式 - -![](index_files/25b8adad-2ef6-4f30-9012-c306b4e49897.png) - -在画图时,应该遵循以下原则: - -1. 把交互频繁的对象尽可能地靠拢。 - -2. 把初始化整个交互活动的对象(有时是一个参与者)放置在最左边。 - -② 生命线 - -生命线从对象的创建开始到对象销毁时终止 - -![](index_files/b7b0eac6-e7ea-4fb6-8bfb-95fec6f235e2.png) - -③ 消息 - -对象之间的交互式通过发送消息来实现的。 - -消息有4种类型: - -1\. 简单消息,不区分同步异步。 - -![](index_files/a13b62da-0fa8-4224-a615-4cadacc08871.png) - -2\. 同步消息,发送消息之后需要暂停活动来等待回应。 - -![](index_files/33821037-dc40-4266-901c-e5b38e618426.png) - -3\. 异步消息,发送消息之后不需要等待。 - -![](index_files/dec6c6cc-1b5f-44ed-b8fd-464fcf849dac.png) - -4\. 返回消息,可选。 - -④ 激活 - -生命线上的方框表示激活状态,其它时间处于休眠状态。 - -![](index_files/6ab5de9b-1c1e-4118-b2c3-fb6c7ed7de6f.png) - - -# 参考资料 - -- Java 编程思想 - -- [面向对象设计的SOLID原则](http://www.cnblogs.com/shanyou/archive/2009/09/21/1570716.html) - -- [看懂UML类图和时序图](http://design-patterns.readthedocs.io/zh_CN/latest/read_uml.html#generalization) - -- [UML系列——时序图(顺序图)sequence diagram](http://www.cnblogs.com/wolf-sun/p/UML-Sequence-diagram.html) - -- [面向对象编程三大特性------封装、继承、多态](http://blog.csdn.net/jianyuerensheng/article/details/51602015) \ No newline at end of file diff --git a/notes/笔记/面向对象程序设计.md.txt b/notes/笔记/面向对象程序设计.md.txt deleted file mode 100644 index 16988cfb..00000000 --- a/notes/笔记/面向对象程序设计.md.txt +++ /dev/null @@ -1,403 +0,0 @@ -[TOC] - -# 解决思路 - -面向对象设计问题要求求职者设计出类和方法,以实现技术问题或描述真实生活中的对象。可以按照以下步骤来进行解决: - -## 1. 处理不明确的地方 - -面向对象设计问题往往会故意放些烟幕弹,意在检验你是武断臆测,还是提出问题以厘清问题。毕竟,开发人员要是没弄清楚自己要开发什么,就直接挽起袖子开始编码,只会浪费公司的财力物力,还可能造成更严重的后果。 - -碰到面向对象设计问题时,你应该先问清楚,谁是使用者、他们将如何使用。对某些问题,你甚至还要问清楚“5W1H”,也就是 Who(谁)、What(什么)、Where(哪里)、When(何时)Why(为什么)、How(如何)。举个例子,假设面试官让你描述咖啡机的面向对象设计。这个问题看似简单明了,其实不然。这台咖啡机可能是一款工业型机器,设计用来放在大餐厅里,每小时要服务几百位顾客,还要能制作 10 种不同口味的咖啡。又或者,它可能是设计给老年人使用的简易咖啡机,只要能制作简单的黑咖啡就行。这些用例将大大影响你的设计。 - -## 2. 定义核心对象 - -比如,假设要为一家餐馆进行面向对象设计。那么,核心对象可能包括员工、服务员、厨师、餐桌、顾客、订单、菜。 - -## 3. 分析对象关系 - -可以利用 UML 类图来分析。其中, - -- 服务员和厨师都继承自员工类; -- 一个服务员要为多个餐桌的顾客提供服务; -- 一个餐桌可以有很多顾客; -- 一个餐桌可以有一个订单; -- 一个订单有多个菜。 -- 一个厨师要处理多个订单; - -![](index_files/97c1d566-ae3a-4ddd-ba86-e8378e26d49b.png) - -## 4. 研究对象的动作 - -可以用时序图来找出对象的动作。 - -多个顾客坐到一个餐桌,服务员来到餐桌前提供服务,并等待顾客填写订单。订单填写完成后,交给厨师处理订单,并等待厨师处理完成,完成之后服务员将上菜。 - -![](index_files/4f901988-481c-48bf-a313-35778e2211bc.png) - -# 示例 - -## 1. 聊天服务器 - -题目描述:请描述该如何设计一个聊天服务器。要求给出各种后台组件、类和方法的细节,并说明其中最难解决的问题会是什么。 - -设计聊天服务器是项大工程,绝非一次面试就能完成。毕竟,就算一整个团队,也要花费数月乃至好几年才能打造出一个聊天服务器。作为求职者,你的工作是专注解决该问题的某个方面,涉及范围要够广,又要够集中,这样才能在一轮面试中搞定。它不一定要与真实情况一模一样,但也应该忠实反映出实际的实现。 - -这里我们会把注意力放在用户管理和对话等核心功能:添加用户、创建对话、更新状态,等等。考虑到时间和空间有限,我们不会探讨这个问题的联网部分,也不描述数据是怎么真正推送到客户端的。 - -另外,我们假设“好友关系”是双向的,如果你是我的联系人之一,那就表示我也是你的联系人之一。我们的聊天系统将支持群组聊天和一对一(私密)聊天,但并不考虑语音聊天、视频聊天或文件传输。 - -### 1.1 需要支持的特定动作 - -这也有待你跟面试官探讨,下面列出几点想法。 - -- 显示在线和离线状态。 -- 添加请求(发送、接受、拒绝)。 -- 更新状态信息。 -- 发起私聊和群聊。 -- 在私聊和群聊中添加新信息。 - -这只是一部分列表,如果时间有富余,还可以多加一些动作。 - -### 1.2 核心组件 - -这个系统可能由一个数据库、一组客户端和一组服务器组成。我们的面向对象设计不会包含这些部分,不过可以讨论一下系统的整体概览。 - -数据库将用来存放更持久的数据,比如用户列表或聊天对话的备份。 SQL 数据库应该是不错的选择,或者,如果可扩展性要求更高,可以选用 BigTable 或其他类似的系统。 - -对于客户端和服务器之间的通信,使用 XML 应该也不错。尽管这种格式不是最紧凑的(你也应该向面试官指出这一点),它仍是很不错的选择,因为不管是计算机还是人类都容易辨识。使用 XML 可以让程序调试起来更轻松,这一点非常重要。 - -服务器由一组机器组成,数据会分散到各台机器上,这样一来,我们可能就必须从一台机器跳到另一台机器。如果可能的话,我们会尽量在所有机器上复制部分数据,以减少查询操作的次数。在此,设计上有个重要的限制条件,就是必须防止出现单点故障。例如,如果一台机器控制所有用户的登录,那么,只要这一台机器断网,就会造成数以百万计的用户无法登录。 - -### 1.3 关键对象和方法 - -代码参考:[Github](https://github.com/careercup/ctci/tree/master/java/Chapter%208/Question8_7) - -系统的关键对象包括用户、对话和状态消息等,我们已经实现了 UserManagement 类。要是更关注这个问题的联网方面或其他组件,我们就可能转而深入探究那些对象。 - -```java -public class UserManager { - private static UserManager instance; - private HashMap usersById = new HashMap(); - private HashMap usersByAccountName = new HashMap(); - private HashMap onlineUsers = new HashMap(); - - public static UserManager getInstance() { - if (instance == null) { - instance = new UserManager(); - } - return instance; - } - - public void addUser(User fromUser, String toAccountName) { - User toUser = usersByAccountName.get(toAccountName); - AddRequest req = new AddRequest(fromUser, toUser, new Date()); - toUser.receivedAddRequest(req); - fromUser.sentAddRequest(req); - } - - public void approveAddRequest(AddRequest req) { - req.status = RequestStatus.Accepted; - User from = req.getFromUser(); - User to = req.getToUser(); - from.addContact(to); - to.addContact(from); - } - - public void rejectAddRequest(AddRequest req) { - req.status = RequestStatus.Rejected; - User from = req.getFromUser(); - User to = req.getToUser(); - from.removeAddRequest(req); - to.removeAddRequest(req); - } - - public void userSignedOn(String accountName) { - User user = usersByAccountName.get(accountName); - if (user != null) { - user.setStatus(new UserStatus(UserStatusType.Available, "")); - onlineUsers.put(user.getId(), user); - } - } - - public void userSignedOff(String accountName) { - User user = usersByAccountName.get(accountName); - if (user != null) { - user.setStatus(new UserStatus(UserStatusType.Offline, "")); - onlineUsers.remove(user.getId()); - } - } -} -``` - -在 User 类中, receivedAddRequest 方法会通知用户 B(User B),用户 A(User A)请求加他 为 好 友 。 用 户 B 会 接 受 或 拒 绝 该 请 求 ( 通 过 UserManager.approveAddRequest 或 rejectAddRequest), UserManager 则负责将用户互相添加到对方的通讯录中。 - -当 UserManager 要将 AddRequest 加入用户 A 的请求列表时,会调用 User 类的 sentAddRequest 方法。综上,整个流程如下。 - -1. 用户 A 点击客户端软件上的“添加用户”,发送给服务器。 -2. 用户 A 调用 requestAddUser(User B)。 -3. 步骤 2 的方法会调用 UserManager.addUser。 -4. UserManager 会调用 User A.sentAddRequest 和 User B.receivedAddRequest。 - -```java -public class User { - private int id; - private UserStatus status = null; - private HashMap privateChats = new HashMap(); - private ArrayList groupChats = new ArrayList(); - private HashMap receivedAddRequests = new HashMap(); - private HashMap sentAddRequests = new HashMap(); - - private HashMap contacts = new HashMap(); - private String accountName; - private String fullName; - - public User(int id, String accountName, String fullName) { - this.accountName = accountName; - this.fullName = fullName; - this.id = id; - } - - public boolean sendMessageToUser(User toUser, String content) { - PrivateChat chat = privateChats.get(toUser.getId()); - if (chat == null) { - chat = new PrivateChat(this, toUser); - privateChats.put(toUser.getId(), chat); - } - Message message = new Message(content, new Date()); - return chat.addMessage(message); - } - - public boolean sendMessageToGroupChat(int groupId, String content) { - GroupChat chat = groupChats.get(groupId); - if (chat != null) { - Message message = new Message(content, new Date()); - return chat.addMessage(message); - } - return false; - } - - public void setStatus(UserStatus status) { - this.status = status; - } - - public UserStatus getStatus() { - return status; - } - - public boolean addContact(User user) { - if (contacts.containsKey(user.getId())) { - return false; - } else { - contacts.put(user.getId(), user); - return true; - } - } - - public void receivedAddRequest(AddRequest req) { - int senderId = req.getFromUser().getId(); - if (!receivedAddRequests.containsKey(senderId)) { - receivedAddRequests.put(senderId, req); - } - } - - public void sentAddRequest(AddRequest req) { - int receiverId = req.getFromUser().getId(); - if (!sentAddRequests.containsKey(receiverId)) { - sentAddRequests.put(receiverId, req); - } - } - - public void removeAddRequest(AddRequest req) { - if (req.getToUser() == this) { - receivedAddRequests.remove(req); - } else if (req.getFromUser() == this) { - sentAddRequests.remove(req); - } - } - - public void requestAddUser(String accountName) { - UserManager.getInstance().addUser(this, accountName); - } - - public void addConversation(PrivateChat conversation) { - User otherUser = conversation.getOtherParticipant(this); - privateChats.put(otherUser.getId(), conversation); - } - - public void addConversation(GroupChat conversation) { - groupChats.add(conversation); - } - - public int getId() { - return id; - } - - public String getAccountName() { - return accountName; - } - - public String getFullName() { - return fullName; - } -} -``` - -Conversation 类实现为一个抽象类,因为所有 Conversation 不是 GroupChat 就是 PrivateChat,同时每个类各有自己的功能。 - -```java -public abstract class Conversation { - protected ArrayList participants = new ArrayList(); - protected int id; - protected ArrayList messages = new ArrayList(); - - public ArrayList getMessages() { - return messages; - } - - public boolean addMessage(Message m) { - messages.add(m); - return true; - } - - public int getId() { - return id; - } -} -``` - -```java -public class GroupChat extends Conversation { - public void removeParticipant(User user) { - participants.remove(user); - } - - public void addParticipant(User user) { - participants.add(user); - } -} -``` - -```java -public class PrivateChat extends Conversation { - public PrivateChat(User user1, User user2) { - participants.add(user1); - participants.add(user2); - } - - public User getOtherParticipant(User primary) { - if (participants.get(0) == primary) { - return participants.get(1); - } else if (participants.get(1) == primary) { - return participants.get(0); - } - return null; - } -} -``` - -```java -public class Message { - private String content; - private Date date; - public Message(String content, Date date) { - this.content = content; - this.date = date; - } - - public String getContent() { - return content; - } - - public Date getDate() { - return date; - } -} -``` - -AddRequest 和 UserStatus 两个类比较简单,功能不多,主要用来将数据聚合在一起,方便其他类使用。 - -```java -public class AddRequest { - private User fromUser; - private User toUser; - private Date date; - RequestStatus status; - - public AddRequest(User from, User to, Date date) { - fromUser = from; - toUser = to; - this.date = date; - status = RequestStatus.Unread; - } - - public RequestStatus getStatus() { - return status; - } - - public User getFromUser() { - return fromUser; - } - - public User getToUser() { - return toUser; - } - - public Date getDate() { - return date; - } -} -``` - -```java -public class UserStatus { -  private String message; -  private UserStatusType type; -  public UserStatus(UserStatusType type, String message) { -  this.type = type; -  this.message = message; -  } -   -  public UserStatusType getStatusType() { -  return type; -  } -   -  public String getMessage() { -  return message; -  } -} -``` - -```java -public enum UserStatusType { - Offline, Away, Idle, Available, Busy -} -``` - -```java -public enum RequestStatus { - Unread, Read, Accepted, Rejected -} -``` - -### 4. 最难解决或最有意思的问题 - -**问题 1:如何确定某人在线** - -虽然希望用户在退出时通知我们,但即便如此也无法确切知道状态。例如,用户的网络连接可能断开了。为了确定用户何时退出,或许可以试着定期询问客户端,以确保它仍然在线。 - -**问题 2:如何处理冲突的信息** - -部分信息存储在计算机内存中,部分则存储在数据库里。如果两者不同步有冲突,那会出什么问题?哪一部分是“正确的”? - -**问题 3:如何才能让服务器在任何负载下都能应付自如** - -前面我们设计聊天服务器时并没怎么考虑可扩展性,但在实际场景中必须予以关注。我们需要将数据分散到多台服务器上,而这又要求我们更关注数据的不同步。 - -**问题 4:如何预防拒绝服务攻击** - -客户端可以向我们推送数据——若它们试图向服务器发起拒绝服务(DOS)攻击,怎么办?该如何预防? - -# 参考资料 - -- GAYLELEAKMANNMCDOWELL. 程序员面试金典 [M]. 人民邮电出版社 , 2013. \ No newline at end of file diff --git a/notes/笔记/黑客与画家.md.txt b/notes/笔记/黑客与画家.md.txt deleted file mode 100644 index 4802509d..00000000 --- a/notes/笔记/黑客与画家.md.txt +++ /dev/null @@ -1,128 +0,0 @@ -[TOC] - -# 译者序 - -作者:美国互联网创业之父 - -最开始 hack 指的是难题的解决办法,那些最能干的人被成为 hacker。黑客包含以下三个特点:好玩、高智商、探索精神。黑客的核心价值观:分享、开放、民主、计算机的自由使用、进步。因此黑客指的是那些专家级程序员。 - -由于黑客常常入侵系统,因此被与那些破坏计算机系统的人联系起来。然而破坏计算机系统的人称为骇客,真正的黑客不这么做,反而是让世界变得更好。 - -# 为什么书呆子不受欢迎 - -作者认为,“书呆子”和“高智商”正相关,而“书呆子”和“受欢迎”负相关。 - -提出问题:既然书呆子智商高,那么为什么不想出受欢迎的方法?因为要想受欢迎,需要投入很大的时间精力去塑造个人魅力。而书呆子没有那么多的时间和精力,相比于受欢迎,他们更在乎其它更有趣的事,比如设计奇妙的火箭等。 - -欺负书呆子的原因: - -- 青少年的孩子更加残忍,书呆子不受欢迎意味着被歧视和受欺负。 - > 11 岁以前,小孩的生活由家长主导,其他孩子的影响有限。 孩子们不是不关心小学里其他同学的想法,但是后者不具有决定性影响。小学毕业以后,这种情形开始发生变化。到了 11 岁左右,孩子们逐渐把家庭生活当作寻常事了。 他们在同伴中开辟了一个新的世界,并认为那个世界才是重要的,比家里的世界更重要。实际上,如果他们在家里与父母发生冲突, 反而能在那个新的世界中挣得面子,而他们也确实更在乎那个世界。但是,问题在于,孩子们自己创造出来的世界是一个非常原始的世界。青少年在心理上还没有摆脱儿童状态, 许多人都会残忍地对待他人。他们折磨书呆子的原因就像拔掉一条蜘蛛腿一样,觉得很好玩。在一个人产生良知之前,折磨就是一种娱乐。 - -- 为了凸显自己。 - > 在任何社会等级制度中,那些对自己没自信的人就会通过虐待他们眼中的下等人来突显自己的身份。我已经意识到,正是因为这个原因,在美国社会中底层白人是对待黑人最残酷的群体。最受欢迎的孩子并不欺负书呆子,他们不需要靠踩在书呆子身上来垫高自己。大部分的欺负来自处于下一等级的学生,那些焦虑的中间层。 - -- 为了和受欢迎的人结成同盟。 - > 没有什么比一个共同的敌人更能使得人们团结起来了。这就好比一个政客,他想让选民忘记糟糕的国内局势,方法就是为国家找出一个敌人,哪怕敌人并不真的存在,他也可以创造一个出来。一群人在一起,挑出一个书呆子,居高临下地欺负他,就会把彼此联系起来。一起攻击一个外人,所有人因此就都成了自己人。这就是为什么最恶劣的以强凌弱的事件都与团体有关的原因。随便找一个书呆子,他都会告诉你,一群人的虐待比一个人的虐待残酷得多。 - -学校老师的只是把教书当成工作。 -> 公立学校的老师很像监狱的狱卒。看管监狱的人主要关心的是犯人都待在自己应该待的位置。然后,让犯人有东西吃,尽可能不要发生斗殴和伤害事件,这就可以了。 - -到社会之后,书呆子会被友好对待,因为他们做的事能够产生更多的影响,而社会正需要这些影响。 - -成年人把孩子在学校受苦受难归结为青春期的激素影响,但是实际上这是学校这种体制的问题,学校只是把孩子圈养起来,不让他们到处乱跑,然而学校内部存在着很多残忍。 - -过去的孩子更早投入社会,也更能够真正学会本领。 -> 过去的社会中,青少年扮演着一个更积极的角色。工业化时代到来前,青少年都是某种形式的学徒,不是在某个作坊,就是在某个农庄,甚至在某艘军舰上。他们不会被扔到一旁,创造自己的小社会。他们是成年人社会的低级成员。以前的青少年似乎也更尊敬成年人,因为成年人都是看得见的专家,会传授他们所要学习的技能。如今的大多数青少年,对他们的家长在遥远的办公室所从事的工作几乎一无所知。他们看不到学校作业与未来走上社会后从事的工作有何联系。 - -学校的使命是教书育人,而并没有外界的压力去监督他们这么做,所以老师和学生双方往往都是敷衍了事。 - -没有外在的对手,孩子们就互相把对方当作对手。 - -成年人贬低了很多美好的东西,比如“人格”。 -> 许多书呆子可能都与我一样,直到高中毕业多年后,才去读中学里的指定读物。但是,我错过的绝不仅仅只是几本书而已。我对许多美好的字眼都嗤之以鼻,比如“人格”、“正直”,因为成年人贬低了这些词。在他们嘴里,这些词似乎都是同一个意思——“听话”。一些孩子因为具备所谓的“人格”和“正直”,而受到夸奖,可是他们不是呆得像一头大笨牛,就是轻浮得像一个不动脑筋的吹牛者。如果“人格”和“正直”就是这种样子,我宁愿不要它们。 - -# 黑客与画家 - -黑客和画家一样,都是艺术家,都在进行创作。 - -黑客比较喜欢脚本语言那种可以动态扩展,并且可以像铅笔画画一样修修改改。 -> 编程语言首要的特性应该是允许动态扩展(malleable)。编程语言是用来帮助思考程序的,而不是用来表达你已经想好的程序。它应该是一支铅笔,而不是一支钢笔。如果大家都像学校教的那样编程,那么静态类型(static typing)是一个不错的概念。但是,我认识的黑客,没有一个人喜欢用静态类型语言编程。我们需要的是一种可以随意涂抹、擦擦改改的语言,我们不想正襟危坐,把一个盛满各种变量类型的茶杯,小心翼翼放在自己的膝盖上,为了与一丝不苟的编译器大婶交谈,努力地挑选词语,确保变量类型匹配,好让自己显得礼貌又周到。 - -编码并不是科学研究,因此数学家不一定写一手好代码。 -> 创作者不同于科学家,明白这一点有很多好处。除了不用为静态类型烦恼以外,还可以免去另一个折磨科学家的难题,那就是“对数学的妒忌”。科学界的每一个人,暗地里都相信数学家比自己聪明。我觉得,数学家自己也相信这一点。最后的结果就是科学家往往会把自己的工作尽可能弄得看上去像数学。对于物理学这样的领域,这可能不会有太大不良影响。但是,你越往自然科学的方向发展,它就越成为一个严重的问题。 - -黑客为了能做自己喜欢做的事并且可以养活自己,往往白天工作晚上做自己的事。 - -热爱编程的人不可避免的会开发自己的项目。 -> 我们面试程序员的时候,主要关注的事情就是业余时间他们写了什么软件。因为如果你不爱一件事,你不可能把它做得真正优秀,要是你很热爱编程,你就不可避免地会开发你自己的项目。 - -黑客不是机械工作的技术工人,而是一位创作者。 -> 如果黑客只是一个负责实现领导意志的技术工人,职责就是根据规格说明书写出代码,那么他其实与一个挖水沟的工人是一样的,从这头挖到那头,仅此而已。但是,如果黑客是一个创作者,他从事的就不是机械性的工作,他必须具备灵感。 - -# 不能说的话 - -流行的道德观念虽然在后世看来会是荒诞的,但是如果在现今去违背这些道德观念,说一些不合适的言论,会让自己处于麻烦之中。 - -自己会深深赞同自己的观点,并且确信会得到别人的认同,很可能是,这些观点是别人给自己灌输的,别人说什么,自己就相信什么。 - -不能说的话往往是正确的话,因为具有争议性,会让某些人暴跳如雷。 - -那些不一定正确,极富争议性的言论被成为“异端邪说“,人们常常会给它们加上标签,目的是为了封杀它们。 -> 亵渎神明、冒犯圣灵、异端都是西方历史上常见的标签,当代的标签则是有伤风化、不得体、破坏国家利益等。 - -禁忌的推崇者是那些处于中等阶层的人,他们不像高等阶层那样有足够自信保护自己的利益,又不同于低等阶层,他们有实力去推行。 - -流行观点的第一批追逐者是为了让自己与众不同,而第二批则是怕自己与众不同。 - -科学家往往需要打破流行观点,从而取得突破。 - -如果因为说了某些话而让干扰了自己的生活,最好还是别说。 -> 这时你要明白,自由思考比畅所欲言更重要。如果你感到一定要跟那些人辩个明白,绝不咽下这口气,一定要把话说清楚,结果很可能是从此你再也无法自由理性地思考了。我认为这样做不可取,更好的方法是在思想和言论之间划一条明确的界线。在心里无所不想,但是不一定要说出来。我就鼓励自己在心里默默思考那些最无法无天的想法。你的思想是一个地下组织,绝不要把那里发生的事情一股脑说给外人听。“格斗俱乐部”的第一条规则,就是不要提到格斗俱乐部。 - -当狂热分子质问你的观点时,最好是说自己还没想好。 - -人们会误认为自己思想开放,但是他们心里早就有一根界限,认为什么是正确的什么是错误的。 - -更深入的认识自己。 -> 儿童精疲力竭时,可能会大发脾气,因为他不知道为了什么;成年人则会了解是个人的身体状况问题,与外界无关,说一句“没关系,我只是累了”。 - -# 另一条路 - -互联网软件的优点 - -1. 不需要用户来管理系统,因此不需要专门的计算机知识,使用浏览器即可使用; -2. 需要的硬件资源很少,只需要能运行浏览器并且联网的设备就行; -3. 更方便,随时随地就可以使用; -4. 不用担心数据丢失,因为数据保存在云端; -5. 易于对软件进行升级,而用户可以在完全不知情的情况下继续使用; -6. 更容易的 bug 发现,更快的 bug 修改; -7. 有专门的技术人员管理服务器,因为更加安全; - - -bug 越快发现越好,而互联网软件因为发布周期短,用户反馈快,因此 bug 的发现也更快。 - -高级用户很乐意帮助寻找 bug。 -> 因为软件发布以后,大多数 bug 都是罕见情况下才会发生的个案,受到影响的用户往往都是高级使用者,他们喜欢试验那些不常用的、难度大的操作。高级使用者对 bug 的容忍度比较高,尤其如果这些 bug 是在开发新功能的过程中引入的,而这些新功能又正是他们所需要的,他们就更能理解了。事实上,因为 bug 不多,你只有经过一些复杂的过程以后才会遇到它们,所以高级使用者往往因为发现了 bug 感到很得意。他们打电话给客服时,多半是一副胜利者的口吻,而不是怒气冲冲的样子,好像他们击败我们得分了一样。 - -好的技术支持是:客服人员和技术人员离得很近,并且当场修复 bug。 - -一个好的想法在实现过程中能引起更多想法,因此尽早去实现它比将它束之高阁能够带来更多好处。 - -不会购买软件的用户使用了盗版软件能够让软件更具有市场影响力。 -> 一定数量的盗版对软件公司是有好处的。不管你的软件定价多少,有些用户永远都不会购买。如果这样的用户使用盗版,你并没有任何损失。事实上,你反而赚到了,因为你的软件现在多了一个用户,市场影响力就更大了一些,而这个用户可能毕业以后就会出钱购买你的软件。 - -# 设计与研究 - -设计追求的是好,而研究追求的是新。 - -用户要求和用户需求有区别,用户可能并不了解自己要什么,所以用户要求的内容往往不是用户真正需求的东西。 - -艺术设计与人为本,而科学研究追求简洁正确。比如数学家不会为了让读者更容易懂而采用一种更麻烦的证明,而是会选择最直接简洁的证明。编程语言也是与人为本的,因此编程语言也是通过设计产生的。 - -大理石做出的东西漂亮,但是制作过程却很难,无法不断的进行雕琢。编程语言也需要这种雕琢过程,如果一种编程语言像大理石一样,只是结果好看,那么它就不是一种好的编程语言。 - -设计软件应当尽快拿出原型来。就像画画一样,是先用几根线画出一个大致准确的轮廓,然后再逐步加工。 -> 还有另一种软件设计思想,也许可以被称为“圣母玛丽亚”模式。它不要求尽快拿出原型,然后再逐步优化,它的观点是你应该等到完整的成品出来以后再一下子隆重地推向市场,就像圣母玛丽亚降临一样,哪怕整个过程漫长得像橄榄球运动员长途奔袭也没有关系。在互联网泡沫时期,无数创业公司因为相信了这种模式而自毁前程。 - -如果觉得做某件事很乏味,那么做出来的东西就会很乏味。快速的原型能够让程序员有更高的士气,从而让他们更有兴趣去实现。也可以理解为快速反馈很重要。 diff --git a/notes/面经/C++后台开发面试常见问题汇总_讨论区_牛客网 -.txt b/notes/面经/C++后台开发面试常见问题汇总_讨论区_牛客网 -.txt deleted file mode 100644 index f90163ce..00000000 --- a/notes/面经/C++后台开发面试常见问题汇总_讨论区_牛客网 -.txt +++ /dev/null @@ -1,1259 +0,0 @@ - - -下载APP 注册或登录 讨论区 -C++后台开发面试常见问题汇总 - -oscarwin -2017-10-27 09:42:47 -13 29 - - -从牛客获得太多的资源,来一发面经,纯技术干货。面试的技巧,心态,简历也都很重要,不过牛客有很多大佬总结的太好了,就不重复了。 - -主要是C++后台开发,基本总结了我自己面试常问到的,看别人的面经问到过的,分享出来交流一下。 C++后台开发面试常见问题汇总 - -原文来自我的博客,欢迎点击: http://blog.csdn.net/shanghairuoxiao/article/details/72876248 C和C++语言基础 - - -extern关键字作用 -extern声明变量在在外部定义? -extern修饰函数? -extern C的作用?用法? - - -static关键字作用 -static修饰局部变量? -static全局变量?(限定变量在一个编译单元内,一个编译单元就是指一个cpp和它包含的头文件,这个回答可以结合编译需要经历的几个过程来答) -static修饰普通函数? -static修饰成员变量? -static修饰成员函数? - - -volatile是干啥的 -访问寄存器要比访问内存要块,因此CPU会优先访问该数据在寄存器中的存储结果,但是内存中的数据可能已经发生了改变,而寄存器中还保留着原来的结果。为了避免这种情况的发生将该变量声明为volatile,告诉CPU每次都从内存去读取数据。 -一个参数可以即是const又是volatile的吗?可以,一个例子是只读状态寄存器,是volatile是因为它可能被意想不到的被改变,是const告诉程序不应该试图去修改他。 - - -说说const的作用,越多越好 -const修饰全局变量; -const修饰局部变量; -const修饰指针,const int *; -const修饰指针指向的对象, int * const; -const修饰引用做形参; -const修饰成员变量,必须在构造函数列表中初始化; -const修饰成员函数,说明该函数不应该修改非静态成员,但是这并不是十分可靠的,指针所指的非成员对象值可能会被改变 - - -new与malloc区别 -new分配内存按照数据类型进行分配,malloc分配内存按照大小分配; -new不仅分配一段内存,而且会调用构造函数,但是malloc则不会。new的实现原理?但是还需要注意的是,之前看到过一个题说int p = new int与int p = new int()的区别,因为int属于C++内置对象,不会默认初始化,必须显示调用默认构造函数,但是对于自定义对象都会默认调用构造函数初始化。翻阅资料后,在C++11中两者没有区别了,自己测试的结构也都是为0; -new返回的是指定对象的指针,而malloc返回的是void*,因此malloc的返回值一般都需要进行类型转化; -new是一个操作符可以重载,malloc是一个库函数; -new分配的内存要用delete销毁,malloc要用free来销毁;delete销毁的时候会调用对象的析构函数,而free则不会; -malloc分配的内存不够的时候,可以用realloc扩容。扩容的原理?new没用这样操作; -new如果分配失败了会抛出bad_malloc的异常,而malloc失败了会返回NULL。因此对于new,正确的姿势是采用try...catch语法,而malloc则应该判断指针的返回值。为了兼容很多c程序员的习惯,C++也可以采用new nothrow的方法禁止抛出异常而返回NULL; -new和new[]的区别,new[]一次分配所有内存,多次调用构造函数,分别搭配使用delete和delete[],同理,delete[]多次调用析构函数,销毁数组中的每个对象。而malloc则只能sizeof(int) * n; -如果不够可以继续谈new和malloc的实现,空闲链表,分配方法(首次适配原则,最佳适配原则,最差适配原则,快速适配原则)。delete和free的实现原理,free为什么直到销毁多大的空间? - - -C++多态性与虚函数表 -C++多态的实现? -多态分为静态多态和动态多态。静态多态是通过重载和模板技术实现,在编译的时候确定。动态多态通过虚函数和继承关系来实现,执行动态绑定,在运行的时候确定。 -动态多态实现有几个条件: -(1) 虚函数; -(2) 一个基类的指针或引用指向派生类的对象; -基类指针在调用成员函数(虚函数)时,就会去查找该对象的虚函数表。虚函数表的地址在每个对象的首地址。查找该虚函数表中该函数的指针进行调用。 -每个对象中保存的只是一个虚函数表的指针,C++内部为每一个类维持一个虚函数表,该类的对象的都指向这同一个虚函数表。 -虚函数表中为什么就能准确查找相应的函数指针呢?因为在类设计的时候,虚函数表直接从基类也继承过来,如果覆盖了其中的某个虚函数,那么虚函数表的指针就会被替换,因此可以根据指针准确找到该调用哪个函数。 - - -虚函数的作用? -虚函数用于实现多态,这点大家都能答上来 -但是虚函数在设计上还具有封装和抽象的作用。比如抽象工厂模式。 - - -动态绑定是如何实现的? -第一个问题中基本回答了,主要都是结合虚函数表来答就行。 - - -静态多态和动态多态 。静态多态是指通过模板技术或者函数重载技术实现的多态,其在编译器确定行为。动态多态是指通过虚函数技术实现在运行期动态绑定的技术。 - - -虚函数表 - - -虚函数表是针对类的还是针对对象的?同一个类的两个对象的虚函数表是怎么维护的? -编译器为每一个类维护一个虚函数表,每个对象的首地址保存着该虚函数表的指针,同一个类的不同对象实际上指向同一张虚函数表。 - - -纯虚函数如何定义,为什么对于存在虚函数的类中析构函数要定义成虚函数 -为了实现多态进行动态绑定,将派生类对象指针绑定到基类指针上,对象销毁时,如果析构函数没有定义为析构函数,则会调用基类的析构函数,显然只能销毁部分数据。如果要调用对象的析构函数,就需要将该对象的析构函数定义为虚函数,销毁时通过虚函数表找到对应的析构函数。 -//纯虚函数定义 -virtual ~myClass() = 0; - - - -析构函数能抛出异常吗 -答案肯定是不能。 - -C++标准指明析构函数不能、也不应该抛出异常。C++异常处理模型最大的特点和优势就是对C++中的面向对象提供了最强大的无缝支持。那么如果对象在运行期间出现了异常,C++异常处理模型有责任清除那些由于出现异常所导致的已经失效了的对象(也即对象超出了它原来的作用域),并释放对象原来所分配的资源, 这就是调用这些对象的析构函数来完成释放资源的任务,所以从这个意义上说,析构函数已经变成了异常处理的一部分。 - - -(1) 如果析构函数抛出异常,则异常点之后的程序不会执行,如果析构函数在异常点之后执行了某些必要的动作比如释放某些资源,则这些动作不会执行,会造成诸如资源泄漏的问题。 - -(2) 通常异常发生时,c++的机制会调用已经构造对象的析构函数来释放资源,此时若析构函数本身也抛出异常,则前一个异常尚未处理,又有新的异常,会造成程序崩溃的问题。 - - -构造函数和析构函数中调用虚函数吗? - - -指针和引用的区别 -指针保存的是所指对象的地址,引用是所指对象的别名,指针需要通过解引用间接访问,而引用是直接访问; -指针可以改变地址,从而改变所指的对象,而引用必须从一而终; -引用在定义的时候必须初始化,而指针则不需要; -指针有指向常量的指针和指针常量,而引用没有常量引用; -指针更灵活,用的好威力无比,用的不好处处是坑,而引用用起来则安全多了,但是比较死板。 - - -指针与数组千丝万缕的联系 -一个一维int数组的数组名实际上是一个int* const 类型; -一个二维int数组的数组名实际上是一个int (*const p)[n]; -数组名做参数会退化为指针,除了sizeof - - -智能指针是怎么实现的?什么时候改变引用计数? -构造函数中计数初始化为1; -拷贝构造函数中计数值加1; -赋值运算符中,左边的对象引用计数减一,右边的对象引用计数加一; -析构函数中引用计数减一; -在赋值运算符和析构函数中,如果减一后为0,则调用delete释放对象。 -share_prt与weak_ptr的区别? - -//share_ptr可能出现循环引用,从而导致内存泄露 -class A -{ -public: - share_ptr p; -}; -class B -{ -public: - share_ptr p; -} -int main() -{ - while(true) - { - share_prt pa(new A()); //pa的引用计数初始化为1 - share_prt pb(new B()); //pb的引用计数初始化为1 - pa->p = pb; //pb的引用计数变为2 - pb->p = pa; //pa的引用计数变为2 - } - //假设pa先离开,引用计数减一变为1,不为0因此不会调用class A的析构函数,因此其成员p也不会被析构,pb的引用计数仍然为2; - //同理pb离开的时候,引用计数也不能减到0 - return 0; -} -/* -** weak_ptr是一种弱引用指针,其存在不会影响引用计数,从而解决循环引用的问题 -*/ - - - -C++四种类型转换 : static_cast, dynamic_cast, const_cast, reinterpret_cast -const_cast用于将const变量转为非const -static_cast用的最多,对于各种隐式转换,非const转const,void*转指针等, static_cast能用于多态想上转化,如果向下转能成功但是不安全,结果未知; -dynamic_cast用于动态类型转换。只能用于含有虚函数的类,用于类层次间的向上和向下转化。只能转指针或引用。向下转化时,如果是非法的对于指针返回NULL,对于引用抛异常。要深入了解内部转换的原理。 -reinterpret_cast几乎什么都可以转,比如将int转指针,可能会出问题,尽量少用; -为什么不使用C的强制转换?C的强制转换表面上看起来功能强大什么都能转,但是转化不够明确,不能进行错误检查,容易出错。 - - -内存对齐的原则 -从0位置开始存储; -变量存储的起始位置是该变量大小的整数倍; -结构体总的大小是其最大元素的整数倍,不足的后面要补齐; -结构体中包含结构体,从结构体中最大元素的整数倍开始存; -如果加入pragma pack(n) ,取n和变量自身大小较小的一个。 - - -内联函数有什么优点?内联函数与宏定义的区别? -宏定义在预编译的时候就会进行宏替换; -内联函数在编译阶段,在调用内联函数的地方进行替换,减少了函数的调用过程,但是使得编译文件变大。因此,内联函数适合简单函数,对于复杂函数,即使定义了内联编译器可能也不会按照内联的方式进行编译。 -内联函数相比宏定义更安全,内联函数可以检查参数,而宏定义只是简单的文本替换。因此推荐使用内联函数,而不是宏定义。 -使用宏定义函数要特别注意给所有单元都加上括号,#define MUL(a, b) a b,这很危险,正确写法:#define MUL(a, b) ((a) (b)) - - -C++内存管理 -C++内存分为那几块?(堆区,栈区,常量区,静态和全局区) -每块存储哪些变量? -学会迁移,可以说到malloc,从malloc说到操作系统的内存管理,说道内核态和用户态,然后就什么高端内存,slab层,伙伴算法,VMA可以巴拉巴拉了,接着可以迁移到fork()。 -STL里的内存池实现 -STL内存分配分为一级分配器和二级分配器,一级分配器就是采用malloc分配内存,二级分配器采用内存池。 - - -二级分配器设计的非常巧妙,分别给8k,16k,..., 128k等比较小的内存片都维持一个空闲链表,每个链表的头节点由一个数组来维护。需要分配内存时从合适大小的链表中取一块下来。假设需要分配一块10K的内存,那么就找到最小的大于等于10k的块,也就是16K,从16K的空闲链表里取出一个用于分配。释放该块内存时,将内存节点归还给链表。 -如果要分配的内存大于128K则直接调用一级分配器。 -为了节省维持链表的开销,采用了一个union结构体,分配器使用union里的next指针来指向下一个节点,而用户则使用union的空指针来表示该节点的地址。 - - -STL里set和map是基于什么实现的。红黑树的特点? -set和map都是基于红黑树实现的。 -红黑树是一种平衡二叉查找树,与AVL树的区别是什么?AVL树是完全平衡的,红黑树基本上是平衡的。 -为什么选用红黑数呢?因为红黑数是平衡二叉树,其插入和删除的效率都是N(logN),与AVL相比红黑数插入和删除最多只需要3次旋转,而AVL树为了维持其完全平衡性,在坏的情况下要旋转的次数太多。 -红黑树的定义: -(1) 节点是红色或者黑色; -(2) 父节点是红色的话,子节点就不能为红色; -(3) 从根节点到每个页子节点路径上黑色节点的数量相同; -(4) 根是黑色的,NULL节点被认为是黑色的。 - - -STL里的其他数据结构和算法实现也要清楚 -这个问题,把STL源码剖析好好看看,不仅面试不慌,自己对STL的使用也会上升一个层次。 - - -必须在构造函数初始化式里进行初始化的数据成员有哪些 -(1) 常量成员,因为常量只能初始化不能赋值,所以必须放在初始化列表里面 -(2) 引用类型,引用必须在定义的时候初始化,并且不能重新赋值,所以也要写在初始化列表里面 -(3) 没有默认构造函数的类类型,因为使用初始化列表可以不必调用默认构造函数来初始化,而是直接调用拷贝构造函数初始化 - - -模板特化 -(1) 模板特化分为全特化和偏特化,模板特化的目的就是对于某一种变量类型具有不同的实现,因此需要特化版本。例如,在STL里迭代器为了适应原生指针就将原生指针进行特化。 - - -定位内存泄露 -(1)在windows平台下通过CRT中的库函数进行检测; -(2)在可能泄漏的调用前后生成块的快照,比较前后的状态,定位泄漏的位置 -(3)Linux下通过工具valgrind检测 - - -手写strcpy -``` -char strcpy(char dst, const char src) -{ -assert(dst); -assert(src); -char ret = dst; -while(( dst++ = src++) != '\0'); -return ret; -} -//该函数是没有考虑重叠的 - - -char strcpy(char dst, const char src) -{ -assert((dst != NULL) && (src != NULL)); -char ret = dst; -int size = strlen(src) + 1; -if(dst > src || dst < src + len) -{ -dst = dst + size - 1; -src = src + size - 1; -while(size--) -{ -dst-- = src--; -} -} -else -{ -while(size--) -{ -dst++ = src++; -} -} -return ret; -} -- **手写memcpy函数** - - -void memcpy(void dst, const void src, size_t size) -{ -if(dst == NULL || src == NULL) -{ -return NULL; -} -void res = dst; -char pdst = (char )dst; -char psrc = (char )src; -if(pdst > psrc && pdst < psrc + size) //重叠 -{ - pdst = pdst + size - 1; - psrc = pdst + size - 1; - while(size--) - { - *pdst-- = *psrc--; - } -} -else //无重叠 -{ - while(size--) - { - *dst++ = *src++; - } -} -return ret; - - -} -- **手写strcat函数** - - -char strcat(char dst, const char src) -{ -char ret = dst; -while(*dst != '\0') - ++dst; -while((*dst++ = *src) != '\0'); -return ret; - - -} -- **手写strcmp函数** - - -int strcmp(const char str1, const char str2) -{ -while(*str1 == *str2 && *str1 != '\0') -{ - ++str1; - ++str2; -} -return *str1 - *str2; - - -} 数据结构与算法 - -这一块考察范围太广,主要靠多刷题吧,牛客网,剑指OFFER,LeetCode等。 Hash表 -Hash表实现(拉链和分散地址) -Hash策略常见的有哪些? -STL中hash_map扩容发生什么? -(1) 创建一个新桶,该桶是原来桶两倍大最接近的质数(判断n是不是质数的方法:用n除2到$sqrt(n)$范围内的数) ; -(2) 将原来桶里的数通过指针的转换,插入到新桶中(注意STL这里做的很精细,没有直接将数据从旧桶遍历拷贝数据插入到新桶,而是通过指针转换) -(3) 通过swap函数将新桶和旧桶交换,销毁新桶。 树 -二叉树结构,二叉查找树实现; -二叉树的六种遍历; -二叉树的按层遍历; -递归是解决二叉树相关问题的神级方法; -树的各种常见算法题( http://blog.csdn.net/xiajun07061225/article/details/12760493); -什么是红黑树? -节点为红色或者黑色; -根节点为黑色; -从根节点到每个叶子节点经过的黑色节点个数的和相同; -如果父节点为红色,那么其子节点就不能为红色。 - -红黑树与AVL树的区别 -红黑树与AVL树都是平衡树,但是AVL是完全平衡的(平衡就是值树中任意节点的左子树和右子树高度差不超过1); -红黑树效率更高,因为AVL为了保证其完全平衡,插入和删除的时候在最坏的情况下要旋转logN次,而红黑树插入和删除的旋转次数要比AVL少。 - -Trie树(字典树) -每个节点保存一个字符 -根节点不保存字符 -每个节点最多有n个子节点(n是所有可能出现字符的个数) -查询的复杂父为O(k),k为查询字符串长度 链表 - -链表和插入和删除,单向和双向链表都要会 -链表的问题考虑多个指针和递归 -(1) 反向打印链表(递归) -(2) 打印倒数第K个节点(前后指针) -(3) 链表是否有环(快慢指针)等等。b ggg 栈和队列 -队列和栈的区别 ?(从实现,应用,自身特点多个方面来阐述,不要只说一个先入先出,先入后出,这个你会别人也会,要展现出你比别人掌握的更深) -典型的应用场景 海量数据问题 -十亿整数(随机生成,可重复)中前K最大的数 -类似问题的解决方法思路:首先哈希将数据分成N个文件,然后对每个文件建立K个元素最小/大堆(根据要求来选择)。最后将文件中剩余的数插入堆中,并维持K个元素的堆。最后将N个堆中的元素合起来分析。可以采用归并的方式来合并。在归并的时候为了提高效率还需要建一个N个元素构成的最大堆,先用N个堆中的最大值填充这个堆,然后就是弹出最大值,指针后移的操作了。当然这种问题在现在的互联网技术中,一般就用map-reduce框架来做了。 -大数据排序相同的思路:先哈希(哈希是好处是分布均匀,相同的数在同一个文件中),然后小文件装入内存快排,排序结果输出到文件。最后建堆归并。 -十亿整数(随机生成,可重复)中出现频率最高的一千个 排序算法 -排序算法当然是基础内容了,必须至少能快速写出,快排,建堆,和归并 -每种算法的时间空间复杂度,最好最差平均情况 位运算 布隆过滤器 几十亿个数经常要查找某一个数在不在里面,使用布隆过滤器,布隆过滤器的原理。布隆过滤器可能出现误判,怎么保证无误差? 网络与TCP/IP -TCP与UDP之间的区别 -(1) IP首部,TCP首部,UDP首部 -(2) TCP和UDP区别 -(3) TCP和UDP应用场景 -(4) 如何实现可靠的UDP -TCP三次握手与四次挥手 -详细说明TCP状态迁移过程 -(1) 三次握手和四次挥手状态变化; -(2) 2MSL是什么状态?作用是什么? -TCP相关技术 -1. TCP重发机制,Nagle算法 -2. TCP的拥塞控制使用的算法和具体过程 -3. TCP的窗口滑动 -TCP客户与服务器模型,用到哪些函数 -UDP客户与服务器模型,用到哪些函数 -域名解析过程,ARP的机制,RARP的实现 -1. RARP用于无盘服务器,开机后通过发送RARP包给RARP服务器,通过mac地址得到IP地址 -Ping和TraceRoute实现原理 -(1) Ping是通过发送ICMP报文回显请求实现。 -(2) TraceRoute通过发送UDP报文,设置目的端口为一个不可能的值,将IP首部中的TTL分别设置从1到N,每次逐个增加,如果收到端口不可达,说明到达目的主机,如果是因为TTL跳数超过,路由器会发送主机不可达的ICMP报文。 -HTTP http/https 1.0、1.1、2.0 - -1. http的主要特点: -简单快速: 当客户端向服务器端发送请求时,只是简单的填写请求路径和请求方法即可,然后就可以通过浏览器或其他方式将该请求发送就行了 -灵活: HTTP 协议允许客户端和服务器端传输任意类型任意格式的数据对象 -无连接: 无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接,采用这种方式可以节省传输时间。(当今多数服务器支持Keep-Alive功能,使用服务器支持长连接,解决无连接的问题) -无状态: 无状态是指协议对于事务处理没有记忆能力,服务器不知道客户端是什么状态。即客户端发送HTTP请求后,服务器根据请求,会给我们发送数据,发送完后,不会记录信息。(使用 cookie 机制可以保持 session,解决无状态的问题) -2. http1.1的特点 -a、默认持久连接节省通信量,只要客户端服务端任意一端没有明确提出断开TCP连接,就一直保持连接,可以发送多次HTTP请求 -b、管线化,客户端可以同时发出多个HTTP请求,而不用一个个等待响应 -c、断点续传ftghh -3. http2.0的特点 -a、HTTP/2采用二进制格式而非文本格式 -b、HTTP/2是完全多路复用的,而非有序并阻塞的——只需一个HTTP连接就可以实现多个请求响应 -c、使用报头压缩,HTTP/2降低了开销 -d、HTTP/2让服务器可以将响应主动“推送”到客户端缓存中 get/post 区别 - -区别一: -get重点在从服务器上获取资源,post重点在向服务器发送数据; -区别二: -get传输数据是通过URL请求,以field(字段)= value的形式,置于URL后,并用"?"连接,多个请求数据间用"&"连接,如http://127.0.0.1/Test/login.action?name=admin&password=admin,这个过程用户是可见的; -post传输数据通过Http的post机制,将字段与对应值封存在请求实体中发送给服务器,这个过程对用户是不可见的; -区别三: -Get传输的数据量小,因为受URL长度限制,但效率较高; -Post可以传输大量数据,所以上传文件时只能用Post方式; -区别四: -get是不安全的,因为URL是可见的,可能会泄露私密信息,如密码等; -post较get安全性较高; - 返回状态码 - -200:请求被正常处理 -204:请求被受理但没有资源可以返回 -206:客户端只是请求资源的一部分,服务器只对请求的部分资源执行GET方法,相应报文中通过Content-Range指定范围的资源。 -301:永久性重定向 -302:临时重定向 -303:与302状态码有相似功能,只是它希望客户端在请求一个URI的时候,能通过GET方法重定向到另一个URI上 -304:发送附带条件的请求时,条件不满足时返回,与重定向无关 -307:临时重定向,与302类似,只是强制要求使用POST方法 -400:请求报文语法有误,服务器无法识别 -401:请求需要认证 -403:请求的对应资源禁止被访问 -404:服务器无法找到对应资源 -500:服务器内部错误 -503:服务器正忙 - http 协议头相关 - -http数据由请求行,首部字段,空行,报文主体四个部分组成 -首部字段分为:通用首部字段,请求首部字段,响应首部字段,实体首部字段 https与http的区别?如何实现加密传输? -https就是在http与传输层之间加上了一个SSL -对称加密与非对称加密 浏览器中输入一个URL发生什么,用到哪些协议? 浏览器中输入URL,首先浏览器要将URL解析为IP地址,解析域名就要用到DNS协议,首先主机会查询DNS的缓存,如果没有就给本地DNS发送查询请求。DNS查询分为两种方式,一种是递归查询,一种是迭代查询。如果是迭代查询,本地的DNS服务器,向根域名服务器发送查询请求,根域名服务器告知该域名的一级域名服务器,然后本地服务器给该一级域名服务器发送查询请求,然后依次类推直到查询到该域名的IP地址。DNS服务器是基于UDP的,因此会用到UDP协议。 -得到IP地址后,浏览器就要与服务器建立一个http连接。因此要用到http协议,http协议报文格式上面已经提到。http生成一个get请求报文,将该报文传给TCP层处理。如果采用https还会先对http数据进行加密。TCP层如果有需要先将HTTP数据包分片,分片依据路径MTU和MSS。TCP的数据包然后会发送给IP层,用到IP协议。IP层通过路由选路,一跳一跳发送到目的地址。当然在一个网段内的寻址是通过以太网协议实现(也可以是其他物理层协议,比如PPP,SLIP),以太网协议需要直到目的IP地址的物理地址,有需要ARP协议。 安全相关 -SQL注入 -XSS -RCFS -APR欺骗 数据库 -SQL语言(内外连接,子查询,分组,聚集,嵌套,逻辑) -MySQL索引方法?索引的优化? -InnoDB与MyISAM区别? -事务的ACID -事务的四个隔离级别 -查询优化(从索引上优化,从SQL语言上优化) -B-与B+树区别? -MySQL的联合索引(又称多列索引)是什么?生效的条件? -分库分表 Linux 进程与线程 (1) 进程与线程区别? -(2) 线程比进程具有哪些优势? -(3) 什么时候用多进程?什么时候用多线程? -(4) LINUX中进程和线程使用的几个函数? -(5) 线程同步? -在Windows下线程同步的方式有:互斥量,信号量,事件,关键代码段 -在Linux下线程同步的方式有:互斥锁,自旋锁,读写锁,屏障(并发完成同一项任务时,屏障的作用特别好使) -知道这些锁之间的区别,使用场景? 进程间通讯方式 - -管道( pipe ) :管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。 - -命名管道 (FIFO) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。 - -信号量 :信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据,有XSI信号量和POSIX信号量,POSIX信号量更加完善。 - -消息队列( message queue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。 - -共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。(原理一定要清楚,常考) - -信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生,常见的信号。 - -套接字( socket ) : 套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。 -匿名管道与命名管道的区别 :匿名管道只能在具有公共祖先的两个进程间使用。 -共享文件映射mmap -mmap建立进程空间到文件的映射,在建立的时候并不直接将文件拷贝到物理内存,同样采用缺页终端。mmap映射一个具体的文件可以实现任意进程间共享内存,映射一个匿名文件,可以实现父子进程间共享内存。 -常见的信号有哪些? :SIGINT,SIGKILL(不能被捕获),SIGTERM(可以被捕获),SIGSEGV,SIGCHLD,SIGALRM 内存管理 1. 虚拟内存的作用? -2. 虚拟内存的实现? -3. 操作系统层面对内存的管理? -4. 内存池的作用?STL里 内存池如何实现 ? -5. 进程空间和内核空间对内存的管理不同? -6. Linux的slab层,VAM? -7. 伙伴算法 -8. 高端内存 进程调度 1. Linux进程分为两种,实时进程和非实时进程; -2. 优先级分为静态优先级和动态优先级,优先级的范围; -3. 调度策略,FIFO,LRU,时间片轮转 -4. 交互进程通过平均睡眠时间而被奖励; 死锁 (1) 死锁产生的条件; -(2) 死锁的避免; 命令行 -Linux命令 在一个文件中,倒序打印第二行前100个大写字母 -``` - - -cat filename | head -n 2 | tail -n 1 | grep '[[:upper:]]' -o | tr -d '\n'| cut -c 1-100 | rev -与CPU,内存,磁盘相关的命令(top,free, df, fdisk) -网络相关的命令netstat,tcpdump等 -sed, awk, grep三个超强大的命名,分别用与格式化修改,统计,和正则查找 -ipcs和ipcrm命令 -查找当前目录以及字母下以.c结尾的文件,且文件中包含"hello world"的文件的路径 -创建定时任务 IO模型 -五种IO模型: 阻塞IO,非阻塞IO,IO复用,信号驱动式IO,异步IO -select,poll,epoll的区别 -select: 是最初解决IO阻塞问题的方法。用结构体fd_set来告诉内核监听多个文件描述符,该结构体被称为描述符集。由数组来维持哪些描述符被置位了。对结构体的操作封装在三个宏定义中。通过轮寻来查找是否有描述符要被处理,如果没有返回 -存在的问题: -1. 内置数组的形式使得select的最大文件数受限与FD_SIZE; -2. 每次调用select前都要重新初始化描述符集,将fd从用户态拷贝到内核态,每次调用select后,都需要将fd从内核态拷贝到用户态; -3. 轮寻排查当文件描述符个数很多时,效率很低; poll: 通过一个可变长度的数组解决了select文件描述符受限的问题。数组中元素是结构体,该结构体保存描述符的信息,每增加一个文件描述符就向数组中加入一个结构体,结构体只需要拷贝一次到内核态。poll解决了select重复初始化的问题。轮寻排查的问题未解决。 -epoll: 轮寻排查所有文件描述符的效率不高,使服务器并发能力受限。因此,epoll采用只返回状态发生变化的文件描述符,便解决了轮寻的瓶颈。 -为什么使用IO多路复用,最主要的原因是什么? -epoll有两种触发模式?这两种触发模式有什么区别?编程的时候有什么区别? -上一题中编程的时候有什么区别,是在边缘触发的时候要把套接字中的数据读干净,那么当有多个套接字时,在读的套接字一直不停的有数据到达,如何保证其他套接字不被饿死(面试网易游戏的时候问的一个问题,答不上来,印象贼深刻)。 -1. select/poll/epoll区别 -2. 几种网络服务器模型的介绍与比较 -3. epoll为什么这么快 (搞懂这篇文章,关于IO复用的问题就信手拈来了) 线程池 Linux的API -fork与vfork区别 -fork和vfork都用于创建子进程。但是vfork创建子进程后,父进程阻塞,直到子进程调用exit()或者excle()。 -对于内核中过程fork通过调用clone函数,然后clone函数调用do_fork()。do_fork()中调用copy_process()函数先复制task_struct结构体,然后复制其他关于内存,文件,寄存器等信息。fork采用写时拷贝技术,因此子进程和父进程的页表指向相同的页框。但是vfork不需要拷贝页表,因为父进程会一直阻塞,直接使用父进程页表。 -exit()与_exit()区别 -exit()清理后进入内核,_exit()直接陷入内核。 -孤儿进程与僵死进程 -1. 孤儿进程是怎么产生的? -2. 僵死进程是怎么产生的? -3. 僵死进程的危害? -4. 如何避免僵死进程的产生? -Linux是如何避免内存碎片的 -1. 伙伴算法,用于管理物理内存,避免内存碎片; -2. 高速缓存Slab层用于管理内核分配内存,避免碎片。 -共享内存的实现原理? -共享内存实现分为两种方式一种是采用mmap,另一种是采用XSI机制中的共享内存方法。mmap是内存文件映射,将一个文件映射到进程的地址空间,用户进程的地址空间的管理是通过vm_area_struct结构体进行管理的。mmap通过映射一个相同的文件到两个不同的进程,就能实现这两个进程的通信,采用该方法可以实现任意进程之间的通信。mmap也可以采用匿名映射,不指定映射的文件,但是只能在父子进程间通信。XSI的内存共享实际上也是通过映射文件实现,只是其映射的是一种特殊文件系统下的文件,该文件是不能通过read和write访问的。 -二者区别: - -1、 系统V共享内存中的数据,从来不写入到实际磁盘文件中去;而通过mmap()映射普通文件实现的共享内存通信可以指定何时将数据写入磁盘文件中。注:前面讲到,系统V共享内存机制实际是通过映射特殊文件系统shm中的文件实现的,文件系统shm的安装点在交换分区上,系统重新引导后,所有的内容都丢失。 -2、 系统V共享内存是随内核持续的,即使所有访问共享内存的进程都已经正常终止,共享内存区仍然存在(除非显式删除共享内存),在内核重新引导之前,对该共享内存区域的任何改写操作都将一直保留。 -3、 通过调用mmap()映射普通文件进行进程间通信时,一定要注意考虑进程何时终止对通信的影响。而通过系统V共享内存实现通信的进程则不然。注:这里没有给出shmctl的使用范例,原理与消息队列大同小异。 -系统调用与库函数(open, close, create, lseek, write, read) -同步方法有哪些? -1. 互斥锁,自旋锁,信号量,读写锁,屏障 -2. 互斥锁与自旋锁的区别:互斥锁得不到资源的时候阻塞,不占用cpu资源。自旋锁得不到资源的时候,不停的查询,而然占用cpu资源。 -3. 死锁 其他 -++i是否是原子操作 -明显不是,++i主要有三个步骤,把数据从内存放在寄存器上,在寄存器上进行自增,把数据从寄存器拷贝会内存,每个步骤都可能被中断。 -判断大小端 -``` - - -union un -{ -int i; -char ch; -}; - -void fun() -{ -union un test; -test.i = 1; -if(ch == 1) -cout << "小端" << endl; -else -cout << "大端" << endl; -} -``` 设计模式 -单例模式线程安全的写法 -STL里的迭代器使用了迭代器模式 -MVC的理解 -分布式系统 -map_reduce原理 (这篇文章讲的很通俗易懂) -负载均衡 -CDN - -29 -精彩评论 -13条回帖 - -康小城 -2017-10-27 09:45:44 -0 0 -满满的干货,干货满满 - -77浩力,百世不敌 -2017-10-27 09:54:24 -0 0 -66666666666 - -苏Per -2017-10-27 09:55:13 -0 0 -干货!赞! - -不知火 -2017-10-27 10:59:40 -0 0 -此贴要顶 - -我家的狗不咬人 -2017-10-27 11:17:04 -0 0 -干货,很棒! - -JackLiao -2017-10-27 11:20:55 -0 0 -干货,多谢 - -寻ME -2017-10-27 12:37:47 -0 0 -太强辣 - -跋锋小寒 -2017-10-27 13:02:45 -0 0 -总结的好 - -三和大神 -2017-10-27 13:55:35 -1 0 -赞一个,有几个位置想提一下自己的意见 -1,new与malloc区别第二条,对于POD对象并无默认构造函数。 - 第七条,new有八个重载版本,失败还可能返回空指针 -2,虚函数表这里,每个类的虚函数表可以有多个的吧,与继承的基类数目相同 -3,内联函数这里。内联函数并不一定只在编译期,还可在编码期,连接期(如VC++7.0),运行期(如RVM),取决于具体编译器的实现,参考more exceptional c++ 条款8,c++编程剖析第25条 -4,c++内存管理这里,c++还有一个自由存储区,new的内存不一定就是堆,还可以是在栈中,全局区中。参考exceptional c++条款35 -5,定位内存泄露这里,无论是CRT还是Valgrind都只是在debug模式下使用,在release模式下方式有:1,对象计数,2,重载new,delete(记录分配点,定期打印堆栈),3,hook 分配内存的API 4,DiagLeak -6, tranceroute这里。tranceroute使用UDP的?我印象中就是ICMP+TTL -7, get和post区别这里,http设计中不是要考虑幂等性吗,怎么get就不安全了呢,对于暴漏url不是有ssl/tls吗,而且url长度限制貌似是取决于浏览器的吧。。 -8,DNS不是基于UDP,是基于UDP和TCP -9,消息队列这里,貌似Posix标准下消息队列本质是一个优先级队列,在system v标准下才是通过消息的标识来提取感兴趣的消息 - 所以system v更灵活。参考linux从应用到内核 第十一章 -10,信号这里补充一下,信号分为可靠信号和不可靠信号,不可靠信号是用位图管理,可能丢失,可靠信号是由一个队列进行管理,在一定限度内不丢失(当队列塞不下的时候就丢失了) -11,进程调度这里,非实时进程采用完全公平调度,通过nice值计算虚拟运行时间,内核对这些进程用红黑树进行管理。实现进程采用fifo和时间片转轮 -12,poll这里,poll只拷贝一次到文件内核态吗?是epoll吧,原理是通过一个额外的文件描述符标识这个文件描述符集 - 那个问题,边沿触发设置一个EPOLLONESHOT事件可以解决事件频繁触发的问题 - -御光飘扬 -2017-10-27 15:55:25 -0 0 -很不错,大佬 - -TheBestIsYetToCome- -2017-10-27 16:16:57 -0 0 -牛批 - -红色小宝马 -2017-10-28 19:38:23 -0 0 -好难 - -广州渣渣辉 -2017-10-28 19:49:34 -0 0 -谢谢大佬 -加载中... -查看更多回复 -没有回复 -添加回复 -查看更多精彩内容 > -回复 - - -0 / -确定 -下载牛客APP,把IT求职神器装进口袋 - -请登录后查看 - - - - -知识点专项练习 - - - - -笔试真题查看 - - - - -公司真题套题练习 - - - - -计算机期末备考专场 - - - - -讨论区 - - - - -我的练习 - - - - - -请登录后设置 - - - -从牛客获得太多的资源,来一发面经,纯技术干货。面试的技巧,心态,简历也都很重要,不过牛客有很多大佬总结的太好了,就不重复了。 - -主要是C++后台开发,基本总结了我自己面试常问到的,看别人的面经问到过的,分享出来交流一下。 C++后台开发面试常见问题汇总 - -原文来自我的博客,欢迎点击: http://blog.csdn.net/shanghairuoxiao/article/details/72876248 C和C++语言基础 - - -extern关键字作用 -extern声明变量在在外部定义? -extern修饰函数? -extern C的作用?用法? - - -static关键字作用 -static修饰局部变量? -static全局变量?(限定变量在一个编译单元内,一个编译单元就是指一个cpp和它包含的头文件,这个回答可以结合编译需要经历的几个过程来答) -static修饰普通函数? -static修饰成员变量? -static修饰成员函数? - - -volatile是干啥的 -访问寄存器要比访问内存要块,因此CPU会优先访问该数据在寄存器中的存储结果,但是内存中的数据可能已经发生了改变,而寄存器中还保留着原来的结果。为了避免这种情况的发生将该变量声明为volatile,告诉CPU每次都从内存去读取数据。 -一个参数可以即是const又是volatile的吗?可以,一个例子是只读状态寄存器,是volatile是因为它可能被意想不到的被改变,是const告诉程序不应该试图去修改他。 - - -说说const的作用,越多越好 -const修饰全局变量; -const修饰局部变量; -const修饰指针,const int *; -const修饰指针指向的对象, int * const; -const修饰引用做形参; -const修饰成员变量,必须在构造函数列表中初始化; -const修饰成员函数,说明该函数不应该修改非静态成员,但是这并不是十分可靠的,指针所指的非成员对象值可能会被改变 - - -new与malloc区别 -new分配内存按照数据类型进行分配,malloc分配内存按照大小分配; -new不仅分配一段内存,而且会调用构造函数,但是malloc则不会。new的实现原理?但是还需要注意的是,之前看到过一个题说int p = new int与int p = new int()的区别,因为int属于C++内置对象,不会默认初始化,必须显示调用默认构造函数,但是对于自定义对象都会默认调用构造函数初始化。翻阅资料后,在C++11中两者没有区别了,自己测试的结构也都是为0; -new返回的是指定对象的指针,而malloc返回的是void*,因此malloc的返回值一般都需要进行类型转化; -new是一个操作符可以重载,malloc是一个库函数; -new分配的内存要用delete销毁,malloc要用free来销毁;delete销毁的时候会调用对象的析构函数,而free则不会; -malloc分配的内存不够的时候,可以用realloc扩容。扩容的原理?new没用这样操作; -new如果分配失败了会抛出bad_malloc的异常,而malloc失败了会返回NULL。因此对于new,正确的姿势是采用try...catch语法,而malloc则应该判断指针的返回值。为了兼容很多c程序员的习惯,C++也可以采用new nothrow的方法禁止抛出异常而返回NULL; -new和new[]的区别,new[]一次分配所有内存,多次调用构造函数,分别搭配使用delete和delete[],同理,delete[]多次调用析构函数,销毁数组中的每个对象。而malloc则只能sizeof(int) * n; -如果不够可以继续谈new和malloc的实现,空闲链表,分配方法(首次适配原则,最佳适配原则,最差适配原则,快速适配原则)。delete和free的实现原理,free为什么直到销毁多大的空间? - - -C++多态性与虚函数表 -C++多态的实现? -多态分为静态多态和动态多态。静态多态是通过重载和模板技术实现,在编译的时候确定。动态多态通过虚函数和继承关系来实现,执行动态绑定,在运行的时候确定。 -动态多态实现有几个条件: -(1) 虚函数; -(2) 一个基类的指针或引用指向派生类的对象; -基类指针在调用成员函数(虚函数)时,就会去查找该对象的虚函数表。虚函数表的地址在每个对象的首地址。查找该虚函数表中该函数的指针进行调用。 -每个对象中保存的只是一个虚函数表的指针,C++内部为每一个类维持一个虚函数表,该类的对象的都指向这同一个虚函数表。 -虚函数表中为什么就能准确查找相应的函数指针呢?因为在类设计的时候,虚函数表直接从基类也继承过来,如果覆盖了其中的某个虚函数,那么虚函数表的指针就会被替换,因此可以根据指针准确找到该调用哪个函数。 - - -虚函数的作用? -虚函数用于实现多态,这点大家都能答上来 -但是虚函数在设计上还具有封装和抽象的作用。比如抽象工厂模式。 - - -动态绑定是如何实现的? -第一个问题中基本回答了,主要都是结合虚函数表来答就行。 - - -静态多态和动态多态 。静态多态是指通过模板技术或者函数重载技术实现的多态,其在编译器确定行为。动态多态是指通过虚函数技术实现在运行期动态绑定的技术。 - - -虚函数表 - - -虚函数表是针对类的还是针对对象的?同一个类的两个对象的虚函数表是怎么维护的? -编译器为每一个类维护一个虚函数表,每个对象的首地址保存着该虚函数表的指针,同一个类的不同对象实际上指向同一张虚函数表。 - - -纯虚函数如何定义,为什么对于存在虚函数的类中析构函数要定义成虚函数 -为了实现多态进行动态绑定,将派生类对象指针绑定到基类指针上,对象销毁时,如果析构函数没有定义为析构函数,则会调用基类的析构函数,显然只能销毁部分数据。如果要调用对象的析构函数,就需要将该对象的析构函数定义为虚函数,销毁时通过虚函数表找到对应的析构函数。 -//纯虚函数定义 -virtual ~myClass() = 0; - - - -析构函数能抛出异常吗 -答案肯定是不能。 - -C++标准指明析构函数不能、也不应该抛出异常。C++异常处理模型最大的特点和优势就是对C++中的面向对象提供了最强大的无缝支持。那么如果对象在运行期间出现了异常,C++异常处理模型有责任清除那些由于出现异常所导致的已经失效了的对象(也即对象超出了它原来的作用域),并释放对象原来所分配的资源, 这就是调用这些对象的析构函数来完成释放资源的任务,所以从这个意义上说,析构函数已经变成了异常处理的一部分。 - - -(1) 如果析构函数抛出异常,则异常点之后的程序不会执行,如果析构函数在异常点之后执行了某些必要的动作比如释放某些资源,则这些动作不会执行,会造成诸如资源泄漏的问题。 - -(2) 通常异常发生时,c++的机制会调用已经构造对象的析构函数来释放资源,此时若析构函数本身也抛出异常,则前一个异常尚未处理,又有新的异常,会造成程序崩溃的问题。 - - -构造函数和析构函数中调用虚函数吗? - - -指针和引用的区别 -指针保存的是所指对象的地址,引用是所指对象的别名,指针需要通过解引用间接访问,而引用是直接访问; -指针可以改变地址,从而改变所指的对象,而引用必须从一而终; -引用在定义的时候必须初始化,而指针则不需要; -指针有指向常量的指针和指针常量,而引用没有常量引用; -指针更灵活,用的好威力无比,用的不好处处是坑,而引用用起来则安全多了,但是比较死板。 - - -指针与数组千丝万缕的联系 -一个一维int数组的数组名实际上是一个int* const 类型; -一个二维int数组的数组名实际上是一个int (*const p)[n]; -数组名做参数会退化为指针,除了sizeof - - -智能指针是怎么实现的?什么时候改变引用计数? -构造函数中计数初始化为1; -拷贝构造函数中计数值加1; -赋值运算符中,左边的对象引用计数减一,右边的对象引用计数加一; -析构函数中引用计数减一; -在赋值运算符和析构函数中,如果减一后为0,则调用delete释放对象。 -share_prt与weak_ptr的区别? - -//share_ptr可能出现循环引用,从而导致内存泄露 -class A -{ -public: - share_ptr p; -}; -class B -{ -public: - share_ptr p; -} -int main() -{ - while(true) - { - share_prt pa(new A()); //pa的引用计数初始化为1 - share_prt pb(new B()); //pb的引用计数初始化为1 - pa->p = pb; //pb的引用计数变为2 - pb->p = pa; //pa的引用计数变为2 - } - //假设pa先离开,引用计数减一变为1,不为0因此不会调用class A的析构函数,因此其成员p也不会被析构,pb的引用计数仍然为2; - //同理pb离开的时候,引用计数也不能减到0 - return 0; -} -/* -** weak_ptr是一种弱引用指针,其存在不会影响引用计数,从而解决循环引用的问题 -*/ - - - -C++四种类型转换 : static_cast, dynamic_cast, const_cast, reinterpret_cast -const_cast用于将const变量转为非const -static_cast用的最多,对于各种隐式转换,非const转const,void*转指针等, static_cast能用于多态想上转化,如果向下转能成功但是不安全,结果未知; -dynamic_cast用于动态类型转换。只能用于含有虚函数的类,用于类层次间的向上和向下转化。只能转指针或引用。向下转化时,如果是非法的对于指针返回NULL,对于引用抛异常。要深入了解内部转换的原理。 -reinterpret_cast几乎什么都可以转,比如将int转指针,可能会出问题,尽量少用; -为什么不使用C的强制转换?C的强制转换表面上看起来功能强大什么都能转,但是转化不够明确,不能进行错误检查,容易出错。 - - -内存对齐的原则 -从0位置开始存储; -变量存储的起始位置是该变量大小的整数倍; -结构体总的大小是其最大元素的整数倍,不足的后面要补齐; -结构体中包含结构体,从结构体中最大元素的整数倍开始存; -如果加入pragma pack(n) ,取n和变量自身大小较小的一个。 - - -内联函数有什么优点?内联函数与宏定义的区别? -宏定义在预编译的时候就会进行宏替换; -内联函数在编译阶段,在调用内联函数的地方进行替换,减少了函数的调用过程,但是使得编译文件变大。因此,内联函数适合简单函数,对于复杂函数,即使定义了内联编译器可能也不会按照内联的方式进行编译。 -内联函数相比宏定义更安全,内联函数可以检查参数,而宏定义只是简单的文本替换。因此推荐使用内联函数,而不是宏定义。 -使用宏定义函数要特别注意给所有单元都加上括号,#define MUL(a, b) a b,这很危险,正确写法:#define MUL(a, b) ((a) (b)) - - -C++内存管理 -C++内存分为那几块?(堆区,栈区,常量区,静态和全局区) -每块存储哪些变量? -学会迁移,可以说到malloc,从malloc说到操作系统的内存管理,说道内核态和用户态,然后就什么高端内存,slab层,伙伴算法,VMA可以巴拉巴拉了,接着可以迁移到fork()。 -STL里的内存池实现 -STL内存分配分为一级分配器和二级分配器,一级分配器就是采用malloc分配内存,二级分配器采用内存池。 - - -二级分配器设计的非常巧妙,分别给8k,16k,..., 128k等比较小的内存片都维持一个空闲链表,每个链表的头节点由一个数组来维护。需要分配内存时从合适大小的链表中取一块下来。假设需要分配一块10K的内存,那么就找到最小的大于等于10k的块,也就是16K,从16K的空闲链表里取出一个用于分配。释放该块内存时,将内存节点归还给链表。 -如果要分配的内存大于128K则直接调用一级分配器。 -为了节省维持链表的开销,采用了一个union结构体,分配器使用union里的next指针来指向下一个节点,而用户则使用union的空指针来表示该节点的地址。 - - -STL里set和map是基于什么实现的。红黑树的特点? -set和map都是基于红黑树实现的。 -红黑树是一种平衡二叉查找树,与AVL树的区别是什么?AVL树是完全平衡的,红黑树基本上是平衡的。 -为什么选用红黑数呢?因为红黑数是平衡二叉树,其插入和删除的效率都是N(logN),与AVL相比红黑数插入和删除最多只需要3次旋转,而AVL树为了维持其完全平衡性,在坏的情况下要旋转的次数太多。 -红黑树的定义: -(1) 节点是红色或者黑色; -(2) 父节点是红色的话,子节点就不能为红色; -(3) 从根节点到每个页子节点路径上黑色节点的数量相同; -(4) 根是黑色的,NULL节点被认为是黑色的。 - - -STL里的其他数据结构和算法实现也要清楚 -这个问题,把STL源码剖析好好看看,不仅面试不慌,自己对STL的使用也会上升一个层次。 - - -必须在构造函数初始化式里进行初始化的数据成员有哪些 -(1) 常量成员,因为常量只能初始化不能赋值,所以必须放在初始化列表里面 -(2) 引用类型,引用必须在定义的时候初始化,并且不能重新赋值,所以也要写在初始化列表里面 -(3) 没有默认构造函数的类类型,因为使用初始化列表可以不必调用默认构造函数来初始化,而是直接调用拷贝构造函数初始化 - - -模板特化 -(1) 模板特化分为全特化和偏特化,模板特化的目的就是对于某一种变量类型具有不同的实现,因此需要特化版本。例如,在STL里迭代器为了适应原生指针就将原生指针进行特化。 - - -定位内存泄露 -(1)在windows平台下通过CRT中的库函数进行检测; -(2)在可能泄漏的调用前后生成块的快照,比较前后的状态,定位泄漏的位置 -(3)Linux下通过工具valgrind检测 - - -手写strcpy -``` -char strcpy(char dst, const char src) -{ -assert(dst); -assert(src); -char ret = dst; -while(( dst++ = src++) != '\0'); -return ret; -} -//该函数是没有考虑重叠的 - - -char strcpy(char dst, const char src) -{ -assert((dst != NULL) && (src != NULL)); -char ret = dst; -int size = strlen(src) + 1; -if(dst > src || dst < src + len) -{ -dst = dst + size - 1; -src = src + size - 1; -while(size--) -{ -dst-- = src--; -} -} -else -{ -while(size--) -{ -dst++ = src++; -} -} -return ret; -} -- **手写memcpy函数** - - -void memcpy(void dst, const void src, size_t size) -{ -if(dst == NULL || src == NULL) -{ -return NULL; -} -void res = dst; -char pdst = (char )dst; -char psrc = (char )src; -if(pdst > psrc && pdst < psrc + size) //重叠 -{ - pdst = pdst + size - 1; - psrc = pdst + size - 1; - while(size--) - { - *pdst-- = *psrc--; - } -} -else //无重叠 -{ - while(size--) - { - *dst++ = *src++; - } -} -return ret; - - -} -- **手写strcat函数** - - -char strcat(char dst, const char src) -{ -char ret = dst; -while(*dst != '\0') - ++dst; -while((*dst++ = *src) != '\0'); -return ret; - - -} -- **手写strcmp函数** - - -int strcmp(const char str1, const char str2) -{ -while(*str1 == *str2 && *str1 != '\0') -{ - ++str1; - ++str2; -} -return *str1 - *str2; - - -} 数据结构与算法 - -这一块考察范围太广,主要靠多刷题吧,牛客网,剑指OFFER,LeetCode等。 Hash表 -Hash表实现(拉链和分散地址) -Hash策略常见的有哪些? -STL中hash_map扩容发生什么? -(1) 创建一个新桶,该桶是原来桶两倍大最接近的质数(判断n是不是质数的方法:用n除2到$sqrt(n)$范围内的数) ; -(2) 将原来桶里的数通过指针的转换,插入到新桶中(注意STL这里做的很精细,没有直接将数据从旧桶遍历拷贝数据插入到新桶,而是通过指针转换) -(3) 通过swap函数将新桶和旧桶交换,销毁新桶。 树 -二叉树结构,二叉查找树实现; -二叉树的六种遍历; -二叉树的按层遍历; -递归是解决二叉树相关问题的神级方法; -树的各种常见算法题( http://blog.csdn.net/xiajun07061225/article/details/12760493); -什么是红黑树? -节点为红色或者黑色; -根节点为黑色; -从根节点到每个叶子节点经过的黑色节点个数的和相同; -如果父节点为红色,那么其子节点就不能为红色。 - -红黑树与AVL树的区别 -红黑树与AVL树都是平衡树,但是AVL是完全平衡的(平衡就是值树中任意节点的左子树和右子树高度差不超过1); -红黑树效率更高,因为AVL为了保证其完全平衡,插入和删除的时候在最坏的情况下要旋转logN次,而红黑树插入和删除的旋转次数要比AVL少。 - -Trie树(字典树) -每个节点保存一个字符 -根节点不保存字符 -每个节点最多有n个子节点(n是所有可能出现字符的个数) -查询的复杂父为O(k),k为查询字符串长度 链表 - -链表和插入和删除,单向和双向链表都要会 -链表的问题考虑多个指针和递归 -(1) 反向打印链表(递归) -(2) 打印倒数第K个节点(前后指针) -(3) 链表是否有环(快慢指针)等等。b ggg 栈和队列 -队列和栈的区别 ?(从实现,应用,自身特点多个方面来阐述,不要只说一个先入先出,先入后出,这个你会别人也会,要展现出你比别人掌握的更深) -典型的应用场景 海量数据问题 -十亿整数(随机生成,可重复)中前K最大的数 -类似问题的解决方法思路:首先哈希将数据分成N个文件,然后对每个文件建立K个元素最小/大堆(根据要求来选择)。最后将文件中剩余的数插入堆中,并维持K个元素的堆。最后将N个堆中的元素合起来分析。可以采用归并的方式来合并。在归并的时候为了提高效率还需要建一个N个元素构成的最大堆,先用N个堆中的最大值填充这个堆,然后就是弹出最大值,指针后移的操作了。当然这种问题在现在的互联网技术中,一般就用map-reduce框架来做了。 -大数据排序相同的思路:先哈希(哈希是好处是分布均匀,相同的数在同一个文件中),然后小文件装入内存快排,排序结果输出到文件。最后建堆归并。 -十亿整数(随机生成,可重复)中出现频率最高的一千个 排序算法 -排序算法当然是基础内容了,必须至少能快速写出,快排,建堆,和归并 -每种算法的时间空间复杂度,最好最差平均情况 位运算 布隆过滤器 几十亿个数经常要查找某一个数在不在里面,使用布隆过滤器,布隆过滤器的原理。布隆过滤器可能出现误判,怎么保证无误差? 网络与TCP/IP -TCP与UDP之间的区别 -(1) IP首部,TCP首部,UDP首部 -(2) TCP和UDP区别 -(3) TCP和UDP应用场景 -(4) 如何实现可靠的UDP -TCP三次握手与四次挥手 -详细说明TCP状态迁移过程 -(1) 三次握手和四次挥手状态变化; -(2) 2MSL是什么状态?作用是什么? -TCP相关技术 -1. TCP重发机制,Nagle算法 -2. TCP的拥塞控制使用的算法和具体过程 -3. TCP的窗口滑动 -TCP客户与服务器模型,用到哪些函数 -UDP客户与服务器模型,用到哪些函数 -域名解析过程,ARP的机制,RARP的实现 -1. RARP用于无盘服务器,开机后通过发送RARP包给RARP服务器,通过mac地址得到IP地址 -Ping和TraceRoute实现原理 -(1) Ping是通过发送ICMP报文回显请求实现。 -(2) TraceRoute通过发送UDP报文,设置目的端口为一个不可能的值,将IP首部中的TTL分别设置从1到N,每次逐个增加,如果收到端口不可达,说明到达目的主机,如果是因为TTL跳数超过,路由器会发送主机不可达的ICMP报文。 -HTTP http/https 1.0、1.1、2.0 - -1. http的主要特点: -简单快速: 当客户端向服务器端发送请求时,只是简单的填写请求路径和请求方法即可,然后就可以通过浏览器或其他方式将该请求发送就行了 -灵活: HTTP 协议允许客户端和服务器端传输任意类型任意格式的数据对象 -无连接: 无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接,采用这种方式可以节省传输时间。(当今多数服务器支持Keep-Alive功能,使用服务器支持长连接,解决无连接的问题) -无状态: 无状态是指协议对于事务处理没有记忆能力,服务器不知道客户端是什么状态。即客户端发送HTTP请求后,服务器根据请求,会给我们发送数据,发送完后,不会记录信息。(使用 cookie 机制可以保持 session,解决无状态的问题) -2. http1.1的特点 -a、默认持久连接节省通信量,只要客户端服务端任意一端没有明确提出断开TCP连接,就一直保持连接,可以发送多次HTTP请求 -b、管线化,客户端可以同时发出多个HTTP请求,而不用一个个等待响应 -c、断点续传ftghh -3. http2.0的特点 -a、HTTP/2采用二进制格式而非文本格式 -b、HTTP/2是完全多路复用的,而非有序并阻塞的——只需一个HTTP连接就可以实现多个请求响应 -c、使用报头压缩,HTTP/2降低了开销 -d、HTTP/2让服务器可以将响应主动“推送”到客户端缓存中 get/post 区别 - -区别一: -get重点在从服务器上获取资源,post重点在向服务器发送数据; -区别二: -get传输数据是通过URL请求,以field(字段)= value的形式,置于URL后,并用"?"连接,多个请求数据间用"&"连接,如http://127.0.0.1/Test/login.action?name=admin&password=admin,这个过程用户是可见的; -post传输数据通过Http的post机制,将字段与对应值封存在请求实体中发送给服务器,这个过程对用户是不可见的; -区别三: -Get传输的数据量小,因为受URL长度限制,但效率较高; -Post可以传输大量数据,所以上传文件时只能用Post方式; -区别四: -get是不安全的,因为URL是可见的,可能会泄露私密信息,如密码等; -post较get安全性较高; - 返回状态码 - -200:请求被正常处理 -204:请求被受理但没有资源可以返回 -206:客户端只是请求资源的一部分,服务器只对请求的部分资源执行GET方法,相应报文中通过Content-Range指定范围的资源。 -301:永久性重定向 -302:临时重定向 -303:与302状态码有相似功能,只是它希望客户端在请求一个URI的时候,能通过GET方法重定向到另一个URI上 -304:发送附带条件的请求时,条件不满足时返回,与重定向无关 -307:临时重定向,与302类似,只是强制要求使用POST方法 -400:请求报文语法有误,服务器无法识别 -401:请求需要认证 -403:请求的对应资源禁止被访问 -404:服务器无法找到对应资源 -500:服务器内部错误 -503:服务器正忙 - http 协议头相关 - -http数据由请求行,首部字段,空行,报文主体四个部分组成 -首部字段分为:通用首部字段,请求首部字段,响应首部字段,实体首部字段 https与http的区别?如何实现加密传输? -https就是在http与传输层之间加上了一个SSL -对称加密与非对称加密 浏览器中输入一个URL发生什么,用到哪些协议? 浏览器中输入URL,首先浏览器要将URL解析为IP地址,解析域名就要用到DNS协议,首先主机会查询DNS的缓存,如果没有就给本地DNS发送查询请求。DNS查询分为两种方式,一种是递归查询,一种是迭代查询。如果是迭代查询,本地的DNS服务器,向根域名服务器发送查询请求,根域名服务器告知该域名的一级域名服务器,然后本地服务器给该一级域名服务器发送查询请求,然后依次类推直到查询到该域名的IP地址。DNS服务器是基于UDP的,因此会用到UDP协议。 -得到IP地址后,浏览器就要与服务器建立一个http连接。因此要用到http协议,http协议报文格式上面已经提到。http生成一个get请求报文,将该报文传给TCP层处理。如果采用https还会先对http数据进行加密。TCP层如果有需要先将HTTP数据包分片,分片依据路径MTU和MSS。TCP的数据包然后会发送给IP层,用到IP协议。IP层通过路由选路,一跳一跳发送到目的地址。当然在一个网段内的寻址是通过以太网协议实现(也可以是其他物理层协议,比如PPP,SLIP),以太网协议需要直到目的IP地址的物理地址,有需要ARP协议。 安全相关 -SQL注入 -XSS -RCFS -APR欺骗 数据库 -SQL语言(内外连接,子查询,分组,聚集,嵌套,逻辑) -MySQL索引方法?索引的优化? -InnoDB与MyISAM区别? -事务的ACID -事务的四个隔离级别 -查询优化(从索引上优化,从SQL语言上优化) -B-与B+树区别? -MySQL的联合索引(又称多列索引)是什么?生效的条件? -分库分表 Linux 进程与线程 (1) 进程与线程区别? -(2) 线程比进程具有哪些优势? -(3) 什么时候用多进程?什么时候用多线程? -(4) LINUX中进程和线程使用的几个函数? -(5) 线程同步? -在Windows下线程同步的方式有:互斥量,信号量,事件,关键代码段 -在Linux下线程同步的方式有:互斥锁,自旋锁,读写锁,屏障(并发完成同一项任务时,屏障的作用特别好使) -知道这些锁之间的区别,使用场景? 进程间通讯方式 - -管道( pipe ) :管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。 - -命名管道 (FIFO) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。 - -信号量 :信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据,有XSI信号量和POSIX信号量,POSIX信号量更加完善。 - -消息队列( message queue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。 - -共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。(原理一定要清楚,常考) - -信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生,常见的信号。 - -套接字( socket ) : 套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。 -匿名管道与命名管道的区别 :匿名管道只能在具有公共祖先的两个进程间使用。 -共享文件映射mmap -mmap建立进程空间到文件的映射,在建立的时候并不直接将文件拷贝到物理内存,同样采用缺页终端。mmap映射一个具体的文件可以实现任意进程间共享内存,映射一个匿名文件,可以实现父子进程间共享内存。 -常见的信号有哪些? :SIGINT,SIGKILL(不能被捕获),SIGTERM(可以被捕获),SIGSEGV,SIGCHLD,SIGALRM 内存管理 1. 虚拟内存的作用? -2. 虚拟内存的实现? -3. 操作系统层面对内存的管理? -4. 内存池的作用?STL里 内存池如何实现 ? -5. 进程空间和内核空间对内存的管理不同? -6. Linux的slab层,VAM? -7. 伙伴算法 -8. 高端内存 进程调度 1. Linux进程分为两种,实时进程和非实时进程; -2. 优先级分为静态优先级和动态优先级,优先级的范围; -3. 调度策略,FIFO,LRU,时间片轮转 -4. 交互进程通过平均睡眠时间而被奖励; 死锁 (1) 死锁产生的条件; -(2) 死锁的避免; 命令行 -Linux命令 在一个文件中,倒序打印第二行前100个大写字母 -``` - - -cat filename | head -n 2 | tail -n 1 | grep '[[:upper:]]' -o | tr -d '\n'| cut -c 1-100 | rev -与CPU,内存,磁盘相关的命令(top,free, df, fdisk) -网络相关的命令netstat,tcpdump等 -sed, awk, grep三个超强大的命名,分别用与格式化修改,统计,和正则查找 -ipcs和ipcrm命令 -查找当前目录以及字母下以.c结尾的文件,且文件中包含"hello world"的文件的路径 -创建定时任务 IO模型 -五种IO模型: 阻塞IO,非阻塞IO,IO复用,信号驱动式IO,异步IO -select,poll,epoll的区别 -select: 是最初解决IO阻塞问题的方法。用结构体fd_set来告诉内核监听多个文件描述符,该结构体被称为描述符集。由数组来维持哪些描述符被置位了。对结构体的操作封装在三个宏定义中。通过轮寻来查找是否有描述符要被处理,如果没有返回 -存在的问题: -1. 内置数组的形式使得select的最大文件数受限与FD_SIZE; -2. 每次调用select前都要重新初始化描述符集,将fd从用户态拷贝到内核态,每次调用select后,都需要将fd从内核态拷贝到用户态; -3. 轮寻排查当文件描述符个数很多时,效率很低; poll: 通过一个可变长度的数组解决了select文件描述符受限的问题。数组中元素是结构体,该结构体保存描述符的信息,每增加一个文件描述符就向数组中加入一个结构体,结构体只需要拷贝一次到内核态。poll解决了select重复初始化的问题。轮寻排查的问题未解决。 -epoll: 轮寻排查所有文件描述符的效率不高,使服务器并发能力受限。因此,epoll采用只返回状态发生变化的文件描述符,便解决了轮寻的瓶颈。 -为什么使用IO多路复用,最主要的原因是什么? -epoll有两种触发模式?这两种触发模式有什么区别?编程的时候有什么区别? -上一题中编程的时候有什么区别,是在边缘触发的时候要把套接字中的数据读干净,那么当有多个套接字时,在读的套接字一直不停的有数据到达,如何保证其他套接字不被饿死(面试网易游戏的时候问的一个问题,答不上来,印象贼深刻)。 -1. select/poll/epoll区别 -2. 几种网络服务器模型的介绍与比较 -3. epoll为什么这么快 (搞懂这篇文章,关于IO复用的问题就信手拈来了) 线程池 Linux的API -fork与vfork区别 -fork和vfork都用于创建子进程。但是vfork创建子进程后,父进程阻塞,直到子进程调用exit()或者excle()。 -对于内核中过程fork通过调用clone函数,然后clone函数调用do_fork()。do_fork()中调用copy_process()函数先复制task_struct结构体,然后复制其他关于内存,文件,寄存器等信息。fork采用写时拷贝技术,因此子进程和父进程的页表指向相同的页框。但是vfork不需要拷贝页表,因为父进程会一直阻塞,直接使用父进程页表。 -exit()与_exit()区别 -exit()清理后进入内核,_exit()直接陷入内核。 -孤儿进程与僵死进程 -1. 孤儿进程是怎么产生的? -2. 僵死进程是怎么产生的? -3. 僵死进程的危害? -4. 如何避免僵死进程的产生? -Linux是如何避免内存碎片的 -1. 伙伴算法,用于管理物理内存,避免内存碎片; -2. 高速缓存Slab层用于管理内核分配内存,避免碎片。 -共享内存的实现原理? -共享内存实现分为两种方式一种是采用mmap,另一种是采用XSI机制中的共享内存方法。mmap是内存文件映射,将一个文件映射到进程的地址空间,用户进程的地址空间的管理是通过vm_area_struct结构体进行管理的。mmap通过映射一个相同的文件到两个不同的进程,就能实现这两个进程的通信,采用该方法可以实现任意进程之间的通信。mmap也可以采用匿名映射,不指定映射的文件,但是只能在父子进程间通信。XSI的内存共享实际上也是通过映射文件实现,只是其映射的是一种特殊文件系统下的文件,该文件是不能通过read和write访问的。 -二者区别: - -1、 系统V共享内存中的数据,从来不写入到实际磁盘文件中去;而通过mmap()映射普通文件实现的共享内存通信可以指定何时将数据写入磁盘文件中。注:前面讲到,系统V共享内存机制实际是通过映射特殊文件系统shm中的文件实现的,文件系统shm的安装点在交换分区上,系统重新引导后,所有的内容都丢失。 -2、 系统V共享内存是随内核持续的,即使所有访问共享内存的进程都已经正常终止,共享内存区仍然存在(除非显式删除共享内存),在内核重新引导之前,对该共享内存区域的任何改写操作都将一直保留。 -3、 通过调用mmap()映射普通文件进行进程间通信时,一定要注意考虑进程何时终止对通信的影响。而通过系统V共享内存实现通信的进程则不然。注:这里没有给出shmctl的使用范例,原理与消息队列大同小异。 -系统调用与库函数(open, close, create, lseek, write, read) -同步方法有哪些? -1. 互斥锁,自旋锁,信号量,读写锁,屏障 -2. 互斥锁与自旋锁的区别:互斥锁得不到资源的时候阻塞,不占用cpu资源。自旋锁得不到资源的时候,不停的查询,而然占用cpu资源。 -3. 死锁 其他 -++i是否是原子操作 -明显不是,++i主要有三个步骤,把数据从内存放在寄存器上,在寄存器上进行自增,把数据从寄存器拷贝会内存,每个步骤都可能被中断。 -判断大小端 -``` - - -union un -{ -int i; -char ch; -}; - -void fun() -{ -union un test; -test.i = 1; -if(ch == 1) -cout << "小端" << endl; -else -cout << "大端" << endl; -} -``` 设计模式 -单例模式线程安全的写法 -STL里的迭代器使用了迭代器模式 -MVC的理解 -分布式系统 -map_reduce原理 (这篇文章讲的很通俗易懂) -负载均衡 -CDN - - -取消 确定 - -确定 \ No newline at end of file diff --git a/notes/面经/MySQL 经典面试题 - 王守昌 - 博客园.txt b/notes/面经/MySQL 经典面试题 - 王守昌 - 博客园.txt deleted file mode 100644 index e8f5b22e..00000000 --- a/notes/面经/MySQL 经典面试题 - 王守昌 - 博客园.txt +++ /dev/null @@ -1,218 +0,0 @@ -Keven King -编程人生 -随笔 - 91, 文章 - 0, 评论 - 11, 引用 - 0 -MySQL 经典面试题 -MySQL 面试 1 存储过程 - -什么是存储过程 -存储过程是一些编译好的SQL语句 -因为系统在调用SQL的时候比较浪费时间,所以之前先将一些基本的额SQL语句代码进行编译(对单表或多表的增删改查),然后再给代码取一个名字,在需要这个功能时去调用它就可以了。 -优缺点 -存储工程是编译后的代码 效率高 -存储过程代替SQL语句,降低网络通信 -在一定的程度确保数据安全 -2 索引 - -索引是什么 -索引是对数据库中一或多个列值的排序,帮助数据库高效获取数据的数据结构 -假如我们用类比的方法,数据库中的索引就相当于书籍中的目录一样,当我们想找到书中的摸个知识点,我们可以直接去目录中找而不是在书中每页的找,但是这也抛出了索引的一个缺点,在对数据库修改的时候要修改索引到导致时间变多。 -几个基本的索引类型 普通索引 唯一索引 主键索引 全文索引 -索引优点 -加快检索速度 -唯一索引确保每行数据的唯一性 -在使用索引的过程可以优化隐藏器,提高系统性能 - - -索引缺点 -插入删除 修改 维护速度下降 -占用物理和数据空间 -3 事务 - -事务的作用 -事务(Transaction)是并发控制的基本单位。事务就是一系列的操作,这些操作要么都执行,要么都不执行。 -事务具有以下4个基本特征 -Atomic(原子性) 事务中的一系列的操作要么都完成,要么全部失败 -Consistency(一致性) 一个成功的事务应该讲数据写入的到数据库,否则就要回滚到最初的状态 -Isolation(隔离性) 并发访问和修改的duli -Durability(持久性) 事务结束应该讲事务的处理结构存储起来 -事务的语句 -开始事物:BEGIN TRANSACTION -提交事物:COMMIT TRANSACTION -回滚事务:ROLLBACK TRANSACTION -4 数据库中的乐观锁和悲观锁 - -根据不同类型可以对数据设置不同的锁权限 -** 乐观 悲观 锁 主要是作用在并发访问控制** -悲观锁 假定会发生并发冲突,屏蔽任何违反数据完整的操作 -乐观锁 假定不会发生冲突,只有在提交操作时检查是否违反数据的完整性 -5 drop, delete truncate 的区别 - -三者都是 删除 的意思,但是三者个有些区别 -delete和truncate只删除表的数据不删除表的结构 -速度 drop > truncate > delete -想删除部分数据时, delete 删除时要带上where语句 -保留表而想删除所有的数据时用 truncate -6 超键 候选键 主键 外键 区别 - -超键 在关系中能唯一标识元组的属性集称为关系模式的超键 ,一个或多个属性组合在一起作为超键。 -候选键 最下超键,没有冗余元素的超键 -主键 数据库中表中唯一和完整标识的数据列或属性集合。 -外键 在一个表中存在另外一个表的主键叫做外键 7视图 - -定义 视图是一种虚拟表,可以对视图进行增删查改 。可以将一个表多个表组合成一个视图。对视图的修改不影响基本表。 8 数据库三大范式介绍 -1NF 字段是最小单元,不可再分 -2NF 满足1NF 表中字段必须完全依赖全部主键而并非部分主键 - - -3NF 满足2NF,非主键外的所有字段必须互不依赖 -4.数据库三范式 -第一范式 字段具有原子性,不可再分 -第二范式 表中的每列都和主键相关 -第三范式 每列都和主键列直接相关,而不是间接相关 - -好文要顶 关注我 收藏该文 - -王守昌 -关注 - 0 -粉丝 - 9 -+加关注 -0 -0 -« 上一篇: MongoDB分片原理篇 -» 下一篇: 类图关系以及类图之间箭头表示 - - -posted on 2017-06-01 19:32 王守昌 阅读( 3298 ) 评论( 0 ) 编辑 收藏 - -刷新评论 刷新页面 返回顶部 -注册用户登录后才能发表评论,请 登录 或 注册 , 访问 网站首页。 -【推荐】50万行VC++源码: 大型组态工控、电力仿真CAD与GIS源码库 -【推荐】Vue.js 2.x 快速入门,大量高效实战示例 -【活动】腾讯云 学生专属优惠套餐 多规格选择 -【活动】释放技术的想象-解码腾讯云软件架构与应用 - - -最新IT新闻 : -· 京东要用机器人和无人机对抗阿里巴巴 刘强东到底胜算几何? -· TF Lite只是故事的一部分,谷歌还一并介绍了新的模型压缩方法 -· “大灰狼”远控开发者被曝猝死 -· UC浏览器遭Google Play下架,被指存在隐私问题 -· 董明珠:不反对在国外买技术但真正创造者要自主研发 -» 更多新闻... - -最新知识库文章 : - -· 关于编程,你的练习是不是有效的? -· 改善程序员生活质量的 3+10 习惯 -· NASA的10条代码编写原则 -· 为什么你参加了那么多培训,却依然表现平平? -· 写给初学前端工程师的一封信 -» 更多知识库文章... -导航 -博客园 -首页 -新随笔 -联系 -订阅 -管理 - - < 2017年11月 > - -日 一 二 三 四 五 六 - 29 30 31 1 2 3 4 - 5 6 7 8 9 10 11 - 12 13 14 15 16 17 18 - 19 20 21 22 23 24 25 - 26 27 28 29 30 1 2 - 3 4 5 6 7 8 9 -公告 -昵称: 王守昌 -园龄: 2年5个月 -粉丝: 9 -关注: 0 -+加关注 -搜索 - - -常用链接 -我的随笔 -我的评论 -我的参与 -最新评论 -我的标签 - -随笔分类 -git 学习与记录(1) -Hadoop 安装(1) -HTML和CSS(5) -HTTP权威指南读书笔记(1) -Java(3) -java Web 分类 (9) -Java 线程池(2) -JavaScript 学习笔记(5) -Java与设计模式(2) -Linux (17) -Maven 笔记(11) -mongodb 随笔(17) -python 入门教程 -rabbitmq 安装使用记录 -redis 学习随笔(3) -thinking in java 笔记(1) -数据结构与算法(1) -随笔档案 -2017年10月 (4) -2017年9月 (10) -2017年8月 (5) -2017年6月 (1) -2017年5月 (15) -2017年4月 (19) -2017年3月 (8) -2016年12月 (29) - -最新评论 - -1. Re:如何高效学习读书笔记 -@王守昌谢谢。... ---北落师门α -2. Re:如何高效学习读书笔记 -@北落师门α用markdown编辑器上编辑,然后直接复制到博客园的编辑框中... ---王守昌 -3. Re:如何高效学习读书笔记 -@退后一步是人生谢谢... ---王守昌 -4. Re:如何高效学习读书笔记 -问下,笔记用什么记录的。 -想请教下怎么整理的这么整齐。什么软件上先写好,再粘到园子里的吧。 ---北落师门α -5. Re:如何高效学习读书笔记 -楼主好总结!!! ---退后一步是人生 - -阅读排行榜 - -1. eclipse配置maven + 创建maven项目(三)(14634) -2. 在java中使用MongoDB数据库(4427) -3. MySQL 经典面试题(3299) -4. maven web 项目中启动报错 Java.lang.ClassNotFoundException: org.springframework.web.servlet.DispatcherServlet(2843) -5. 自己把jar包添加到maven仓库中(2157) - -评论排行榜 - -1. 如何高效学习读书笔记(7) -2. 在java中使用MongoDB数据库(2) -3. Maven下载、安装和配置(二)(1) -4. MongoDB的备份和部署 高级功能索引,聚合复制,分片(1) - -推荐排行榜 - -1. eclipse配置maven + 创建maven项目(三)(2) -2. MongoDB 分布式架构 复制 分片 适用性范围(1) -3. 将eclipse左边目录结构改为 树形结构(1) -4. 如何高效学习读书笔记(1) -5. MongoDB分片原理篇(1) - - - -Powered by: -博客园 -Copyright © 王守昌 \ No newline at end of file diff --git a/notes/面经/csdn-腾讯后台开发面试经验.md.txt b/notes/面经/csdn-腾讯后台开发面试经验.md.txt deleted file mode 100644 index e26fa663..00000000 --- a/notes/面经/csdn-腾讯后台开发面试经验.md.txt +++ /dev/null @@ -1,13 +0,0 @@ -面试准备:主要准备的知识包括,C/C++、TCP/IP、HTTP、操作系统、数据结构和算法、熟悉Linux操作、熟悉Linux网络编程、数据库 -(主要是MySql,Redis,Memcached)。 - -C/C++必看书籍:C++ Primer、深度探索C++对象模型、STL源码剖析、还有其它一些经典书籍,比如effective系列,侯捷翻译的其它经典 -TCP/IP必看书籍:TCP/IP详解 卷1:协议, 图解TCP/IP -HTTP必看书籍:图解HTTP,HTTP权威指南(这本书我就看了一点) -操作系统必看书籍:现代操作系统 -数据结构和算法必看书籍:数据结构与算法分析C++描述 -Linux知识必看书籍:鸟哥的Linux私房菜基础 -Linux编程必看书籍:APUE,UNP。 -数据库必看书籍:MySql入门很简单,高性能MySql - -链接:http://blog.csdn.net/linux_ever/article/details/53575251 diff --git a/notes/面经/java后台研发面经.md.txt b/notes/面经/java后台研发面经.md.txt deleted file mode 100644 index 248f80c7..00000000 --- a/notes/面经/java后台研发面经.md.txt +++ /dev/null @@ -1 +0,0 @@ -- [牛客网:如何准备校招技术面试](https://www.nowcoder.com/discuss/50990?type=0&order=0&pos=14&page=0) diff --git a/notes/面经/亚辉的面试经验-cvte.md.txt b/notes/面经/亚辉的面试经验-cvte.md.txt deleted file mode 100644 index ff08d2bf..00000000 --- a/notes/面经/亚辉的面试经验-cvte.md.txt +++ /dev/null @@ -1,92 +0,0 @@ -# 一面 -## 问有没有做过JAVA Web的项目,选一个讲 -答:选择了一个做的CMS来讲,因为我是这个项目的总负责人 - - -## 问项目上有没有遇到什么困难 -答:没有。。 -## 技术选型是如何决定的。 -答:感觉不是真让回答技术选型的过程。我就对比了SpringMVC和struts的优缺点,Mybatis和Hibernate的区别。 -然后他又问了为什么选择了shiro而不是spring security。我就说因为之前没有接触过权限管理的框架,在网上看帖子说spring security比shiro更为复杂和庞大,对于我们项目没有很多的时间和学习成本去接触spring security,shiro更容易上手。 - - -## 数据库查询 -三个表,其中一个表是其他两个表的关系表。问如何根据一个表的字段查找出另外一张表的字段。 -回答出sql。 -我回答了子查询,其实用join更好点,但是一时间不知道怎么join。 - - -## 数据库索引有了解吗?索引的优缺点。 - - -查询快,插入慢。所以适合查询为主的字段。 - - -## 常用的集合有哪些 - LinkedList,ArrayList,Hashmap。 - - -## LinkedList和ArrayList的区别 -底层实现不同。LinkedList双向链表,ArrayList数组。ArrayList插入慢,但是查询快。LinkedList插入快,查询慢。 - - -## LinkedList的底层实现是怎么样的?假如往LinkedList的第k个位置插入数据,时间复杂度?最后一个位置插入呢? -双向链表。 -第k个位置插入数据:k+1 ,遍历k位,插入一次。 -最后一个位置:1。因为是双向链表。 - - -## Hashmap能不能用对象做key。 -可以。但是得重写hashcode方法和equals方法。 - - -## Hashmap用对象做key时,put后,又修改了这个对象,会发生什么。 -我就说那得看你设计的这个hashcode和equals方法跟你修改得属性有没有关系,假如有,那会导致put进去,get不出来 - - -## java的异常分类 - - -Error、Exception,还有一个比较特别的RuntimeException - - -## Java如何捕获异常 -回答:try...catch.. -然后问finally不是吗? -回答:不是。finally只是配合使用的,没有异常的时候单独使用finally也是可以的。 -然后问 什么情况下会在finally里面写代码? -回答:举了数据库连接的例子。。 - - -## JVM何时会发生内存泄露 -不知道。。可以看这个帖子 -http://blog.csdn.net/anxpp/article/details/51325838 - - -看了这个帖子。。估计面试官是想问我数据库连接也会导致内存泄露吧。。。 -## JVM的内存划分是如何的 -堆,栈,方法区,程序计数器什么呢。 - - -## 方法区都存放了哪些东西 -类的信息,静态类和静态变量。 - - - - -## 平常私下会做什么事 -讲了我github一直在维护的个人项目。爬虫和tinyjvm。 - - -## 算法题:0~n共n+1个数,从中随机抽取一个数,剩下的n个数都知道是什么,问随机抽出的数是什么。 - - -刚开始回答了0~n加起来,然后减去剩下的n个数之和,就可以得到了。 -他问假如n很大会发生什么情况? -我说会超出int或者long的最大值。 -然后选择了bitmap的方式来解决,一个数对应一个bit。 -#二面 -聊项目 -#HR面 -聊性格 - diff --git a/notes/面经/亚辉的面试经验-美团.md.txt b/notes/面经/亚辉的面试经验-美团.md.txt deleted file mode 100644 index e8164c7b..00000000 --- a/notes/面经/亚辉的面试经验-美团.md.txt +++ /dev/null @@ -1,36 +0,0 @@ -# 一面 -## 开闭原则 -## 简单工厂和工厂模式的区别 -## MySQL的隔离级别 -## MySQL的索引,原理 -## MySQL幻读和脏读的区别 -## Hashmap是不是线程安全的。 -## Hashmap的底层机制。除了拉链法,还有哪些解决冲突的方法。 -## static的用法 -## sleep和wait的区别 -sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。 - - -在调用sleep()方法的过程中,线程不会释放对象锁。 - - -而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备 - - -获取对象锁进入运行状态。 -## notify和notifyAll的区别 -这个问题的主要考点在于锁池和等待池 -http://blog.csdn.net/emailed/article/details/4689220 -## spring的底层思想是什么 -## 为什么会有GC? -## JVM的垃圾收集算法,以及年轻代、年老代是如何选择垃圾收集算法 -新生代 复制算法 Eden、Survivor -老年代 标记整理/标记清除 - - -## HTTP是哪一层的? -## GET和POST的区别 -## 类加载的流程 -## 一次浏览器到服务器端的HTTP请求是怎么样的。 -https://www.zhihu.com/question/34873227 -## 怎么加快HTTP请求的速度? \ No newline at end of file diff --git a/notes/面经/亚辉的面试经验-腾讯.md.txt b/notes/面经/亚辉的面试经验-腾讯.md.txt deleted file mode 100644 index d93a7b0a..00000000 --- a/notes/面经/亚辉的面试经验-腾讯.md.txt +++ /dev/null @@ -1,261 +0,0 @@ -# 一面 -## 讲下class文件结构 - - -## import是在class文件哪里存储的 - - -## 类是从哪里加载的 -bootstrap、ext、user - - -## 栈指令集和寄存器指令集的区别 -http://www.voidcn.com/blog/pq258280920/article/p-4274066.html - - -## GC算法。 -感觉这个必问的。说了下分代收集。 - - -## Http的action都有哪些 -我回答了GET、POST、PUT、DELETE、HEAD。我说应该还有,但一时想不起来了。他就没继续问了。 -TRACE、OPTIONS -## http1.0和2.0的区别在哪里 -我说最大的区别就是安全方面。 然后他说还有呢? - - -## TCP和UDP的区别 - - -## 线程和进程的区别 - - -## QQ应该用哪一种? -我说QQ主要保证消息不丢失。可以采用TCP和UDP混合的。在网络环境差的时候,用TCP保证消息不丢失。网络环境好的时候,可以采用UDP加快速度。 - - -我感觉对于TCP和UDP的理解不是很到位,知乎有个问题 -https://www.zhihu.com/question/20292749 -## TCP是怎么保证消息可靠性的? - - -## Git和SVN的区别。 -讲了Git的分布式版本库和SVN的集中式版本库。 - - -## MySQL假如查询非常慢,怎么解决? -1. 建索引 -2. 分表。 -面试官感觉不太够,问还有吗? -http://tech.meituan.com/mysql-index.html -《高性能MySQL》 查询性能优化 -## 排序算法都有哪些。每个的时间复杂度都是多少? - - -## 单链表排序 -时间复杂度O(nlgn) 空间复杂度O(1) -可以使用归并排序来做。 -http://noalgo.info/388.html -## 给两分钟讲下一个你做的最好项目。 -我讲了税务主题爬虫项目。介绍了爬虫的技术。以及我负责URL去重,正文提取以及后期的重构。 -然后面试官根据这几部分分别展开问。 - - -## url去重。 -BloomFilter + 根据文件名去重。 -讲了下为什么这么做。然后面试官问为什么存文件。我说是因为后期恰好也要建搜索引擎,刚好存文件的时候判断文件是否存在。 -面试官好像对搜索引擎很感兴趣,于是似乎想问我搜索引擎的知识,我说这块不是我负责的,但是知道一些。面试官说,不是你负责的就算了吧。 - - -## 正文提取的步骤 -简单说了下所用的哈工大的基于行块密度的正文提取算法,中间也加了常见的杂质过滤,以保证最终结果的纯净。 - - -面试官问了怎么组织这些杂质信息呢?这些信息都放在哪里了呢? -我说放在了数据库里面,然后启动之后加载到redis里面。 - - -## redis用过吗?讲下你所知道的redis? -讲了下IO多路复用 - - -## 说下IO多路复用。 epoll和select的区别。epoll的水平触发和边缘触发。 - - -## 说下BloomFilter的原理。 - - -这个幸好我寒假有研究。具体可以看我的博客。这块回答的蛮好的。 -http://www.cyhone.com/2017/02/07/Introduce-to-BloomFilter/ - - -## Hashmap 底层实现 -讲一遍Hashmap的原理。 - - -## Hashmap 可以存null吗? -value可以为null。key只能有一个为null。 - - -## ConcurrenyHashMap的底层? -我说没看过源码,但知道他是由分段锁来实现的。也没继续问下去。 - - -## 除了synchronized,还有其他的同步方式? -重入锁、读写锁。还有一些java提供的同步类。 - - -## 读写锁。加读锁的时候能不能写? - - -## Web后端是怎么分层的。为什么这么分。 -讲了下MVC和后端的service、dao和action。 - - -我说主要是业务的解耦以及可以分工合作。 -## 讲一下你所知道的docker。 -我说了下docker和虚拟机的区别,主要区别是docker是基于宿主机的内核,而虚拟机是有自己的操作系统内核。 -主要相似性就是都提供了运行环境的隔离。 -http://dockone.io/article/723 - - -然后面试官就根据这个点,深入问了一个问题。 -## 宿主机能不能看到docker的进程 -我说不能看到。其实是可以看到的! -http://dockone.io/question/529 - - -## NOSQL和SQL的区别,以及每个的应用场景 - - -# 二面 - - -## StringBuffer和StringBuilder的区别 -StringBuilder 线程不安全 -## Java中final的用处 -1. 用于类 不可继承 -2. 用于方法 不可重写 -3. 用于属性 值不可修改,对象不可修改地址 - - -## 什么情况下不用final会编译失败 -这个没回答上来。下来查了一下 -1. 匿名内部类来自外部闭包环境的自由变量必须是final的 -2. 在外部类成员方法内部的内部类。 -3. 在一个代码块block里的内部类。 -https://www.zhihu.com/question/21395848 - - -## 说下HashMap的底层实现 -这个目前所有面试中都问了。 所以HashMap的源码一定要好好看。 -## Java8解决冲突的方式 - 我说还是拉链法,似乎不是很满意,然后我说,当超过冲突某个值时,Java8会把链表改为Treemap。 -## ThreadLocal了解多少 - 讲了下ThreadLocal的源码实现,thread的localmap对象之类的。 -## 快排的最大时间复杂度、最小时间复杂度、平均时间复杂度。 -## 快排是不是稳定的。 -快排是不稳定的。 -这个真是自己找死,我又说了堆排序是稳定的。实际上是不稳定啊脑子啊脑子。。 -http://baike.baidu.com/item/%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95%E7%A8%B3%E5%AE%9A%E6%80%A7 - - -## 1000万个数据的Top K - 说了最大堆。然后让我计算1000万个int有多大,然后说内存足够。 - 然后我讲了下如何用快排实现topk的过程。 - 当内存足够的情况下,用快排时间求topk复杂度可以为O(n) -## TCP的四次挥手 -## UDP一次最大发送多少个字节 -## Http状态码 - 主要问了302、404、500 - http://seo.ibw.cn/display.asp?id=82 - -## redis和memcached的区别 - 给了个选择题。实际上就是问redis和memcached的差异,最大的差异就是memcached不支持复杂类型如list等。 -## 最快的进程间通信方式是怎么样的 - - -## linux命令 - df -h 查看磁盘空间 - du -h 查看文件夹大小 - lsof 查看端口占用 - top | grep 查看cpu占用 - ps -aux | grep - - - linux如何查看一个进程的执行路径? - http://lovesoo.org/view-processes-running-linux-full-path-method.html - ll /proc/{pid} - - 通过进程名查找进程PID可以通过 pidof -## 逻辑题。 -五辆汽车加油问题。 -1/5+1/4+1/3+1/2+1 -## 问了一个多分布式消费者请求生产者的问题 -当时一时间没想出来。就往raft协议和一致性hash上面说 -## raft协议、一致性hash -扯了一波主节点分发,领导者选举,一致性hash啥的。我都不知道我在说啥了,估计面试官也晕晕的。。 -raft协议说完后,面试官问如果没有主控节点,所有节点都是平级的应该怎么如何设计。 -我说应该加分布式锁。 -## 如何实现一个分布式锁。 -我说使用redis可以实现分布式锁,但是具体的怎么实践还不是很清楚。 -具体的分布式锁的细节,都在此博客中有 -http://ifeve.com/redis-lock/ - - -最后面试官介绍说他们是腾讯手机QQ团队 - - -前面两面电面面试完之后,HR通知我去深圳面试,当天有三面和四面。 -# 三面 -手写代码。 -1. 两个字符串前n位忽略大小写的比较。 -2. bitmap实现。 - - -手写完代码之后面试官直接带我去四面,四面是总监面。 -# 四面 - - -1. 英语六级 -我的六级是飘过,面试官对此略不满意。 -2. linux熟悉吗. -我说了下/proc.面试官让我讲下底层实现。。 -然后面试官问linux下的inode熟悉吗。 -我依稀记得是apue里面讲过,但一点也想不起来了。就说了不知道。 - - -3. TCP为什么四次挥手。 -4. TIME_WAIT阶段是干嘛的。 -5. “六度空间理论”该如何表示 -我说了下用一个巨大的图,然后将其转化为各个节点之间的最短路径问题。 -6. 链表倒数第k个怎么快速找。 -双指针方法 -7. Mac os的发展史 -8. nio -9. 二叉树遍历,非递归的方式 - - -四面我个人总体感觉还不错,但是面试完后在腾讯大厦等了一下午也没HR面,因为要回武汉就没继续等下去,后来通知结果跪了。 -想想四面走来最终最后跪了,也是蛮遗憾。但是也发现了个人在很多方面的不足。 - - - - -后来发现竟然是因为提前走了被认定为自动放弃。。。。好气。。 -后来参加了腾讯在武汉的校招。到了现场后发现自己被调到了运营开发岗。 -# 一面(运营开发) -## 从浏览器到服务器请求结果,整个流程 - - -面试完之后面试官通知我过了一面。看面试官心情好,就申请了转岗。 -晚上6点多的时候,被TEG的捞了起来。 - - -# 一面 - - -# 二面 - - -# HR面 \ No newline at end of file diff --git a/notes/面经/亚辉的面试经验-阿里.md.txt b/notes/面经/亚辉的面试经验-阿里.md.txt deleted file mode 100644 index 15e90c4d..00000000 --- a/notes/面经/亚辉的面试经验-阿里.md.txt +++ /dev/null @@ -1,147 +0,0 @@ -# 简历筛选面 - - -## 介绍下自己 - - -## 介绍下爬虫项目 - - -## 爬虫项目正文提取的算法 -介绍了下正文提取算法。面试官没继续问下去。。 - - -## 说下你们的爬虫跟其他爬虫比主要不一样的地方是哪里 - - -## 爬虫里面simhash是怎么回事 -介绍了下simhash是干嘛的。面试官也没继续问下去。。 - - -## 会Java吗。。介绍下垃圾回收方面的东西。 -回答了分代收集的流程 -## 会算法吗。。说下常见的排序。。 - - -## 说下冒泡排序的优势。。 - - -## 假如有1000万个数据需要排序,用哪个合适。 -说的堆排序,这个说错了。实际上应该是二路归并排序,有点遗憾。 - - -## 说下重载和重写。。 - - -## 说下Hashmap -没太明白让我说什么。。就把Hashmap的流程说了下。然后问我key和value是怎么存的。。有点懵逼,不知道想问啥,就说了Entry。 - - -总体面试的很不舒服。过了一个多星期接到一个电话告诉我之前的那个是简历筛选面。然后约了面试的时间。 - - -# 一面(38分钟) - - -## 问了学了那些课?数据结构考了多少分?专业排名怎么样 - - -## 都会哪些语言 -Java、Golang、Python、PHP。 -其中Java用的最多。 - - -## 怎么学习Java的?都看了哪些书?英文版的还是中文版的。 -看书+实践 -《Think in Java》、《Effective Java》、《Java并发编程指南》、《深入理解Java虚拟机》 -一般是以中文版为主,中间有歧义的句子会对照着英文版的看。 - - -## Java内存回收 -分代收集那一套讲一遍。 - - -## 并发。 -线程间通信方式。 -ThreadLocal原理、场景。 -volatile原理:Java内存模型划分为主内存工作内存之类的。 - - -## ARP协议。 -IP地址->物理地址。 - - -## 有没有做过路由器相关的开发。 -实际上计算机网络实验课还是弄过的。。但是全忘光了。。只能说没有。。 - - -## 递归的优点与缺点,递归优化。 - - -通过使用尾递归,编译器可以将递归代码优化为非递归的。 -http://blog.csdn.net/whinah/article/details/6419680 -http://www.ruanyifeng.com/blog/2015/04/tail-call.html - - -## 排序算法,了解哪些排序算法,那些是稳定的。 - - -## MySQL索引方面 -查询一条语句的时候,怎么知道他用了哪些索引。 -explain -show index from -## 常见的Linux命令。用的哪个发行版。 -ps、lsof、df、du、top - - -## Redis和Memcached的区别。 - - -## 说下项目 - - -# 二面 - - -## 数学建模比赛的情况。 -## 项目 -## 线程和进程的区别 -## 对java线程的理解 - java内存模型 -## hashmap与ConcurrentHashMap -## 线程的几种状态,状态之间是怎么轮转的 -## ThreadLocal应用场景 -## mybatis与jdbc template -## limit字段使用 - - -# 三面 - - -## 项目 -爬虫去重是怎么做的? -正文提取是怎么做的。 -只可以抓取静态页面吗?如果要抓取js渲染的页面应该怎么做。 -可以抓取Https的网站吗? - - -## 毕设 -内容相似度是怎么比较的。 -余弦相似度的流程。 - - -## 用户体验你怎么看? -回答这个问题时候有点懵。。 -语言组织有点乱。就说了 -用户UI和用户交互 -功能不要太深,层次在3-4层就可以。 -用户行为日志分析 -用户反馈 - - -## 还报了哪些公司? - - -这个面试总共十五分钟。。面试官说看了前两面的面试记录,觉得没太多问题要问我的了。 -心情愉悦。。 - diff --git a/notes/面经/知识点差缺补漏.txt b/notes/面经/知识点差缺补漏.txt deleted file mode 100644 index fef84d86..00000000 --- a/notes/面经/知识点差缺补漏.txt +++ /dev/null @@ -1,6 +0,0 @@ -IO多路复用 -Java HashMap LinkedList CurrentHashMap 源码 1 2 3 4 5 -Java 并发:countDownLatch CAS -浏览器访问网站全过程 -C++ 基础语法 -​sql注入​ \ No newline at end of file diff --git a/notes/面经/腾讯后台研发面试.md.txt b/notes/面经/腾讯后台研发面试.md.txt deleted file mode 100644 index 2b29d927..00000000 --- a/notes/面经/腾讯后台研发面试.md.txt +++ /dev/null @@ -1 +0,0 @@ -- [牛客网:腾讯后台开发一面二面纪实](https://www.nowcoder.com/discuss/5082) diff --git a/notes/面经/面经文档.txt b/notes/面经/面经文档.txt deleted file mode 100644 index 8b137891..00000000 --- a/notes/面经/面经文档.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/notes/面经/面经汇总.md.txt b/notes/面经/面经汇总.md.txt deleted file mode 100644 index 1319ca4e..00000000 --- a/notes/面经/面经汇总.md.txt +++ /dev/null @@ -1,119 +0,0 @@ -# 算法 - -- 九九乘法表(美团) -- 大文件小内存如何排序,大概的思路是大文件划分为小文件然后排序归并。(网易) -- 100亿个long int,取中位数(腾富博) -- 一个矩阵中元素分类的算法。一个矩阵中的元素和它周围八个元素是相邻的。如果两个元素相邻切相等就认为他们是一类。(微软) -- 给2TB数据,2GB的内存,详细说明外部排序算法,算法复杂度。然后回答一共读了多少数据量,写了多少数据。(2路,多路都问了) - -# Java - -- String,StringBuffer,StringBuider区别,结合线程和单例模式变换着问(美团) -- JVM垃圾回收,老年和新生代区别(美团) -- JVM内存分配(美团) -- HashMap原理,寻找第一个entry位置(美团) -- Wait()和sleep()区别(58到家) -- 线程安全集合类(58到家) -- 不加锁,如何实现线程安全(58到家) -- String源码看过么?(58到家) -- Volatile有什么功能,能否实现线程安全(58到家) -- 线程池的四种创建方式,以及线程池的好处(猎聘) -- HashMap底层原理,扩容机制(猎聘) -- 如何组织列对象序列化,关键字是什么(猎聘) -- GC是不是守护线程(盖亚互娱) -- 阻塞和非阻塞有什么区别(Rokid) -- ArrayList()的扩容机制(随行付) -- 如何实现多线程异步方式(Keep) -- Sychronized和Lock锁的区别(Keep) -- 堆和栈结构(Keep) -- 堆一般存放那几种对象(Keep) -- Thread和Runnable区别 -- 字节流和字符流区别(猎豹) -- Arraylist和Linkedlis源码(猎豹) - -# Java EE - -- 对Mybatis的理解,使用的好处(Rokid) -- 说说Mybatis优点,缓冲,与Hibernate有什么区别 -- Mybatis的映射原理是什么(Rokid) -- 说说BIO,AIO,NIO的区别(Rokid) -- Spring和springMVC的处理异常问题(Keep) - -# C++ - -- 动态链接库的认识(360) -- 与 Java 的区别 -- vector 动态增长 -- Socket 通信 - -# 计算机网络 - -- tcp udp区别,报文格式(网易) -- 网络粘包问题及解决方案。(cvte) - -# 操作系统 - -- 为什么用epoll,不用select,区别是什么(腾富博) -- write和fwrite区别 (58) - -# 数据库 - -- 数据库隔离级别,分别产生的错误(美团) -- 数据库分页的关键字是什么,如何实现(猎聘) -- 数据库中分表的原因(随行付) -- Mysql索引原理,怎么实现,符合索引条件(Keep) -- 为什么串行化消耗的资源大?(网易) -- mysql和Oracle的区别(美团) -- 你的项目中给用户密码进行了salt加密,如果加密算法泄漏了怎么办?(美团) - -# Linux - -- 如何查看连接本主机的其他ip(Rokid) -- netstat如何查看端口使用情况,参数是什么(Rokid) -- Linux如何进行文件删除(神州信息) - - -# 分布式 - -- 问我了解分布式吗(网易) -- MapReduce -- Hadoop - -# 设计模式 - -- 了解的设计模式,结合 jdk(随行付) - -# 面向对象 - -- 多态的理解,细说了重载和重写(Keep) - -# 系统设计 - -- 设计一个类似于qq的东西,客户端和服务端要怎么设计(网易) - -# 智力题 - -- a,b,aa,ab,ba,bb ,,,,,问接下来的是什么(画了一个二叉树,根节点空,左右子树a,b以此类推),然后又改造了一下,换成01,这样移动或加减1就可解决了(Keep) - -- 有足够量的2分、5分、1分硬币,如果想凑齐一元钱,可以有多少种方法(猎豹) - -# 其它 - -- 平时爱看什么非技术书(美团) -- 学习规划(猎聘) -- 群面,开放性思维题(Rokid) -- 家庭情况(Keep) -- 平时爱好(Keep) -- 自身优势(第一:我自学能力不错,做事能坚持,以我的计算机知识为例。第二:我有明确的目标和规划,不会在考研工作之前徘徊。)(网易) -- 宁做鸡头不做凤尾  这句话你怎么看?(网易) -- 你周围的人怎么评价你?(网易) -- 你的自学过程是怎样的?(网易) -- 有没女朋友(网易) -- 问成绩,问爱好,家在哪,有没有男朋友,工作意向地点是哪(58) -- 有没有其他offer(招银) -- 对加班怎么看(招银) -- 你更希望从事哪方面的工作,应用型还是研究型的工作?为什么? -- 你觉得应用和研究之间的关系和区别? -- 平时会逛技术论坛吗? -- 平时会自己写一些小程序吗? -- 来一段英文的自我介绍。 \ No newline at end of file diff --git a/notes/面经/面试.md.txt b/notes/面经/面试.md.txt deleted file mode 100644 index 3ff3ef4b..00000000 --- a/notes/面经/面试.md.txt +++ /dev/null @@ -1,7 +0,0 @@ -# [Leetcode刷题五遍还没offer - 听我分析为什么找工作光刷题没用](http://blog.sina.com.cn/s/blog_686de9d90102w0rf.html) - -- 面试官希望你跟他沟通,面试有来有往,上来二话不说闷头就写代码最糟糕。 - -- 有些时候,面试官甚至把题目做点小变化,或者是有意无意把题目说模糊点,让你想当然的以为是某种情况,一头跳进去,折腾一番发现跟想象的不一样,结果就是稳稳的拒掉你了。 - -- 解决方法很简单:每次做题之前,跟对方迅速说下你的思路,看看是否是他想听到的方法,再动手,就避免问题了。 \ No newline at end of file diff --git a/notes/面经/面试题-HTTP.txt b/notes/面经/面试题-HTTP.txt deleted file mode 100644 index 82fc606d..00000000 --- a/notes/面经/面试题-HTTP.txt +++ /dev/null @@ -1,6 +0,0 @@ - - HTTP缓存机制(cache-control、Expires之类的一系列请求与相应报头字段) - -HTTP1.0和HTTP1.1区别 - -session和cookie的区别,禁用cookie后怎么办 diff --git a/notes/面经/面试题-Linux.md.txt b/notes/面经/面试题-Linux.md.txt deleted file mode 100644 index d55874c9..00000000 --- a/notes/面经/面试题-Linux.md.txt +++ /dev/null @@ -1,144 +0,0 @@ -硬链接和软连接区别 -kill用法,某个进程杀不掉的原因(进入内核态,忽略kill信号) -linux用过的命令 -系统管理命令(如查看内存使用、网络情况) -管道的使用 | -grep的使用,一定要掌握,每次都会问在文件中查找 -shell脚本 -find命令 -awk使用 - -作者:oscarwin -链接:https://www.nowcoder.com/discuss/59394 -来源:牛客网 - -进程与线程 - -(1) 进程与线程区别? -(2) 线程比进程具有哪些优势? -(3) 什么时候用多进程?什么时候用多线程? -(4) LINUX中进程和线程使用的几个函数? -(5) 线程同步? -在Windows下线程同步的方式有:互斥量,信号量,事件,关键代码段 -在Linux下线程同步的方式有:互斥锁,自旋锁,读写锁,屏障(并发完成同一项任务时,屏障的作用特别好使) -知道这些锁之间的区别,使用场景? -进程间通讯方式 - -管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。 - -命名管道 (FIFO) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。 - -信号量:信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据,有XSI信号量和POSIX信号量,POSIX信号量更加完善。 - -消息队列( message queue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。 - -共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。(原理一定要清楚,常考) - -信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生,常见的信号。 - -套接字( socket ) : 套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。 - -匿名管道与命名管道的区别:匿名管道只能在具有公共祖先的两个进程间使用。 -共享文件映射mmap -mmap建立进程空间到文件的映射,在建立的时候并不直接将文件拷贝到物理内存,同样采用缺页终端。mmap映射一个具体的文件可以实现任意进程间共享内存,映射一个匿名文件,可以实现父子进程间共享内存。 -常见的信号有哪些?:SIGINT,SIGKILL(不能被捕获),SIGTERM(可以被捕获),SIGSEGV,SIGCHLD,SIGALRM -内存管理 - -1. 虚拟内存的作用? -2. 虚拟内存的实现? -3. 操作系统层面对内存的管理? -4. 内存池的作用?STL里内存池如何实现? -5. 进程空间和内核空间对内存的管理不同? -6. Linux的slab层,VAM? -7. 伙伴算法 -8. 高端内存 -进程调度 - -1. Linux进程分为两种,实时进程和非实时进程; -2. 优先级分为静态优先级和动态优先级,优先级的范围; -3. 调度策略,FIFO,LRU,时间片轮转 -4. 交互进程通过平均睡眠时间而被奖励; -死锁 - -(1) 死锁产生的条件; -(2) 死锁的避免; -命令行 - -Linux命令 在一个文件中,倒序打印第二行前100个大写字母 -``` -cat filename | head -n 2 | tail -n 1 | grep '[[:upper:]]' -o | tr -d '\n'| cut -c 1-100 | rev -``` - -与CPU,内存,磁盘相关的命令(top,free, df, fdisk) -网络相关的命令netstat,tcpdump等 -sed, awk, grep三个超强大的命名,分别用与格式化修改,统计,和正则查找 -ipcs和ipcrm命令 -查找当前目录以及字母下以.c结尾的文件,且文件中包含"hello world"的文件的路径 -创建定时任务 -IO模型 - -五种IO模型:阻塞IO,非阻塞IO,IO复用,信号驱动式IO,异步IO -select,poll,epoll的区别 -select:是最初解决IO阻塞问题的方法。用结构体fd_set来告诉内核监听多个文件描述符,该结构体被称为描述符集。由数组来维持哪些描述符被置位了。对结构体的操作封装在三个宏定义中。通过轮寻来查找是否有描述符要被处理,如果没有返回 -存在的问题: -1. 内置数组的形式使得select的最大文件数受限与FD_SIZE; -2. 每次调用select前都要重新初始化描述符集,将fd从用户态拷贝到内核态,每次调用select后,都需要将fd从内核态拷贝到用户态; -3. 轮寻排查当文件描述符个数很多时,效率很低; poll:通过一个可变长度的数组解决了select文件描述符受限的问题。数组中元素是结构体,该结构体保存描述符的信息,每增加一个文件描述符就向数组中加入一个结构体,结构体只需要拷贝一次到内核态。poll解决了select重复初始化的问题。轮寻排查的问题未解决。 -epoll:轮寻排查所有文件描述符的效率不高,使服务器并发能力受限。因此,epoll采用只返回状态发生变化的文件描述符,便解决了轮寻的瓶颈。 -为什么使用IO多路复用,最主要的原因是什么? -epoll有两种触发模式?这两种触发模式有什么区别?编程的时候有什么区别? -上一题中编程的时候有什么区别,是在边缘触发的时候要把套接字中的数据读干净,那么当有多个套接字时,在读的套接字一直不停的有数据到达,如何保证其他套接字不被饿死(面试网易游戏的时候问的一个问题,答不上来,印象贼深刻)。 -1. select/poll/epoll区别 -2. 几种网络服务器模型的介绍与比较 -3. epoll为什么这么快(搞懂这篇文章,关于IO复用的问题就信手拈来了) -线程池 - -Linux的API - -fork与vfork区别 -fork和vfork都用于创建子进程。但是vfork创建子进程后,父进程阻塞,直到子进程调用exit()或者excle()。 -对于内核中过程fork通过调用clone函数,然后clone函数调用do_fork()。do_fork()中调用copy_process()函数先复制task_struct结构体,然后复制其他关于内存,文件,寄存器等信息。fork采用写时拷贝技术,因此子进程和父进程的页表指向相同的页框。但是vfork不需要拷贝页表,因为父进程会一直阻塞,直接使用父进程页表。 -exit()与_exit()区别 -exit()清理后进入内核,_exit()直接陷入内核。 -孤儿进程与僵死进程 -1. 孤儿进程是怎么产生的? -2. 僵死进程是怎么产生的? -3. 僵死进程的危害? -4. 如何避免僵死进程的产生? -Linux是如何避免内存碎片的 -1. 伙伴算法,用于管理物理内存,避免内存碎片; -2. 高速缓存Slab层用于管理内核分配内存,避免碎片。 -共享内存的实现原理? -共享内存实现分为两种方式一种是采用mmap,另一种是采用XSI机制中的共享内存方法。mmap是内存文件映射,将一个文件映射到进程的地址空间,用户进程的地址空间的管理是通过vm_area_struct结构体进行管理的。mmap通过映射一个相同的文件到两个不同的进程,就能实现这两个进程的通信,采用该方法可以实现任意进程之间的通信。mmap也可以采用匿名映射,不指定映射的文件,但是只能在父子进程间通信。XSI的内存共享实际上也是通过映射文件实现,只是其映射的是一种特殊文件系统下的文件,该文件是不能通过read和write访问的。 -二者区别: -1、 系统V共享内存中的数据,从来不写入到实际磁盘文件中去;而通过mmap()映射普通文件实现的共享内存通信可以指定何时将数据写入磁盘文件中。注:前面讲到,系统V共享内存机制实际是通过映射特殊文件系统shm中的文件实现的,文件系统shm的安装点在交换分区上,系统重新引导后,所有的内容都丢失。 -2、 系统V共享内存是随内核持续的,即使所有访问共享内存的进程都已经正常终止,共享内存区仍然存在(除非显式删除共享内存),在内核重新引导之前,对该共享内存区域的任何改写操作都将一直保留。 -3、 通过调用mmap()映射普通文件进行进程间通信时,一定要注意考虑进程何时终止对通信的影响。而通过系统V共享内存实现通信的进程则不然。注:这里没有给出shmctl的使用范例,原理与消息队列大同小异。 - -系统调用与库函数(open, close, create, lseek, write, read) -同步方法有哪些? -1. 互斥锁,自旋锁,信号量,读写锁,屏障 -2. 互斥锁与自旋锁的区别:互斥锁得不到资源的时候阻塞,不占用cpu资源。自旋锁得不到资源的时候,不停的查询,而然占用cpu资源。 -3. 死锁 -其他 - -++i是否是原子操作 -明显不是,++i主要有三个步骤,把数据从内存放在寄存器上,在寄存器上进行自增,把数据从寄存器拷贝会内存,每个步骤都可能被中断。 -判断大小端 - -``` -union un -{ -int i; -char ch; -}; -void fun() -{ -union un test; -test.i = 1; -if(ch == 1) -cout << "小端" << endl; -else -cout << "大端" << endl; -} -``` diff --git a/notes/面经/面试题-java.md.txt b/notes/面经/面试题-java.md.txt deleted file mode 100644 index 76e504aa..00000000 --- a/notes/面经/面试题-java.md.txt +++ /dev/null @@ -1 +0,0 @@ -- [最近5年133个Java面试问题列表](http://www.importnew.com/17232.html) diff --git a/notes/面经/面试题-mysql.md.txt b/notes/面经/面试题-mysql.md.txt deleted file mode 100644 index 1a0c4e70..00000000 --- a/notes/面经/面试题-mysql.md.txt +++ /dev/null @@ -1 +0,0 @@ -https://www.jianshu.com/p/977a9e7d80b3 diff --git a/notes/面经/面试题-操作系统.md.txt b/notes/面经/面试题-操作系统.md.txt deleted file mode 100644 index c715b924..00000000 --- a/notes/面经/面试题-操作系统.md.txt +++ /dev/null @@ -1,2 +0,0 @@ -http://wdxtub.com/interview/14520847747820.html -https://zhuanlan.zhihu.com/p/23755202 diff --git a/notes/面经/面试题-计算机网络.md.txt b/notes/面经/面试题-计算机网络.md.txt deleted file mode 100644 index 4d6ad33f..00000000 --- a/notes/面经/面试题-计算机网络.md.txt +++ /dev/null @@ -1 +0,0 @@ -[计算机网络方面的面试基础题](https://wenku.baidu.com/view/6ad2acc0aa00b52acfc7ca21.html) \ No newline at end of file