月度归档: 2021 年 6 月

3. 无重复字符的*长子串(JS实现)

3. 无重复字符的*长子串(JS实现)

1 题目
给定一个字符串,请你找出其中不含有重复字符的 *长子串 的长度。
示例 1:
输入: “abcabcbb”
输出: 3
解释: 因为无重复字符的*长子串是 “abc”,所以其长度为 3。
示例 2:
输入: “bbbbb”
输出: 1
解释: 因为无重复字符的*长子串是 “b”,所以其长度为 1。
示例 3:
输入: “pwwkew”
输出: 3
解释: 因为无重复字符的*长子串是 “wke”,所以其长度为 3。
请注意,你的答案必须是 子串 的长度,“pwke” 是一个子序列,不是子串。

2 思路
这道题应用滑动窗口的概念,重点关注符合题目条件的子字符串的首字符位置,当没有重复字符时,首字符位置不变,当有重复时,首字符位置向后滑动至之前记录的重复字符位置

3代码
/**
* @param {string} s
* @return {number}
*/
var lengthOfLongestSubstring = function(s) {
var map = {};
var maxLength = 0;
var x;
var first = 0;
for (var i=0;i<s.length;i++) {
x = s.charAt(i);
if (!(typeof map[x] === ‘undefined’ || map[x] < first)) { //若有字符重复,首字符串位置向后滑动
first = map[x] + 1;
}
map[x] = i; //记录新加入字符位置

if ((i – first + 1) > maxLength) {
maxLength = i – first + 1;
}
}

return maxLength;
};

5. *长回文子串(JS实现)

5. *长回文子串(JS实现)

1 题目
给定一个字符串 s,找到 s 中*长的回文子串。你可以假设 s 的*大长度为 1000。
示例 1:
输入: “babad”
输出: “bab”
注意: “aba” 也是一个有效答案。
示例 2:
输入: “cbbd”
输出: “bb”

2 思路
这道题利用动态规划的方法,若一个字符串是回文字符串,那么在其首尾同时加上一个相同的字符得到的字符串也是回文字符串。在建立动态规划二维表时,首先判断1个字符和2个字符的情况,其他长度的字符串通过状态转移方程得出答案

3代码
/**
* @param {string} s
* @return {string}
*/
var longestPalindrome = function(s) {
var n = s.length;
var d = [];
for (var k=0; k<n; k++) {
d.push([]);
}

var ans = ”;
var j;
for (var l=1; l<=n; l++) { //l为子字符串字符长度
for(var i=0; i<n; i++) { //i为子字符串起始位置
j = i + l -1; //j为子字符串终止位置
if (j >= n) break;

if (i == j) {
d[i][j] = true;
} else if (j == i+1) {
d[i][j] = (s[i] == s[j]);
} else {
d[i][j] = d[i+1][j-1] && (s[i] == s[j]);
}

if (d[i][j] && ((j – i + 1) > ans.length)) {
ans = s.slice(i,j+1);
}
}
}

return ans;
};

6. Z 字形变换(JS实现)

6. Z 字形变换(JS实现)

1 题目
将一个给定字符串根据给定的行数,以从上往下、从左到右进行 Z 字形排列。
比如输入字符串为 “LEETCODEISHIRING” 行数为 3 时,排列如下:
L C I R
E T O E S I I G
E D H N
之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:“LCIRETOESIIGEDHN”。
请你实现这个将字符串进行指定行数变换的函数:
string convert(string s, int numRows);
示例 1:
输入: s = “LEETCODEISHIRING”, numRows = 3
输出: “LCIRETOESIIGEDHN”
示例 2:
输入: s = “LEETCODEISHIRING”, numRows = 4
输出: “LDREOEIIECIHNTSG”
解释:
L D R
E O E I I
E C I H N
T S G

2 思路
这道题使用二维数组,刚开始我按照行列坐标进行填写,后来发现列不重要,只需要行就可以了,你会发现行号的变化是规律的

3代码
/**
* @param {string} s
* @param {number} numRows
* @return {string}
*/
var convert = function(s, numRows) {
if (numRows === 1) return s;

var d = [];
for (var i=0; i<numRows; i++) {
d.push([]);
}
var i = 0;
var flag = false;

for (var k=0; k<s.length; k++) {
d[getRows()].push(s[k]);
}

var ans = ”;
d.forEach(function(arr){
ans += arr.join(”);
});

return ans;

function getRows() {
if (i == numRows – 1 || i == 0) flag = !flag;
if (flag) {
return i++;
} else {
return i–;
}
}
};

8. 字符串转换整数 (atoi)(JS实现)

8. 字符串转换整数 (atoi)(JS实现)

1 题目
请你来实现一个 atoi 函数,使其能将字符串转换成整数。
首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到*个非空格的字符为止。接下来的转化规则如下:
如果*个非空字符为正或者负号时,则将该符号与之后面尽可能多的连续数字字符组合起来,形成一个有符号整数。
假如*个非空字符是数字,则直接将其与之后连续的数字字符组合起来,形成一个整数。
该字符串在有效的整数部分之后也可能会存在多余的字符,那么这些字符可以被忽略,它们对函数不应该造成影响。
注意:假如该字符串中的*个非空格字符不是一个有效整数字符、字符串为空或字符串仅包含空白字符时,则你的函数不需要进行转换,即无法进行有效转换。
在任何情况下,若函数不能进行有效的转换时,请返回 0 。
提示:
本题中的空白字符只包括空格字符 ’ ’ 。
假设我们的环境只能存储 32 位大小的有符号整数,那么其数值范围为 [−231, 231 − 1]。如果数值超过这个范围,请返回 INT_MAX (231 − 1) 或 INT_MIN (−231) 。
示例 1:
输入: “42”
输出: 42
示例 2:
输入: ” -42″
输出: -42
解释: *个非空白字符为 ‘-’, 它是一个负号。
我们尽可能将负号与后面所有连续出现的数字组合起来,*后得到 -42 。
示例 3:
输入: “4193 with words”
输出: 4193
解释: 转换截止于数字 ‘3’ ,因为它的下一个字符不为数字。
示例 4:
输入: “words and 987”
输出: 0
解释: *个非空字符是 ‘w’, 但它不是数字或正、负号。
因此无法执行有效的转换。
示例 5:
输入: “-91283472332”
输出: -2147483648
解释: 数字 “-91283472332” 超过 32 位有符号整数范围。
因此返回 INT_MIN (−231) 。

2 思路
这道题边界条件超多,如果直接写if语句,可能会漏掉,参考官方代码,此时需要考虑使用状态机来实现,用一个二维表格进行维护

3代码
/**
* @param {string} str
* @return {number}
*/
var myAtoi = function(str) {
var INT_MAX = 2 ** 31 – 1;
var INT_MIN = (-2) ** 31;
var ans = 0;
var sign = 1;
var table = {
‘start’: [‘start’, ‘sign’, ‘number’, ‘end’],
‘sign’: [‘end’, ‘end’, ‘number’, ‘end’],
‘number’: [‘end’, ‘end’, ‘number’, ‘end’],
‘end’: [‘end’, ‘end’, ‘end’, ‘end’],
}
var state = ‘start’;

function getCols(c) {
if (c === ‘ ‘) { //空格
return 0;
} else if (c === ‘+’ || c === ‘-‘) { //符号
return 1;
} else if (!isNaN(c % 1)) { //整数
return 2
} else { //其他字符
return 3;
}
}

function input(c) {
var inputState = table[state][getCols(c)];
state = inputState;
if (inputState === ‘sign’) {
sign = c === ‘-‘ ? -1 : 1;
} else if (inputState === ‘number’) {
ans = ans * 10 + parseInt(c);
if (ans > INT_MAX || ans * sign < INT_MIN) ans = sign > 0 ? INT_MAX : -INT_MIN;
} else if (inputState === ‘end’) { //退出循环
return true;
}
}

for (var i=0; i<str.length; i++) {
if (input(str[i])) {
break;
}
}
return sign * ans;
};

