glog本身是很高效的,google的大牛肯定知道大规模的写日志用glog的话肯定会影响业务线程的处理,带负荷的磁盘IO谁都桑不起。比方levelDB就是默认异步写,更不用说google的三驾马车都是分布式的。之前看过其论文,简直是引领时代。

在glog的issue里有人提出了异步写的问题,可是语焉不详,只是0.33版本号已经有了接口,可是还不友好,可是全然能够实现磁盘日志的异步写。

今天算是花了点时间踩了点坑,算是基本能够搞了。稳定之后会把这个版本号和glog,g2log,mudo logging一起測试下。mudo对buffer做了些trick,内部有两个bufferptr,做了双缓冲,据说效率非常高,只是仅仅有linux平台的,只是但把它的log抽离出来也不难,陈老师封装了mutex,thread,conditional等,在gcc4.8,clang3.3,VS2010都不是问题,已经没多大必要,并且之前为之乐道的linux下的threadsafe的initonce,如今C++11x也有了支持。

glog中能够让client定制接口是:

  1. class GOOGLE_GLOG_DLL_DECL Logger {
  2. public:
  3. virtual ~Logger();
  4.  
  5. // Writes "message[0,message_len-1]" corresponding to an event that
  6. // occurred at "timestamp". If "force_flush" is true, the log file
  7. // is flushed immediately.
  8. //
  9. // The input message has already been formatted as deemed
  10. // appropriate by the higher level logging facility. For example,
  11. // textual log messages already contain timestamps, and the
  12. // file:linenumber header.
  13. virtual void Write(bool force_flush,
  14. time_t timestamp,
  15. const char* message,
  16. int message_len) = 0;
  17.  
  18. // Flush any buffered messages
  19. virtual void Flush() = 0;
  20.  
  21. // Get the current LOG file size.
  22. // The returned value is approximate since some
  23. // logged data may not have been flushed to disk yet.
  24. virtual uint32 LogSize() = 0;
  25.  
  26. virtual void SetBasename(const char* basename) = 0;
  27. virtual void SetExtension(const char* ext) = 0 ;
  28. virtual void SetSymlinkBasename(const char* symlink_basename) = 0;
  29.  
  30. };

我在里面另外加了几个接口,为了之后的方便。

用Active object模式非常好解决,就是我们通常所说的生产者消费者,在logmsg析构时就会fflush到磁盘,这次就会调用logger的write方法,此时就是我们接手的机会,把数据封装下,投递到业务线程,然后取出,实际写磁盘就好。