iOS数据层处理

前言
刚毕业就做iOS开发,到现在也有些年头了,感觉自己在工作中慢慢摸索、总结出了一套自己的程序设计思路,而且这套思路也不错的经受住了很多个不同类型项目、不同团队的实际考验,一直都有着不错的表现,*近不是很忙,借着博客记录一下在程序设计上自己的收获与心得。

个人总结,app客户端程序可以划分为两个部分,下层部分与数据打交道,包含网络请求与本地数据操作(存取);上层部分就是应用层部分,负责UI交互与业务逻辑的串联,如下图:

%title插图%num
一般在项目实施时,我们*步都会先商量、定义好底层的数据模型、本地数据存储接口以及网络层接口(本地的+服务器的)。然后,两组人同时起头并进进行开发,负责上层的同事完全不用关心下层(数据相关)的具体实现(数据是怎么从网上请求回来的;请求返回后如何处理、存储数据;本地数据是如何存储获取的;底层的存储是怎么实现的,等等),他们只需要知道之前定义好的本地网络层及数据层接口就行了,在合适的时机去调用即可。而下层开发的同事,也是不用去考虑上层的具体实现,两拨人的工作是不会产生任何冲突的。一般情况下,下层的开发工作会相对更快的完成,然后下层的开发同事就可以返过头来去参与上层同事的开发工作,*终完成项目。

当然,要达到上面所说的效果,就离不开前期一定的设计工作,下面就从数据层、网络层和应用层三个层次讲一下我一直以来采用的设计方法。

总的来说,明确职责,各司其职,尽力而为,是我在程序设计时的基本指导思想;

点到为止、量力而行,不要过度设计、过度模式是我的设计原则,够用就行。其实设计的目的主要就是为了让代码有更高的独立性、纯粹性(不臃肿、职责明确)、通用性和扩展性,手段一般也就是不断的换着花样分层或解藕。个人感觉,这部分工作在大型互联网企业、大团队、特大项目中还是很有必要的,是可以不断追求的,因为大团队人多,业务繁杂,如果设计层面上不做好,不花精力去实现、改进,都揉杂在一起,团队间相互牵制,基本无法配合,而且还会带来后续扩展性差、可能会重复实现、重复造轮子等问题;但是,设计都是需要花费精力去思考和实现的,同时往往也会在某种程度上增加代码的”复杂性”与代码量,像中小型团队(非特大型项目),人不多,项目相对独立、业务也不是太过庞杂等,沟通与后续开发修改成本本身不会太大,所以权衡设计、把握设计的尺度就显的尤为重要。

数据层设计
1.基本设计
数据层负责模型定义+本地数据存取(数据操作),其实看看realm或者coredata这类框架的设计,当你定义好模型后,数据的存取操作也随之完成,也印证了我们对这个层次在职责划分上的合理性。这个层次可以说是**单纯的一层,在代码层面上与其他层次完全没有任何缠绕,在实现上可以做到完全的独立,有着很好的复用与迁移性。

这个层次位于整个架构的*底层,是其它所有层次的根基与”工具”。

在数据层里定义好项目中所有可能用到的模型类,同时还要实现好每种模型对应的所有数据操作

%title插图%num

[objc]  view plain  copy
//模型定义
@interface JHSMS : NSObject

@property (nonatomic,copy) NSString *uniqueId;
@property (nonatomic,copy) NSString *phoneNum;
@property (nonatomic,copy) NSString *content;
@property (nonatomic,copy) NSString *relatedWorkNum;
@property (nonatomic,strong) NSDate *msgDate;
@property (nonatomic,assign) BOOL isInComing;
@property (nonatomic,assign) SMS_SENDSTATUS sendStatus;
……..

//数据操作
+(void)saveSmsAry:(NSMutableArray *)smsAry;
+(BOOL)saveSingleSms:(JHSMS *)sms;
+(void)updateMsgStatus:(SMS_SENDSTATUS)sendStatus uniqueId:(NSString *)uniqueId;
+(NSMutableArray *)getRecentlySMSs:(NSString *)phoneNum;
+(NSMutableArray *)getSMSs:(NSString *)phoneNum beforeSms:(JHSMS *)sms;
+(void)resendSms:(JHSMS *)sms;
…….
@end
具体数据操作实现
[objc]  view plain  copy
+(BOOL)saveSingleSms:(JHSMS *)sms
{
BOOL __block result = YES;
[_shareQueue inTransaction:^(FMDatabase *db, BOOLBOOL *rollback) {

@try {
NSString *insertStr = @”insert into ‘SMS'(‘uniqueID’,’appUserWorkId’,’phoneNum’,’content’,’msgDate’,’contactType’,’friendContactId’,’relatedWorkNum’,’isInComing’,’sendStatus’) values (?,?,?,?,?,?,?,?,?,?)”;
CONATACT_TYPE contactType;

FMResultSet *rs = [db executeQuery:@”select relatedWorkId from PhonesInCorpContacts where phoneNum = ?”,sms.phoneNum];
if ([rs next])
{
sms.friendContactId = [rs stringForColumn:@”relatedWorkId”];
contactType = CORP_CONTACT;
}
else
{
FMResultSet *rs2 = [db executeQuery:@”select relatedRecordId from PhonesInLocalContacts where appUserWorkId = ? and phoneNum = ?”,appUserWorkId,sms.phoneNum];
…………..
[rs2 close];
}
…………
}
}];
return result;
}
2.数据层的设计改进
数据层的设计改进主要针对的是数据操作部分,上面的设计中存在两个问题,导致了模型类的过于臃肿和耦合性过大

(1)模型定义与数据操作耦合性过大

数据的本地存取操作可能会有很多不同方式(nsuerdefault、数据库、文件等等)及不同的业务需求,如果按照上面的实现,我们没有一个单独的层次去分离出数据操作的具体实现,那么如果以后要更换底层的存储方式,或是修改、添加数据操作实现时,那么就会对模型类造成影响,导致模型类的暴露与修改。所以我们还可以在数据层里单独划分出模型层+数据操作层.

%title插图%num

%title插图%num

在另一个项目中,我加入saver层,在这个层次里完成具体的数据存储操作,可以是数据库操作,也可以文件操作或是别的,甚至我还完成过同时支持多种存储形式,然后交由调用者选择的需求,无论怎样,这些不同的底层实现都封装可以在这个层次中单独的处理,对使用者透明,修改、实现时不会对模型类产生任何影响,同时大大减少了模型类的代码体量与功能承载压力

(2)数据操作与业务有缠绕

第二个问题是,针对每种模型(数据),其实**纯粹根本的操作就是对一个(一组)数据的增、删、改、查,而在我上面的代码示例中

[objc]  view plain  copy
+(void)updateMsgStatus:(SMS_SENDSTATUS)sendStatus uniqueId:(NSString *)uniqueId;
+(void)resendSms:(JHSMS *)sms;
+(NSMutableArray *)getRecentlySMSs:(NSString *)phoneNum;
这些操作明显是依据业务而定义(衍生)的,不具备普遍的通用性,没有复用的价值,同时造成了数据操作层与业务的耦合,于是我们可以对数据操作层再细分

%title插图%num