封装了简单的Active模式,Activer里封装了LogData用来封装打印实体,Buffer用来线程间传递数据,另外要显式设置Active的回调函数callBack.线程间传递数据用了C++11里的currentQueue,就不须要自己造轮子了:

  1. /** ==========================================================================
  2. * 2010 by KjellKod.cc. This is PUBLIC DOMAIN to use at your own risk and comes
  3. * with no warranties. This code is yours to share, use and modify with no
  4. * strings attached and no restrictions or obligations.
  5. * ============================================================================
  6. *
  7. * Example of a Active Object, using C++11 std::thread mechanisms to make it
  8. * safe for thread communication.
  9. *
  10. * This was originally published at http://sites.google.com/site/kjellhedstrom2/active-object-with-cpp0x
  11. * and inspired from Herb Sutter's C++11 Active Object
  12. * http://herbsutter.com/2010/07/12/effective-concurrency-prefer-using-active-objects-instead-of-naked-threads
  13. *
  14. * The code below uses JustSoftware Solutions Inc std::thread implementation
  15. * http://www.justsoftwaresolutions.co.uk
  16. *
  17. * Last update 2012-10-10, by Kjell Hedstrom,
  18. * e-mail: hedstrom at kjellkod dot cc
  19. * linkedin: http://linkedin.com/se/kjellkod */
  20.  
  21. #ifndef ACTIVE_H_
  22. #define ACTIVE_H_
  23.  
  24. #include <thread>
  25. #include <functional>
  26. #include <condition_variable>
  27. #include <mutex>
  28. #include <memory>
  29. #include <concurrent_queue.h>
  30. #include "shared_queue.h"
  31.  
  32. struct Buffer
  33. {
  34. Buffer():m_Len(0), m_pMsg(NULL){}
  35. ~Buffer()
  36. {
  37. if (NULL != m_pMsg)
  38. delete []m_pMsg;
  39. }
  40. Buffer(int size):m_Len(size)
  41. , m_pMsg(new char[m_Len])
  42. {
  43.  
  44. }
  45. int m_Len;
  46. char* m_pMsg;
  47. };
  48.  
  49. typedef std::function<void(Buffer*)> Callback;
  50.  
  51. class Active {
  52. private:
  53. Active(const Active&); // c++11 feature not yet in vs2010 = delete;
  54. Active& operator=(const Active&); // c++11 feature not yet in vs2010 = delete;
  55. Active(); // Construction ONLY through factory createActive();
  56. void doDone(){done_ = true;}
  57. void run();
  58. void setCallBack(Callback aCallBack);
  59.  
  60. Concurrency::concurrent_queue<Buffer*> mq_;
  61. std::thread thd_;
  62. bool done_; // finished flag to be set through msg queue by ~Active
  63. Callback callBack_;
  64.  
  65. public:
  66. virtual ~Active();
  67. void send(Buffer* apBuffer);
  68. static std::unique_ptr<Active> createActive(Callback aCallBack); // Factory: safe construction & thread start
  69. };
  70.  
  71. #endif
  72. /** ==========================================================================
  73. * 2010 by KjellKod.cc. This is PUBLIC DOMAIN to use at your own risk and comes
  74. * with no warranties. This code is yours to share, use and modify with no
  75. * strings attached and no restrictions or obligations.
  76. * ============================================================================
  77. *
  78. * Example of a Active Object, using C++11 std::thread mechanisms to make it
  79. * safe for thread communication.
  80. *
  81. * This was originally published at http://sites.google.com/site/kjellhedstrom2/active-object-with-cpp0x
  82. * and inspired from Herb Sutter's C++11 Active Object
  83. * http://herbsutter.com/2010/07/12/effective-concurrency-prefer-using-active-objects-instead-of-naked-threads
  84. *
  85. * The code below uses JustSoftware Solutions Inc std::thread implementation
  86. * http://www.justsoftwaresolutions.co.uk
  87. *
  88. * Last update 2012-10-10, by Kjell Hedstrom,
  89. * e-mail: hedstrom at kjellkod dot cc
  90. * linkedin: http://linkedin.com/se/kjellkod */
  91.  
  92. #include "active.h"
  93. #include <cassert>
  94.  
  95. Active::Active(): done_(false){}
  96.  
  97. Active::~Active() {
  98. Callback quit_token = std::bind(&Active::doDone, this);
  99. thd_.join();
  100. }
  101.  
  102. // Add asynchronously a work-message to queue
  103. void Active::send( Buffer* apBuffer )
  104. {
  105. if (NULL != apBuffer)
  106. {
  107. mq_.push(apBuffer);
  108. }
  109. }
  110.  
  111. void Active::run() {
  112. while (!done_) {
  113. if (!mq_.empty())
  114. {
  115. Buffer* pBuffer = NULL;
  116. mq_.try_pop(pBuffer);
  117. if (NULL != pBuffer)
  118. {
  119. callBack_(pBuffer);
  120.  
  121. delete pBuffer;
  122. }
  123. }
  124. }
  125. }
  126.  
  127. // Factory: safe construction of object before thread start
  128. std::unique_ptr<Active> Active::createActive(Callback aCallBack){
  129. std::unique_ptr<Active> aPtr(new Active());
  130. aPtr->thd_ = std::thread(&Active::run, aPtr.get());
  131. aPtr->callBack_ = aCallBack;
  132. return aPtr;
  133. }
  134.  
  135. void Active::setCallBack( Callback aCallBack )
  136. {
  137. callBack_ = aCallBack;
  138. }
  1.  

重点是在threadlogger里,实现了Logger的接口。Write函数实现真正的写逻辑,几个set函数会在内部被调用。

  1. #pragma once
  2. #include <glog/logging.h>
  3. #include <mutex>
  4. #include "active.h"
  5.  
  6. using namespace std;
  7.  
  8. namespace google
  9. {
  10.  
  11. class ThreadLog : public google::base::Logger
  12. {
  13. public:
  14. ThreadLog();
  15. ~ThreadLog();
  16. virtual void Write(bool force_flush,
  17. time_t timestamp,
  18. const char* message,
  19. int message_len) ;
  20. virtual void Flush();
  21. virtual uint32 LogSize();
  22.  
  23. // Configuration options
  24. void SetBasename(const char* basename);
  25. void SetExtension(const char* ext);
  26. void SetSymlinkBasename(const char* symlink_basename);
  27. void CallBack(Buffer* pBuffer);
  28.  
  29. private:
  30. static const uint32 kRolloverAttemptFrequency = 0x20;
  31. mutex lock_;
  32. bool base_filename_selected_;
  33. string base_filename_;
  34. string symlink_basename_;
  35. string filename_extension_; // option users can specify (eg to add port#)
  36. FILE* file_;
  37. LogSeverity severity_;
  38. uint32 bytes_since_flush_;
  39. uint32 file_length_;
  40. unsigned int rollover_attempt_;
  41. int64 next_flush_time_; // cycle count at which to flush log
  42. string hostname;
  43. bool stopWriting;
  44. std::unique_ptr<Active> m_pActive;
  45. bool CreateLogfile(const string& time_pid_string);
  46. void FlushUnlocked();
  47. void WriteInteral(bool force_flush, time_t timestamp, const char* message, int message_len);
  48. };
  49.  
  50. }
  51.  
  52. #include "ThreadLog.h"
  53. #include "port.h"
  54. #include <fcntl.h>
  55. #include <iomanip>
  56. #include "utilities.h"
  57. #include <functional>
  58.  
  59. namespace google
  60. {
  61. static int GetSize(bool& force_flush, time_t& timestamp, const char* message, int& message_len)
  62. {
  63. return sizeof(force_flush)+sizeof(timestamp)+sizeof(message_len)+message_len;
  64. }
  65.  
  66. void ThreadLog::Write( bool force_flush, time_t timestamp, const char* message, int message_len )
  67. {
  68. Buffer* pBuffer = new Buffer(GetSize(force_flush, timestamp, message, message_len));
  69. char* curData = pBuffer->m_pMsg;
  70. memcpy(curData, &force_flush, sizeof(force_flush));
  71. curData += sizeof(force_flush);
  72.  
  73. memcpy(curData, ×tamp, sizeof(timestamp));
  74. curData += sizeof(timestamp);
  75.  
  76. memcpy(curData, &message_len, sizeof(message_len));
  77. curData += sizeof(message_len);
  78.  
  79. memcpy(curData, message, message_len);
  80. curData += message_len;
  81.  
  82. m_pActive->send(pBuffer);
  83. }
  84.  
  85. void ThreadLog::Flush()
  86. {
  87.  
  88. }
  89.  
  90. google::uint32 ThreadLog::LogSize()
  91. {
  92. return 0;
  93. }
  94.  
  95. void ThreadLog::SetBasename( const char* basename )
  96. {
  97. std::lock_guard<std::mutex> lock(lock_);
  98. base_filename_selected_ = true;
  99. if (base_filename_ != basename)
  100. {
  101. if (file_ != NULL)
  102. {
  103. fclose(file_);
  104. file_ = NULL;
  105. rollover_attempt_ = kRolloverAttemptFrequency-1;
  106. }
  107. base_filename_ = basename;
  108. }
  109. }
  110.  
  111. void ThreadLog::SetExtension( const char* ext )
  112. {
  113. std::lock_guard<std::mutex> lock(lock_);
  114. if (filename_extension_ != ext)
  115. {
  116. // Get rid of old log file since we are changing names
  117. if (file_ != NULL)
  118. {
  119. fclose(file_);
  120. file_ = NULL;
  121. rollover_attempt_ = kRolloverAttemptFrequency-1;
  122. }
  123. filename_extension_ = ext;
  124. }
  125. }
  126.  
  127. void ThreadLog::SetSymlinkBasename( const char* symlink_basename )
  128. {
  129. std::lock_guard<std::mutex> lock(lock_);
  130. symlink_basename_ = symlink_basename;
  131. }
  132.  
  133. bool ThreadLog::CreateLogfile( const string& time_pid_string )
  134. {
  135. string string_filename = base_filename_+filename_extension_+
  136. time_pid_string;
  137. const char* filename = string_filename.c_str();
  138. int fd = open(filename, O_WRONLY | O_CREAT | O_EXCL, 0664);
  139. if (fd == -1) return false;
  140. #ifdef HAVE_FCNTL
  141. // Mark the file close-on-exec. We don't really care if this fails
  142. fcntl(fd, F_SETFD, FD_CLOEXEC);
  143. #endif
  144.  
  145. file_ = fdopen(fd, "a"); // Make a FILE*.
  146. if (file_ == NULL) { // Man, we're screwed!
  147. close(fd);
  148. unlink(filename); // Erase the half-baked evidence: an unusable log file
  149. return false;
  150. }
  151.  
  152. if (!symlink_basename_.empty()) {
  153. // take directory from filename
  154. const char* slash = strrchr(filename, '/');
  155. const string linkname =
  156. symlink_basename_ + '.' + LogSeverityNames[severity_];
  157. string linkpath;
  158. if ( slash ) linkpath = string(filename, slash-filename+1); // get dirname
  159. linkpath += linkname;
  160. unlink(linkpath.c_str()); // delete old one if it exists
  161.  
  162. // We must have unistd.h.
  163. #ifdef HAVE_UNISTD_H
  164. // Make the symlink be relative (in the same dir) so that if the
  165. // entire log directory gets relocated the link is still valid.
  166. const char *linkdest = slash ? (slash + 1) : filename;
  167. if (symlink(linkdest, linkpath.c_str()) != 0) {
  168. // silently ignore failures
  169. }
  170.  
  171. // Make an additional link to the log file in a place specified by
  172. // FLAGS_log_link, if indicated
  173. if (!FLAGS_log_link.empty()) {
  174. linkpath = FLAGS_log_link + "/" + linkname;
  175. unlink(linkpath.c_str()); // delete old one if it exists
  176. if (symlink(filename, linkpath.c_str()) != 0) {
  177. // silently ignore failures
  178. }
  179. }
  180. #endif
  181. }
  182.  
  183. return true; // Everything worked
  184. }
  185.  
  186. void ThreadLog::FlushUnlocked()
  187. {
  188. if (file_ != NULL)
  189. {
  190. fflush(file_);
  191. bytes_since_flush_ = 0;
  192. }
  193.  
  194. const int64 next = (FLAGS_logbufsecs * static_cast<int64>(1000000)); // in usec
  195. next_flush_time_ = CycleClock_Now() + UsecToCycles(next);
  196. }
  197.  
  198. ThreadLog::ThreadLog(): file_(NULL)
  199. , bytes_since_flush_(0)
  200. , file_length_(0)
  201. , rollover_attempt_(0)
  202. , next_flush_time_(0)
  203. , stopWriting(false)
  204. , m_pActive(Active::createActive(std::bind(&ThreadLog::CallBack, this, std::placeholders::_1)))
  205. {
  206. }
  207.  
  208. ThreadLog::~ThreadLog()
  209. {
  210.  
  211. }
  212.  
  213. void ThreadLog::WriteInteral( bool force_flush, time_t timestamp, const char* message, int message_len )
  214. {
  215. if (base_filename_selected_ && base_filename_.empty())
  216. {
  217. return;
  218. }
  219.  
  220. if (static_cast<int>(file_length_ >> 20) >= MaxLogSize())
  221. {
  222. if (file_ != NULL)
  223. fclose(file_);
  224. file_ = NULL;
  225. file_length_ = bytes_since_flush_ = 0;
  226. rollover_attempt_ = kRolloverAttemptFrequency-1;
  227. }
  228.  
  229. if (file_ == NULL)
  230. {
  231. //if (++rollover_attempt_ != kRolloverAttemptFrequency)
  232. // return;
  233. //rollover_attempt_ = 0;
  234.  
  235. struct ::tm tm_time;
  236. localtime_rtamp, &tm_time);
  237. ostringstream time_pid_stream;
  238. time_pid_stream.fill('0');
  239. time_pid_stream << 1900+tm_time.tm_year
  240. << setw(2) << 1+tm_time.tm_mon
  241. << setw(2) << tm_time.tm_mday
  242. << '-'
  243. << setw(2) << tm_time.tm_hour
  244. << setw(2) << tm_time.tm_min
  245. << setw(2) << tm_time.tm_sec
  246. << '.'
  247. << GetCurrentThreadId();
  248. const string& time_pid_string = time_pid_stream.str();
  249.  
  250. if (base_filename_selected_)
  251. {
  252. if (!CreateLogfile(time_pid_string))
  253. {
  254. perror("Could not create log file");
  255. fprintf(stderr, "COULD NOT CREATE LOGFILE '%s'!\n", time_pid_string.c_str());
  256. return;
  257. }
  258. }
  259. else
  260. {
  261. string stripped_filename(glog_internal_namespace_::ProgramInvocationShortName());
  262. GetHostName(&hostname);
  263. string uidname = MyUserName();
  264. if (uidname.empty())
  265. uidname = "invalid-user";
  266.  
  267. stripped_filename = stripped_filename+'.'+hostname+'.'+uidname+".log."+LogSeverityNames[severity_]+'.';
  268. const vector<string> & log_dirs = GetLoggingDirectories();
  269.  
  270. bool success = false;
  271. for (vector<string>::const_iterator dir = log_dirs.begin();dir != log_dirs.end(); ++dir)
  272. {
  273. base_filename_ = *dir + "/" + stripped_filename;
  274. if ( CreateLogfile(time_pid_string) )
  275. {
  276. success = true;
  277. break;
  278. }
  279. }
  280.  
  281. if ( success == false )
  282. {
  283. perror("Could not create logging file");
  284. fprintf(stderr, "COULD NOT CREATE A LOGGINGFILE %s!",
  285. time_pid_string.c_str());
  286. return;
  287. }
  288. }
  289.  
  290. ostringstream file_header_stream;
  291. file_header_stream.fill('0');
  292. file_header_stream << "Log file created at: "
  293. << 1900+tm_time.tm_year << '/'
  294. << setw(2) << 1+tm_time.tm_mon << '/'
  295. << setw(2) << tm_time.tm_mday
  296. << ' '
  297. << setw(2) << tm_time.tm_hour << ':'
  298. << setw(2) << tm_time.tm_min << ':'
  299. << setw(2) << tm_time.tm_sec << '\n'
  300. << "Running on machine: "
  301. << hostname << '\n'
  302. << "Log line format: [IWEF]mmdd hh:mm:ss.uuuuuu "
  303. << "threadid file:line] msg" << '\n';
  304. const string& file_header_string = file_header_stream.str();
  305.  
  306. const int header_len = file_header_string.size();
  307. fwrite(file_header_string.data(), 1, header_len, file_);
  308. file_length_ += header_len;
  309. bytes_since_flush_ += header_len;
  310. }
  311.  
  312. if ( !stopWriting )
  313. {
  314. errno = 0;
  315. fwrite(message, 1, message_len, file_);
  316. if ( FLAGS_stop_logging_if_full_disk && errno == ENOSPC )
  317. { // disk full, stop writing to disk
  318. stopWriting = true; // until the disk is
  319. return;
  320. }
  321. else
  322. {
  323. file_length_ += message_len;
  324. bytes_since_flush_ += message_len;
  325. }
  326. }
  327. else
  328. {
  329. if ( CycleClock_Now() >= next_flush_time_ )
  330. stopWriting = true; // check to see if disk has free space.
  331. }
  332.  
  333. if ( force_flush || (bytes_since_flush_ >= 1000000) || (CycleClock_Now() >= next_flush_time_) ) {
  334. FlushUnlocked();
  335. #ifdef OS_LINUX
  336. if (FLAGS_drop_log_memory) {
  337. if (file_length_ >= logging::kPageSize) {
  338. // don't evict the most recent page
  339. uint32 len = file_length_ & ~(logging::kPageSize - 1);
  340. posix_fadvise(fileno(file_), 0, len, POSIX_FADV_DONTNEED);
  341. }
  342. }
  343. #endif
  344. }
  345. }
  346.  
  347. void ThreadLog::CallBack( Buffer* pBuffer )
  348. {
  349. char* curData = pBuffer->m_pMsg;
  350. bool force_flush = *(bool*)curData;
  351. curData += sizeof(force_flush);
  352. time_t timestamp = *(time_t*)curData;
  353. curData += sizeof(timestamp);
  354. int message_len = *(int*)curData;
  355. curData += sizeof(message_len);
  356. char* message = curData;
  357. WriteInteral(force_flush, timestamp, message, message_len);
  358. }
  359.  
  360. }