划分后,基础操作层可能包含下列这类函数实现,分离后它们具有很好的”原子性”、通用性与复用性

[objc]  view plain  copy
+(BOOL)saveSmsAry:(NSMutableArray *)smsAry;
+(BOOL)saveSingleSms:(JHSMS *)sms;
+(BOOL)deleteSms:(JHSMS *)sms;
+(NSMutableArray *)getSMSsWithCondition:(NSString *)condition;
+(BOOL)updateSmsForProperty:(NSString *)property value:(NSString *)value;
而业务操作层则是根据你具体的项目业务需求,对基础操作层进行不同的组合、调用

在数据层的实际设计开发中,我大部分情况下都是按基础设计去做的,除非有特殊需要,比较少的对数据操作层进行进一步细分.数据层比较单纯、独立、基础,其实修改、实现成本本身就不大,除非有特殊需求,一般我也就模型、数据操作写一起了,这样开发起来速度*快,复用、修改起来的工作量与难度也并不会很大.

iOS 数据存储的几种方式

在iOS开发过程中常用的本地化存储有五种方式:

1.plist (XML属性列表归档 NSArray\NSDictionary)

2.preference (偏好设置\NSUserDefaults) (本质还是通过plist来存储数据,但是使用更加简单,无需关注文件、文件夹路径和名称)

3.NSCoding (NSKeyedArchiver\NSKeyedUnarchiver)  (能把任何对象都直接保存成文件的方式)
4.SQLite3  (当非常大量的数据时候才会使用)
5.Core Data (是对SQLite3的封装,更加面向对象,效率没有SQLite3高)

沙盒(sandbox):每个iOS应用都有自己的应用沙盒(应用沙盒就是应用的文件夹),与其他文件系统隔离。应用必须待在自己的沙盒里,其他应用不能访问该沙盒
应用沙盒的文件系统目录,如下图所示(假设应用的名称叫Layer)

沙盒结构分析:
应用程序包:(上图中的Layer)包含了所有的资源文件和可执行文件
Documents:保存应用运行时生成的需要持久化的数据,iTunes同步设备时会备份该目录。例如,游戏应用可将游戏存档保存在该目录。保存相对重要的数据。

tmp:保存应用运行时所需的临时数据,使用完毕后再将相应的文件从该目录删除。应用没有运行时,系统也可能会清除该目录下的文件。iTunes同步设备时不会备份该目录。保存不重要的并且大的数据。

Library/Caches:保存应用运行时生成的需要持久化的数据,iTunes同步设备时不会备份该目录。一般存储体积大、不需要备份的非重要数据

Library/Preference:保存应用的所有偏好设置,iOS的Settings(设置)应用会在该目录中查找应用的设置信息。iTunes同步设备时会备份该目录。该目录由系统管理, 无需我们来管理。通常用来存储一些基本的软件配置信息, 比如记住密码、自动登录等。

总结: 我们平时操作数据主要使用Documents目录。

沙盒的目录的获取方式:
沙盒根目录的获取:
NSString *home = NSHomeDirectory();
1.利用沙盒根目录拼接”Documents“字符串:
//这种方式不建议使用,因为如果新版本的操作系统可能会修改目录的名称
NSString *home = NSHomeDirectory();
NSString *documents = [home stringByAppendingPathComponent:@“Documents”];

2.利用NSSearchPathForDirectoriesInDomains函数
// NSUserDomainMask 代表从用户文件夹下找
// YES 代表展开路径中的波浪字符“~”
NSArray *array =  NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
// 在iOS中,只有一个目录跟传入的参数匹配,所以这个集合里面只有一个元素
NSString *documents = [array objectAtIndex:0];

常见沙盒目录获取方式:
tmp:NSString *tmp = NSTemporaryDirectory();
Library/Caches:(跟Documents类似的2种方法)
利用沙盒根目录拼接”Caches”字符串
利用NSSearchPathForDirectoriesInDomains函数(将函数的第2个参数改为:NSCachesDirectory即可)
Library/Preference:通过NSUserDefaults类存取该目录下的设置信息

1、XML属性列表(plist):(不能存储基本数据类型只能存储OC对象)

属性列表是一种 xml格式的文件,拓展名为plist,如果对象是(必须是OC对象,不能存储基本数据类型)NSString、NSDictionary/NSArray、NSData 、NSNumber等类型,就可以使用writeToFile:atomically:方法直接将对象写到属性列表文件中,然后用dictionaryWithContentsOfFile读取数据。

示例代码如下:

//保存
– (IBAction)save {

//1.获取沙盒根路径
NSString *home = NSHomeDirectory();
//2.document路径
NSString *docPath = [home stringByAppendingPathComponent:@”Documents”];
//3.新建数据  数据一般只能存储oc对象  字典也可以
NSArray *data = @[@”jack”,@”mack”,@15];
//4.写数据
NSString *filePath = [docPath stringByAppendingPathComponent:@”data.plist”];
[data writeToFile:filePath atomically:YES];
}
//读取
– (IBAction)read {

//1.获取沙盒根路径
NSString *home = NSHomeDirectory();
//2.document路径
NSString *docPath = [home stringByAppendingPathComponent:@”Documents”];
//3.文件路径
NSString *filePath = [docPath stringByAppendingPathComponent:@”data.plist”];
//4.读取数据
NSArray *data = [NSArray arrayWithContentsOfFile:filePath];
NSLog(@”%@”,data);
}

//将一个NSDictionary对象归档到一个plist属性列表中
// 将数据封装成字典
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
[dict setObject:@”母鸡” forKey:@”name”];
[dict setObject:@”15013141314″ forKey:@”phone”];
[dict setObject:@”27″ forKey:@”age”];
// 将字典持久化到Documents/stu.plist文件中
[dict writeToFile:path atomically:YES];

//读取属性列表,恢复NSDictionary对象
// 读取Documents/stu.plist的内容,实例化NSDictionary
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:path];
NSLog(@”name:%@”, [dict objectForKey:@”name”]);
NSLog(@”phone:%@”, [dict objectForKey:@”phone”]);
NSLog(@”age:%@”, [dict objectForKey:@”age”]);
2、prefrrence (偏好设置)

很多iOS应用都支持偏好设置,比如保存用户名、密码、字体大小等设置,iOS提供了一套标准的解决方案来为应用加入偏好设置功能
每个应用都有个NSUserDefaults实例,通过它来存取偏好设置,比如,保存用户名、字体大小、是否自动登录等。

//存储数据
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:@”itcast” forKey:@”username”];
[defaults setFloat:18.0f forKey:@”text_size”];
[defaults setBool:YES forKey:@“auto_login”];
//读取上次保存的设置
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *username = [defaults stringForKey:@”username”];
float textSize = [defaults floatForKey:@”text_size”];
BOOL autoLogin = [defaults boolForKey:@”auto_login”];
[defaults synchornize];(立刻同步)
注意:UserDefaults设置数据时,不是立即写入,而是根据时间戳定时地把缓存中的数据写入本地磁盘。所以调用了set方法之后数据有可能还没有写入磁盘应用程序就终止了。出现以上问题,可以通过调用synchornize方法强制写入
示例代码如下:

//偏好设置数据存储:(一般用来存储软件的配置,存储在Library/Preferences中,不需要关注文件夹的路径)

//存储
– (IBAction)save {

//1.利用NSUserDefaults,就能直接访问软件的偏好设置(Library/Preferences
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
//2.存储数据
[defaults setObject:@”jack” forKey:@”name”];
[defaults setInteger:10 forKey:@”age”];
[defaults setBool:YES forKey:@”Login”];
//3.立刻同步
[defaults synchronize];
}

//读取
– (IBAction)read {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *name = [defaults objectForKey:@”name”];
BOOL login = [defaults objectForKey:@”Login”];
NSLog(@”%@– %d”,name,login);
}

3、NSCoding (NSKeyedArchiver\NSKeyedUnarchiver)
如果对象是NSString、NSDictionary、NSArray、NSData、NSNumber等类型,可以直接用NSKeyedArchiver进行归档和恢复。
不是所有的对象(非OC对象)都可以直接用这种方法进行归档,只有遵守了NSCoding协议的对象才可以
NSCoding协议有2个方法:
encodeWithCoder:每次归档对象时,都会调用这个方法。一般在这个方法里面指定如何归档对象中的每个实例变量,可以使用encodeObject:forKey:方法归档实例变量
initWithCoder:每次从文件中恢复(解码)对象时,都会调用这个方法。一般在这个方法里面指定如何解码文件中的数据为对象的实例变量,可以使用decodeObject:forKey方法解码实例变量

示例代码如下:

NSKeyedArchiver数据存储,不仅可以用来存储OC对象,也可以用来存储非OC对象,以一个Person对象为例:(想归档某个对象,这个对象一定要遵守NSCoding这个协议)
#import <Foundation/Foundation.h>
@interface TWPerson : NSObject <NSCoding>
@property (nonatomic,copy) NSString *name;
@property (nonatomic,assign) int age;
@property (nonatomic,assign) double height;
@end

@implementation TWPerson
//将对象归档的时候调用(将对象写入文件之前调用)
//在这个方法里说清楚:1.哪些属性需要存储  2.怎样存储这些属性
-(void)encodeWithCoder:(NSCoder *)enCoder
{
//将_name属性进行编码(会将_name的值存进文件)
[enCoder encodeObject:_name forKey:@”name”];
[enCoder encodeInt:_age forKey:@”age”];
[enCoder encodeDouble:_height forKey:@”height”];

}
//当从文件中解析对象时使用
//在这个方法说清楚: 1.那个属性需要解析(读取)  2.怎样解析(读取)这些属性
– (id)initWithCoder:(NSCoder *)Decoder
{

if (self = [super init]) {
_name = [Decoder decodeObjectForKey:@”name”];
_age = [Decoder decodeIntForKey:@”age”];
_height = [Decoder decodeDoubleForKey:@”height”];
}
return self;
}
@end

//存储数据
– (IBAction)save {
TWPerson *p = [[TWPerson alloc]init];
p.name = @”jack”;
p.age = 10;
p.height = 1.82;
NSString *path = @”/Users/wtw/Desktop/person.data”;
//归档
[NSKeyedArchiver archiveRootObject:p toFile:path];
}
//读取数据
– (IBAction)read {
NSString *path = @”/Users/wtw/Desktop/person.data”;
//读档(反归档)
TWPerson *p = [NSKeyedUnarchiver  unarchiveObjectWithFile:path];
NSLog(@”%@ — %f — %d”,p.name,p.height,p.age);
}
4、SQLite3
SQLite3 是一款开源的轻型的嵌入式关系型数据库,可移植性好、易使用、内存开销小,SQLite3 是无类型的,可以保存任何类型的数据到任意的字段中。
数据库存储数据的步骤:

》新建一个数据库(DataBase)

》新建一张表(table)

》添加多个字段(column,列,属性)

》添加多行记录(row,每行存放多个字段对应的值)

SQL语句的分类:

数据定义语句:(DDL :Data Definition Language)

包括create 和 drop 等操作。

在数据库中创建新表或者删除表(creat table 或 drop table)
数据库操作语句: (DML : Data manipulation Language)

包括 insert 、update 、delete 等操作。

上面的3种操作分别用于添加、修改、删除表中的数据

数据查询语句 :(DQL : Data Query Language)

可以用于查询获得表中的数据

关键字select 是 DQL 用的*多的操作

其他DQL 常用的关键字有 where 、order by、group by 和 having 等。
创建表:

//创建表:
/*
创建数据表
CREATE TABLE ‘表名’ (
‘字段1’ 类型(INTEGER, REAL, TEXT, BLOB)
NOT NULL 不允许为空
PRIMARY KEY 主键
AUTOINCREMENT 自增长,
‘字段名2′ 类型,

)
*/
提示:可以从navicat先创建好表,复制粘贴,需要自己增加 IF NOT EXISTS

CREATE TABLE IF NOT EXISTS “T_Person” (
“id” INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
“name” TEXT,
“age” INTEGER,
“heigth” REAL
)
删除表:
//删除表
//DROP TABLE t_student;
DROP TABLE IF EXISTS t_student;
插入数据:

//插入命令:
INSERT INTO 表名
(字段名1,字段名2…..)
VALUES
(值1,值2。。。。)
//注意:值和字段一定要一致
//提示:SQLite的字段属性,只是给程序员看的,存什么都行

INSERT INTO t_student
(age, score, name)
VALUES
(’28’, 100, ‘zhangsan’);
更新数据:

//更新数据:
UPDATE 表名
SET
字段1 = ‘值1’ , //提示: 如果是字符串,需要使用单引号引起来
字段2 = 值2 ,…
/*更新记录的name*/
UPDATE t_student SET name = ‘zhangsan’;
//注意:如果更新时没有指定更新哪一条记录,那么会更新所有的数据

删除数据:

//删除指令:
//注意:删除数据的时候,一定要设置条件
//如果条件不满足,什么也不会发生

DELETE FROM 表名
WHERE 主键 = 值;

//例如:
DELETE FROM t_student;
DELETE FROM t_student WHERE age < 50;

查询数据:

//查询指令:
/* 分页 */
SELECT * FROM t_student
ORDER BY id ASC LIMIT 30, 10;
/* 排序 */
SELECT * FROM t_student
WHERE score > 50
ORDER BY age DESC;
SELECT * FROM t_student
WHERE score < 50
ORDER BY age ASC , score DESC;
/* 计量 */
SELECT COUNT(*)
FROM t_student
WHERE age > 50;
/* 别名 */
SELECT name as myName, age as myAge, score as myScore
FROM t_student;
SELECT name myName, age myAge, score myScore
FROM t_student;
SELECT s.name myName, s.age myAge, s.score myScore
FROM t_student s
WHERE s.age > 50;
/* 查询 */
SELECT name, age, score FROM t_student;
SELECT * FROM t_student;
数据库的基本使用:

@property (nonatomic, assign) sqlite3 *db;
/*
1.创建数据库
2.创建表(指定字段)
3.操作数据库
*/
/*
*个参数:是告诉系统数据库文件在什么地方.
>如果文件不存在就会自动创建, 然后再打开
>如果数据库存在就会自动打开
第二个参数:返回打开的数据库对象
*/
// 1.拼接数据库地址
NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)lastObject];
NSString *sqlFile = [path stringByAppendingPathComponent:@”student.sqlite”];
// sqlite3 *db = NULL;
// 2.打开数据库
int result = sqlite3_open(sqlFile.UTF8String, &_db);// [self db]
// 3.判断是否打开成功
if (result == SQLITE_OK) {
NSLog(@”打开成功”);
// 3.1创建表
/*
*个参数: 需要执行SQL语句的数据库对象
第二个参数: 需要执行的SQL语句
第三个参数: 回调函数
第四个参数: 第三个参数的参数
第五个参数: 接收错误信息
*/
NSString *sql = @”CREATE TABLE IF NOT EXISTS t_student(id INTEGER PRIMARY KEY AUTOINCREMENT , name TEXT, age INTEGER, score REAL);”;
result = sqlite3_exec(_db, sql.UTF8String, NULL, NULL, NULL);
if (result == SQLITE_OK) {
NSLog(@”创建表成功”);
}else
{
NSLog(@”创建表失败”);
}
}else
{
NSLog(@”打开失败”);
}
– (IBAction)insertClick:(id)sender {

NSString *sql = @”INSERT INTO t_student(age, score, name) VALUES (’28’, 100, ‘jonathan’);”;
int result = sqlite3_exec(_db, sql.UTF8String, NULL, NULL, NULL);
if (result == SQLITE_OK) {
NSLog(@”插入成功”);
}
}