这样搞定之后,main函数能够这样使用,就能够把自己的ThreadLog类内嵌到glog里。

  1. #define GLOG_NO_ABBREVIATED_SEVERITIES
  2. #include <windows.h>
  3. #include <glog/logging.h>
  4. #include "ThreadLog.h"
  5.  
  6. using namespace google;
  7. int main(int argc, char* argv[]) {
  8. google::InitGoogleLogging("test/testsss");
  9. google::base::Logger* mylogger = new google::ThreadLog;
  10. SetLogger(google::GLOG_INFO, mylogger);
  11.  
  12. google::SetLogDestination(google::GLOG_INFO, "../Debug/logtestInfo");
  13. //google::SetLogDestination(google::GLOG_ERROR, "../Debug/logtestDebug");
  14.  
  15. int num_cookies = 0;
  16.  
  17. google::SetStderrLogging(google::GLOG_INFO);
  18. //google::SetStderrLogging(google::GLOG_ERROR);
  19. //google::LogToStderr();
  20. for (int i = 0; i < 1000; ++i){
  21. LOG(INFO) << "how are " << i << " cookies";
  22. }
  23.  
  24. google::ShutdownGoogleLogging();
  25. }

当然直接用这源代码是无法编译成功的,我改动了glog内部的源代码。