– (IBAction)updateClick:(id)sender {
NSString *sql = @”UPDATE t_student SET name = ‘XXX’;”;
int result = sqlite3_exec(_db, sql.UTF8String, NULL, NULL, NULL);
if (result == SQLITE_OK) {
NSLog(@”修改成功”);
}
}

– (IBAction)deleteClick:(id)sender {
NSString *sql = @”DELETE FROM t_student WHERE id = 1; “;
int result = sqlite3_exec(_db, sql.UTF8String, NULL, NULL, NULL);
if (result == SQLITE_OK) {
NSLog(@”删除成功”);
}6
}

– (IBAction)selectClick:(id)sender {
/*
sqlite3操作中, 所有DML语句都是使用sqlite3_exec函数执行SQL语句即可,
但是如果是需要查询数据库, 不能使用sqlite3_exec, 因为它并没有返回查询到得结果发给我们
*/
/*
NSString *sql = @”SELECT * FROM t_student;”;
int result = sqlite3_exec(_db, sql.UTF8String, NULL, NULL, NULL);
if (result == SQLITE_OK) {
NSLog(@”查询成功”);
}
*/
// 1.准备查询
/*
*个参数:需要执行SQL语句的数据库
第二个参数:需要执行的SQL语句
第三个参数: 告诉系统SQL语句的长度, 如果传入一个小于0的数, 系统会自动计算
第四个参数:结果集, 里面存放所有查询到的数据(不严谨)
*/
NSString *sql = @”SELECT * FROM t_student;”;
sqlite3_stmt *stemt = NULL;
sqlite3_prepare_v2(_db, sql.UTF8String, -1, &stemt, NULL);
// 2.判断有没有查询结果
// int result = sqlite3_step(stemt);
// if (result == SQLITE_ROW) {
while (sqlite3_step(stemt) == SQLITE_ROW) {
// NSLog(@”查询到了数据”);
// 3.取出查询到得结果
const unsigned char *name = sqlite3_column_text(stemt, 1);
int age = sqlite3_column_int(stemt, 2);
double score = sqlite3_column_double(stemt, 3);
NSLog(@”%s %d %f”, name, age, score);
}
}

数据库的基本的封装:
@interface SQLManager : NSObject

+ (instancetype)shareManage;
//插入新数据
– (BOOL)insertStudent:(HMStudent *)student;
//删除数据
– (BOOL)deleteWithStudent:(HMStudent *)student;
//更新数据
– (BOOL)updateWithStudent:(HMStudent *)student;
//查询数据
– (NSArray *)query;
@end
#import “SQLManager.h”
#import <sqlite3.h>

@implementation SQLManager

static SQLManager *_instance;
+ (instancetype)shareManage
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [[SQLManager alloc] init];
});
return _instance;
}

sqlite3 *_db;
+ (void)initialize
{
// 1.拼接数据库地址
NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)lastObject];
NSString *sqlFile = [path stringByAppendingPathComponent:@”student.sqlite”];
// 2.打开数据库
int result = sqlite3_open(sqlFile.UTF8String, &_db);// [self db]
// 3.判断是否打开成功
if (result == SQLITE_OK) {
NSLog(@”打开成功”);
// 3.1创建表
NSString *sql = @”CREATE TABLE IF NOT EXISTS t_student(id INTEGER PRIMARY KEY AUTOINCREMENT , name TEXT, age INTEGER, score REAL);”;
result = sqlite3_exec(_db, sql.UTF8String, NULL, NULL, NULL);
}
}

– (BOOL)insertStudent:(HMStudent *)student
{
NSString *sql = [NSString stringWithFormat: @”INSERT INTO t_student(age, score, name) VALUES (%d, %f, ‘%@’);”, student.age, student.score, student.name];
int result = sqlite3_exec(_db, sql.UTF8String, NULL, NULL, NULL);
if (result == SQLITE_OK) {
return YES;
}
return NO;
}
– (BOOL)deleteWithStudent:(HMStudent *)student
{
NSString *sql = [NSString stringWithFormat:@”DELETE FROM t_student WHERE id = %d;”, student.ID];
int result = sqlite3_exec(_db, sql.UTF8String, NULL, NULL, NULL);
if (result == SQLITE_OK) {
return YES;
}
return NO;
}
– (BOOL)updateWithStudent:(HMStudent *)student
{

NSString *sql = [NSString stringWithFormat:@”UPDATE t_student SET name = ‘%@’;”, student.name];
int result = sqlite3_exec(_db, sql.UTF8String, NULL, NULL, NULL);
if (result == SQLITE_OK) {
return YES;
}
return NO;
}
– (NSArray *)query
{
NSString *sql = @”SELECT * FROM t_student;”;
sqlite3_stmt *stemt = NULL;
sqlite3_prepare_v2(_db, sql.UTF8String, -1, &stemt, NULL);
// 2.判断有没有查询结果
NSMutableArray *arrM = [NSMutableArray array];
while (sqlite3_step(stemt) == SQLITE_ROW) {
// 3.取出查询到得结果
const unsigned char *name = sqlite3_column_text(stemt, 1);
int age = sqlite3_column_int(stemt, 2);
double score = sqlite3_column_double(stemt, 3);
HMStudent *stu = [[HMStudent alloc] init];
stu.name = [NSString stringWithUTF8String:name];
stu.age = age;
stu.score = score;
[arrM addObject:stu];
}
return arrM;
}
@end
FMDB的基本使用:

以OC的方式封装了SQLite的C语言API.

优点是:

使用起来更加面向对象,省去麻烦冗余的C语言的代码,对比苹果的Core Data 框架,更加的轻量级和灵活,提供了多线程安全的数据库操作方法,有效防止数据混乱。

FMDB有三个三个主要的类:

FMDataBase :一个FMDataBase 对象就代表一个单独的DataBase数据库,用来执行SQL语句。

FMResultSet: 使用FMResultSet执行查询后的结果集。

FMDataBaseQueue: 用于多线程执行多个查询或者更新,他是线程安全的。
@property (nonatomic, strong) FMDatabase *db;
@implementation ViewController
– (void)viewDidLoad {
[super viewDidLoad];
/*
1.创建数据库
2.创建表
3.操作
*/
// 1.拼接数据库地址
NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)lastObject];
NSString *sqlFile = [path stringByAppendingPathComponent:@”student.sqlite”];
//该方法接收一个路径, 它会根据地址创建一个数据库, 如果不存在就会自动创建
FMDatabase *db = [FMDatabase databaseWithPath:sqlFile];
self.db = db;
if([db open]){
// 2.创建表
NSString *sql = @”CREATE TABLE IF NOT EXISTS t_student(id INTEGER PRIMARY KEY AUTOINCREMENT , name TEXT, age INTEGER, score REAL);”;

// 注意: 在FMDB中除了查询以外的操作都称之为更新
if([db executeUpdate:sql]){
NSLog(@”创建表成功”);
}
}
}
– (IBAction)insertClick:(id)sender
{ NSString *sql = @”INSERT INTO t_student(age, score, name) VALUES (’28’, 100, ‘jonathan’);”;
if([self.db executeUpdate:sql])
{
NSLog(@”插入成功”);
}
}
– (IBAction)selectClick:(id)sender
{
NSString *sql = @”SELECT * FROM t_student;”;
// 查询数据, 会将所有查询到得数据, 放到results中
FMResultSet *results = [self.db executeQuery:sql];
// 从results中获取数据
while ([results next]) {
NSString *name = [results stringForColumn:@”name”];
int age = [results intForColumn:@”age”];
double score = [results doubleForColumn:@”score”];
NSLog(@”%@ %d %f”, name, age, score);
}
}
FMDataBaseQueue:

@property (nonatomic, strong) FMDatabaseQueue *dbQueue;
@implementation ViewController

– (void)viewDidLoad {
[super viewDidLoad];

// 1.拼接数据库地址
NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)lastObject];
NSString *sqlFile = [path stringByAppendingPathComponent:@”student.sqlite”];
// 创建一个数据库队列
FMDatabaseQueue *dbQueue = [FMDatabaseQueue databaseQueueWithPath:sqlFile];
self.dbQueue = dbQueue;
//只要调用dbQueue的inDatabase方法, 系统就会传递一个已经打开并且线程安全的数据库给我们
[dbQueue inDatabase:^(FMDatabase *db) {
// 2.创建表
NSString *sql = @”CREATE TABLE IF NOT EXISTS t_student(id INTEGER PRIMARY KEY AUTOINCREMENT , name TEXT, age INTEGER, score REAL);”;

// 注意: 在FMDB中除了查询以外的操作都称之为更新
if([db executeUpdate:sql]){
NSLog(@”创建表成功”);
}
}];
}
– (IBAction)insertClick:(id)sender
{
[self.dbQueue inDatabase:^(FMDatabase *db) {
NSString *sql = @”INSERT INTO t_student(age, score, name) VALUES (’28’, 100, ‘jonathan’);”;
if([db executeUpdate:sql])
{
NSLog(@”插入成功”);
}
}];
}
– (IBAction)selectClick:(id)sender
{
[self.dbQueue inDatabase:^(FMDatabase *db) {
NSString *sql = @”SELECT * FROM t_student;”;
// 查询数据, 会将所有查询到得数据, 放到results中
FMResultSet *results = [db executeQuery:sql];
// 从results中获取数据
while ([results next]) {
NSString *name = [results stringForColumn:@”name”];
int age = [results intForColumn:@”age”];
double score = [results doubleForColumn:@”score”];
NSLog(@”%@ %d %f”, name, age, score);
}
}];
}
@end

事务:可以将多条SQL数据绑定在一起,要么一起执行成功要么一起执行失败。
CoreData:
Core Data框架提供了对象-关系映射(ORM)的功能,即能够将OC对象转化成数据,保存在SQLite3数据库文件中,也能够将保存在数据库中的数据还原成OC对象。在此数据操作期间,不需要编写任何SQL语句。

 

Oracle Cloud 可能把测试版界面放上来了

不小心按了一下 F12,发现 console 里不停地蹦调试信息,难怪很多人说这是*难用的云计算操作台呢,简直是搞笑来的

F12 操作台 Oracle console37 条回复 • 2021-06-22 07:20:47 +08:00
Mitt 1
Mitt 2 天前 ❤️ 2
你是真不理解 DevTools 是干啥用的吗?
harwck 2
harwck 2 天前
也不知道是你在搞笑還是別人在搞笑
msg7086 3
msg7086 2 天前
笑笑,有谢到。
Rocketer 4
Rocketer 2 天前 via iPhone ❤️ 2
@Mitt 真不理解,求解惑。

我工作过的几个公司,都禁止在生产环境中打印信息,难道我是少数派,打印才是正常的?
Rocketer 5
Rocketer 2 天前 via iPhone ❤️ 1
@harwck 笑完了请解释一下为什么笑
ericls 6
ericls 2 天前 via iPhone
我觉得这个肯定是不对的,生产环境的错误信息应该直接到日志服务器 而不是用户
Kinnice 7
Kinnice 2 天前
从登录到创建完一台 VM 打印了大约 400 条日志。
mxT52CRuqR6o5 8
mxT52CRuqR6o5 2 天前 via Android
B 端项目不太在意这些东西吧
yohole 9
yohole 2 天前
同样上周开了 Oracle Cloud 用了几天了,发现它落后还是有原因的,控制台*其难用,很多常用的工具和信息混乱不堪,布局不合理
iAIR 10
iAIR 2 天前 ❤️ 2
我至今都搞不懂 Oracle 的账号系统为什么要设计得如此扭曲:
* 首先右上角登陆有 Cloud Account 和 Oracle Account 两个选项
* Cloud Account 有 Cloud Account Name (不是邮箱)
* 填完之后让你用 oracleidentitycloudservice 服务进行 SSO 登陆
* 然后又让你用 Oracle Cloud Account 的邮箱(不是用户名)和密码登陆

我知道这是先选择 Tenant,然后登陆该 Tenant 的 User 账号,但就不能学一下巨硬和谷歌根据 User 的邮箱自动判断属于哪个 Tenant 吗,而且全都叫 Oracle, Cloud, Account 这类词。

j3n5en 11
j3n5en 1 天前 via Android
都一样,你去 azure 看注册路上就一直蹦
wazggcd 12
wazggcd 1 天前 via Android
*坑的是账号强行绑定主区域,还不能换区,不知道为啥要这样设计
darknoll 13
darknoll 1 天前
oracle 是个不思进取的企业,就是个垃圾
labulaka521 14
labulaka521 1 天前 via iPhone
那个 sdk 超级复杂
changwei 15
changwei 1 天前
前三楼也挺自以为是的的
你们可以去看看像 Facebook,Twitter 这些国际大厂的 SPA 项目,除了找不到 map 文件和 xhr 消息以外,生产环节几乎是没有任何业务有关的日志信息的,稍微大一点的厂都知道用 sentry 等服务收集 C 端日志信息
反倒是国内的大厂,像百度一些 PC 端常年没有更新过的老旧网站也是和楼主说的 Oracle 一样在 console 里面打印一大堆的 warn 和 error
yuguorui96 16
yuguorui96 1 天前
楼主说的没问题,不停的弹调试信息基本就意味着赶工和质量堪忧。
learningman 17
learningman 1 天前
前面的别太自以为是了啊。。。大家都是程序员,生产环境一堆 console.log 肯定不合理啊
AkideLiu 18
AkideLiu 1 天前 via iPhone
可能 bug 太对改不过来,想一想用户大多也是程序员 /技术人员。没准顺手帮他们解决俩 bug 。狗头
jones2000 19
jones2000 1 天前
f12 调试多方便, 用后台日志收集多麻烦, 费人费时, 预算充足,人员超配的情况下可以考虑用. 把 console.log 覆盖了, 写一个提交后台日志不就可以了. 但是这样你起码要多配 1-2 个后台人员. 根据 2W 一个人每月, 1 年就要增加 24W-48W 成本, 还不算 5 险 1 金的钱. 说白了就钱没到位.
vk42 20
vk42 1 天前
@iAIR 感觉 Oracle 就是故意用屎一样的 UX 来劝退不挣钱的 C 端用户,B 端用户真正做决定的都是采购或者领导,真正会被恶心到的开发和维护也没啥话语权。另外还能顺便卖培训和认证证书服务,老本行了……
jhdxr 21
jhdxr 1 天前
说生产环境错误信息直接收集到日志服务器的都是后端开发吧???
Mitt 22
Mitt 1 天前
@Rocketer #4 你知道 windows 下有个软件叫 DebugView 吗,如果你打开这个软件你会发现 windows 上所有软件都在不停的给你报调试信息,包括 windows 自己,你会觉得 windows 是来搞笑的吗? DevTools 和 DebugView 性质一样,从你打开的那一刻你就开启了网站本身的调试模式,只不过是生产模式下的调试信息,你公司禁止在生产环境打印信息我不知道是出于什么目的,但是生产环境只要不是把开发版本发布出去,我不觉得这有啥问题

而且楼上说的前端收集日志反馈给后端的,这个是常规操作没错,但这不是唯一选啊,如果因为网络问题这些日志无法收集,难道前端 console 还不能把错误信息报出来吗,而且你作为一个用户你用 DevTools 本身就是个迷惑行为,大厂还用那么大的个字告诉你 console 粘贴代码执行是不安全的,从你决定打开 DevTools 的时候,这些调试信息就是给你看的,用 DevTools 不管是看 DOM 、console 、还是 network 本身都是“调试行为”

说真的,除非 console 里面把 oracle 内部私钥都给你打印出来,否则就算打印你输入的密码我也不觉得有啥大问题
ericls 23
ericls 1 天前
@jhdxr C 端一样可以,C 端没有 source map 的情况下 错误信息本来就不好看。

而且如果等到用户遇到问题的时候,你会让用户手动去打开 devtool 截图给你看吗?这就已经晚了。
ericls 24
ericls 1 天前
@Mitt 就像你说的,这些都是调试行为,所以你会在生产环境调试吗?
Rocketer 25
Rocketer 1 天前 via iPhone
@Mitt 你说明的也只是“可以”这么做,而不是“应该”这么做。

我不知道具体为什么我工作过的公司都禁止在生产环境用 console.log ,也许我们只是“猴子不能吃香蕉”的受害者。

但我确实亲自体验过 console.log 导致的内存泄漏——有时下班懒得关闭浏览器,放在那儿继续运行,打印量大的话第二天早上就很可能看到浏览器死住或者崩溃。

所以我猜不在生产环境打印调试信息应该有这方面的考虑,毕竟总有一些用户的配置很低,可能很快就内存泄漏了。

再就是楼上的问题——在生产环境打印调试信息,这是要给谁看呢?
rannnn 26
rannnn 1 天前
前几楼也挺自以为是的,显示和不显示都有自己的道理。我们公司发到日志服务器,也打印一部分 log,另一部分 dev 可以用工具开启。2B 每个客户有各种各样的 integration,很多时候 dev 环境是不能复现问题的。所以我们的确会在生产环境经过用户授权后 impersonate 调试。
rannnn 27
rannnn 1 天前
还有 B 端用户 report 问题一般都是通过 IT 部门反馈的,所以本身反馈的人也是技术人员。直接打印出来也是为了他们在 file ticket 的时候就能直接附上一些常见信息。前面列举的 Facebook Twitter 都是 2C 的产品,你开几个 2B 比如 Azure,SAP,Workday 就可以看到多多少少会 log 一些东西。
JoJoJoJ 28
JoJoJoJ 1 天前 via iPhone
@yohole aws 一样难用
Rocketer 29
Rocketer 1 天前 via iPhone
@rannnn 谢谢,27 楼的说法比较合理
netnr 30
netnr 1 天前 via Android
歪,昨天刚注册,密码搞忘了(规则跟常用密码冲突),邮件重置密码链接打开报 404,无语
Mitt 31
Mitt 1 天前 via iPhone
@Rocketer 这就纯搞笑了 首先如果 devtools 不打开 console.log 不会产生任何内存数据,其次哪个正常用户会开着 console 用网页,请不要吧把自己开发者身份代入,还是我说的,windows 自己就有 debugview 照你这么说 windows 岂不是每秒都在导致内存爆炸
Rocketer 32
Rocketer 1 天前 via iPhone
@Mitt 我是应用开发者,不是浏览器开发者,不懂浏览器的内存管理,谢谢扫盲
learningman 33
learningman 1 天前
@Mitt #22 你说的是事件管理器吧。。。我是没见几个没事往这里面冒泡的,设备驱动之类的倒是会,但是人家本来就是这么通信的啊
jhdxr 34
jhdxr 20 小时 51 分钟前
@ericls 我不知道是我还是你对于调试这个词有什么误解。『打印日志』都能算作调试了???另外,C 端(客户端为主,网页有但不多)的确也有日志采集,但这和把日志打印出来并不冲突。

@Rocketer 你主贴里表达的是『不应该』这么做。 @Mitt 已经说明了『可以』这么做,这就足以反驳『不应该』了。
Rocketer 35
Rocketer 16 小时 3 分钟前
@jhdxr 我就不抠字眼玩文字游戏了。

本质上就是我见识太少,因为我工作过的公司都只在测试版里才打印日志,我就以为这是行业惯例了。但真理往往掌握在少数人手里,所以你是对的。
Mitt 36
Mitt 8 小时 14 分钟前
@jhdxr #34 没有 我的意思是 DevTools 这个工具本身是属于调试工具,console.log 本身可能不完全算
Mitt 37
Mitt 8 小时 2 分钟前
@jhdxr #34 而且其实打印日志本身的主要目的还是为了调试( Debug ),包括 mac 上的 console 日志,其目的还是为了辅助调试,mac 崩溃的时候会把日志本身上传,其目的就是辅助回溯调试信息,因为这些信息都不是给正常用户看或用的,而是为了开发者。

求教, docker 安装 redis cluster 后, 客户端/redis-cli -c 跳转啥的访问到的是 docker 内部的 ip. 连接失败.

创建了一个 bridge network, 解决了容器之间网络访问问题, 但客户端连接 redis cluster 后, 获取到的节点的 ip 是 docker 分配给容器的 ip. 这肯定访问不了.

有大侠知道解决办法吗.. 头都秃噜皮了.

Docker Redis Cluster 容器4 条回复 • 2021-06-22 13:48:34 +08:00
nvkou 1
nvkou 3 小时 26 分钟前 via Android
容器之间使用容器名称访问。内部自带 Host 。
对外服务的端口映射回主机即可
BBCCBB 2
BBCCBB 3 小时 15 分钟前
@nvkou
> 对外服务的端口映射回主机即可

请问这个要咋操作呢? 添加一个 host network 吗?
nvkou 3
nvkou 2 小时 5 分钟前
@BBCCBB 这个拓扑不好说呢
在创建容器时,比如说 docker-compose 文件里给每个容器指定名称(比如 A,B), 然后这个容器组里指定一个网络(比如 network1). 然后你配置文件里就可以用名称指代容器.假设在容器创建时 A 获得 ip 10.0.0.5. 那么 b 的 redis 配置文件里可以简单用 A:6379 指代 10.0.0.5:6379 简单理解为 docker 内部有 host 帮你路由即可. 因为是桥接网络,从容器组外部访问容器(包括从宿主机)就必须走网桥. 因此你的宿主机要做好这一块的路由以便对本地或外部访问

端口映射是针对容器而言的. 对应 docker 的 -p 参数. 假设你的集群是固定的一个容器对外暴露服务,那么只需要使用 -p 参数 映射容器内部端口到宿主机端口即可.这部分魔法由 docker 提供.