整个项目地址:git@github.com:boyxiaolong/Proejcts.git

測试还有点问题,偶尔会有乱码,并且须要优化的是那个Buffer的动态申请。

只是都是后话了。

glog另启动线程写文本日志的更多相关文章

  1. C#写文本日志帮助类(支持多线程)

    代码: using System; using System.Configuration; using System.IO; using System.Threading.Tasks; namespa ...

  2. C#写文本日志帮助类(支持多线程)改进版(不适用于ASP.NET程序)

    由于iis的自动回收机制,不适用于ASP.NET程序 代码: using System; using System.Collections.Concurrent; using System.Confi ...

  3. Asp.Net写文本日志

    底层代码: using System; using System.Collections.Generic; using System.Linq; using System.Text; namespac ...

  4. 如何将Unicode文本写到日志文件中

    有时为了定位问题,我们需要结合打印日志来处理.特别是较难复现的,一般都需要查看上下文日志才能找出可能存在的问题.考虑到程序要在不同语言的操作系统上运行,程序界面显示要支持Unicode,打印出来的日志 ...

  5. Struts+Spring+Hibernate项目的启动线程

    在Java Web项目中,经常要在项目开始运行时启动一个线程,每隔一定的时间就运行一定的代码,比如扫描数据库的变化等等.要实现这个功能,可以现在web.xml文件中定义一个Listener,然后在这个 ...

  6. Spring AOP 实现写事件日志功能

    什么是AOP?AOP使用场景?AOP相关概念?Spring AOP组件?如何使用Spring AOP?等等这些问题请参考博文:Spring AOP 实现原理 下面重点介绍如何写事件日志功能,把日志保存 ...

  7. Java开发笔记(九十八)利用Callable启动线程

    前面介绍了如何利用Runnable接口构建线程任务,该方式确实方便了线程代码的复用与共享,然而Runnable不像公共方法那样有返回值,也就无法将线程代码的处理结果传给外部,造成外部既不知晓该线程是否 ...

  8. Java开发笔记(九十七)利用Runnable启动线程

    前面介绍了线程的基本用法,按理说足够一般的场合使用了,只是每次开辟新线程,都得单独定义专门的线程类,着实开销不小.注意到新线程内部真正需要开发者重写的仅有run方法,其实就是一段代码块,分线程启动之后 ...

  9. Qt 进程和线程之二:启动线程

    Qt提供了对线程的支持,这包括一组与平台无关的线程类.一个线程安全的发送事件的方式,以及跨线程的信号槽的关联.这些使得可以很容易地开发可移植的多线程Qt应用程序,可以充分利用多处理器的机器.多线程编程 ...

随机推荐

  1. PyQt写的浏览单web页面的browser - 开源中国社区

    PyQt写的浏览单web页面的browser - 开源中国社区 PyQt写的浏览单web页面的browser

  2. Scraping JavaScript webpages with webkit | WebScraping.com

    Scraping JavaScript webpages with webkit | WebScraping.com Scraping JavaScript webpages with webkit ...

  3. 在 Linux RedHatEL6 环境下安装配置 JDK1.7 + Tomcat7.0 + MySQL5.6

    RedHatEL6 JDK安装路径: /usr/java/jdk1.7 Tomcat安装路径:/usr/local/tomcat7/ MySQL安装路径: /usr/local/mysql 总共分为以 ...

  4. android AChartEngine源代码

    昨天翻自己曾经下过的apache开源project项目,看到一个AChartEnginee看了一下自带的Demo才意识到这个东西的强大.立刻想把源代码down一份,在CSDN上有人挂5分让人下载,实在 ...

  5. Objective-c 方法的调用

    在书写了类的声明和实现后,应用程序如何去调用它呢? 在Objective-c中,调用方法的简单格式如下: 1⃣   [实例  方法];    如: [person setAge:32];  其中 pe ...

  6. JSPatch技术文档

    一.背景需求介绍 为什么我们需要一个热修复(hot-fix)技术? 工作中容易犯错.bug难以避免. 开发和测试人力有限. 苹果Appstore审核周期太长,一旦出现严重bug难以快速上线新版本. 作 ...

  7. java反射机制入门3

    Method对象的机制与实现 1.Method对象概述 1)java.lang.reflect.Method类是用于表示类中.接口中方法对象的类. 2)可以操作类中私有,以及公有等全部方法. 2.Me ...

  8. CentOS6.5安装MySQL5.7详细教程

    注:文中所写的安装过程均在CentOS6.5 x86下通过测试 主要参考博文: https://segmentfault.com/a/1190000003049498 http://www.th7.c ...

  9. PHP学习笔记10-图片加水印

    先找好一张图片,更名为face.jpeg,创建watermark.php: <?php /** * Created by PhpStorm. * User: Administrator * Da ...

  10. poj 2480 Longge's problem

    /** 大意: 计算f(n) = ∑ gcd(i, N) 1<=i <=N. 思路: gcd(i,x*y) = gcd(i,x) * gcd(i, y ) 所以gcd 为积性函数 又因为积 ...