举个例子 docker run tomcat -p 8080:80 起来一个容器之后, 在宿主机访问 localhost:80 即可访问到内部容器.
BBCCBB 4
BBCCBB 1 小时 33 分钟前
@nvkou 我现在容器之间访问没问题了. 外部也能访问.

但每个 redis 实例的 ip 是 docker 分配的. 比如

redis1: 172.18.0.1:6379, 这些 ip 都是 docker network 内部分配, 宿主机访问不到
redis2: 172.18.0.2:6379
redis3: 172.18.0.1:6379

redis1 的 port 映射比如是 50000:6379, 我在宿主机是能通过 127.0.0.1:50000 访问到单个节点的.

用 redis 客户端连接上之后, 获取到的 redis 集群的 ip 列表就是上面几个, 然后连接的时候就会 无法访问.

LeakCanary工具使用

LeakCanary工具使用

添加LeakCanary依赖包
https://github.com/square/leakcanary
在主模块app下的build.gradle下添加如下依赖:
debugCompile ‘com.squareup.leakcanary:leakcanary-android:1.3.1’
releaseCompile ‘com.squareup.leakcanary:leakcanary-android-no-op:1.3.1’

%title插图%num

开启LeakCanary
添加Application子类
首先创建一个ExampleApplication,该类继承于Application,在该类的onCreate方法中添加如下代码开启LeakCanary监控:
LeakCanary.install(this);

%title插图%num

在配置文件中注册ExampleApplication
在AndroidManifest.xml中的application标签中添加如下信息:
android:name=”.ExampleApplication”

%title插图%num

这个时候安装应用到手机,会自动安装一个Leaks应用,如下图:

%title插图%num
制造一个内存泄漏的点
建立一个ActivityManager类,单例模式,里面有一个数组用来保存Activity:
package com.example.android.sunshine.app;
import android.app.Activity;
import android.util.SparseArray;
import android.view.animation.AccelerateInterpolator;
import java.util.List;
public class ActivityManager {
private SparseArray container = new SparseArray();
private int key = 0;
private static ActivityManager mInstance;
private ActivityManager(){}
public static ActivityManager getInstance(){
if(mInstance == null){
mInstance = new ActivityManager();
}
return mInstance;
}

public void addActivity(Activity activity){
container.put(key++,activity);
}

}
然后在DetailActivity中的onCreate方法中将当前activity添加到ActivityManager的数组中:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_detail);
ActivityManager.getInstance().addActivity(this);
if (savedInstanceState == null) {
// Create the detail fragment and add it to the activity
// using a fragment transaction.

Bundle arguments = new Bundle();
arguments.putParcelable(DetailFragment.DETAIL_URI, getIntent().getData());

DetailFragment fragment = new DetailFragment();
fragment.setArguments(arguments);

getSupportFragmentManager().beginTransaction()
.add(R.id.weather_detail_container, fragment)
.commit();
}
}

我们从首页跳转到详情页的时候会进入DetailActivity的onCreate的方法,然后就将当前activity添加到了数组中,当返回时,我们没把他从数组中删除。再次进入的时候,会创建新的activity 并添加到数组中,但是之前的activity仍然被引用,无法释放,但是这个activity不会再被使用,这个时候就造成了内存泄漏。我们来看看LeakCanary是如何报出这个问题的。
演示

%title插图%num

解析的过程有点耗时,所以需要等待一会才会在Leaks应用中,当我们点开某一个信息时,会看到详细的泄漏信息:

%title插图%num

————————————————

群晖用哪种硬盘格式比较好呢?

EXT4 ? BTRFS ?其他? Raid 在磁盘出现故障时,恢复数据的几率大吗? 比如 2 块盘,坏了 1 块,会不会出现一定几率无法恢复 是 1 台 nas 开启 raid 好呢?还是 2 台 nas 备份好呢

NAS raid 几率 ext49 条回复 • 2021-06-22 15:09:03 +08:00
XiLingHost 1
XiLingHost 41 分钟前
数据的安全还是靠备份,硬盘格式用成熟稳定的就行,没必要开 raid,raid 是为了可以热恢复
banricho 2
banricho 39 分钟前
双盘 raid 1 或者三盘以上 raid 5
常见四槽可以三个组 raid 5,剩下那个当下载盘或者 SSD (有 M.2 的 NAS 就不用纠结这个问题)

至于你想保证数据安全的话,光 NAS 肯定不能 100% 保证,重要资料*好用 Cloud Sync 同步到网盘。
HDMItoDP 3
HDMItoDP 33 分钟前
@banricho 哦哦,重要数据我准备在 onedrive 存一份了
HDMItoDP 4
HDMItoDP 28 分钟前
就是担心 raid 打开了,数据依然有无法恢复的可能
LokiSharp 5
LokiSharp 25 分钟前
用默认的 btrfs,用其他的有些特性会用不了。然后可靠性的话,重要数据 Glacier Backup 备份一份到 AWS
gscsnm 6
gscsnm 25 分钟前
就用 ext4 就行,我的方案如下,在加上同步云盘就更好了(还没做)。另外,raid0 就是单纯的为了提高老硬盘的读写速度:
重要资料(照片、个人文档之类的):2 块 2T 硬盘组 raid1,+ 脚本自动同步到第 3 块硬盘上 +定期移动硬盘备份;
没那么重要的(可以接受短期内更新丢失的):raid0 + 定期移动硬盘备份;
不重要的(随意丢的):raid0 ;
hotcool100 7
hotcool100 21 分钟前
920+ 默认格式,raid1 8T 资料 /相册 + 8T 影视 + 4T 下载 ,onedirve 云备份
HDMItoDP 8
HDMItoDP 7 分钟前
@LokiSharp 这个格式稳定吗
HDMItoDP 9
HDMItoDP 6 分钟前
@hotcool100 脚本自动同步到第 3 块硬盘上。这个第三块硬盘也在本台 nas 里面吗?

友情链接: SITEMAP | 旋风加速器官网 | 旋风软件中心 | textarea | 黑洞加速器 | jiaohess | 老王加速器 | 烧饼哥加速器 | 小蓝鸟 | tiktok加速器 | 旋风加速度器 | 旋风加速 | quickq加速器 | 飞驰加速器 | 飞鸟加速器 | 狗急加速器 | hammer加速器 | trafficace | 原子加速器 | 葫芦加速器 | 麦旋风 | 油管加速器 | anycastly | INS加速器 | INS加速器免费版 | 免费vqn加速外网 | 旋风加速器 | 快橙加速器 | 啊哈加速器 | 迷雾通 | 优途加速器 | 海外播 | 坚果加速器 | 海外vqn加速 | 蘑菇加速器 | 毛豆加速器 | 接码平台 | 接码S | 西柚加速器 | 快柠檬加速器 | 黑洞加速 | falemon | 快橙加速器 | anycast加速器 | ibaidu | moneytreeblog | 坚果加速器 | 派币加速器 | 飞鸟加速器 | 毛豆APP | PIKPAK | 安卓vqn免费 | 一元机场加速器 | 一元机场 | 老王加速器 | 黑洞加速器 | 白石山 | 小牛加速器 | 黑洞加速 | 迷雾通官网 | 迷雾通 | 迷雾通加速器 | 十大免费加速神器 | 猎豹加速器 | 蚂蚁加速器 | 坚果加速器 | 黑洞加速 | 银河加速器 | 猎豹加速器 | 海鸥加速器 | 芒果加速器 | 小牛加速器 | 极光加速器 | 黑洞加速 | movabletype中文网 | 猎豹加速器官网 | 烧饼哥加速器官网 | 旋风加速器度器 | 哔咔漫画 | PicACG | 雷霆加速